diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/AbstractBatchConfiguration.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/AbstractBatchConfiguration.java index 0967c2e8b..ad15f974f 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/AbstractBatchConfiguration.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/AbstractBatchConfiguration.java @@ -15,10 +15,6 @@ */ package org.springframework.batch.core.configuration.annotation; -import java.util.Collection; - -import javax.sql.DataSource; - import org.springframework.batch.core.configuration.JobRegistry; import org.springframework.batch.core.configuration.support.MapJobRegistry; import org.springframework.batch.core.launch.JobLauncher; @@ -35,6 +31,9 @@ import org.springframework.core.type.AnnotationMetadata; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.util.Assert; +import javax.sql.DataSource; +import java.util.Collection; + /** * Base {@code Configuration} class providing common structure for enabling and using Spring Batch. Customization is * available by implementing the {@link BatchConfigurer} interface. {@link BatchConfigurer}. @@ -92,16 +91,21 @@ public abstract class AbstractBatchConfiguration implements ImportAware { return this.configurer; } if (configurers == null || configurers.isEmpty()) { - if (dataSources == null || dataSources.isEmpty() || dataSources.size() > 1) { - throw new IllegalStateException( - "To use the default BatchConfigurer the context must contain precisely one DataSource, found " - + (dataSources == null ? 0 : dataSources.size())); + if (dataSources == null || dataSources.isEmpty()) { + DefaultBatchConfigurer configurer = new DefaultBatchConfigurer(); + configurer.initialize(); + this.configurer = configurer; + return configurer; + } else if(dataSources != null && dataSources.size() == 1) { + DataSource dataSource = dataSources.iterator().next(); + DefaultBatchConfigurer configurer = new DefaultBatchConfigurer(dataSource); + configurer.initialize(); + this.configurer = configurer; + return configurer; + } else { + throw new IllegalStateException("To use the default BatchConfigurer the context must contain no more than" + + "one DataSource, found " + dataSources.size()); } - DataSource dataSource = dataSources.iterator().next(); - DefaultBatchConfigurer configurer = new DefaultBatchConfigurer(dataSource); - configurer.initialize(); - this.configurer = configurer; - return configurer; } if (configurers.size() > 1) { throw new IllegalStateException( diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/DefaultBatchConfigurer.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/DefaultBatchConfigurer.java index 7a672a491..5564bbd86 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/DefaultBatchConfigurer.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/DefaultBatchConfigurer.java @@ -15,27 +15,32 @@ */ package org.springframework.batch.core.configuration.annotation; -import javax.annotation.PostConstruct; -import javax.sql.DataSource; - +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.batch.core.launch.JobLauncher; import org.springframework.batch.core.launch.support.SimpleJobLauncher; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; +import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean; +import org.springframework.batch.support.transaction.ResourcelessTransactionManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.stereotype.Component; import org.springframework.transaction.PlatformTransactionManager; +import javax.annotation.PostConstruct; +import javax.sql.DataSource; + @Component public class DefaultBatchConfigurer implements BatchConfigurer { + private static final Log logger = LogFactory.getLog(DefaultBatchConfigurer.class); private DataSource dataSource; private PlatformTransactionManager transactionManager; private JobRepository jobRepository; private JobLauncher jobLauncher; - @Autowired + @Autowired(required = false) public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; this.transactionManager = new DataSourceTransactionManager(dataSource); @@ -64,7 +69,20 @@ public class DefaultBatchConfigurer implements BatchConfigurer { @PostConstruct public void initialize() throws Exception { - this.jobRepository = createJobRepository(); + if(dataSource == null) { + logger.warn("No datasource was provided...using a Map based JobRepository"); + + if(this.transactionManager == null) { + this.transactionManager = new ResourcelessTransactionManager(); + } + + MapJobRepositoryFactoryBean factory = new MapJobRepositoryFactoryBean(this.transactionManager); + factory.afterPropertiesSet(); + this.jobRepository = factory.getObject(); + } else { + this.jobRepository = createJobRepository(); + } + this.jobLauncher = createJobLauncher(); } @@ -82,5 +100,4 @@ public class DefaultBatchConfigurer implements BatchConfigurer { factory.afterPropertiesSet(); return factory.getObject(); } - } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java index bb13f561f..662197c0c 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2014 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. @@ -15,14 +15,6 @@ */ package org.springframework.batch.core.configuration.annotation; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import javax.sql.DataSource; - import org.springframework.batch.core.configuration.JobRegistry; import org.springframework.batch.core.configuration.support.ApplicationContextFactory; import org.springframework.batch.core.configuration.support.AutomaticJobRegistrar; @@ -31,6 +23,13 @@ import org.springframework.batch.core.repository.JobRepository; import org.springframework.context.annotation.Import; import org.springframework.transaction.PlatformTransactionManager; +import javax.sql.DataSource; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + /** *

* Enable Spring Batch features and provide a base configuration for setting up batch jobs in an @Configuration @@ -62,7 +61,7 @@ import org.springframework.transaction.PlatformTransactionManager; * } * * - * The user has to provide a {@link DataSource} as a bean in the context, or else implement {@link BatchConfigurer} in + * The user should to provide a {@link DataSource} as a bean in the context, or else implement {@link BatchConfigurer} in * the configuration class itself, e.g. * *

@@ -85,6 +84,9 @@ import org.springframework.transaction.PlatformTransactionManager;
  * }
  * 
* + * If a user does not provide a {@link javax.sql.DataSource} within the context, a Map based + * {@link org.springframework.batch.core.repository.JobRepository} will be used. + * * Note that only one of your configuration classes needs to have the @EnableBatchProcessing * annotation. Once you have an @EnableBatchProcessing class in your configuration you will have an * instance of {@link StepScope} so your beans inside steps can have @Scope("step"). You will also be diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/MapJobRepositoryConfigurationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/MapJobRepositoryConfigurationTests.java new file mode 100644 index 000000000..e82a4f59e --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/MapJobRepositoryConfigurationTests.java @@ -0,0 +1,119 @@ +/* + * Copyright 2014 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.batch.core.configuration.annotation; + +import org.junit.Test; +import org.springframework.batch.core.BatchStatus; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.PooledEmbeddedDataSource; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.stereotype.Component; + +import javax.sql.DataSource; + +import static junit.framework.Assert.assertEquals; + +public class MapJobRepositoryConfigurationTests { + + JobLauncher jobLauncher; + JobRepository jobRepository; + Job job; + + @Test + public void testRoseyScenario() throws Exception { + testConfigurationClass(MapRepositoryBatchConfiguration.class); + } + + @Test + public void testOneDataSource() throws Exception { + testConfigurationClass(HsqlBatchConfiguration.class); + } + + @Test(expected = IllegalStateException.class) + public void testMultipleDataSources() throws Exception { + testConfigurationClass(InvalidBatchConfiguration.class); + } + + private void testConfigurationClass(Class clazz) throws Exception { + GenericApplicationContext context = new AnnotationConfigApplicationContext(clazz); + this.jobLauncher = context.getBean(JobLauncher.class); + this.jobRepository = context.getBean(JobRepository.class); + this.job = context.getBean(Job.class); + + JobExecution jobExecution = jobLauncher.run(job, new JobParameters()); + assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); + JobExecution repositoryJobExecution = jobRepository.getLastJobExecution(job.getName(), new JobParameters()); + assertEquals(jobExecution.getId(), repositoryJobExecution.getId()); + context.close(); + } + + public static class InvalidBatchConfiguration extends HsqlBatchConfiguration { + + @Bean + DataSource dataSource2() { + return new PooledEmbeddedDataSource(new EmbeddedDatabaseBuilder().setName("badDatabase").build()); + } + } + + public static class HsqlBatchConfiguration extends MapRepositoryBatchConfiguration { + + @Bean + DataSource dataSource() { + return new PooledEmbeddedDataSource(new EmbeddedDatabaseBuilder(). + addScript("classpath:org/springframework/batch/core/schema-drop-hsqldb.sql"). + addScript("classpath:org/springframework/batch/core/schema-hsqldb.sql"). + build()); + } + } + + @Component + @EnableBatchProcessing + public static class MapRepositoryBatchConfiguration { + @Autowired + JobBuilderFactory jobFactory; + + @Autowired + StepBuilderFactory stepFactory; + + @Bean + Step step1 () { + return stepFactory.get("step1").tasklet(new Tasklet() { + @Override + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { + return RepeatStatus.FINISHED; + } + }).build(); + } + + @Bean + Job job() { + return jobFactory.get("job").start(step1()).build(); + } + } +}