From 1b1ac759ff2e86abad1a25e11b237f3a5a689fa9 Mon Sep 17 00:00:00 2001 From: Keith Donald Date: Wed, 1 Aug 2007 14:31:36 +0000 Subject: [PATCH] enhanced conversation persistence context listeners to support conditional "persistenceContext" and "commit" attributes. improved tests. --- ...bernateSessionPerConversationListener.java | 37 +-- ...teSessionPerConversationListenerTests.java | 265 +++++++++++------- 2 files changed, 190 insertions(+), 112 deletions(-) diff --git a/spring-webflow/src/main/java/org/springframework/webflow/support/persistence/HibernateSessionPerConversationListener.java b/spring-webflow/src/main/java/org/springframework/webflow/support/persistence/HibernateSessionPerConversationListener.java index 7c9e2aff..47021e0e 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/support/persistence/HibernateSessionPerConversationListener.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/support/persistence/HibernateSessionPerConversationListener.java @@ -62,35 +62,36 @@ public class HibernateSessionPerConversationListener extends FlowExecutionListen } public void sessionCreated(RequestContext context, FlowSession session) { - if (session.isRoot()) { + if (session.isRoot() && session.getDefinition().getAttributes().contains("persistenceContext")) { Session hibernateSession = createSession(context); context.getConversationScope().put(HIBERNATE_SESSION_ATTRIBUTE, hibernateSession); - bind(hibernateSession, context); + bind(hibernateSession, context, session); } } public void resumed(RequestContext context) { - Session hibSession = getHibernateSession(context); - bind(hibSession, context); + bind(getHibernateSession(context), context, context.getFlowExecutionContext().getActiveSession()); } public void paused(RequestContext context, ViewSelection selectedView) { - Session session = getHibernateSession(context); - unbind(session, context); + unbind(getHibernateSession(context), context, context.getFlowExecutionContext().getActiveSession()); } public void sessionEnded(RequestContext context, FlowSession session, AttributeMap output) { if (session.isRoot()) { Session hibernateSession = (Session) context.getConversationScope().remove(HIBERNATE_SESSION_ATTRIBUTE); - hibernateSession.flush(); - unbind(hibernateSession, context); - hibernateSession.close(); + Boolean commitStatus = session.getState().getAttributes().getBoolean("commit"); + if (commitStatus == null || commitStatus.equals(Boolean.TRUE)) { + // assume a commit by default and when 'commit' attribute = true + hibernateSession.flush(); + hibernateSession.close(); + } + unbind(hibernateSession, context, session); } } public void exceptionThrown(RequestContext context, FlowExecutionException exception) { - Session session = getHibernateSession(context); - unbind(session, context); + unbind(getHibernateSession(context), context, context.getFlowExecutionContext().getActiveSession()); } // internal helpers @@ -105,18 +106,18 @@ public class HibernateSessionPerConversationListener extends FlowExecutionListen return (Session) context.getConversationScope().get(HIBERNATE_SESSION_ATTRIBUTE); } - private void bind(Session hibSession, RequestContext context) { - SessionHolder sessionHolder = new SessionHolder(hibSession); - if (context.getActiveFlow().getAttributes().getBoolean("transactional").booleanValue() == true) { - Transaction tx = hibSession.beginTransaction(); + private void bind(Session session, RequestContext context, FlowSession flowSession) { + SessionHolder sessionHolder = new SessionHolder(session); + if (flowSession.getDefinition().getAttributes().contains("transactional")) { + Transaction tx = session.beginTransaction(); sessionHolder.setTransaction(tx); } TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder); } - private void unbind(Session hibSession, RequestContext context) { - if (context.getActiveFlow().getAttributes().getBoolean("transactional").booleanValue() == true) { - hibSession.getTransaction().commit(); + private void unbind(Session session, RequestContext context, FlowSession flowSession) { + if (flowSession.getDefinition().getAttributes().contains("transactional")) { + session.getTransaction().commit(); } TransactionSynchronizationManager.unbindResource(sessionFactory); } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/support/persistence/HibernateSessionPerConversationListenerTests.java b/spring-webflow/src/test/java/org/springframework/webflow/support/persistence/HibernateSessionPerConversationListenerTests.java index e28d38a8..3ef6415e 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/support/persistence/HibernateSessionPerConversationListenerTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/support/persistence/HibernateSessionPerConversationListenerTests.java @@ -32,6 +32,9 @@ import org.springframework.jdbc.datasource.DriverManagerDataSource; import org.springframework.orm.hibernate3.HibernateCallback; import org.springframework.orm.hibernate3.HibernateTemplate; import org.springframework.orm.hibernate3.LocalSessionFactoryBean; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.webflow.engine.EndState; +import org.springframework.webflow.execution.FlowExecutionException; import org.springframework.webflow.execution.ViewSelection; import org.springframework.webflow.test.MockFlowSession; import org.springframework.webflow.test.MockRequestContext; @@ -42,114 +45,188 @@ import org.springframework.webflow.test.MockRequestContext; * @author Ben Hale */ public class HibernateSessionPerConversationListenerTests extends TestCase { - - private JdbcTemplate jdbcTemplate; - private HibernateTemplate hibernateTemplate; + private SessionFactory sessionFactory; - private HibernateSessionPerConversationListener listener; + private JdbcTemplate jdbcTemplate; - protected void setUp() throws Exception { - DataSource dataSource = getDataSource(); - populateDataBase(dataSource); - jdbcTemplate = new JdbcTemplate(dataSource); - SessionFactory sessionFactory = getSessionFactory(dataSource); - hibernateTemplate = new HibernateTemplate(sessionFactory); - hibernateTemplate.setCheckWriteOperations(false); - listener = new HibernateSessionPerConversationListener(sessionFactory); - } + private HibernateTemplate hibernateTemplate; - public void testSameSession() { - MockRequestContext context = new MockRequestContext(); - MockFlowSession flowSession = new MockFlowSession(); - listener.sessionCreated(context, flowSession); + private HibernateSessionPerConversationListener listener; - // Session created and bound to conversation - final Session hibSession = (Session) context.getConversationScope().get("hibernate.session"); - assertNotNull("Should have been populated", hibSession); - listener.paused(context, ViewSelection.NULL_VIEW); + protected void setUp() throws Exception { + DataSource dataSource = getDataSource(); + populateDataBase(dataSource); + jdbcTemplate = new JdbcTemplate(dataSource); + sessionFactory = getSessionFactory(dataSource); + hibernateTemplate = new HibernateTemplate(sessionFactory); + hibernateTemplate.setCheckWriteOperations(false); + listener = new HibernateSessionPerConversationListener(sessionFactory); + } - // Session bound to thread local variable - listener.resumed(context); - hibernateTemplate.execute(new HibernateCallback() { - public Object doInHibernate(Session session) throws HibernateException, SQLException { - assertSame("Should have been original instance", hibSession, session); - return null; - } - }, true); - listener.paused(context, ViewSelection.NULL_VIEW); - } + public void testSameSession() { + MockRequestContext context = new MockRequestContext(); + MockFlowSession flowSession = new MockFlowSession(); + flowSession.getDefinitionInternal().getAttributeMap().put("persistenceContext", "true"); + listener.sessionCreated(context, flowSession); + assertSessionBound(); - public void testSingleState() { - assertEquals("Table should only have one row", 1, jdbcTemplate.queryForInt("select count(*) from T_BEAN")); - MockRequestContext context = new MockRequestContext(); - MockFlowSession flowSession = new MockFlowSession(); - listener.sessionCreated(context, flowSession); + // Session created and bound to conversation + final Session hibSession = (Session) context.getConversationScope().get("hibernate.session"); + assertNotNull("Should have been populated", hibSession); + listener.paused(context, ViewSelection.NULL_VIEW); + assertSessionNotBound(); - TestBean bean = new TestBean("Keith Donald"); - hibernateTemplate.save(bean); - assertEquals("Table should still only have one row", 1, jdbcTemplate.queryForInt("select count(*) from T_BEAN")); - - listener.sessionEnded(context, flowSession, null); - assertEquals("Table should only have two rows", 2, jdbcTemplate.queryForInt("select count(*) from T_BEAN")); - } - - public void testMultipleState() { - assertEquals("Table should only have one row", 1, jdbcTemplate.queryForInt("select count(*) from T_BEAN")); - MockRequestContext context = new MockRequestContext(); - MockFlowSession flowSession = new MockFlowSession(); - listener.sessionCreated(context, flowSession); + // Session bound to thread local variable + listener.resumed(context); + assertSessionBound(); - TestBean bean1 = new TestBean("Keith Donald"); - hibernateTemplate.save(bean1); - assertEquals("Table should still only have one row", 1, jdbcTemplate.queryForInt("select count(*) from T_BEAN")); - listener.paused(context, ViewSelection.NULL_VIEW); - - listener.resumed(context); - TestBean bean2 = new TestBean("Keith Donald"); - hibernateTemplate.save(bean2); - assertEquals("Table should still only have one row", 1, jdbcTemplate.queryForInt("select count(*) from T_BEAN")); - - - listener.sessionEnded(context, flowSession, null); - assertEquals("Table should only have two rows", 3, jdbcTemplate.queryForInt("select count(*) from T_BEAN")); - } + hibernateTemplate.execute(new HibernateCallback() { + public Object doInHibernate(Session session) throws HibernateException, SQLException { + assertSame("Should have been original instance", hibSession, session); + return null; + } + }, true); + listener.paused(context, ViewSelection.NULL_VIEW); + assertSessionNotBound(); + } - private DataSource getDataSource() { - DriverManagerDataSource dataSource = new DriverManagerDataSource(); - dataSource.setDriverClassName("org.hsqldb.jdbcDriver"); - dataSource.setUrl("jdbc:hsqldb:mem:hspcl"); - dataSource.setUsername("sa"); - dataSource.setPassword(""); - return dataSource; - } + public void testFlowNotAPersistenceContext() { + MockRequestContext context = new MockRequestContext(); + MockFlowSession flowSession = new MockFlowSession(); + listener.sessionCreated(context, flowSession); + assertSessionNotBound(); + } - private void populateDataBase(DataSource dataSource) { - Connection connection = null; + public void testFlowEndsInSingleRequest() { + assertEquals("Table should only have one row", 1, jdbcTemplate.queryForInt("select count(*) from T_BEAN")); + MockRequestContext context = new MockRequestContext(); + MockFlowSession flowSession = new MockFlowSession(); + flowSession.getDefinitionInternal().getAttributeMap().put("persistenceContext", "true"); + listener.sessionCreated(context, flowSession); + assertSessionBound(); + + TestBean bean = new TestBean("Keith Donald"); + hibernateTemplate.save(bean); + assertEquals("Table should still only have one row", 1, jdbcTemplate.queryForInt("select count(*) from T_BEAN")); + + listener.sessionEnded(context, flowSession, null); + assertEquals("Table should only have two rows", 2, jdbcTemplate.queryForInt("select count(*) from T_BEAN")); + assertSessionNotBound(); + assertFalse(flowSession.getScope().contains("hibernate.session")); + } + + public void testFlowSpansMultipleRequests() { + assertEquals("Table should only have one row", 1, jdbcTemplate.queryForInt("select count(*) from T_BEAN")); + MockRequestContext context = new MockRequestContext(); + MockFlowSession flowSession = new MockFlowSession(); + flowSession.getDefinitionInternal().getAttributeMap().put("persistenceContext", "true"); + listener.sessionCreated(context, flowSession); + assertSessionBound(); + + TestBean bean1 = new TestBean("Keith Donald"); + hibernateTemplate.save(bean1); + assertEquals("Table should still only have one row", 1, jdbcTemplate.queryForInt("select count(*) from T_BEAN")); + listener.paused(context, ViewSelection.NULL_VIEW); + assertSessionNotBound(); + + listener.resumed(context); + TestBean bean2 = new TestBean("Keith Donald"); + hibernateTemplate.save(bean2); + assertEquals("Table should still only have one row", 1, jdbcTemplate.queryForInt("select count(*) from T_BEAN")); + assertSessionBound(); + + listener.sessionEnded(context, flowSession, null); + assertEquals("Table should only have three rows", 3, jdbcTemplate.queryForInt("select count(*) from T_BEAN")); + assertFalse(flowSession.getScope().contains("hibernate.session")); + + assertSessionNotBound(); + assertFalse(flowSession.getScope().contains("hibernate.session")); + + } + + public void testExceptionThrown() { + assertEquals("Table should only have one row", 1, jdbcTemplate.queryForInt("select count(*) from T_BEAN")); + MockRequestContext context = new MockRequestContext(); + MockFlowSession flowSession = new MockFlowSession(); + flowSession.getDefinitionInternal().getAttributeMap().put("persistenceContext", "true"); + listener.sessionCreated(context, flowSession); + assertSessionBound(); + + TestBean bean1 = new TestBean("Keith Donald"); + hibernateTemplate.save(bean1); + assertEquals("Table should still only have one row", 1, jdbcTemplate.queryForInt("select count(*) from T_BEAN")); + listener.exceptionThrown(context, new FlowExecutionException("bla", "bla", "bla")); + assertEquals("Table should still only have one row", 1, jdbcTemplate.queryForInt("select count(*) from T_BEAN")); + assertSessionNotBound(); + + } + + public void testCancelEndState() { + assertEquals("Table should only have one row", 1, jdbcTemplate.queryForInt("select count(*) from T_BEAN")); + MockRequestContext context = new MockRequestContext(); + MockFlowSession flowSession = new MockFlowSession(); + flowSession.getDefinitionInternal().getAttributeMap().put("persistenceContext", "true"); + listener.sessionCreated(context, flowSession); + assertSessionBound(); + + TestBean bean = new TestBean("Keith Donald"); + hibernateTemplate.save(bean); + assertEquals("Table should still only have one row", 1, jdbcTemplate.queryForInt("select count(*) from T_BEAN")); + + EndState endState = new EndState(flowSession.getDefinitionInternal(), "cancel"); + endState.getAttributeMap().put("commit", Boolean.FALSE); + flowSession.setState(endState); + listener.sessionEnded(context, flowSession, null); + assertEquals("Table should only have two rows", 1, jdbcTemplate.queryForInt("select count(*) from T_BEAN")); + assertSessionNotBound(); + assertFalse(flowSession.getScope().contains("hibernate.session")); + } + + private DataSource getDataSource() { + DriverManagerDataSource dataSource = new DriverManagerDataSource(); + dataSource.setDriverClassName("org.hsqldb.jdbcDriver"); + dataSource.setUrl("jdbc:hsqldb:mem:hspcl"); + dataSource.setUsername("sa"); + dataSource.setPassword(""); + return dataSource; + } + + private void populateDataBase(DataSource dataSource) { + Connection connection = null; + try { + connection = dataSource.getConnection(); + connection.createStatement().execute("drop table T_BEAN if exists;"); + connection.createStatement().execute( + "create table T_BEAN (ID integer primary key, NAME varchar(50) not null);"); + connection.createStatement().execute("insert into T_BEAN (ID, NAME) values (0, 'Ben Hale');"); + } catch (SQLException e) { + throw new RuntimeException("SQL exception occurred acquiring connection", e); + } finally { + if (connection != null) { try { - connection = dataSource.getConnection(); - connection.createStatement().execute("drop table T_BEAN if exists;"); - connection.createStatement().execute( - "create table T_BEAN (ID integer primary key, NAME varchar(50) not null);"); - connection.createStatement().execute("insert into T_BEAN (ID, NAME) values (0, 'Ben Hale');"); + connection.close(); } catch (SQLException e) { - throw new RuntimeException("SQL exception occurred acquiring connection", e); - } finally { - if (connection != null) { - try { - connection.close(); - } catch (SQLException e) { - } - } } + } } + } + + private SessionFactory getSessionFactory(DataSource dataSource) throws Exception { + LocalSessionFactoryBean factory = new LocalSessionFactoryBean(); + factory.setDataSource(dataSource); + factory.setMappingLocations(new Resource[] { new ClassPathResource( + "org/springframework/webflow/support/persistence/TestBean.hbm.xml") }); + factory.afterPropertiesSet(); + return (SessionFactory) factory.getObject(); + } + + private void assertSessionNotBound() { + assertNull(TransactionSynchronizationManager.getResource(sessionFactory)); + } + + private void assertSessionBound() { + assertNotNull(TransactionSynchronizationManager.getResource(sessionFactory)); + } - private SessionFactory getSessionFactory(DataSource dataSource) throws Exception { - LocalSessionFactoryBean factory = new LocalSessionFactoryBean(); - factory.setDataSource(dataSource); - factory.setMappingLocations(new Resource[] { new ClassPathResource( - "org/springframework/webflow/support/persistence/TestBean.hbm.xml") }); - factory.afterPropertiesSet(); - return (SessionFactory) factory.getObject(); - } }