Announcement Announcement Module
Collapse
No announcement yet.
Hibernate Long Session Per Flow? Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Hibernate Long Session Per Flow?

    A FormAction with a flow-scoped form backing object is a nice way to load a domain object using Hibernate and modify it over the course of a flow. It's natural that at the end of the flow, the object could then be submitted to Hibernate for persistence.

    The unfortunate truth of the matter is that keeping a detached object in the flow scope and attempting to reassociate it with another Hibernate Session can be a difficult proposition.

    The "Long Session" is often billed as the answer to this problem. Elsewhere in these forums Spring users have been asking about Hibernate Sessions that span for the length of an application transaction and the answer always seems to be: "but when will we know at what point to close the Hibernate Session?"

    Does SWF alleviate this issue by adequately demarcating the beginning and the end of an application transaction? With SWF as a first class member of the Spring family is it conceivable that we'll see a long session implementation that relies on flows?

  • #2
    Putting the Hibernate session in flow scope would easily give you 'flow scoped long Hibernate sessions'. You'd have to combine this with a FlowExecutionListener to connect/disconnect the session, e.g. using the requestSubmitted & requestProcessed hooks. With SWF it would probably be the flow itself that decides when to commit and/or discard the session, e.g. using an action that fires when you submit the work done in the flow.

    Erwin

    Comment


    • #3
      Originally posted by klr8
      Putting the Hibernate session in flow scope would easily give you 'flow scoped long Hibernate sessions'. You'd have to combine this with a FlowExecutionListener to connect/disconnect the session, e.g. using the requestSubmitted & requestProcessed hooks. With SWF it would probably be the flow itself that decides when to commit and/or discard the session, e.g. using an action that fires when you submit the work done in the flow.

      Erwin
      I had the same thought regarding implementation. As it happened, I also needed to write a new continuation storage class as well. Hibernate Sessions don't work so well after they've been serialized.

      Thanks for the suggestion.
      -Alex

      Comment


      • #4
        That's bizar, since the Session interface implements Serializable and the JavaDoc states 'A Session instance is serializable if its persistent classes are serializable'.

        Hibernate in Action also describes a long session scenario where the session is stored in the HttpSession.

        Erwin

        Comment


        • #5
          Yes. You're quite right. Looks like I should have gone to bed sooner.

          Comment


          • #6
            Originally posted by klr8
            Putting the Hibernate session in flow scope would easily give you 'flow scoped long Hibernate sessions'. You'd have to combine this with a FlowExecutionListener to connect/disconnect the session, e.g. using the requestSubmitted & requestProcessed hooks. With SWF it would probably be the flow itself that decides when to commit and/or discard the session, e.g. using an action that fires when you submit the work done in the flow.

            Erwin
            Hi Erwin, can you provide an example of how this can be done. I would greatly appreciate it. I am pretty sure other people may be interested in this aproach.

            All of my form objects (domain objects) are persisted using hibernate. Currently, I pass my form objects to a service layer and persistence is done there, openning/closing hibernate session and transaction.

            It would be nice to open up a hibernate session at a start of flow, modify the form objects within the session and at the end of the flow the session is closed and the form object is persisted.

            Curtney

            Comment


            • #7
              Erwin,

              I've implemented long sessions as FlowExecutionListener which uses Spring's TransactionSynchronizationManager for thread local storage of a Hibernate Session in much the same way that the OpenSessionInViewFilter works.

              The primary difference, of course, is that the Hibernate session remains open throughout the duration of a flow session, is connected and disconnected from the db with each request, and is serialized with the flow continuation. Additionally, Hibernate sessions can be shared with subflows by including the proper annotation in the parent flow's declaration.

              I'd be happy to submit it for your consideration for inclusion to SWF if you think that other users might find it an adequate solution.

              -Alex

              Comment


              • #8
                Start by posting the code here on the forum, that way other users can already have a look!

                One potential danger to look out for: what if the user doesn't correctly finish the flow: e.g. he closes his browser window in the middle of the flow. In that case the Hibernate session will only be cleaned up when the flow or HttpSession expires.

                Erwin

                Comment


                • #9
                  Originally posted by klr8
                  Start by posting the code here on the forum, that way other users can already have a look!

                  One potential danger to look out for: what if the user doesn't correctly finish the flow: e.g. he closes his browser window in the middle of the flow. In that case the Hibernate session will only be cleaned up when the flow or HttpSession expires.

                  Erwin
                  OK. I'll post it here.

                  I don't think it's necessary to explicitly close a Session as long as it's disconnected. In that respect, it was my intention that this code leave the Session in a 'terminated' state after each request. As far as orphaned Sessions go, I agree with you. The use of a flow cleanup filter is highly recommended and would be the only thing preventing severe memory bloat on the server resulting from abandonded flows that contain Session references.

                  Comment


                  • #10
                    Will the code be posted to this forum?

                    Curtney

                    Comment


                    • #11
                      Originally posted by curtney
                      Will the code be posted to this forum?

                      Curtney
                      Yes. Sorry. I got side tracked working on something else. Code is forthcoming.

                      Here's a few of the challenges to implementing long hibernate sessions using a FlowExecutionListener. Please weigh in if you're so inclined.

                      1) As of this moment, my OpenSessionInFlowListener applies to all flows without exception. There doesn't seem to be a convenient way to disqualify a listener from hearing about an execution of a certain flow. It might be nice to be able to 'turn off' this listener for certain flows. This deficiency exacerbates the problem realized by having a hibernate session stored in flow scope: a potentially large memory footprint per flow.
                      2) Objects returned by hibernate queries become increasingly stale over the life of the flow.
                      3) Hibernate sessions are started even for flows that don't access hibernate. This is also the case with the OpenSessionInView strategy.
                      4) I don't think the OpenSessionInView strategy will play nice with the OpenSessionInFlowListener. OpenSessionInView tries to close the hibernate session when the request is finished. This spoils the point of the long hibernate sessions and will likely cause an exception.

                      Issue #1 can be mitigated by use of the ExpiredFlowCleanupFilter which will remove expired flows and their associated hibernate sessions. I'm also tinkering with the idea of a FlowExecutionManager that enforces certain flows as singletons per http session. This will also help keep the memory footprint in check.

                      Issue #2 might or might not be a problem for your application. Either way, the current implementation allows you to optionally clear the hibernate session every time the flow session becomes active. This is configured on a per-flow basis and is arguably not the most flexible solution.

                      Issue #3 is not a problem that deserves any real attention. Does anyone disagree?

                      Issue #4 is sort of a stumper. I have a feeling that there might be a way to extend the OpenSessionInView components in such a way that they don't step on the OpenSessionInFlowListener's work. Any ideas here are of course welcome.

                      -A
                      Last edited by akw; Dec 8th, 2005, 07:25 PM.

                      Comment


                      • #12
                        Code:
                        import net.sf.hibernate.HibernateException;
                        import net.sf.hibernate.Session;
                        import net.sf.hibernate.SessionFactory;
                        import org.apache.log4j.Logger;
                        import org.springframework.jdbc.support.SQLExceptionTranslator;
                        import org.springframework.orm.hibernate.HibernateSystemException;
                        import org.springframework.orm.hibernate.SessionFactoryUtils;
                        import org.springframework.orm.hibernate.SessionHolder;
                        import org.springframework.transaction.support.TransactionSynchronizationManager;
                        import org.springframework.util.Assert;
                        import org.springframework.webflow.*;
                        
                        import java.io.Serializable;
                        
                        /**
                         * Manages Hibernate Sessions using the SWF lifecycle. By default, each new flow session is paired
                         * with a new Hibernate Session which is bound to the executing thread using the
                         * <code>TransactionSynchronizationManager</code>. When a client request is made, this listener
                         * retrieves an existing Hibernate Session from the flow scope and reconnects it to the datasource.
                         * After the request is complete, the Hibernate Session is disconnected. 
                         * <p/>
                         * By specifying the <code>subflowsParticipateInApplicationTransaction</code> annotation on the
                         * parent flow, all subflows may participate in the same Hibernate Session as the parent. If this is the case,
                         * the child flow defers closure of the Hibernate Session to the parent flow.
                         * <p/>
                         * Specifying the <code>clearOnFlowActivation</code> annotation on the flow causes the Hibernate 
                         * Session to clear every time the flow becomes active. This is intended to be used to force Hibernate
                         * to reload objects from the db and roughly approximates a the OpenSessionView strategy. Be warned,
                         * however, that when this flag is set, activation of the flow will cause unpersisted changes to
                         * be lost.
                         *
                         * @author Alex Wolfe
                         */
                        public class OpenSessionInFlowListener extends ExtendedFlowExecutionListener implements Serializable &#123;
                            private static final transient Logger log = Logger.getLogger&#40;OpenSessionInFlowListener.class&#41;;
                            private static final transient String HIBERNATE_SESSION_SHARED = "HIBERNATE_SESSION_SHARED";
                            private static final transient String SUBFLOWS_PARTICIPATE = "subflowsParticipateInApplicationTransaction";
                            private static final transient String CLEAR_ON_FLOW_ACTIVATION = "clearOnFlowActivation";
                        
                            static final transient String HIBERNATE_SESSION = "HIBERNATE_SESSION";
                        
                            private transient SQLExceptionTranslator sqlExceptionTranslator;
                            private transient SessionFactory sessionFactory;
                            private transient long nextSessionId = 1;
                        
                            public void setSessionFactory&#40;SessionFactory sessionFactory&#41; &#123;
                                this.sessionFactory = sessionFactory;
                            &#125;
                        
                            public SQLExceptionTranslator getSqlExceptionTranslator&#40;&#41; &#123;
                                if &#40;sqlExceptionTranslator == null&#41;
                                    sqlExceptionTranslator = SessionFactoryUtils.newJdbcExceptionTranslator&#40;sessionFactory&#41;;
                                return sqlExceptionTranslator;
                            &#125;
                        
                            /**
                             * The current flow is active.  Reconnect the active flow's hibernate session. If the
                             * <code>clearOnFlowActivation</code> annotation is set on the flow, then invoke
                             * the <code>clear&#40;&#41;</code> method on its hibernate session.
                             *
                             * @param ctx
                             */
                            public void sessionActive&#40;RequestContext ctx&#41; &#123;
                                Flow activeFlow = ctx.getFlowExecutionContext&#40;&#41;.getActiveFlow&#40;&#41;;
                                reconnectSession&#40;activeFlow.getId&#40;&#41;, getHibernateSession&#40;ctx.getFlowScope&#40;&#41;&#41;&#41;;
                                if &#40;hasClearAnnotation&#40;activeFlow&#41;&#41;
                                    getHibernateSession&#40;ctx.getFlowScope&#40;&#41;&#41;.getSession&#40;&#41;.clear&#40;&#41;;
                            &#125;
                        
                            /**
                             * Get the hibernate session from the parent flow scope.
                             *
                             * @param activeSession
                             * @return The parent's wrapped session
                             * @throws IllegalStateException if this flow expects to use the parent flow's hibernate session, but
                             *                               the parent does not have a hibernate session in scope.
                             */
                            protected SessionWrapper getParentHibernateSession&#40;FlowSession activeSession&#41; &#123;
                                FlowSession parentSession = activeSession.getParent&#40;&#41;;
                                Assert.notNull&#40;parentSession,
                                        "Flow attributes specify that the " + activeSession.getFlow&#40;&#41;.getId&#40;&#41; +
                                                " flow should reuse its parent's hibernate session, but no parent FlowSession exists"
                                &#41;;
                        
                                SessionWrapper parentSessionWrapper = getHibernateSession&#40;parentSession.getScope&#40;&#41;&#41;;
                                Assert.notNull&#40;parentSessionWrapper,
                                        new StringBuffer&#40;"Flow attributes specify that the "&#41;
                                                .append&#40;activeSession.getFlow&#40;&#41;.getId&#40;&#41;&#41;
                                                .append&#40;" flow should reuse its parent's hibernate session, but no parent hibernate session could be found in "&#41;
                                                .append&#40;parentSession.getFlow&#40;&#41;.getId&#40;&#41;&#41;
                                                .append&#40;" parent flow scope"&#41;.toString&#40;&#41;
                                &#41;;
                        
                                return parentSessionWrapper;
                            &#125;
                        
                            /**
                             * Does the flow contain the annotation required to trigger hibernate session clear
                             * when a flow session is activated?
                             *
                             * @param flow The flow
                             * @return <code>true</code> if the <code>CLEAR_ON_FLOW_ACTIVATION</code> flag is specified in
                             *         the flow, otherwise <code>false</code>
                             */
                            protected boolean hasClearAnnotation&#40;Flow flow&#41; &#123;
                                return flow.containsAttribute&#40;CLEAR_ON_FLOW_ACTIVATION&#41; &&
                                        Boolean.valueOf&#40;&#40;String&#41; flow.getAttribute&#40;CLEAR_ON_FLOW_ACTIVATION&#41;&#41;.equals&#40;Boolean.TRUE&#41;;
                            &#125;
                        
                            /**
                             * Does the flow contain the annotation required for child flows to participate in the same
                             * hibernate session?
                             *
                             * @param flow The flow
                             * @return <code>true</code> if the child flow can participate in the parent's hibernate session,
                             *         otherwise, <code>false</code>
                             */
                            protected boolean hasSubflowParticipationAnnotation&#40;Flow flow&#41; &#123;
                                return flow.containsAttribute&#40;SUBFLOWS_PARTICIPATE&#41;;
                            &#125;
                        
                            /**
                             * Does this flow use its parent's hibernate session?
                             *
                             * @param scope
                             * @return <code>true</code> if ths session is shared, otherwise <code>false</code>
                             */
                            protected boolean canSubflowParticipate&#40;Scope scope&#41; &#123;
                                Boolean canParticipate = &#40;Boolean&#41; scope.getAttribute&#40;HIBERNATE_SESSION_SHARED&#41;;
                                return canParticipate != null && canParticipate.equals&#40;Boolean.TRUE&#41;;
                            &#125;
                        
                            /**
                             * Set the flag that indicates whether the hibernate session for this flow is
                             * shared with the parent flow. This flag is set if the parent flow has the
                             * <code>SUBFLOWS_PARTICIPATE</code> annotation.
                             *
                             * @param scope
                             * @param flag
                             */
                            protected void setSubflowParticipationFlag&#40;String flowId, Scope scope, boolean flag&#41; &#123;
                                if &#40;scope.containsAttribute&#40;HIBERNATE_SESSION_SHARED&#41;&#41; return;
                                log.debug&#40;"Flow will " + &#40;!flag ? "not " &#58; ""&#41; + "participate in application transaction&#58; " + flowId&#41;;
                                scope.setAttribute&#40;HIBERNATE_SESSION_SHARED, Boolean.valueOf&#40;flag&#41;&#41;;
                            &#125;
                        
                            /**
                             * Reconnect the hibernate session to the datasource.
                             *
                             * @param flowId  The flow Id of the active flow
                             * @param wrapper The wrapped session
                             */
                            protected void reconnectSession&#40;String flowId, SessionWrapper wrapper&#41; &#123;
                                if &#40;wrapper.getSession&#40;&#41;.isConnected&#40;&#41;&#41; return;
                                log.debug&#40;"Reconnecting session in " + flowId + " flow&#58; " + wrapper&#41;;
                                try &#123;
                                    wrapper.getSession&#40;&#41;.reconnect&#40;&#41;;
                                    bindSession&#40;wrapper.getSession&#40;&#41;&#41;; // bind the reconnected session to the thread
                                &#125; catch &#40;HibernateException e&#41; &#123;
                                    throw new HibernateSystemException&#40;e&#41;;
                                &#125;
                            &#125;
                        
                            /**
                             * The executing flow's start state has been entered.  Set the appropriate hibernate session in the flow
                             * scope.  If the flow is participating in the same hibernate session as its parent, then set the parent's
                             * hibernate session in flow scope, otherwise use the thread bound session &#40;they should be the same session&#41;.
                             * <p/>
                             * Each new session is wrapped by the &#123;@link SessionWrapper&#125; which maintains an identifier for the Session.
                             * This is helpful for determining session identity even through serialization.
                             *
                             * @param ctx   The request context
                             * @param state The start state
                             */
                            public void stateEntered&#40;RequestContext ctx, State previousState, State state&#41; &#123;
                                SessionWrapper wrapper;
                                FlowSession activeSession = ctx.getFlowExecutionContext&#40;&#41;.getActiveSession&#40;&#41;;
                                String flowId = ctx.getFlowExecutionContext&#40;&#41;.getActiveFlow&#40;&#41;.getId&#40;&#41;;
                                setSubflowParticipationFlag&#40;flowId, activeSession.getScope&#40;&#41;, canSubflowParticipate&#40;ctx.getRequestScope&#40;&#41;&#41;&#41;;
                                if &#40;canSubflowParticipate&#40;ctx.getFlowScope&#40;&#41;&#41;&#41; &#123;
                                    wrapper = getParentHibernateSession&#40;activeSession&#41;;
                                &#125; else if &#40;getHibernateSession&#40;activeSession.getScope&#40;&#41;&#41; != null&#41; &#123;
                                    wrapper = getHibernateSession&#40;activeSession.getScope&#40;&#41;&#41;;
                                &#125; else &#123;
                                    wrapper = new SessionWrapper&#40;nextSessionId++, getBoundSession&#40;&#41;&#41;;
                                    log.debug&#40;"Creating hibernate session " + wrapper.getId&#40;&#41; + " for flow&#58; " + flowId&#41;;
                                &#125;
                                setHibernateSession&#40;flowId, ctx.getFlowScope&#40;&#41;, wrapper&#41;;
                                super.stateEntered&#40;ctx, previousState, state&#41;;
                            &#125;
                        
                            /**
                             * A sub flow is launching. Reuses hibernate session from parent flow if that flow has the appropriate
                             * annotation.  Otherwise, a new hibernate session is used for the subflow.
                             * <p/>
                             * In the event that a new hibernate session is created for the launching subflow, the parent's session
                             * is unbound from the thread and new session bound.
                             *
                             * @param parentSession The parent flow's session
                             * @param childFlow     The child flow
                             * @param ctx           The request context &#93;
                             * @throws IllegalStateException An invalid state is encountered while creating a new session for the subflow
                             * @see #hasSubflowParticipationAnnotation&#40;Flow&#41;
                             */
                            public void launchingSubflow&#40;FlowSession parentSession, Flow childFlow, RequestContext ctx&#41; &#123;
                                log.debug&#40;"Launching subflow&#58; " + parentSession.getFlow&#40;&#41;.getId&#40;&#41; + " -> " + childFlow.getId&#40;&#41;&#41;;
                                String parentFlowId = parentSession.getFlow&#40;&#41;.getId&#40;&#41;;
                                SessionWrapper parentWrapper = getHibernateSession&#40;parentSession.getScope&#40;&#41;&#41;;
                        
                                // For now, this flag is added to the request scope.  It cannot be added to the
                                // child flow's scope yet because the child flow does not become active until later
                                // in the flow lifecycle.  When the child flow is activated, this flag will be placed
                                // in the child flow's scope.  This occurs in the stateEntered method.
                                setSubflowParticipationFlag&#40;parentFlowId, ctx.getRequestScope&#40;&#41;, hasSubflowParticipationAnnotation&#40;parentSession.getFlow&#40;&#41;&#41;&#41;;
                                if &#40;canSubflowParticipate&#40;ctx.getRequestScope&#40;&#41;&#41;&#41; &#123;
                                    log.debug&#40;"Reusing hibernate session " + parentWrapper + " from " + parentSession.getFlow&#40;&#41;.getId&#40;&#41; + " flow in " + childFlow.getId&#40;&#41; + " subflow"&#41;;
                                &#125; else &#123;
                                    SessionWrapper wrapper = getHibernateSession&#40;parentSession.getScope&#40;&#41;&#41;;
                                    disconnectSession&#40;parentFlowId, wrapper.getSession&#40;&#41;, new Long&#40;wrapper.getId&#40;&#41;&#41;&#41;;
                                    Session parent = parentWrapper.getSession&#40;&#41;;
                                    Session thread = getBoundSession&#40;&#41;;
                                    Session child = replaceBoundSession&#40;&#41;;
                                    log.debug&#40;"Replaced session " + parentWrapper + " with " + child + " on thread"&#41;;
                                    Assert.isTrue&#40;parent == thread, "Parent scope has different hibernate session than thread"&#41;;
                                    Assert.isTrue&#40;parent != child, "Session replacement returned the same hibernate session"&#41;;
                                    Assert.isTrue&#40;child == getBoundSession&#40;&#41;, "Session replacement did not replace the hibernate session"&#41;;
                                    Assert.isTrue&#40;parent != getBoundSession&#40;&#41;, "Session replacement returned the parent hibernate session"&#41;;
                                &#125;
                            &#125;
                        
                            /**
                             * A sub flow session has ended. If the flow had its own hibernate session it will be closed and the
                             * unbound from the thread.
                             *
                             * @param parent The parent flow session
                             * @param child  The child flow session
                             * @param ctx    The request context
                             * @throws IllegalStateException if the hibernate session should shared between parent and child, but
                             *                               the corresponding flow scopes contain different session instances or
                             *                               if the hibernate session should not be shared, but the parent and child
                             *                               flow scopes contain the same session instance
                             */
                            public void subflowSessionEnded&#40;FlowSession parent, FlowSession child, RequestContext ctx&#41; &#123;
                                log.info&#40;"Finished subflow&#58; " + child.getFlow&#40;&#41;.getId&#40;&#41; + " -> " + parent.getFlow&#40;&#41;.getId&#40;&#41;&#41;;
                                SessionWrapper childWrapper = getHibernateSession&#40;child.getScope&#40;&#41;&#41;;
                                SessionWrapper parentWrapper = getHibernateSession&#40;parent.getScope&#40;&#41;&#41;;
                                if &#40;canSubflowParticipate&#40;child.getScope&#40;&#41;&#41;&#41; &#123;
                                    if &#40;log.isDebugEnabled&#40;&#41;&#41;
                                        log.debug&#40;"Deferring closure of hibernate session " + childWrapper.getId&#40;&#41; + " from " + child.getFlow&#40;&#41;.getId&#40;&#41; + " child flow " +
                                                "to " + parent.getFlow&#40;&#41;.getId&#40;&#41; + " parent flow"&#41;;
                                    Assert.isTrue&#40;childWrapper.equals&#40;parentWrapper&#41;,
                                            new StringBuffer&#40;"Hibernate session for "&#41;
                                                    .append&#40;child.getFlow&#40;&#41;.getId&#40;&#41;&#41;
                                                    .append&#40;" child flow is not "&#41;
                                                    .append&#40;"the same hibernate session as "&#41;
                                                    .append&#40;parent.getFlow&#40;&#41;.getId&#40;&#41;&#41;.toString&#40;&#41;
                                    &#41;;
                                &#125; else &#123;
                                    Assert.isTrue&#40;!parentWrapper.equals&#40;childWrapper&#41;, "Parent and child FlowSessions contain the same hibernate session"&#41;;
                                    log.debug&#40;"Closing session for flow " + child.getFlow&#40;&#41;.getId&#40;&#41; + "&#58; " + childWrapper&#41;;
                                    closeSession&#40;&#41;;
                                    log.debug&#40;"Replacing session " + childWrapper + " with session " + parentWrapper + " on thread"&#41;;
                                    replaceBoundSession&#40;parentWrapper.getSession&#40;&#41;&#41;;
                                    Assert.isTrue&#40;getBoundSession&#40;&#41; == parentWrapper.getSession&#40;&#41;, "Parent hibernate session is not bound to thread"&#41;;
                                &#125;
                            &#125;
                        
                            /**
                             * The root flow session has ended. Close the flow's hibernate session and unbind it from the thread.
                             *
                             * @param endedSession The ended session
                             * @param ctx          The request context
                             * @throws IllegalStateException if the hibernate session bound to the thread is not the same instance as
                             *                               the hibernate session found in the ended flow
                             */
                            public void rootFlowSessionEnded&#40;FlowSession endedSession, RequestContext ctx&#41; &#123;
                                log.debug&#40;"Finished flow&#58; " + endedSession.getFlow&#40;&#41;.getId&#40;&#41;&#41;;
                                SessionWrapper wrapper = getHibernateSession&#40;endedSession.getScope&#40;&#41;&#41;;
                                if &#40;wrapper != null&#41;
                                    Assert.isTrue&#40;wrapper.getSession&#40;&#41; == getBoundSession&#40;&#41;, "Unexpected hibernate session is bound to thread"&#41;;
                                String flowId = endedSession.getFlow&#40;&#41;.getId&#40;&#41;;
                                log.debug&#40;"Closing session for flow " + flowId + "&#58; " + getBoundSession&#40;&#41;&#41;;
                                closeSession&#40;&#41;;
                            &#125;
                        
                            /**
                             * A request from the client was processed.  Disconnect the hibernate session from
                             * the datasource and unbind it from the thread.
                             *
                             * @param context The request context
                             */
                            public void requestProcessed&#40;RequestContext context&#41; &#123;
                                String flowId = null;
                                Long sessionId = null;
                                Session session = getBoundSession&#40;&#41;;
                                if &#40;context.getFlowExecutionContext&#40;&#41;.isActive&#40;&#41;&#41; &#123;
                                    flowId = context.getFlowExecutionContext&#40;&#41;.getActiveFlow&#40;&#41;.getId&#40;&#41;;
                                    SessionWrapper wrapper = getHibernateSession&#40;context.getFlowScope&#40;&#41;&#41;;
                                    session = wrapper.getSession&#40;&#41;;
                                    sessionId = new Long&#40;wrapper.getId&#40;&#41;&#41;;
                                &#125;
                        
                                disconnectSession&#40;flowId, session, sessionId&#41;;
                                unbind&#40;&#41;;
                            &#125;
                        
                            protected void disconnectSession&#40;String flowId, Session session, Long sessionId&#41; &#123;
                                Assert.notNull&#40;session&#41;;
                                if &#40;!session.isConnected&#40;&#41;&#41; return;
                                String sessionString = sessionId != null ? String.valueOf&#40;sessionId.longValue&#40;&#41;&#41; &#58; session.toString&#40;&#41;;
                                if &#40;log.isDebugEnabled&#40;&#41;&#41;
                                    log.debug&#40;"Disconnecting session for flow" + &#40;flowId != null ? " " + flowId &#58; ""&#41; + "&#58; " + sessionString&#41;;
                                try &#123;
                                    session.disconnect&#40;&#41;;
                                &#125; catch &#40;HibernateException e&#41; &#123;
                                    throw new HibernateSystemException&#40;e&#41;;
                                &#125;
                            &#125;
                        
                            protected SessionWrapper getHibernateSession&#40;Scope scope&#41; &#123;
                                return &#40;SessionWrapper&#41; scope.getAttribute&#40;HIBERNATE_SESSION&#41;;
                            &#125;
                        
                            protected void setHibernateSession&#40;String flowId, Scope scope, SessionWrapper wrapper&#41; &#123;
                                if &#40;getHibernateSession&#40;scope&#41; != null &&
                                        getHibernateSession&#40;scope&#41;.equals&#40;wrapper&#41;&#41; return;
                                log.debug&#40;"Hibernate session saved to scope for " + flowId + "&#58; " + wrapper&#41;;
                                scope.setAttribute&#40;HIBERNATE_SESSION, wrapper&#41;;
                            &#125;
                        
                            /**
                             * Get the Hibernate Session bound to the currently executing thread if it exists, otherwise return a
                             * new Session.
                             *
                             * @return Hibernate Session
                             */
                            public Session getBoundSession&#40;&#41; &#123;
                                return bind&#40;SessionFactoryUtils.getSession&#40;sessionFactory, null, getSqlExceptionTranslator&#40;&#41;&#41;&#41;;
                            &#125;
                        
                            /**
                             * Bind a Hibernate Session to the currently executing thread, replacing the existing Session if
                             * one is already bound.
                             *
                             * @param session
                             */
                            public void bindSession&#40;Session session&#41; &#123;
                                replaceBoundSession&#40;session&#41;;
                            &#125;
                        
                            protected boolean threadHasSession&#40;&#41; &#123;
                                return TransactionSynchronizationManager.hasResource&#40;sessionFactory&#41;;
                            &#125;
                        
                            /**
                             * Replace the Hibernate Session bound to the currently executing thread with
                             * a new session.
                             *
                             * @return The new session
                             */
                            public Session replaceBoundSession&#40;&#41; &#123;
                                unbind&#40;&#41;;
                                return getBoundSession&#40;&#41;;
                            &#125;
                        
                            /**
                             * Replace this thread's session with the given session
                             *
                             * @param session The new session
                             */
                            protected void replaceBoundSession&#40;Session session&#41; &#123;
                                unbind&#40;&#41;;
                                bind&#40;session&#41;;
                            &#125;
                        
                            protected void closeSession&#40;&#41; &#123;
                                Session session = getBoundSession&#40;&#41;;
                                unbind&#40;&#41;;
                                SessionFactoryUtils.releaseSession&#40;session, sessionFactory&#41;;
                                Assert.isTrue&#40;!session.isOpen&#40;&#41;, "Hibernate Session is still open after release"&#41;;
                            &#125;
                        
                            protected void unbind&#40;&#41; &#123;
                                if &#40;threadHasSession&#40;&#41;&#41;
                                    TransactionSynchronizationManager.unbindResource&#40;sessionFactory&#41;;
                            &#125;
                        
                            protected Session bind&#40;Session session&#41; &#123;
                                if &#40;!threadHasSession&#40;&#41;&#41;
                                    TransactionSynchronizationManager.bindResource&#40;sessionFactory, new SessionHolder&#40;session&#41;&#41;;
                                return session;
                            &#125;
                        
                            private final class SessionWrapper implements Serializable &#123;
                                private final long id;
                                private final Session session;
                        
                                public SessionWrapper&#40;long id, Session session&#41; &#123;
                                    this.session = session;
                                    this.id = id;
                                &#125;
                        
                                public long getId&#40;&#41; &#123;
                                    return id;
                                &#125;
                        
                                public Session getSession&#40;&#41; &#123;
                                    return session;
                                &#125;
                        
                                public String toString&#40;&#41; &#123;
                                    return String.valueOf&#40;id&#41;;
                                &#125;
                        
                                public boolean equals&#40;Object o&#41; &#123;
                                    if &#40;this == o&#41; return true;
                                    if &#40;o == null || getClass&#40;&#41; != o.getClass&#40;&#41;&#41; return false;
                        
                                    final SessionWrapper that = &#40;SessionWrapper&#41; o;
                        
                                    return id == that.id;
                                &#125;
                        
                                public int hashCode&#40;&#41; &#123;
                                    return &#40;int&#41; &#40;id ^ &#40;id >>> 32&#41;&#41;;
                                &#125;
                            &#125;
                        &#125;
                        Code:
                        import org.springframework.util.Assert;
                        import org.springframework.webflow.*;
                        import org.springframework.webflow.execution.EnterStateVetoException;
                        import org.springframework.webflow.execution.FlowExecutionListenerAdapter;
                        
                        import java.util.Map;
                        
                        /**
                         * Adaptation of flow lifecycle events.
                         *
                         * @author Alex Wolfe
                         */
                        public class ExtendedFlowExecutionListener extends FlowExecutionListenerAdapter &#123;
                            private static final String FIRST_STATE_ENTERED = "FIRST_STATE_ENTERED";
                        
                            /**
                             * Invoked when a flow is launched. The launching flow is not active.
                             *
                             * @param newFlow The launching flow
                             * @param ctx     The request context
                             */
                            public void launchingNewFlow&#40;Flow newFlow, RequestContext ctx&#41; &#123;
                            &#125;
                        
                            /**
                             * Invoked when a subflow is launched. The child flow session is not
                             * available to implementations of this method because the flow session for
                             * the child flow has not yet started.
                             * <p/>
                             * If you need to add items to the subflow scope, use the
                             * &#123;@link #sessionActive&#40;RequestContext&#41;&#125; method.
                             *
                             * @param parentSession The active parent flow session
                             * @param child         The child flow
                             * @param ctx           The request context
                             */
                            public void launchingSubflow&#40;FlowSession parentSession, Flow child, RequestContext ctx&#41; &#123;
                            &#125;
                        
                            /**
                             * Invoked when the a sub-flow session has ended.
                             *
                             * @param parent The parent flow's session
                             * @param child  The ended child flow's session
                             * @param ctx    The request context
                             */
                            public void subflowSessionEnded&#40;FlowSession parent, FlowSession child, RequestContext ctx&#41; &#123;
                            &#125;
                        
                            /**
                             * Invoked when the root flow session has ended.
                             *
                             * @param endedSession The ended session
                             * @param ctx          The request context
                             */
                            public void rootFlowSessionEnded&#40;FlowSession endedSession, RequestContext ctx&#41; &#123;
                            &#125;
                        
                            /**
                             * The currently executing flow session is active.
                             * This occurs after the first state for the executing request has been entered.
                             * Invoked once per request and provides access to the active flow session prior to
                             * any actions being performed for the current request.
                             *
                             * @param ctx           The request context
                             */
                            public void sessionActive&#40;RequestContext ctx&#41; &#123;
                            &#125;
                        
                            /**
                             * Called when any client request is submitted to manipulate this flow execution.
                             * Sets a flag in the request scope that is activated when the first state is entered
                             * during this request. This flag is required in order to trigger execution of the
                             * &#123;@link #sessionActive&#40;RequestContext&#41;&#125; method.
                             *
                             * @param context The request context
                             */
                            public void requestSubmitted&#40;RequestContext context&#41; &#123;
                                context.getRequestScope&#40;&#41;.setAttribute&#40;FIRST_STATE_ENTERED, Boolean.FALSE&#41;;
                            &#125;
                        
                            /**
                             * The flow session is starting.  This method invokes &#123;@link #launchingNewFlow&#40;Flow, RequestContext&#41;&#125;
                             * if the launching flow session is the root flow.  Otherwise, the
                             * &#123;@link #launchingSubflow&#40;FlowSession, Flow, RequestContext&#41;&#125; method is invoked.
                             *
                             * @param context    The request context
                             * @param startState The start state
                             * @param input
                             * @throws EnterStateVetoException The start state transition was not allowed
                             */
                            public void sessionStarting&#40;RequestContext context, State startState, Map input&#41; throws EnterStateVetoException &#123;
                                FlowExecutionContext exeCtx = context.getFlowExecutionContext&#40;&#41;;
                                FlowSession activeSession = exeCtx.isActive&#40;&#41; ? exeCtx.getActiveSession&#40;&#41; &#58; null;
                                Flow newFlow = startState.getFlow&#40;&#41;;
                        
                                if &#40;activeSession == null && newFlow != null&#41;
                                    launchingNewFlow&#40;newFlow, context&#41;;
                                else if &#40;activeSession != null & newFlow != null&#41;
                                    launchingSubflow&#40;activeSession, newFlow, context&#41;;
                            &#125;
                        
                            /**
                             * Called when a flow execution session ends.
                             * If the ended session was the root session of the flow execution,
                             * the &#123;@link #rootFlowSessionEnded&#40;FlowSession, RequestContext&#41;&#125; method is invoked
                             * and the <code>SESSION_ACTIVE</code> flag is reset.
                             * <p/>
                             * If the ended session was not the root session, then the
                             * &#123;@link #subflowSessionEnded&#40;FlowSession, FlowSession, RequestContext&#41;&#125;
                             * method is invoked.
                             *
                             * @param context      The source of the event
                             * @param endedSession The ended FlowSession
                             */
                            public void sessionEnded&#40;RequestContext context, FlowSession endedSession&#41; &#123;
                                FlowExecutionContext exeCtx = context.getFlowExecutionContext&#40;&#41;;
                                FlowSession newSession = &#40;exeCtx.isActive&#40;&#41; ? exeCtx.getActiveSession&#40;&#41; &#58; null&#41;;
                        
                                if &#40;endedSession != null && newSession != null&#41; &#123;
                                    subflowSessionEnded&#40;newSession, endedSession, context&#41;;
                                    context.getRequestScope&#40;&#41;.setAttribute&#40;FIRST_STATE_ENTERED, Boolean.FALSE&#41;;
                                &#125; else if &#40;endedSession != null && newSession == null&#41;
                                    rootFlowSessionEnded&#40;endedSession, context&#41;;
                            &#125;
                        
                            /**
                             * Called when a state transitions, after the transition occured. If the state entered
                             * is the first state entered for the request, then the &#123;@link #sessionActive&#40;RequestContext&#41;&#125; method is invoked.
                             *  
                             * @param context       The request context
                             * @param previousState The previous state
                             * @param state         The entered state
                             */
                            public void stateEntered&#40;RequestContext context, State previousState, State state&#41; &#123;
                                if &#40;!firstStateEntered&#40;context&#41;&#41; &#123;
                                    context.getRequestScope&#40;&#41;.setAttribute&#40;FIRST_STATE_ENTERED, Boolean.TRUE&#41;;
                                    sessionActive&#40;context&#41;;
                                &#125;
                            &#125;
                        
                            /**
                             * Determine whether the current request has entered a state.
                             *
                             * @param ctx The request context
                             * @return <code>true</code> if a state has already been entered during the current request,
                             *         otherwise <code>false</code>
                             */
                            protected boolean firstStateEntered&#40;RequestContext ctx&#41; &#123;
                                Assert.isTrue&#40;ctx.getRequestScope&#40;&#41;.getAttribute&#40;FIRST_STATE_ENTERED&#41; != null&#41;;
                                return ctx.getRequestScope&#40;&#41;.getAttribute&#40;FIRST_STATE_ENTERED&#41;.equals&#40;Boolean.TRUE&#41;;
                            &#125;
                        
                            protected boolean isStartState&#40;RequestContext ctx, State state&#41; &#123;
                                return state.equals&#40;ctx.getFlowExecutionContext&#40;&#41;.getActiveFlow&#40;&#41;.getStartState&#40;&#41;&#41;;
                            &#125;
                        
                        &#125;

                        Comment


                        • #13
                          Thanks for posting the code.

                          Regarding issue 1: check http://static.springframework.org/sp...rCriteria.html: FlowExecutionListenerCriteria - Strategy interface that determines if a flow execution listener should attach to executions of a specific flow definition. This could also help with issue 3 since the FlowExecutionListenerCriteria could look at a flow property (specified in the XML) to see if the flow needs a Hibernate session.

                          Erwin

                          Comment


                          • #14
                            Ah yes. You guys are truly one step ahead. Thanks for the tip.

                            Comment


                            • #15
                              Any possiblity of getting this into the code base in the SWF 1.0 timeframe?
                              This feature would be very useful for many real world projects battling to use spring + hibernate in production.

                              Comment

                              Working...
                              X