diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/AbstractJobRepositoryFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/AbstractJobRepositoryFactoryBean.java new file mode 100644 index 000000000..75d9e1be9 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/AbstractJobRepositoryFactoryBean.java @@ -0,0 +1,124 @@ +package org.springframework.batch.core.repository.support; + +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.aop.support.DefaultPointcutAdvisor; +import org.springframework.aop.support.NameMatchMethodPointcut; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.repository.dao.JobExecutionDao; +import org.springframework.batch.core.repository.dao.JobInstanceDao; +import org.springframework.batch.core.repository.dao.StepExecutionDao; +import org.springframework.batch.support.PropertiesConverter; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.interceptor.TransactionInterceptor; +import org.springframework.util.Assert; + +/** + * A {@link FactoryBean} that automates the creation of a + * {@link SimpleJobRepository}. Declares abstract methods for providing DAO + * object implementations. + * + * @see JobRepositoryFactoryBean + * @see MapJobRepositoryFactoryBean + * + * @author Ben Hale + * @author Lucas Ward + * @author Robert Kasanicky + */ +public abstract class AbstractJobRepositoryFactoryBean implements FactoryBean, InitializingBean { + + /** + * Default value for isolation level in create* method. + */ + private static final String DEFAULT_ISOLATION_LEVEL = "ISOLATION_SERIALIZABLE"; + + private ProxyFactory proxyFactory; + + private String isolationLevelForCreate = DEFAULT_ISOLATION_LEVEL; + + private PlatformTransactionManager transactionManager; + + /** + * @return fully configured {@link JobInstanceDao} implementation. + */ + protected abstract JobInstanceDao createJobInstanceDao() throws Exception; + + /** + * @return fully configured {@link JobExecutionDao} implementation. + */ + protected abstract JobExecutionDao createJobExecutionDao() throws Exception; + + /** + * @return fully configured {@link StepExecutionDao} implementation. + */ + protected abstract StepExecutionDao createStepExecutionDao() throws Exception; + + public Object getObject() throws Exception { + return proxyFactory.getProxy(); + } + + /** + * The type of object to be returned from {@link #getObject()}. + * + * @return JobRepository.class + * @see org.springframework.beans.factory.FactoryBean#getObjectType() + */ + public Class getObjectType() { + return JobRepository.class; + } + + public boolean isSingleton() { + return true; + } + + public void afterPropertiesSet() throws Exception { + + Assert.notNull(transactionManager, "TransactionManager must not be null."); + + initializeProxy(); + } + + protected void initializeProxy() throws Exception { + proxyFactory = new ProxyFactory(); + TransactionInterceptor advice = new TransactionInterceptor(transactionManager, PropertiesConverter + .stringToProperties("create*=PROPAGATION_REQUIRES_NEW," + isolationLevelForCreate + + "\n*=PROPAGATION_REQUIRED")); + DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(advice); + NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut(); + pointcut.addMethodName("*"); + advisor.setPointcut(pointcut); + proxyFactory.addAdvisor(advisor); + proxyFactory.setProxyTargetClass(false); + proxyFactory.addInterface(JobRepository.class); + proxyFactory.setTarget(getTarget()); + } + + private Object getTarget() throws Exception { + return new SimpleJobRepository(createJobInstanceDao(), createJobExecutionDao(), createStepExecutionDao()); + } + + /** + * Public setter for the {@link PlatformTransactionManager}. + * @param transactionManager the transactionManager to set + */ + public void setTransactionManager(PlatformTransactionManager transactionManager) { + this.transactionManager = transactionManager; + } + + /** + * Public setter for the isolation level to be used for the transaction when + * job execution entities are initially created. The default is + * ISOLATION_SERIALIZABLE, which prevents accidental concurrent execution of + * the same job (ISOLATION_REPEATABLE_READ would work as well). + * + * @param isolationLevelForCreate the isolation level name to set + * + * @see SimpleJobRepository#createJobExecution(org.springframework.batch.core.Job, + * org.springframework.batch.core.JobParameters) + */ + public void setIsolationLevelForCreate(String isolationLevelForCreate) { + this.isolationLevelForCreate = isolationLevelForCreate; + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/JobRepositoryFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/JobRepositoryFactoryBean.java index d01d6d3e8..5aab3650f 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/JobRepositoryFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/JobRepositoryFactoryBean.java @@ -18,10 +18,6 @@ package org.springframework.batch.core.repository.support; import javax.sql.DataSource; -import org.springframework.aop.framework.ProxyFactory; -import org.springframework.aop.support.DefaultPointcutAdvisor; -import org.springframework.aop.support.NameMatchMethodPointcut; -import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.repository.dao.AbstractJdbcBatchMetadataDao; import org.springframework.batch.core.repository.dao.JdbcJobExecutionDao; import org.springframework.batch.core.repository.dao.JdbcJobInstanceDao; @@ -31,67 +27,33 @@ import org.springframework.batch.core.repository.dao.JobInstanceDao; import org.springframework.batch.core.repository.dao.StepExecutionDao; import org.springframework.batch.item.database.support.DataFieldMaxValueIncrementerFactory; import org.springframework.batch.item.database.support.DefaultDataFieldMaxValueIncrementerFactory; -import org.springframework.batch.support.PropertiesConverter; import org.springframework.beans.factory.FactoryBean; -import org.springframework.beans.factory.InitializingBean; +import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.interceptor.TransactionInterceptor; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * A {@link FactoryBean} that automates the creation of a - * {@link SimpleJobRepository}. Requires the user to describe what kind of + * {@link SimpleJobRepository} using JDBC DAO implementations which persist + * batch metadata in database. Requires the user to describe what kind of * database they are using. * * @author Ben Hale * @author Lucas Ward */ -public class JobRepositoryFactoryBean implements FactoryBean, InitializingBean { - - /** - * Default value for isolation level in create* method. - */ - private static final String DEFAULT_ISOLATION_LEVEL = "ISOLATION_SERIALIZABLE"; +public class JobRepositoryFactoryBean extends AbstractJobRepositoryFactoryBean { private DataSource dataSource; + private JdbcOperations jdbcTemplate; + private String databaseType; private String tablePrefix = AbstractJdbcBatchMetadataDao.DEFAULT_TABLE_PREFIX; private DataFieldMaxValueIncrementerFactory incrementerFactory; - private PlatformTransactionManager transactionManager; - - private ProxyFactory proxyFactory; - - private String isolationLevelForCreate = DEFAULT_ISOLATION_LEVEL; - - /** - * Public setter for the {@link PlatformTransactionManager}. - * @param transactionManager the transactionManager to set - */ - public void setTransactionManager(PlatformTransactionManager transactionManager) { - this.transactionManager = transactionManager; - } - - /** - * Public setter for the isolation level to be used for the transaction when - * job execution entities are initially created. The default is - * ISOLATION_SERIALIZABLE, which prevents accidental concurrent execution of - * the same job (ISOLATION_REPEATABLE_READ would work as well). - * - * @param isolationLevelForCreate the isolation level name to set - * - * @see SimpleJobRepository#createJobExecution(org.springframework.batch.core.Job, - * org.springframework.batch.core.JobParameters) - */ - public void setIsolationLevelForCreate(String isolationLevelForCreate) { - this.isolationLevelForCreate = isolationLevelForCreate; - } - public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } @@ -109,8 +71,9 @@ public class JobRepositoryFactoryBean implements FactoryBean, InitializingBean { } public void afterPropertiesSet() throws Exception { + Assert.notNull(dataSource, "DataSource must not be null."); - Assert.notNull(transactionManager, "TransactionManager must not be null."); + jdbcTemplate = new JdbcTemplate(dataSource); if (incrementerFactory == null) { incrementerFactory = new DefaultDataFieldMaxValueIncrementerFactory(dataSource); @@ -120,61 +83,10 @@ public class JobRepositoryFactoryBean implements FactoryBean, InitializingBean { + "' is an unsupported database type. The supported database types are " + StringUtils.arrayToCommaDelimitedString(incrementerFactory.getSupportedIncrementerTypes())); - initializeProxy(); + super.afterPropertiesSet(); } - protected void initializeProxy() throws Exception { - proxyFactory = new ProxyFactory(); - TransactionInterceptor advice = new TransactionInterceptor(transactionManager, PropertiesConverter - .stringToProperties("create*=PROPAGATION_REQUIRES_NEW," + isolationLevelForCreate - + "\n*=PROPAGATION_REQUIRED")); - DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(advice); - NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut(); - pointcut.addMethodName("*"); - advisor.setPointcut(pointcut); - proxyFactory.addAdvisor(advisor); - proxyFactory.setProxyTargetClass(false); - proxyFactory.addInterface(JobRepository.class); - proxyFactory.setTarget(getTarget()); - } - - /** - * @return a SimpleJobRepository - */ - private SimpleJobRepository getTarget() throws Exception { - JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); - JobInstanceDao jobInstanceDao = createJobInstanceDao(jdbcTemplate); - JobExecutionDao jobExecutionDao = createJobExecutionDao(jdbcTemplate); - StepExecutionDao stepExecutionDao = createStepExecutionDao(jdbcTemplate); - return new SimpleJobRepository(jobInstanceDao, jobExecutionDao, stepExecutionDao); - } - - /** - * Get a concrete {@link JobRepository}. The repository will be a proxy - * containing transaction advice using the supplied transaction manager. - * - * @return a {@link JobRepository} - * @see org.springframework.beans.factory.FactoryBean#getObject() - */ - public Object getObject() throws Exception { - return proxyFactory.getProxy(); - } - - /** - * The type of object to be returned from {@link #getObject()}. - * - * @return JobRepository.class - * @see org.springframework.beans.factory.FactoryBean#getObjectType() - */ - public Class getObjectType() { - return JobRepository.class; - } - - public boolean isSingleton() { - return true; - } - - private JobInstanceDao createJobInstanceDao(JdbcTemplate jdbcTemplate) throws Exception { + protected JobInstanceDao createJobInstanceDao() throws Exception { JdbcJobInstanceDao dao = new JdbcJobInstanceDao(); dao.setJdbcTemplate(jdbcTemplate); dao.setJobIncrementer(incrementerFactory.getIncrementer(databaseType, tablePrefix + "JOB_SEQ")); @@ -183,7 +95,7 @@ public class JobRepositoryFactoryBean implements FactoryBean, InitializingBean { return dao; } - private JobExecutionDao createJobExecutionDao(JdbcTemplate jdbcTemplate) throws Exception { + protected JobExecutionDao createJobExecutionDao() throws Exception { JdbcJobExecutionDao dao = new JdbcJobExecutionDao(); dao.setJdbcTemplate(jdbcTemplate); dao.setJobExecutionIncrementer(incrementerFactory.getIncrementer(databaseType, tablePrefix @@ -193,7 +105,7 @@ public class JobRepositoryFactoryBean implements FactoryBean, InitializingBean { return dao; } - private StepExecutionDao createStepExecutionDao(JdbcTemplate jdbcTemplate) throws Exception { + protected StepExecutionDao createStepExecutionDao() throws Exception { JdbcStepExecutionDao dao = new JdbcStepExecutionDao(); dao.setJdbcTemplate(jdbcTemplate); dao.setStepExecutionIncrementer(incrementerFactory.getIncrementer(databaseType, tablePrefix diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/MapJobRepositoryFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/MapJobRepositoryFactoryBean.java new file mode 100644 index 000000000..be8083de3 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/MapJobRepositoryFactoryBean.java @@ -0,0 +1,32 @@ +package org.springframework.batch.core.repository.support; + +import org.springframework.batch.core.repository.dao.JobExecutionDao; +import org.springframework.batch.core.repository.dao.JobInstanceDao; +import org.springframework.batch.core.repository.dao.MapJobExecutionDao; +import org.springframework.batch.core.repository.dao.MapJobInstanceDao; +import org.springframework.batch.core.repository.dao.MapStepExecutionDao; +import org.springframework.batch.core.repository.dao.StepExecutionDao; +import org.springframework.beans.factory.FactoryBean; + +/** + * A {@link FactoryBean} that automates the creation of a + * {@link SimpleJobRepository} using non-persistent in-memory DAO + * implementations. + * + * @author Robert Kasanicky + */ +public class MapJobRepositoryFactoryBean extends AbstractJobRepositoryFactoryBean { + + protected JobExecutionDao createJobExecutionDao() throws Exception { + return new MapJobExecutionDao(); + } + + protected JobInstanceDao createJobInstanceDao() throws Exception { + return new MapJobInstanceDao(); + } + + protected StepExecutionDao createStepExecutionDao() throws Exception { + return new MapStepExecutionDao(); + } + +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/JobRepositoryFactoryBeanTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/JobRepositoryFactoryBeanTests.java index 81a79c778..caada2ba1 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/JobRepositoryFactoryBeanTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/JobRepositoryFactoryBeanTests.java @@ -105,6 +105,11 @@ public class JobRepositoryFactoryBeanTests extends TestCase { factory.setTransactionManager(null); try { + incrementerFactory.isSupportedIncrementerType(null); + incrementerControl.setReturnValue(true); + incrementerFactory.getSupportedIncrementerTypes(); + incrementerControl.setReturnValue(new String[0]); + incrementerControl.replay(); factory.afterPropertiesSet(); fail(); } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MapJobRepositoryFactoryBeanTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MapJobRepositoryFactoryBeanTests.java new file mode 100644 index 000000000..c197abe3b --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MapJobRepositoryFactoryBeanTests.java @@ -0,0 +1,45 @@ +package org.springframework.batch.core.repository.support; + +import junit.framework.TestCase; + +import org.springframework.batch.core.Job; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.job.JobSupport; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.repository.JobRestartException; +import org.springframework.batch.support.transaction.ResourcelessTransactionManager; +import org.springframework.transaction.PlatformTransactionManager; + +/** + * Tests for {@link MapJobRepositoryFactoryBean}. + */ +public class MapJobRepositoryFactoryBeanTests extends TestCase { + + private MapJobRepositoryFactoryBean tested = new MapJobRepositoryFactoryBean(); + + private PlatformTransactionManager transactionManager = new ResourcelessTransactionManager(); + + protected void setUp() throws Exception { + tested.setTransactionManager(transactionManager); + tested.afterPropertiesSet(); + } + + /** + * Use the factory to create repository and check the repository remembers + * created executions. + */ + public void testCreateRepository() throws Exception { + JobRepository repository = (JobRepository) tested.getObject(); + Job job = new JobSupport("jobName"); + JobParameters jobParameters = new JobParameters(); + + repository.createJobExecution(job, jobParameters); + + try { + repository.createJobExecution(job, jobParameters); + } + catch (JobRestartException e) { + // expected + } + } +}