SWF-360 Add flow managed persistence context propagation

This commit is contained in:
Rossen Stoyanchev
2010-10-28 10:42:47 +00:00
parent 155649fd40
commit 890d96554a
14 changed files with 626 additions and 248 deletions

View File

@@ -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));
}

View File

@@ -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));
}

View File

@@ -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) {
}
}
}
}
}

View File

@@ -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"));
}
}

View File

@@ -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() {

View File

@@ -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));
}
}

View File

@@ -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();
}
}

View File

@@ -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() {

View File

@@ -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));
}
}

View File

@@ -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();
}
}

View File

@@ -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++;
}
}

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
<persistence-context/>
<on-start>
<evaluate expression="incrementCountAction" />
</on-start>
<view-state id="view">
<transition on="finish" to="finish"/>
</view-state>
<end-state id="finish" commit="true"/>
</flow>

View File

@@ -6,35 +6,29 @@
<persistence-context/>
<on-start>
<evaluate expression="loadTestBean" />
<evaluate expression="incrementCountAction" />
</on-start>
<view-state id="view">
<transition on="view2" to="view2"/>
<transition on="subflow" to="subflow"/>
<transition on="managed" to="managed"/>
<transition on="notmanaged" to="notmanaged"/>
<transition on="cancel" to="cancel"/>
<transition on="finish" to="finish"/>
</view-state>
<subflow-state id="subflow" subflow="foo">
<subflow-state id="managed" subflow="managed-child-flow">
<transition on="finish" to="view2">
<evaluate expression="loadTestBean" />
<evaluate expression="incrementCountAction" />
<evaluate expression="assertCountAction.execute(flowRequestContext,3)" />
</transition>
</subflow-state>
<subflow-state id="notmanaged" subflow="notmanaged">
<subflow-state id="notmanaged" subflow="notmanaged-child-flow">
<transition on="finish" to="view2">
<evaluate expression="loadTestBean" />
<evaluate expression="incrementCountAction" />
<evaluate expression="assertCountAction.execute(flowRequestContext,2)" />
</transition>
</subflow-state>
<view-state id="view2">
<transition on="finish" to="finish"/>
</view-state>
<view-state id="view2"/>
<end-state id="finish" commit="true"/>
<end-state id="cancel"/>
</flow>

View File

@@ -16,6 +16,9 @@
<basic name="name">
<column name="NAME" />
</basic>
<basic name="count">
<column name="counter" />
</basic>
<one-to-many name="addresses" target-entity="TestAddress">
<join-column name="BEAN_ID"/>
</one-to-many>