diff --git a/spring-webflow/src/main/java/org/springframework/webflow/persistence/HibernateFlowExecutionListener.java b/spring-webflow/src/main/java/org/springframework/webflow/persistence/HibernateFlowExecutionListener.java index 630cc94e..5f16991d 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/persistence/HibernateFlowExecutionListener.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/persistence/HibernateFlowExecutionListener.java @@ -105,15 +105,18 @@ public class HibernateFlowExecutionListener extends FlowExecutionListenerAdapter } public void sessionStarting(RequestContext context, FlowSession session, MutableAttributeMap input) { - if (!session.isRoot()) { - FlowSession parent = session.getParent(); - if (isPersistenceContext(parent.getDefinition())) { - unbind(getHibernateSession(parent)); + boolean reusePersistenceContext = false; + if (isParentPersistenceContext(session)) { + if (isPersistenceContext(session.getDefinition())) { + setHibernateSession(session, getHibernateSession(session.getParent())); + reusePersistenceContext = true; + } else { + unbind(getHibernateSession(session.getParent())); } } - if (isPersistenceContext(session.getDefinition())) { + if (isPersistenceContext(session.getDefinition()) && (!reusePersistenceContext)) { Session hibernateSession = createSession(context); - session.getScope().put(PERSISTENCE_CONTEXT_ATTRIBUTE, hibernateSession); + setHibernateSession(session, hibernateSession); bind(hibernateSession); } } @@ -133,6 +136,9 @@ public class HibernateFlowExecutionListener extends FlowExecutionListenerAdapter } public void sessionEnding(RequestContext context, FlowSession session, String outcome, MutableAttributeMap output) { + if (isParentPersistenceContext(session)) { + return; + } if (isPersistenceContext(session.getDefinition())) { final Session hibernateSession = getHibernateSession(session); Boolean commitStatus = session.getState().getAttributes().getBoolean("commit"); @@ -151,10 +157,9 @@ public class HibernateFlowExecutionListener extends FlowExecutionListenerAdapter } public void sessionEnded(RequestContext context, FlowSession session, String outcome, AttributeMap output) { - if (!session.isRoot()) { - FlowSession parent = session.getParent(); - if (isPersistenceContext(parent.getDefinition())) { - bind(getHibernateSession(parent)); + if (isParentPersistenceContext(session)) { + if (!isPersistenceContext(session.getDefinition())) { + bind(getHibernateSession(session.getParent())); } } } @@ -173,6 +178,10 @@ public class HibernateFlowExecutionListener extends FlowExecutionListenerAdapter return flow.getAttributes().contains(PERSISTENCE_CONTEXT_ATTRIBUTE); } + private boolean isParentPersistenceContext(FlowSession flowSession) { + return ((!flowSession.isRoot()) && isPersistenceContext(flowSession.getParent().getDefinition())); + } + private Session createSession(RequestContext context) { Session session = (entityInterceptor != null ? sessionFactory.openSession(entityInterceptor) : sessionFactory .openSession()); @@ -184,6 +193,10 @@ public class HibernateFlowExecutionListener extends FlowExecutionListenerAdapter return (Session) session.getScope().get(PERSISTENCE_CONTEXT_ATTRIBUTE); } + private void setHibernateSession(FlowSession session, Session hibernateSession) { + session.getScope().put(PERSISTENCE_CONTEXT_ATTRIBUTE, hibernateSession); + } + private void bind(Session session) { TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session)); } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/persistence/JpaFlowExecutionListener.java b/spring-webflow/src/main/java/org/springframework/webflow/persistence/JpaFlowExecutionListener.java index d1b0701b..51e72a8b 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/persistence/JpaFlowExecutionListener.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/persistence/JpaFlowExecutionListener.java @@ -95,13 +95,16 @@ public class JpaFlowExecutionListener extends FlowExecutionListenerAdapter { } public void sessionStarting(RequestContext context, FlowSession session, MutableAttributeMap input) { - if (!session.isRoot()) { - FlowSession parent = session.getParent(); - if (isPersistenceContext(parent.getDefinition())) { - unbind(getEntityManager(parent)); + boolean reusePersistenceContext = false; + if (isParentPersistenceContext(session)) { + if (isPersistenceContext(session.getDefinition())) { + setEntityManager(session, getEntityManager(session.getParent())); + reusePersistenceContext = true; + } else { + unbind(getEntityManager(session.getParent())); } } - if (isPersistenceContext(session.getDefinition())) { + if (isPersistenceContext(session.getDefinition()) && (!reusePersistenceContext)) { EntityManager em = entityManagerFactory.createEntityManager(); session.getScope().put(PERSISTENCE_CONTEXT_ATTRIBUTE, em); bind(em); @@ -121,6 +124,9 @@ public class JpaFlowExecutionListener extends FlowExecutionListenerAdapter { } public void sessionEnding(RequestContext context, FlowSession session, String outcome, MutableAttributeMap output) { + if (isParentPersistenceContext(session)) { + return; + } if (isPersistenceContext(session.getDefinition())) { final EntityManager em = getEntityManager(session); Boolean commitStatus = session.getState().getAttributes().getBoolean("commit"); @@ -137,10 +143,9 @@ public class JpaFlowExecutionListener extends FlowExecutionListenerAdapter { } public void sessionEnded(RequestContext context, FlowSession session, String outcome, AttributeMap output) { - if (!session.isRoot()) { - FlowSession parent = session.getParent(); - if (isPersistenceContext(parent.getDefinition())) { - bind(getEntityManager(parent)); + if (isParentPersistenceContext(session)) { + if (!isPersistenceContext(session.getDefinition())) { + bind(getEntityManager(session.getParent())); } } } @@ -159,10 +164,18 @@ public class JpaFlowExecutionListener extends FlowExecutionListenerAdapter { return flow.getAttributes().contains(PERSISTENCE_CONTEXT_ATTRIBUTE); } + private boolean isParentPersistenceContext(FlowSession flowSession) { + return ((!flowSession.isRoot()) && isPersistenceContext(flowSession.getParent().getDefinition())); + } + private EntityManager getEntityManager(FlowSession session) { return (EntityManager) session.getScope().get(PERSISTENCE_CONTEXT_ATTRIBUTE); } + private void setEntityManager(FlowSession session, EntityManager em) { + session.getScope().put(PERSISTENCE_CONTEXT_ATTRIBUTE, em); + } + private void bind(EntityManager em) { TransactionSynchronizationManager.bindResource(entityManagerFactory, new EntityManagerHolder(em)); } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/persistence/AbstractFlowManagedPersistenceIntegrationTests.java b/spring-webflow/src/test/java/org/springframework/webflow/persistence/AbstractFlowManagedPersistenceIntegrationTests.java new file mode 100644 index 00000000..6e759a36 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/persistence/AbstractFlowManagedPersistenceIntegrationTests.java @@ -0,0 +1,142 @@ +package org.springframework.webflow.persistence; + +import java.sql.Connection; +import java.sql.SQLException; + +import javax.sql.DataSource; + +import junit.framework.TestCase; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.jdbc.datasource.DriverManagerDataSource; +import org.springframework.webflow.engine.EndState; +import org.springframework.webflow.engine.Flow; +import org.springframework.webflow.engine.builder.FlowAssembler; +import org.springframework.webflow.engine.builder.model.FlowModelFlowBuilder; +import org.springframework.webflow.engine.impl.FlowExecutionImplFactory; +import org.springframework.webflow.engine.model.builder.DefaultFlowModelHolder; +import org.springframework.webflow.engine.model.builder.xml.XmlFlowModelBuilder; +import org.springframework.webflow.execution.Action; +import org.springframework.webflow.execution.FlowExecution; +import org.springframework.webflow.execution.FlowExecutionListener; +import org.springframework.webflow.execution.factory.StaticFlowExecutionListenerLoader; +import org.springframework.webflow.test.MockExternalContext; +import org.springframework.webflow.test.MockFlowBuilderContext; + +public abstract class AbstractFlowManagedPersistenceIntegrationTests extends TestCase { + + private FlowExecutionListener persistenceListener; + + private FlowExecution flowExecution; + + private DataSource dataSource; + + public DataSource getDataSource() { + return dataSource; + } + + protected void setUp() throws Exception { + initDataSource(); + populateDataBase(); + persistenceListener = createFlowExecutionListener(); + + ClassPathResource resource = new ClassPathResource("managed-root-flow.xml", getClass()); + DefaultFlowModelHolder holder = new DefaultFlowModelHolder(new XmlFlowModelBuilder(resource)); + FlowModelFlowBuilder builder = new FlowModelFlowBuilder(holder); + MockFlowBuilderContext context = new MockFlowBuilderContext("managed-root-flow"); + FlowAssembler assembler = new FlowAssembler(builder, context); + Flow rootFlow = assembler.assembleFlow(); + + ClassPathResource childFlowResource = new ClassPathResource("managed-child-flow.xml", getClass()); + DefaultFlowModelHolder childFlowHolder = new DefaultFlowModelHolder(new XmlFlowModelBuilder(childFlowResource)); + FlowModelFlowBuilder childFlowBuilder = new FlowModelFlowBuilder(childFlowHolder); + MockFlowBuilderContext childFlowContext = new MockFlowBuilderContext("managed-child-flow"); + FlowAssembler childFlowAssembler = new FlowAssembler(childFlowBuilder, childFlowContext); + Flow childFlow = childFlowAssembler.assembleFlow(); + + Flow notManaged = new Flow("notmanaged-child-flow"); + new EndState(notManaged, "finish"); + + context.registerSubflow(childFlow); + context.registerSubflow(notManaged); + + Action incrementCountAction = incrementCountAction(); + context.registerBean("incrementCountAction", incrementCountAction); + childFlowContext.registerBean("incrementCountAction", incrementCountAction); + + Object assertCountAction = assertCountAction(); + context.registerBean("assertCountAction", assertCountAction); + childFlowContext.registerBean("assertCountAction", assertCountAction); + + FlowExecutionImplFactory factory = new FlowExecutionImplFactory(); + factory.setExecutionListenerLoader(new StaticFlowExecutionListenerLoader(persistenceListener)); + flowExecution = factory.createFlowExecution(rootFlow); + } + + public void testFlowWithSubflow() { + MockExternalContext context = new MockExternalContext(); + flowExecution.start(null, context); + context.setEventId("managed"); + flowExecution.resume(context); + context.setEventId("finish"); + flowExecution.resume(context); + } + + public void testManagedFlowWithUnmanagedSubflow() { + MockExternalContext context = new MockExternalContext(); + flowExecution.start(null, context); + context.setEventId("notmanaged"); + flowExecution.resume(context); + } + + /* Methods for subclasses */ + + protected abstract FlowExecutionListener createFlowExecutionListener() throws Exception; + + protected abstract Action incrementCountAction(); + + protected abstract Object assertCountAction(); + + protected abstract void assertSessionBound(); + + /* private helper methods */ + + private void initDataSource() { + DriverManagerDataSource dmds = new DriverManagerDataSource(); + dmds.setDriverClassName("org.hsqldb.jdbcDriver"); + dmds.setUrl("jdbc:hsqldb:mem:jpa"); + dmds.setUsername("sa"); + dmds.setPassword(""); + dataSource = dmds; + } + + private void populateDataBase() { + Connection connection = null; + try { + connection = dataSource.getConnection(); + connection.createStatement().execute("drop table T_ADDRESS if exists;"); + 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, COUNTER integer);"); + connection.createStatement().execute( + "create table T_ADDRESS (ID integer primary key, BEAN_ID integer, VALUE varchar(50) not null);"); + connection + .createStatement() + .execute( + "alter table T_ADDRESS add constraint FK_BEAN_ADDRESS foreign key (BEAN_ID) references T_BEAN(ID) on delete cascade"); + connection.createStatement().execute("insert into T_BEAN (ID, NAME, COUNTER) values (0, 'Ben Hale',0);"); + connection.createStatement().execute( + "insert into T_ADDRESS (ID, BEAN_ID, VALUE) values (0, 0, 'Melbourne')"); + } catch (SQLException e) { + throw new RuntimeException("SQL exception occurred acquiring connection", e); + } finally { + if (connection != null) { + try { + connection.close(); + } catch (SQLException e) { + } + } + } + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/persistence/AbstractPersistenceContextPropagationTests.java b/spring-webflow/src/test/java/org/springframework/webflow/persistence/AbstractPersistenceContextPropagationTests.java new file mode 100644 index 00000000..b878b4eb --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/persistence/AbstractPersistenceContextPropagationTests.java @@ -0,0 +1,193 @@ +/* + * Copyright 2004-2010 the original author or authors. + * + * 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.springframework.webflow.persistence; + +import java.sql.Connection; +import java.sql.SQLException; + +import javax.sql.DataSource; + +import junit.framework.TestCase; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DriverManagerDataSource; +import org.springframework.webflow.engine.EndState; +import org.springframework.webflow.execution.FlowExecutionListener; +import org.springframework.webflow.execution.FlowSession; +import org.springframework.webflow.test.MockFlowSession; +import org.springframework.webflow.test.MockRequestContext; + +public abstract class AbstractPersistenceContextPropagationTests extends TestCase { + + private MockRequestContext requestContext; + + private JdbcTemplate jdbcTemplate; + + public JdbcTemplate getJdbcTemplate() { + return jdbcTemplate; + } + + protected final void setUp() throws Exception { + requestContext = new MockRequestContext(); + DataSource dataSource = getDataSource(); + jdbcTemplate = new JdbcTemplate(dataSource); + populateDataBase(dataSource); + setUpResources(dataSource); + } + + public void testSessionStarting_NoPc_ParentPc() { + MockFlowSession parentSession = newFlowSession(true, null); + MockFlowSession childSession = newFlowSession(false, parentSession); + + getListener().sessionStarting(new MockRequestContext(), parentSession, null); + assertSessionBound(); + assertSessionInScope(parentSession); + + getListener().sessionStarting(new MockRequestContext(), childSession, null); + assertSessionNotBound(); + assertSessionNotInScope(childSession); + } + + public void testSessionStarting_Pc_ParentPc() { + MockFlowSession parentSession = newFlowSession(true, null); + MockFlowSession childSession = newFlowSession(true, parentSession); + + getListener().sessionStarting(new MockRequestContext(), parentSession, null); + assertSessionBound(); + assertSessionInScope(parentSession); + + getListener().sessionStarting(new MockRequestContext(), childSession, null); + assertSessionBound(); + assertSessionInScope(childSession); + assertSame("Parent PersistenceContext should be re-used", parentSession.getScope().get("persistenceContext"), + childSession.getScope().get("persistenceContext")); + } + + public void testSessionEnd_Pc_NoParentPc() { + MockFlowSession parentSession = newFlowSession(false, null); + MockFlowSession childSession = newFlowSession(true, parentSession); + + getListener().sessionStarting(requestContext, parentSession, null); + getListener().sessionStarting(requestContext, childSession, null); + + assertCommitState(true, false); + + requestContext.setActiveSession(childSession); + + // Session ending commits, unbinds/closes PersistenceContext + getListener().sessionEnding(requestContext, childSession, "success", null); + assertSessionNotBound(); + + // sessionEnded has no effect + getListener().sessionEnded(requestContext, childSession, "success", null); + assertSessionNotBound(); + assertCommitState(false, true); + } + + public void testSessionEnd_Pc_ParentPc() { + MockFlowSession parentSession = newFlowSession(true, null); + MockFlowSession childSession = newFlowSession(true, parentSession); + + getListener().sessionStarting(requestContext, parentSession, null); + getListener().sessionStarting(requestContext, childSession, null); + + assertCommitState(true, false); + + requestContext.setActiveSession(childSession); + + // sessionEnding is a no-op + getListener().sessionEnding(requestContext, childSession, "success", null); + assertSessionBound(); + assertCommitState(true, false); + + // sessionEnded binds Parent PersistenceContext + getListener().sessionEnded(requestContext, childSession, "success", null); + assertSessionBound(); + } + + private MockFlowSession newFlowSession(boolean persistenceContext, FlowSession parent) { + MockFlowSession flowSession = new MockFlowSession(); + flowSession.setParent(parent); + if (persistenceContext) { + flowSession.getDefinition().getAttributes().put("persistenceContext", "true"); + } + EndState endState = new EndState(flowSession.getDefinitionInternal(), "success"); + endState.getAttributes().put("commit", Boolean.TRUE); + flowSession.setState(endState); + return flowSession; + } + + 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_ADDRESS if exists;"); + 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( + "create table T_ADDRESS (ID integer primary key, BEAN_ID integer, VALUE varchar(50) not null);"); + connection + .createStatement() + .execute( + "alter table T_ADDRESS add constraint FK_BEAN_ADDRESS foreign key (BEAN_ID) references T_BEAN(ID) on delete cascade"); + connection.createStatement().execute("insert into T_BEAN (ID, NAME) values (0, 'Ben Hale');"); + connection.createStatement().execute( + "insert into T_ADDRESS (ID, BEAN_ID, VALUE) values (0, 0, 'Melbourne')"); + } catch (SQLException e) { + throw new RuntimeException("SQL exception occurred acquiring connection", e); + } finally { + if (connection != null) { + try { + connection.close(); + } catch (SQLException e) { + } + } + } + } + + /* methods for subclasses */ + + protected abstract void setUpResources(DataSource dataSource) throws Exception; + + protected abstract FlowExecutionListener getListener(); + + protected abstract void assertSessionBound(); + + protected abstract void assertSessionNotBound(); + + protected abstract void assertCommitState(boolean b, boolean c); + + /* private helper methods */ + + private void assertSessionInScope(FlowSession session) { + assertTrue(session.getScope().contains("persistenceContext")); + } + + private void assertSessionNotInScope(FlowSession session) { + assertFalse(session.getScope().contains("persistenceContext")); + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/persistence/HibernateFlowExecutionListenerTests.java b/spring-webflow/src/test/java/org/springframework/webflow/persistence/HibernateFlowExecutionListenerTests.java index 10874c17..bf3f6883 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/persistence/HibernateFlowExecutionListenerTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/persistence/HibernateFlowExecutionListenerTests.java @@ -122,7 +122,6 @@ public class HibernateFlowExecutionListenerTests extends TestCase { hibernateListener.sessionEnded(context, flowSession, "success", 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 testFlowCommitsAfterMultipleRequests() { @@ -153,11 +152,8 @@ public class HibernateFlowExecutionListenerTests extends TestCase { hibernateListener.sessionEnding(context, flowSession, "success", null); hibernateListener.sessionEnded(context, flowSession, "success", 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 testCancelEndState() { @@ -180,7 +176,6 @@ public class HibernateFlowExecutionListenerTests extends TestCase { hibernateListener.sessionEnded(context, flowSession, "cancel", null); assertEquals("Table should only have two rows", 1, jdbcTemplate.queryForInt("select count(*) from T_BEAN")); assertSessionNotBound(); - assertFalse(flowSession.getScope().contains("hibernate.session")); } public void testNoCommitAttributeSetOnEndState() { @@ -198,10 +193,8 @@ public class HibernateFlowExecutionListenerTests extends TestCase { hibernateListener.sessionEnding(context, flowSession, "success", null); hibernateListener.sessionEnded(context, flowSession, "cancel", null); assertEquals("Table should only have three rows", 1, jdbcTemplate.queryForInt("select count(*) from T_BEAN")); - assertFalse(flowSession.getScope().contains("hibernate.session")); assertSessionNotBound(); - assertFalse(flowSession.getScope().contains("hibernate.session")); } public void testExceptionThrown() { diff --git a/spring-webflow/src/test/java/org/springframework/webflow/persistence/HibernateFlowManagedPersistenceIntegrationTests.java b/spring-webflow/src/test/java/org/springframework/webflow/persistence/HibernateFlowManagedPersistenceIntegrationTests.java index d17b6184..75aab0d9 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/persistence/HibernateFlowManagedPersistenceIntegrationTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/persistence/HibernateFlowManagedPersistenceIntegrationTests.java @@ -1,134 +1,64 @@ package org.springframework.webflow.persistence; -import java.sql.Connection; -import java.sql.SQLException; - import javax.sql.DataSource; -import junit.framework.TestCase; - import org.hibernate.Session; import org.hibernate.SessionFactory; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; -import org.springframework.jdbc.datasource.DriverManagerDataSource; -import org.springframework.orm.hibernate3.HibernateTemplate; import org.springframework.orm.hibernate3.HibernateTransactionManager; import org.springframework.orm.hibernate3.LocalSessionFactoryBean; import org.springframework.transaction.support.TransactionSynchronizationManager; -import org.springframework.webflow.engine.EndState; -import org.springframework.webflow.engine.Flow; -import org.springframework.webflow.engine.builder.FlowAssembler; -import org.springframework.webflow.engine.builder.model.FlowModelFlowBuilder; -import org.springframework.webflow.engine.impl.FlowExecutionImplFactory; -import org.springframework.webflow.engine.model.builder.DefaultFlowModelHolder; -import org.springframework.webflow.engine.model.builder.xml.XmlFlowModelBuilder; import org.springframework.webflow.execution.Action; import org.springframework.webflow.execution.Event; -import org.springframework.webflow.execution.FlowExecution; +import org.springframework.webflow.execution.FlowExecutionListener; import org.springframework.webflow.execution.RequestContext; -import org.springframework.webflow.execution.factory.StaticFlowExecutionListenerLoader; -import org.springframework.webflow.test.MockExternalContext; -import org.springframework.webflow.test.MockFlowBuilderContext; -public class HibernateFlowManagedPersistenceIntegrationTests extends TestCase { +public class HibernateFlowManagedPersistenceIntegrationTests extends AbstractFlowManagedPersistenceIntegrationTests { private SessionFactory sessionFactory; - private HibernateTemplate hibernateTemplate; - - private HibernateFlowExecutionListener hibernateListener; - - private FlowExecution flowExecution; - - protected void setUp() throws Exception { - DataSource dataSource = getDataSource(); - populateDataBase(dataSource); - sessionFactory = getSessionFactory(dataSource); - hibernateTemplate = new HibernateTemplate(sessionFactory); - hibernateTemplate.setCheckWriteOperations(false); + @Override + protected FlowExecutionListener createFlowExecutionListener() throws Exception { + sessionFactory = getSessionFactory(getDataSource()); HibernateTransactionManager tm = new HibernateTransactionManager(sessionFactory); - hibernateListener = new HibernateFlowExecutionListener(sessionFactory, tm); + return new HibernateFlowExecutionListener(sessionFactory, tm); + } - ClassPathResource res = new ClassPathResource("flow-managed-persistence.xml", getClass()); - DefaultFlowModelHolder holder = new DefaultFlowModelHolder(new XmlFlowModelBuilder(res)); - FlowModelFlowBuilder builder = new FlowModelFlowBuilder(holder); - MockFlowBuilderContext context = new MockFlowBuilderContext("foo"); - FlowAssembler assembler = new FlowAssembler(builder, context); - Flow flow = assembler.assembleFlow(); - context.registerSubflow(flow); - Flow notManaged = new Flow("notmanaged"); - new EndState(notManaged, "finish"); - context.registerSubflow(notManaged); - context.registerBean("loadTestBean", new Action() { + @Override + protected Action incrementCountAction() { + return new Action() { public Event execute(RequestContext context) throws Exception { assertSessionBound(); Session session = (Session) context.getFlowScope().get("persistenceContext"); TestBean bean = (TestBean) session.get(TestBean.class, new Long(0)); - assertNotNull(bean); - context.getFlowScope().put("testBean", bean); + bean.incrementCount(); return new Event(this, "success"); } - }); - FlowExecutionImplFactory factory = new FlowExecutionImplFactory(); - factory.setExecutionListenerLoader(new StaticFlowExecutionListenerLoader(hibernateListener)); - flowExecution = factory.createFlowExecution(flow); + }; } - public void testFlowWithSubflow() { - MockExternalContext context = new MockExternalContext(); - flowExecution.start(null, context); - context.setEventId("subflow"); - flowExecution.resume(context); - context.setEventId("finish"); - flowExecution.resume(context); - } - - public void testManagedFlowWithUnmanagedSubflow() { - MockExternalContext context = new MockExternalContext(); - flowExecution.start(null, context); - context.setEventId("notmanaged"); - flowExecution.resume(context); - } - - private DataSource getDataSource() { - DriverManagerDataSource dataSource = new DriverManagerDataSource(); - dataSource.setDriverClassName("org.hsqldb.jdbcDriver"); - dataSource.setUrl("jdbc:hsqldb:mem:jpa"); - 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_ADDRESS if exists;"); - 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( - "create table T_ADDRESS (ID integer primary key, BEAN_ID integer, VALUE varchar(50) not null);"); - connection - .createStatement() - .execute( - "alter table T_ADDRESS add constraint FK_BEAN_ADDRESS foreign key (BEAN_ID) references T_BEAN(ID) on delete cascade"); - connection.createStatement().execute("insert into T_BEAN (ID, NAME) values (0, 'Ben Hale');"); - connection.createStatement().execute( - "insert into T_ADDRESS (ID, BEAN_ID, VALUE) values (0, 0, 'Melbourne')"); - } catch (SQLException e) { - throw new RuntimeException("SQL exception occurred acquiring connection", e); - } finally { - if (connection != null) { - try { - connection.close(); - } catch (SQLException e) { - } + @Override + protected Object assertCountAction() { + return new Object() { + @SuppressWarnings("unused") + public void execute(RequestContext context, int expected) throws Exception { + assertSessionBound(); + Session session = (Session) context.getFlowScope().get("persistenceContext"); + TestBean bean = (TestBean) session.get(TestBean.class, new Long(0)); + assertEquals(expected, bean.getCount()); } - } + }; } + @Override + protected void assertSessionBound() { + assertNotNull(TransactionSynchronizationManager.getResource(sessionFactory)); + } + + /* private helper methods */ + + @SuppressWarnings("cast") private SessionFactory getSessionFactory(DataSource dataSource) throws Exception { LocalSessionFactoryBean factory = new LocalSessionFactoryBean(); factory.setDataSource(dataSource); @@ -139,7 +69,4 @@ public class HibernateFlowManagedPersistenceIntegrationTests extends TestCase { return (SessionFactory) factory.getObject(); } - private void assertSessionBound() { - assertNotNull(TransactionSynchronizationManager.getResource(sessionFactory)); - } } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/persistence/HibernatePersistenceContextPropagationTests.java b/spring-webflow/src/test/java/org/springframework/webflow/persistence/HibernatePersistenceContextPropagationTests.java new file mode 100644 index 00000000..32b050eb --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/persistence/HibernatePersistenceContextPropagationTests.java @@ -0,0 +1,74 @@ +package org.springframework.webflow.persistence; + +import javax.sql.DataSource; + +import org.hibernate.SessionFactory; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.orm.hibernate3.HibernateTemplate; +import org.springframework.orm.hibernate3.HibernateTransactionManager; +import org.springframework.orm.hibernate3.LocalSessionFactoryBean; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.webflow.execution.FlowExecutionListener; + +public class HibernatePersistenceContextPropagationTests extends AbstractPersistenceContextPropagationTests { + + private SessionFactory sessionFactory; + + private HibernateTemplate hibernateTemplate; + + private HibernateFlowExecutionListener executionListener; + + private int rowCount; + + @Override + protected void setUpResources(DataSource dataSource) throws Exception { + sessionFactory = getSessionFactory(dataSource); + hibernateTemplate = new HibernateTemplate(sessionFactory); + hibernateTemplate.setCheckWriteOperations(false); + HibernateTransactionManager tm = new HibernateTransactionManager(sessionFactory); + executionListener = new HibernateFlowExecutionListener(sessionFactory, tm); + rowCount = 1; + } + + @Override + protected FlowExecutionListener getListener() { + return executionListener; + } + + @Override + protected void assertSessionNotBound() { + assertNull(TransactionSynchronizationManager.getResource(sessionFactory)); + } + + @Override + protected void assertSessionBound() { + assertNotNull(TransactionSynchronizationManager.getResource(sessionFactory)); + } + + @Override + protected void assertCommitState(boolean insertRow, boolean isCommited) { + if (insertRow) { + hibernateTemplate.save(new TestBean(rowCount++, "Keith Donald")); + } + if (!isCommited) { + assertEquals("Nothing should be committed yet", 1, + getJdbcTemplate().queryForInt("select count(*) from T_BEAN")); + } else { + assertEquals("All rows should be committed", rowCount, + getJdbcTemplate().queryForInt("select count(*) from T_BEAN")); + } + } + + @SuppressWarnings("cast") + private SessionFactory getSessionFactory(DataSource dataSource) throws Exception { + LocalSessionFactoryBean factory = new LocalSessionFactoryBean(); + factory.setDataSource(dataSource); + factory.setMappingLocations(new Resource[] { + new ClassPathResource("org/springframework/webflow/persistence/TestBean.hbm.xml"), + new ClassPathResource("org/springframework/webflow/persistence/TestAddress.hbm.xml") }); + factory.afterPropertiesSet(); + return (SessionFactory) factory.getObject(); + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/persistence/JpaFlowExecutionListenerTests.java b/spring-webflow/src/test/java/org/springframework/webflow/persistence/JpaFlowExecutionListenerTests.java index 42d1c6d9..3d6d5835 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/persistence/JpaFlowExecutionListenerTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/persistence/JpaFlowExecutionListenerTests.java @@ -72,7 +72,6 @@ public class JpaFlowExecutionListenerTests extends TestCase { jpaListener.sessionEnded(context, flowSession, "success", 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 testFlowCommitsAfterMultipleRequests() { @@ -103,10 +102,8 @@ public class JpaFlowExecutionListenerTests extends TestCase { jpaListener.sessionEnding(context, flowSession, "success", null); jpaListener.sessionEnded(context, flowSession, "success", 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 testCancelEndState() { @@ -129,7 +126,6 @@ public class JpaFlowExecutionListenerTests extends TestCase { jpaListener.sessionEnded(context, flowSession, "success", null); assertEquals("Table should only have two rows", 1, jdbcTemplate.queryForInt("select count(*) from T_BEAN")); assertSessionNotBound(); - assertFalse(flowSession.getScope().contains("hibernate.session")); } public void testNoCommitAttributeSetOnEndState() { @@ -147,10 +143,8 @@ public class JpaFlowExecutionListenerTests extends TestCase { jpaListener.sessionEnding(context, flowSession, "cancel", null); jpaListener.sessionEnded(context, flowSession, "success", null); assertEquals("Table should only have three rows", 1, jdbcTemplate.queryForInt("select count(*) from T_BEAN")); - assertFalse(flowSession.getScope().contains("hibernate.session")); assertSessionNotBound(); - assertFalse(flowSession.getScope().contains("hibernate.session")); } public void testExceptionThrown() { diff --git a/spring-webflow/src/test/java/org/springframework/webflow/persistence/JpaFlowManagedPersistenceIntegrationTests.java b/spring-webflow/src/test/java/org/springframework/webflow/persistence/JpaFlowManagedPersistenceIntegrationTests.java index 9a9ce9ad..055c65e4 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/persistence/JpaFlowManagedPersistenceIntegrationTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/persistence/JpaFlowManagedPersistenceIntegrationTests.java @@ -1,129 +1,64 @@ package org.springframework.webflow.persistence; -import java.sql.Connection; -import java.sql.SQLException; - import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; -import junit.framework.TestCase; - -import org.springframework.core.io.ClassPathResource; -import org.springframework.jdbc.datasource.DriverManagerDataSource; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.vendor.OpenJpaVendorAdapter; import org.springframework.transaction.support.TransactionSynchronizationManager; -import org.springframework.webflow.engine.EndState; -import org.springframework.webflow.engine.Flow; -import org.springframework.webflow.engine.builder.FlowAssembler; -import org.springframework.webflow.engine.builder.model.FlowModelFlowBuilder; -import org.springframework.webflow.engine.impl.FlowExecutionImplFactory; -import org.springframework.webflow.engine.model.builder.DefaultFlowModelHolder; -import org.springframework.webflow.engine.model.builder.xml.XmlFlowModelBuilder; import org.springframework.webflow.execution.Action; import org.springframework.webflow.execution.Event; -import org.springframework.webflow.execution.FlowExecution; +import org.springframework.webflow.execution.FlowExecutionListener; import org.springframework.webflow.execution.RequestContext; -import org.springframework.webflow.execution.factory.StaticFlowExecutionListenerLoader; -import org.springframework.webflow.test.MockExternalContext; -import org.springframework.webflow.test.MockFlowBuilderContext; -public class JpaFlowManagedPersistenceIntegrationTests extends TestCase { +public class JpaFlowManagedPersistenceIntegrationTests extends AbstractFlowManagedPersistenceIntegrationTests { private EntityManagerFactory entityManagerFactory; - private JpaFlowExecutionListener jpaListener; - - private FlowExecution flowExecution; - - protected void setUp() throws Exception { - DataSource dataSource = getDataSource(); - populateDataBase(dataSource); - entityManagerFactory = getEntityManagerFactory(dataSource); + @Override + protected FlowExecutionListener createFlowExecutionListener() throws Exception { + entityManagerFactory = getEntityManagerFactory(getDataSource()); JpaTransactionManager tm = new JpaTransactionManager(entityManagerFactory); - jpaListener = new JpaFlowExecutionListener(entityManagerFactory, tm); + return new JpaFlowExecutionListener(entityManagerFactory, tm); + } - ClassPathResource res = new ClassPathResource("flow-managed-persistence.xml", getClass()); - DefaultFlowModelHolder holder = new DefaultFlowModelHolder(new XmlFlowModelBuilder(res)); - FlowModelFlowBuilder builder = new FlowModelFlowBuilder(holder); - MockFlowBuilderContext context = new MockFlowBuilderContext("foo"); - FlowAssembler assembler = new FlowAssembler(builder, context); - Flow flow = assembler.assembleFlow(); - context.registerSubflow(flow); - Flow notManaged = new Flow("notmanaged"); - new EndState(notManaged, "finish"); - context.registerSubflow(notManaged); - context.registerBean("loadTestBean", new Action() { + @Override + protected Action incrementCountAction() { + return new Action() { + @SuppressWarnings("cast") public Event execute(RequestContext context) throws Exception { assertSessionBound(); EntityManager em = (EntityManager) context.getFlowScope().get("persistenceContext"); TestBean bean = (TestBean) em.getReference(TestBean.class, new Integer(0)); + bean.incrementCount(); assertNotNull(bean); - context.getFlowScope().put("testBean", bean); return new Event(this, "success"); } - }); - FlowExecutionImplFactory factory = new FlowExecutionImplFactory(); - factory.setExecutionListenerLoader(new StaticFlowExecutionListenerLoader(jpaListener)); - flowExecution = factory.createFlowExecution(flow); + }; } - public void testManagedFlowWithManagedSubflow() { - MockExternalContext context = new MockExternalContext(); - flowExecution.start(null, context); - context.setEventId("subflow"); - flowExecution.resume(context); - context.setEventId("finish"); - flowExecution.resume(context); - } - - public void testManagedFlowWithUnmanagedSubflow() { - MockExternalContext context = new MockExternalContext(); - flowExecution.start(null, context); - context.setEventId("notmanaged"); - flowExecution.resume(context); - } - - private DataSource getDataSource() { - DriverManagerDataSource dataSource = new DriverManagerDataSource(); - dataSource.setDriverClassName("org.hsqldb.jdbcDriver"); - dataSource.setUrl("jdbc:hsqldb:mem:jpa"); - 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_ADDRESS if exists;"); - 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( - "create table T_ADDRESS (ID integer primary key, BEAN_ID integer, VALUE varchar(50) not null);"); - connection - .createStatement() - .execute( - "alter table T_ADDRESS add constraint FK_BEAN_ADDRESS foreign key (BEAN_ID) references T_BEAN(ID) on delete cascade"); - connection.createStatement().execute("insert into T_BEAN (ID, NAME) values (0, 'Ben Hale');"); - connection.createStatement().execute( - "insert into T_ADDRESS (ID, BEAN_ID, VALUE) values (0, 0, 'Melbourne')"); - } catch (SQLException e) { - throw new RuntimeException("SQL exception occurred acquiring connection", e); - } finally { - if (connection != null) { - try { - connection.close(); - } catch (SQLException e) { - } + @Override + protected Object assertCountAction() { + return new Object() { + @SuppressWarnings({ "unused", "cast" }) + public void execute(RequestContext context, int expected) throws Exception { + assertSessionBound(); + EntityManager em = (EntityManager) context.getFlowScope().get("persistenceContext"); + TestBean bean = (TestBean) em.getReference(TestBean.class, new Integer(0)); + assertEquals(expected, bean.getCount()); } - } + }; } + @Override + protected void assertSessionBound() { + assertNotNull(TransactionSynchronizationManager.getResource(entityManagerFactory)); + } + + /* private helper methods */ + private EntityManagerFactory getEntityManagerFactory(DataSource dataSource) throws Exception { LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean(); factory.setDataSource(dataSource); @@ -134,7 +69,4 @@ public class JpaFlowManagedPersistenceIntegrationTests extends TestCase { return factory.getObject(); } - private void assertSessionBound() { - assertNotNull(TransactionSynchronizationManager.getResource(entityManagerFactory)); - } } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/persistence/JpaPersistenceContextPropagationTests.java b/spring-webflow/src/test/java/org/springframework/webflow/persistence/JpaPersistenceContextPropagationTests.java new file mode 100644 index 00000000..588d7920 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/persistence/JpaPersistenceContextPropagationTests.java @@ -0,0 +1,71 @@ +package org.springframework.webflow.persistence; + +import javax.persistence.EntityManagerFactory; +import javax.sql.DataSource; + +import org.springframework.orm.jpa.JpaTemplate; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.orm.jpa.vendor.OpenJpaVendorAdapter; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.webflow.execution.FlowExecutionListener; + +public class JpaPersistenceContextPropagationTests extends AbstractPersistenceContextPropagationTests { + + private EntityManagerFactory entityManagerFactory; + + private JpaFlowExecutionListener executionListener; + + private JpaTemplate jpaTemplate; + + private int rowCount; + + @Override + protected void setUpResources(DataSource dataSource) throws Exception { + entityManagerFactory = getEntityManagerFactory(dataSource); + JpaTransactionManager tm = new JpaTransactionManager(entityManagerFactory); + executionListener = new JpaFlowExecutionListener(entityManagerFactory, tm); + jpaTemplate = new JpaTemplate(entityManagerFactory); + rowCount = 1; + } + + @Override + protected FlowExecutionListener getListener() { + return executionListener; + } + + @Override + protected void assertSessionNotBound() { + assertNull(TransactionSynchronizationManager.getResource(entityManagerFactory)); + } + + @Override + protected void assertSessionBound() { + assertNotNull(TransactionSynchronizationManager.getResource(entityManagerFactory)); + } + + @Override + protected void assertCommitState(boolean insertRow, boolean isCommited) { + if (insertRow) { + jpaTemplate.persist(new TestBean(rowCount++, "Keith Donald")); + } + if (!isCommited) { + assertEquals("Nothing should be committed yet", 1, + getJdbcTemplate().queryForInt("select count(*) from T_BEAN")); + } else { + assertEquals("All rows should be committed", rowCount, + getJdbcTemplate().queryForInt("select count(*) from T_BEAN")); + } + } + + private EntityManagerFactory getEntityManagerFactory(DataSource dataSource) throws Exception { + LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean(); + factory.setDataSource(dataSource); + factory.setPersistenceXmlLocation("classpath:org/springframework/webflow/persistence/persistence.xml"); + OpenJpaVendorAdapter openJpa = new OpenJpaVendorAdapter(); + factory.setJpaVendorAdapter(openJpa); + factory.afterPropertiesSet(); + return factory.getObject(); + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/persistence/TestBean.java b/spring-webflow/src/test/java/org/springframework/webflow/persistence/TestBean.java index 2094b958..fd6caba0 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/persistence/TestBean.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/persistence/TestBean.java @@ -26,6 +26,8 @@ public class TestBean { private Set addresses = new HashSet(); + private int count; + public TestBean() { } @@ -62,4 +64,13 @@ public class TestBean { public void setAddresses(Set addresses) { this.addresses = addresses; } + + public int getCount() { + return count; + } + + public void incrementCount() { + this.count++; + } + } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/persistence/managed-child-flow.xml b/spring-webflow/src/test/java/org/springframework/webflow/persistence/managed-child-flow.xml new file mode 100644 index 00000000..30e540db --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/persistence/managed-child-flow.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-webflow/src/test/java/org/springframework/webflow/persistence/flow-managed-persistence.xml b/spring-webflow/src/test/java/org/springframework/webflow/persistence/managed-root-flow.xml similarity index 56% rename from spring-webflow/src/test/java/org/springframework/webflow/persistence/flow-managed-persistence.xml rename to spring-webflow/src/test/java/org/springframework/webflow/persistence/managed-root-flow.xml index 89e38012..729052c2 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/persistence/flow-managed-persistence.xml +++ b/spring-webflow/src/test/java/org/springframework/webflow/persistence/managed-root-flow.xml @@ -6,35 +6,29 @@ - + - + - - - + - + + - + - + + - - - - + - - - \ No newline at end of file diff --git a/spring-webflow/src/test/java/org/springframework/webflow/persistence/orm.xml b/spring-webflow/src/test/java/org/springframework/webflow/persistence/orm.xml index fbdd657b..d563bd10 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/persistence/orm.xml +++ b/spring-webflow/src/test/java/org/springframework/webflow/persistence/orm.xml @@ -16,6 +16,9 @@ + + +