Page 2 of 3 FirstFirst 123 LastLast
Results 11 to 20 of 22

Thread: Hibernate Long Session Per Flow?

  1. #11
    Join Date
    Nov 2004
    Location
    Austin, Texas USA
    Posts
    46

    Default

    Quote 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 at 07:25 PM.

  2. #12
    Join Date
    Nov 2004
    Location
    Austin, Texas USA
    Posts
    46

    Default

    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;

  3. #13
    Join Date
    Sep 2004
    Location
    Leuven, Belgium
    Posts
    1,853

    Default

    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

  4. #14
    Join Date
    Nov 2004
    Location
    Austin, Texas USA
    Posts
    46

    Default

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

  5. #15
    Join Date
    Oct 2005
    Posts
    17

    Default

    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.

  6. #16
    Join Date
    Sep 2004
    Location
    Leuven, Belgium
    Posts
    1,853

    Default

    It's not a high priority at the moment, but certainly something we're considering. Anyway, the code posted above should get you started with PR5.

    Erwin

  7. #17
    Join Date
    Nov 2005
    Location
    Lille-FRANCE
    Posts
    21

    Default

    Hello,

    I have problems when i use the OpenSessionInFlowListner and the OpenSessionInViewInterceptor (to do lazy loading).

    The long session in flow works fine but i've got an error in the interceptor :
    Code:
    java.lang.IllegalStateException: No value for key [net.sf.hibernate.impl.SessionFactoryImpl@70c242] bound to thread [http8080-Processor25]
    	at org.springframework.transaction.support.TransactionSynchronizationManager.unbindResource(TransactionSynchronizationManager.java:175)
    	at org.springframework.orm.hibernate.support.OpenSessionInViewInterceptor.afterCompletion(OpenSessionInViewInterceptor.java:214)
    	at org.springframework.web.servlet.DispatcherServlet.triggerAfterCompletion(DispatcherServlet.java:978)
    	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:720)
    	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:625)
    	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:392)
    	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:347)
    	at javax.servlet.http.HttpServlet.service(HttpServlet.java:743)
    	at javax.servlet.http.HttpServlet.service(HttpServlet.java:856)
    	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:284)
    	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:204)
    	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:257)
    	at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:151)
    	at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:564)
    	at org.apache.catalina.core.StandardContextValve.invokeInternal(StandardContextValve.java:245)
    	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:199)
    	at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:151)
    	at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:564)
    	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:195)
    	at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:151)
    	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:164)
    	at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:149)
    	at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:564)
    	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:156)
    	at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:151)
    	at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:564)
    	at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:972)
    	at org.apache.coyote.tomcat5.CoyoteAdapter.service(CoyoteAdapter.java:206)
    	at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:828)
    	at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.processConnection(Http11Protocol.java:700)
    	at org.apache.tomcat.util.net.TcpWorkerThread.runIt(PoolTcpEndpoint.java:584)
    	at org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:683)
    	at java.lang.Thread.run(Thread.java:534)
    java.lang.NullPointerException
    	at org.springframework.orm.hibernate.support.OpenSessionInViewInterceptor.postHandle(OpenSessionInViewInterceptor.java:180)
    	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:690)
    	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:625)
    	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:392)
    	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:347)
    	at javax.servlet.http.HttpServlet.service(HttpServlet.java:743)
    	at javax.servlet.http.HttpServlet.service(HttpServlet.java:856)
    	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:284)
    	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:204)
    	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:257)
    	at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:151)
    	at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:564)
    	at org.apache.catalina.core.StandardContextValve.invokeInternal(StandardContextValve.java:245)
    	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:199)
    	at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:151)
    	at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:564)
    	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:195)
    	at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:151)
    	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:164)
    	at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:149)
    	at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:564)
    	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:156)
    	at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:151)
    	at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:564)
    	at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:972)
    	at org.apache.coyote.tomcat5.CoyoteAdapter.service(CoyoteAdapter.java:206)
    	at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:828)
    	at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.processConnection(Http11Protocol.java:700)
    	at org.apache.tomcat.util.net.TcpWorkerThread.runIt(PoolTcpEndpoint.java:584)
    	at org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:683)
    	at java.lang.Thread.run(Thread.java:534)
    In fact, i try to find a way to have long session per flow and lazy loading

  8. #18
    Join Date
    Nov 2005
    Location
    Lille-FRANCE
    Posts
    21

    Default

    In fact the method requestProcessed(..) of OpenSessionInFlowListner close the session opened by the OpenSessionInViewInterceptor...

  9. #19
    Join Date
    Nov 2004
    Location
    Austin, Texas USA
    Posts
    46

    Default OpenSessionInView interferes with OpenSessionInFlowListener

    Quote Originally Posted by Thieumdesp
    In fact the method requestProcessed(..) of OpenSessionInFlowListner close the session opened by the OpenSessionInViewInterceptor...
    Yes. This is an unfortunate consequence of using the OpenSessionInViewInterceptor in tandem with the OpenSessionInFlowListener - the interceptor closes the hibernate session that should remain open from flow request to flow request. One way to avoid this is to use the OpenSessionInViewFilter instead. Using the filter allows to you map execution of the OpenSessionInView logic only to non-webflow urls by creating the appropriate filter-mapping entry in your web.xml file.

    If there seems to be a demand for it, I could write extensions of the OpenSessionInView classes that gracefully avoid terminating sessions opened for flows...

  10. #20
    Join Date
    Nov 2005
    Location
    Lille-FRANCE
    Posts
    21

    Default

    I reply in this post (because it's the same subject) :
    http://forum.springframework.org/showthread.php?t=20322

Similar Threads

  1. OpenSessionInView and portlet support
    By garpinc2 in forum Web Flow
    Replies: 31
    Last Post: Apr 9th, 2010, 11:12 AM
  2. Is a 'singleton' flow a bad idea?
    By akw in forum Web Flow
    Replies: 4
    Last Post: Oct 6th, 2005, 01:16 AM
  3. Loosing my SecureContext
    By sklakken in forum Security
    Replies: 3
    Last Post: Jul 21st, 2005, 01:44 PM
  4. Replies: 3
    Last Post: Nov 19th, 2004, 07:16 PM
  5. Replies: 9
    Last Post: Sep 25th, 2004, 12:35 PM

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •