/* Copyright 2004-2005 Graeme Rocher
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.codehaus.groovy.grails.webflow.persistence;

import org.springframework.webflow.persistence.HibernateFlowExecutionListener;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.FlowSession;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.core.collection.AttributeMap;
import org.springframework.webflow.definition.FlowDefinition;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.orm.hibernate3.SessionHolder;
import org.hibernate.SessionFactory;
import org.hibernate.Session;
import org.hibernate.FlushMode;

/**
 * Extends the HibernateFlowExecutionListener and doesn't bind a session if one is already present
 *
 * @author Graeme Rocher
 * @since 1.0
 *        <p/>
 *        Created: Feb 12, 2008
 */
public class SessionAwareHibernateFlowExecutionListener extends HibernateFlowExecutionListener {
    private SessionFactory localSessionFactory;
    private TransactionTemplate localTransactionTemplate;
    private static final String HIBERNATE_SESSION_ATTRIBUTE = "session";
    private static final String PERSISTENCE_CONTEXT_ATTRIBUTE = "persistenceContext";

    /**
     * Create a new Hibernate Flow Execution Listener using giving Hibernate session factory and transaction manager.
     *
     * @param sessionFactory     the session factory to use
     * @param transactionManager the transaction manager to drive transactions
     */
    public SessionAwareHibernateFlowExecutionListener(SessionFactory sessionFactory, PlatformTransactionManager transactionManager) {
        super(sessionFactory, transactionManager);
        this.localSessionFactory = sessionFactory;
        this.localTransactionTemplate = new TransactionTemplate(transactionManager);
    }

    public void sessionCreated(RequestContext context, FlowSession session) {
        if (!TransactionSynchronizationManager.hasResource(localSessionFactory)) {
            super.sessionCreated(context, session);
        }
        else {
            obtainCurrentSession(context);
        }
        
    }

    public void sessionEnded(RequestContext context, FlowSession session, AttributeMap output) {
		if (isPersistenceContext(session.getDefinition())) {
			final Session hibernateSession = (Session) session.getScope().remove(HIBERNATE_SESSION_ATTRIBUTE);
			Boolean commitStatus = session.getState().getAttributes().getBoolean("commit");
			if (Boolean.TRUE.equals(commitStatus)) {
                try {
                    if(TransactionSynchronizationManager.hasResource(localSessionFactory)) {
                        SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.getResource(localSessionFactory);
                        sessionHolder.addSession(hibernateSession);
                    }
                    localTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
                        protected void doInTransactionWithoutResult(TransactionStatus status) {
                            localSessionFactory.getCurrentSession();
                            // nothing to do; a flush will happen on commit automatically as this is a read-write
                            // transaction
                        }
                    });
                } finally {
                    if(TransactionSynchronizationManager.hasResource(localSessionFactory)) {
                        SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.getResource(localSessionFactory);
                        sessionHolder.removeSession(hibernateSession);
                    }

                }
            }
			unbind(hibernateSession);
			hibernateSession.close();
		}
    }

    private boolean isPersistenceContext(FlowDefinition flow) {
        return flow.getAttributes().contains(PERSISTENCE_CONTEXT_ATTRIBUTE);
    }

    private void unbind(Session session) {
		if (TransactionSynchronizationManager.hasResource(localSessionFactory)) {
			TransactionSynchronizationManager.unbindResource(localSessionFactory);
		}
	}

    private Session createSession() {
		Session session = localSessionFactory.openSession();
		session.setFlushMode(FlushMode.MANUAL);
		return session;
	}

    private void obtainCurrentSession(RequestContext context) {
        MutableAttributeMap flowScope = context.getFlowScope();
        if(flowScope.get(HIBERNATE_SESSION_ATTRIBUTE) == null) {
            SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.getResource(localSessionFactory);
            if(sessionHolder!=null)
                flowScope.put(HIBERNATE_SESSION_ATTRIBUTE, sessionHolder.getSession());
        }
    }

    public void resumed(RequestContext context) {
        if (!TransactionSynchronizationManager.hasResource(localSessionFactory)) {
            super.resumed(context);
        }
        else {
            obtainCurrentSession(context);
        }
    }
}

@@ -3288,19 +3288,76 @@
 IBUTE);%0A
-%09%09%09
+            if(hibernateSession.isOpen()) %7B%0A                
 Boolean 
@@ -3428,19 +3428,32 @@
 mmit%22);%0A
-%09%09%09
+                
 if (Bool
@@ -3505,14 +3505,22 @@
     
+    
 try %7B%0A
+    
     
@@ -3624,32 +3624,36 @@
                 
+  
+  
 SessionHolder se
@@ -3766,32 +3766,36 @@
                 
+    
 sessionHolder.ad
@@ -3834,34 +3834,41 @@
                 
-%7D%0A
+    %7D%0A   
                 
@@ -3867,24 +3867,25 @@
             
+ 
 localTransac
@@ -3970,16 +3970,20 @@
         
+    
 protecte
@@ -4070,24 +4070,28 @@
             
+  
+  
 localSession
@@ -4115,24 +4115,28 @@
 tSession();%0A
+    
             
@@ -4265,16 +4265,20 @@
         
+ 
+   
 // trans
@@ -4284,16 +4284,20 @@
 saction%0A
+    
         
@@ -4334,16 +4334,20 @@
         
+    
 %7D);%0A    
@@ -4358,16 +4358,20 @@
         
+  
+  
 %7D finall
@@ -4370,24 +4370,28 @@
 %7D finally %7B%0A
+    
             
@@ -4463,32 +4463,36 @@
 sionFactory)) %7B%0A
+    
                 
@@ -4629,32 +4629,36 @@
                 
+   
+ 
 sessionHolder.re
@@ -4704,24 +4704,28 @@
             
+    
 %7D%0A%0A         
@@ -4723,34 +4723,42 @@
                 
-%7D%0A
+    %7D%0A    
             %7D%0A%09%09
@@ -4755,19 +4755,32 @@
       %7D%0A
-%09%09%09
+                
 unbind(h
@@ -4797,19 +4797,32 @@
 ssion);%0A
-%09%09%09
+                
 hibernat
@@ -4839,16 +4839,30 @@
 lose();%0A
+            %7D%0A
 %09%09%7D%0A    

fix for bug where subflow attempted to close already closed hibernate session