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 new file mode 100644 index 000000000..f6c2b96fa --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/AbstractBatchConfiguration.java @@ -0,0 +1,140 @@ +/* + * Copyright 2012-2013 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 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; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.scope.StepScope; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportAware; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.util.Assert; + +/** + * Base {@code Configuration} class providing common structure for enabling and using Spring Batch. Customization is + * available by implementing the {@link BatchConfigurer} interface. + * {@link BatchConfigurer}. + * + * @author Dave Syer + * @since 2.2 + * @see EnableBatchProcessing + */ +@Configuration +@Import(StepScopeConfiguration.class) +public abstract class AbstractBatchConfiguration implements ImportAware { + + @Autowired + private StepScope stepScope; + + @Autowired + private ApplicationContext context; + + @Autowired(required = false) + private Collection dataSources; + + private BatchConfigurer configurer; + + @Bean + public JobBuilderFactory jobBuilders() throws Exception { + return new JobBuilderFactory(jobRepository()); + } + + @Bean + public StepBuilderFactory stepBuilders() throws Exception { + return new StepBuilderFactory(jobRepository(), transactionManager()); + } + + @Bean + public abstract JobRepository jobRepository() throws Exception; + + @Bean + public abstract JobLauncher jobLauncher() throws Exception; + + @Bean + public JobRegistry jobRegistry() throws Exception { + return new MapJobRegistry(); + } + + @Bean + public abstract PlatformTransactionManager transactionManager() throws Exception; + + @Override + public void setImportMetadata(AnnotationMetadata importMetadata) { + AnnotationAttributes enabled = AnnotationAttributes.fromMap(importMetadata.getAnnotationAttributes( + EnableBatchProcessing.class.getName(), false)); + Assert.notNull(enabled, + "@EnableBatchProcessing is not present on importing class " + importMetadata.getClassName()); + if (enabled.getBoolean("proxyTargetClass")) { + stepScope.setProxyTargetClass(true); + } + } + + protected BatchConfigurer getConfigurer(Collection configurers) throws Exception { + if (this.configurer!=null) { + 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())); + } + DataSource dataSource = dataSources.iterator().next(); + DefaultBatchConfigurer configurer = new DefaultBatchConfigurer(dataSource); + configurer.initialize(); + this.configurer = configurer; + return configurer; + } + if (configurers.size() > 1) { + throw new IllegalStateException( + "To use a custom BatchConfigurer the context must contain precisely one, found " + + configurers.size()); + } + this.configurer = configurers.iterator().next(); + return this.configurer; + } + +} + +/** + * Extract step scope configuration into a separate unit so that it can be non-static. + * + * @author Dave Syer + * + */ +@Configuration +class StepScopeConfiguration { + + private StepScope stepScope = new StepScope(); + + @Bean + public StepScope stepScope() { + return stepScope; + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchConfigurationSelector.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchConfigurationSelector.java new file mode 100644 index 000000000..19a93a81c --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchConfigurationSelector.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-2013 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.springframework.context.annotation.ImportSelector; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.util.Assert; + +/** + * Base {@code Configuration} class providing common structure for enabling and using Spring Batch. Customization is + * available by implementing the {@link BatchConfigurer} interface. + * + * @author Dave Syer + * @since 2.2 + * @see EnableBatchProcessing + */ +public class BatchConfigurationSelector implements ImportSelector { + + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + Class annotationType = EnableBatchProcessing.class; + AnnotationAttributes attributes = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes( + annotationType.getName(), false)); + Assert.notNull(attributes, String.format("@%s is not present on importing class '%s' as expected", + annotationType.getSimpleName(), importingClassMetadata.getClassName())); + + String[] imports; + if (attributes.containsKey("modular") && attributes.getBoolean("modular")) { + imports = new String[] { ModularBatchConfiguration.class.getName() }; + } + else { + imports = new String[] { SimpleBatchConfiguration.class.getName() }; + } + + return imports; + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchConfigurer.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchConfigurer.java new file mode 100644 index 000000000..e5064d5ab --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchConfigurer.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-2013 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.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.transaction.PlatformTransactionManager; + +/** + * Strategy interface for users to provide as a factory for custom components needed by a Batch system. + * + * @author Dave Syer + * + */ +public interface BatchConfigurer { + + JobRepository getJobRepository() throws Exception; + + PlatformTransactionManager getTransactionManager() throws Exception; + + JobLauncher getJobLauncher() throws Exception; + +} 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 new file mode 100644 index 000000000..df60dccef --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/DefaultBatchConfigurer.java @@ -0,0 +1,83 @@ +/* + * Copyright 2012-2013 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 javax.annotation.PostConstruct; +import javax.sql.DataSource; + +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.beans.factory.annotation.Autowired; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.stereotype.Component; +import org.springframework.transaction.PlatformTransactionManager; + +@Component +public class DefaultBatchConfigurer implements BatchConfigurer { + + private DataSource dataSource; + private PlatformTransactionManager transactionManager; + private JobRepository jobRepository; + private JobLauncher jobLauncher; + + @Autowired + public void setDataSource(DataSource dataSource) { + this.dataSource = dataSource; + this.transactionManager = new DataSourceTransactionManager(dataSource); + } + + protected DefaultBatchConfigurer() {} + + public DefaultBatchConfigurer(DataSource dataSource) { + setDataSource(dataSource); + } + + public JobRepository getJobRepository() { + return jobRepository; + } + + public PlatformTransactionManager getTransactionManager() { + return transactionManager; + } + + public JobLauncher getJobLauncher() { + return jobLauncher; + } + + @PostConstruct + public void initialize() throws Exception { + this.jobRepository = createJobRepository(); + this.jobLauncher = createJobLauncher(); + } + + private JobLauncher createJobLauncher() throws Exception { + SimpleJobLauncher jobLauncher = new SimpleJobLauncher(); + jobLauncher.setJobRepository(jobRepository); + jobLauncher.afterPropertiesSet(); + return jobLauncher; + } + + protected JobRepository createJobRepository() throws Exception { + JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean(); + factory.setDataSource(dataSource); + factory.setTransactionManager(transactionManager); + factory.afterPropertiesSet(); + return (JobRepository) 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 new file mode 100644 index 000000000..9b34683d2 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java @@ -0,0 +1,178 @@ +/* + * Copyright 2012-2013 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 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.support.ApplicationContextFactory; +import org.springframework.batch.core.configuration.support.AutomaticJobRegistrar; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.scope.StepScope; +import org.springframework.context.annotation.Import; +import org.springframework.transaction.PlatformTransactionManager; + +/** + *

+ * Enable Spring Batch features and provide a base configuration for setting up batch jobs in an @Configuration + * class, roughly equivalent to using the {@code } XML namespace. + * + *

+ * @Configuration
+ * @EnableBatchProcessing
+ * @Import(DataSourceCnfiguration.class)
+ * public class AppConfig {
+ * 
+ * 	@Autowired
+ * 	private JobBuilderFactory jobs;
+ * 
+ * 	@Bean
+ * 	public Job job() {
+ * 		return jobs.get("myJob").start(step1()).next(step2()).build();
+ * 	}
+ * 
+ * 	@Bean
+ *    protected Step step1() {
+ *       ...
+ *    }
+ * 
+ * 	@Bean
+ *    protected Step step2() {
+ *     ...
+ *    }
+ * }
+ * 
+ * + * The user has to provide a {@link DataSource} as a bean in the context, or else implement {@link BatchConfigurer} in + * the configuration class itself, e.g. + * + *
+ * @Configuration
+ * @EnableBatchProcessing
+ * public class AppConfig extends DefaultBatchConfigurer {
+ * 
+ *    @Bean
+ *    public Job job() {
+ *       ...
+ *    }
+ * 
+ *    @Override
+ *    protected JobRepository createJobRepository() {
+ *       ...
+ *    }
+ *  
+ *  ...
+ *  
+ * }
+ * 
+ * + * 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 + * able to @Autowired some useful stuff into your context: + * + * + * + * If the configuration is specified as modular=true then the context will also contain an + * {@link AutomaticJobRegistrar}. The job registrar is useful for modularizing your configuration if there are multiple + * jobs. It works by creating separate child application contexts containing job configurations and registering those + * jobs. The jobs can then create steps and other dependent components without needing to worry about bean definition + * name clashes. Beans of type {@link ApplicationContextFactory} will be registered automatically with the job + * registrar. Example: + * + *
+ * @Configuration
+ * @EnableBatchProcessing(modular=true)
+ * public class AppConfig {
+ * 
+ *    @Bean
+ *    public ApplicationContextFactory someJobs() {
+ *       return new GenericApplicationContextFactory(SomeJobConfiguration.class);
+ *    }
+ * 
+ *    @Bean
+ *    public ApplicationContextFactory moreJobs() {
+ *       return new GenericApplicationContextFactory(MoreJobConfiguration.class);
+ *    }
+ *  
+ *  ...
+ *  
+ * }
+ * 
+ * + * Note that a modular parent context in general should not itself contain @Bean definitions for job, + * especially if a {@link BatchConfigurer} is provided, because cyclic configuration dependencies are otherwise likely + * to develop. + * + *

+ * + *

+ * For reference, the first example above can be compared to the following Spring XML configuration: + * + *

+ * {@code
+ * 
+ *     
+ *     
+ *       
+ *       
+ *     
+ *     
+ *     
+ *       
+ *     
+ * 
+ * }
+ * 
+ * + * @author Dave Syer + * + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(BatchConfigurationSelector.class) +public @interface EnableBatchProcessing { + + /** + * Indicate whether beans in scope="step" should use subclass-based (CGLIB) proxies are to be created + * as opposed to standard Java interface-based proxies. The default is {@code false}. + */ + boolean proxyTargetClass() default false; + + /** + * Indicate whether the configuration is going to be modularized into multiple application contexts. If true then + * you should not create any @Bean Job definitions in this context, but rather supply them in separate (child) + * contexts through an {@link ApplicationContextFactory}. + */ + boolean modular() default false; + +} \ No newline at end of file diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/JobBuilderFactory.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/JobBuilderFactory.java new file mode 100644 index 000000000..b6414d3f5 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/JobBuilderFactory.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012-2013 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.springframework.batch.core.job.builder.JobBuilder; +import org.springframework.batch.core.repository.JobRepository; + +/** + * Convenient factory for a {@link JobBuilder} which sets the {@link JobRepository} automatically. + * + * @author Dave Syer + * + */ +public class JobBuilderFactory { + + private JobRepository jobRepository; + + public JobBuilderFactory(JobRepository jobRepository) { + this.jobRepository = jobRepository; + } + + /** + * Creates a job builder and initializes its job repository. Note that if the builder is used to create a @Bean + * definition then the name of the job and the bean name might be different. + * + * @param name the name of the job + * @return a job builder + */ + public JobBuilder get(String name) { + JobBuilder builder = new JobBuilder(name).repository(jobRepository); + return builder; + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/ModularBatchConfiguration.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/ModularBatchConfiguration.java new file mode 100644 index 000000000..37d44d5e1 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/ModularBatchConfiguration.java @@ -0,0 +1,74 @@ +/* + * Copyright 2012-2013 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 java.util.Collection; + +import org.springframework.batch.core.configuration.support.ApplicationContextFactory; +import org.springframework.batch.core.configuration.support.AutomaticJobRegistrar; +import org.springframework.batch.core.configuration.support.DefaultJobLoader; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.transaction.PlatformTransactionManager; + +/** + * Base {@code Configuration} class providing common structure for enabling and using Spring Batch. Customization is + * available by implementing the {@link BatchConfigurer} interface. + * + * @author Dave Syer + * @since 2.2 + * @see EnableBatchProcessing + */ +@Configuration +public class ModularBatchConfiguration extends AbstractBatchConfiguration { + + @Autowired + private ApplicationContext context; + + @Autowired(required = false) + private Collection configurers; + + private AutomaticJobRegistrar registrar = new AutomaticJobRegistrar(); + + @Bean + public JobRepository jobRepository() throws Exception { + return getConfigurer(configurers).getJobRepository(); + } + + @Bean + public JobLauncher jobLauncher() throws Exception { + return getConfigurer(configurers).getJobLauncher(); + } + + @Bean + public PlatformTransactionManager transactionManager() throws Exception { + return getConfigurer(configurers).getTransactionManager(); + } + + @Bean + public AutomaticJobRegistrar jobRegistrar() throws Exception { + registrar.setJobLoader(new DefaultJobLoader(jobRegistry())); + for (ApplicationContextFactory factory : context.getBeansOfType(ApplicationContextFactory.class).values()) { + registrar.addApplicationContextFactory(factory); + } + return registrar; + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/SimpleBatchConfiguration.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/SimpleBatchConfiguration.java new file mode 100644 index 000000000..65b1c2ce1 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/SimpleBatchConfiguration.java @@ -0,0 +1,133 @@ +/* + * Copyright 2012-2013 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 java.util.concurrent.atomic.AtomicReference; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.aop.target.AbstractLazyCreationTargetSource; +import org.springframework.batch.core.configuration.JobRegistry; +import org.springframework.batch.core.configuration.support.MapJobRegistry; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.transaction.PlatformTransactionManager; + +/** + * Base {@code Configuration} class providing common structure for enabling and using Spring Batch. Customization is + * available by implementing the {@link BatchConfigurer} interface. The main components are created as lazy proxies that + * only initialize when a method is called. This is to prevent (as much as possible) configuration cycles from + * developing when these components are needed in a configuration resource that itself provides a + * {@link BatchConfigurer}. + * + * @author Dave Syer + * @since 2.2 + * @see EnableBatchProcessing + */ +@Configuration +public class SimpleBatchConfiguration extends AbstractBatchConfiguration { + + @Autowired + private ApplicationContext context; + + private boolean initialized = false; + + private AtomicReference jobRepository = new AtomicReference(); + + private AtomicReference jobLauncher = new AtomicReference(); + + private AtomicReference jobRegistry = new AtomicReference(); + + private AtomicReference transactionManager = new AtomicReference(); + + @Bean + public JobRepository jobRepository() throws Exception { + return createLazyProxy(jobRepository, JobRepository.class); + } + + @Bean + public JobLauncher jobLauncher() throws Exception { + return createLazyProxy(jobLauncher, JobLauncher.class); + } + + @Bean + public JobRegistry jobRegistry() throws Exception { + return createLazyProxy(jobRegistry, JobRegistry.class); + } + + @Bean + public PlatformTransactionManager transactionManager() throws Exception { + return createLazyProxy(transactionManager, PlatformTransactionManager.class); + } + + private T createLazyProxy(AtomicReference reference, Class type) { + ProxyFactory factory = new ProxyFactory(); + factory.setTargetSource(new ReferenceTargetSource(reference)); + factory.addAdvice(new PassthruAdvice()); + factory.setInterfaces(new Class[] { type }); + @SuppressWarnings("unchecked") + T proxy = (T) factory.getProxy(); + return proxy; + } + + /** + * Sets up the basic components by extracting them from the {@link BatchConfigurer configurer}, defaulting to some + * sensible values as long as a unique DataSource is available. + * + * @throws Exception if there is a problem in the configurer + */ + protected void initialize() throws Exception { + if (initialized) { + return; + } + BatchConfigurer configurer = getConfigurer(context.getBeansOfType(BatchConfigurer.class).values()); + jobRepository.set(configurer.getJobRepository()); + jobLauncher.set(configurer.getJobLauncher()); + transactionManager.set(configurer.getTransactionManager()); + jobRegistry.set(new MapJobRegistry()); + initialized = true; + } + + private class PassthruAdvice implements MethodInterceptor { + + @Override + public Object invoke(MethodInvocation invocation) throws Throwable { + return invocation.proceed(); + } + + } + + private class ReferenceTargetSource extends AbstractLazyCreationTargetSource { + + private AtomicReference reference; + + public ReferenceTargetSource(AtomicReference reference) { + this.reference = reference; + } + + @Override + protected Object createObject() throws Exception { + initialize(); + return reference.get(); + } + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/StepBuilderFactory.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/StepBuilderFactory.java new file mode 100644 index 000000000..fecca1672 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/StepBuilderFactory.java @@ -0,0 +1,53 @@ +/* + * Copyright 2012-2013 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.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.builder.StepBuilder; +import org.springframework.transaction.PlatformTransactionManager; + +/** + * Convenient factory for a {@link StepBuilder} which sets the {@link JobRepository} and + * {@link PlatformTransactionManager} automatically. + * + * @author Dave Syer + * + */ +public class StepBuilderFactory { + + private JobRepository jobRepository; + + private PlatformTransactionManager transactionManager; + + public StepBuilderFactory(JobRepository jobRepository, PlatformTransactionManager transactionManager) { + this.jobRepository = jobRepository; + this.transactionManager = transactionManager; + } + + /** + * Creates a step builder and initializes its job repository and transaction manager. Note that if the builder is + * used to create a @Bean definition then the name of the step and the bean name might be different. + * + * @param name the name of the step + * @return a step builder + */ + public StepBuilder get(String name) { + StepBuilder builder = (StepBuilder) new StepBuilder(name).repository(jobRepository).transactionManager( + transactionManager); + return builder; + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/AbstractApplicationContextFactory.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/AbstractApplicationContextFactory.java new file mode 100644 index 000000000..7ce13a3e9 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/AbstractApplicationContextFactory.java @@ -0,0 +1,231 @@ +/* + * Copyright 2006-2007 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.support; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.CustomEditorConfigurer; +import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; +import org.springframework.beans.factory.support.AbstractBeanFactory; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.util.Assert; + +/** + * {@link ApplicationContextFactory} implementation that takes a parent context and a path to the context to create. + * When createApplicationContext method is called, the child {@link ApplicationContext} will be returned. The child + * context is not re-created every time it is requested, it is lazily initialized and cached. Clients should ensure that + * it is closed when it is no longer needed. If a path is not set, the parent will always be returned. + * + */ +public abstract class AbstractApplicationContextFactory implements ApplicationContextFactory, ApplicationContextAware { + + private static final Log logger = LogFactory.getLog(AbstractApplicationContextFactory.class); + + private Object resource; + + private ConfigurableApplicationContext parent; + + private boolean copyConfiguration = true; + + private Collection> beanFactoryPostProcessorClasses; + + private Collection> beanPostProcessorExcludeClasses; + + /** + * Create a factory instance with the resource specified. The resource is a Spring configuration file or java + * package containing configuration files. + */ + public AbstractApplicationContextFactory(Object resource) { + + this.resource = resource; + beanFactoryPostProcessorClasses = new ArrayList>(); + beanFactoryPostProcessorClasses.add(PropertyPlaceholderConfigurer.class); + beanFactoryPostProcessorClasses.add(CustomEditorConfigurer.class); + beanPostProcessorExcludeClasses = new ArrayList>(); + /* + * Assume that a BeanPostProcessor that is BeanFactoryAware must be specific to the parent and remove it from + * the child (e.g. an AutoProxyCreator will not work properly). Unfortunately there might still be a a + * BeanPostProcessor with a dependency that itself is BeanFactoryAware, but we can't legislate for that here. + */ + beanPostProcessorExcludeClasses.add(BeanFactoryAware.class); + } + + /** + * Flag to indicate that configuration such as bean post processors and custom editors should be copied from the + * parent context. Defaults to true. + * + * @param copyConfiguration the flag value to set + */ + public void setCopyConfiguration(boolean copyConfiguration) { + this.copyConfiguration = copyConfiguration; + } + + /** + * Protected access for subclasses to the flag determining whether configuration should be copied from parent + * context. + * + * @return the flag value + */ + protected final boolean isCopyConfiguration() { + return copyConfiguration; + } + + /** + * Determines which bean factory post processors (like property placeholders) should be copied from the parent + * context. Defaults to {@link PropertyPlaceholderConfigurer} and {@link CustomEditorConfigurer}. + * + * @param copyBeanFactoryPostProcessors the flag value to set + */ + + public void setBeanFactoryPostProcessorClasses( + Class[] beanFactoryPostProcessorClasses) { + this.beanFactoryPostProcessorClasses = new ArrayList>(); + for (int i = 0; i < beanFactoryPostProcessorClasses.length; i++) { + this.beanFactoryPostProcessorClasses.add(beanFactoryPostProcessorClasses[i]); + } + } + + /** + * Determines by exclusion which bean post processors should be copied from the parent context. Defaults to + * {@link BeanFactoryAware} (so any post processors that have a reference to the parent bean factory are not copied + * into the child). Note that these classes do not themselves have to be {@link BeanPostProcessor} implementations + * or sub-interfaces. + * + * @param beanPostProcessorExcludeClasses the classes to set + */ + public void setBeanPostProcessorExcludeClasses(Class[] beanPostProcessorExcludeClasses) { + this.beanPostProcessorExcludeClasses = new ArrayList>(); + for (int i = 0; i < beanPostProcessorExcludeClasses.length; i++) { + this.beanPostProcessorExcludeClasses.add(beanPostProcessorExcludeClasses[i]); + } + + } + + /** + * Protected access to the list of bean factory post processor classes that should be copied over to the context + * from the parent. + * + * @return the classes for post processors that were nominated for copying + */ + protected final Collection> getBeanFactoryPostProcessorClasses() { + return beanFactoryPostProcessorClasses; + } + + /** + * Setter for the parent application context. + * + * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) + */ + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + if (applicationContext == null) { + return; + } + Assert.isInstanceOf(ConfigurableApplicationContext.class, applicationContext); + parent = (ConfigurableApplicationContext) applicationContext; + } + + /** + * Creates an {@link ApplicationContext} from the provided path. + * + * @see ApplicationContextFactory#createApplicationContext() + */ + public ConfigurableApplicationContext createApplicationContext() { + + if (resource == null) { + return parent; + } + + return createApplicationContext(parent, resource); + + } + + protected abstract ConfigurableApplicationContext createApplicationContext(ConfigurableApplicationContext parent, + Object resource); + + /** + * Extension point for special subclasses that want to do more complex things with the context prior to refresh. The + * default implementation does nothing. + * + * @param parent the parent for the new application context + * @param context the new application context before it is refreshed, but after bean factory is initialized + * + * @see AbstractApplicationContextFactory#setBeanFactoryPostProcessorClasses(Class[]) + */ + protected void prepareContext(ConfigurableApplicationContext parent, ConfigurableApplicationContext context) { + } + + /** + * Extension point for special subclasses that want to do more complex things with the bean factory prior to + * refresh. The default implementation copies all configuration from the parent according to the + * {@link #setCopyConfiguration(boolean) flag} set. + * + * @param parent the parent bean factory for the new context (will never be null) + * @param beanFactory the new bean factory before bean definitions are loaded + * + * @see AbstractApplicationContextFactory#setCopyConfiguration(boolean) + * @see DefaultListableBeanFactory#copyConfigurationFrom(ConfigurableBeanFactory) + */ + protected void prepareBeanFactory(ConfigurableListableBeanFactory parent, + ConfigurableListableBeanFactory beanFactory) { + if (copyConfiguration && parent != null) { + beanFactory.copyConfigurationFrom(parent); + List beanPostProcessors = beanFactory instanceof AbstractBeanFactory ? ((AbstractBeanFactory) beanFactory) + .getBeanPostProcessors() : new ArrayList(); + for (BeanPostProcessor beanPostProcessor : new ArrayList(beanPostProcessors)) { + for (Class cls : beanPostProcessorExcludeClasses) { + if (cls.isAssignableFrom(beanPostProcessor.getClass())) { + logger.debug("Removing bean post processor: " + beanPostProcessor + " of type " + cls); + beanPostProcessors.remove(beanPostProcessor); + } + } + } + } + } + + @Override + public String toString() { + return "ApplicationContextFactory [resource=" + resource + "]"; + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + return toString().equals(obj.toString()); + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/AutomaticJobRegistrar.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/AutomaticJobRegistrar.java index 2d5d7c7db..423c2819a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/AutomaticJobRegistrar.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/AutomaticJobRegistrar.java @@ -34,18 +34,17 @@ import org.springframework.core.Ordered; import org.springframework.util.Assert; /** - * Loads and unloads {@link Job Jobs} when the application context is created - * and destroyed. Each resource provided is loaded as an application context - * with the current context as its parent, and then all the jobs from the child - * context are registered under their bean names. A {@link JobRegistry} is - * required. + * Loads and unloads {@link Job Jobs} when the application context is created and destroyed. Each resource provided is + * loaded as an application context with the current context as its parent, and then all the jobs from the child context + * are registered under their bean names. A {@link JobRegistry} is required. * * @author Lucas Ward * @author Dave Syer * * @since 2.1 */ -public class AutomaticJobRegistrar implements Ordered, Lifecycle, ApplicationListener, ApplicationContextAware, InitializingBean { +public class AutomaticJobRegistrar implements Ordered, Lifecycle, ApplicationListener, ApplicationContextAware, + InitializingBean { private Collection applicationContextFactories = new ArrayList(); @@ -56,15 +55,14 @@ public class AutomaticJobRegistrar implements Ordered, Lifecycle, ApplicationLis private volatile boolean running = false; private Object lifecycleMonitor = new Object(); - + private int order = Ordered.LOWEST_PRECEDENCE; /** - * The enclosing application context, which can be used to check if - * {@link ApplicationEvent events} come from the expected source. + * The enclosing application context, which can be used to check if {@link ApplicationEvent events} come from the + * expected source. * - * @param applicationContext the enclosing application context if there is - * one + * @param applicationContext the enclosing application context if there is one * @see ApplicationContextAware#setApplicationContext(ApplicationContext) */ public void setApplicationContext(ApplicationContext applicationContext) { @@ -74,8 +72,19 @@ public class AutomaticJobRegistrar implements Ordered, Lifecycle, ApplicationLis /** * Add some factories to the set that will be used to load contexts and jobs. * - * @param applicationContextFactories the {@link ApplicationContextFactory} - * values to use + * @param applicationContextFactories the {@link ApplicationContextFactory} values to use + */ + public void addApplicationContextFactory(ApplicationContextFactory applicationContextFactory) { + if (applicationContextFactory instanceof ApplicationContextAware) { + ((ApplicationContextAware) applicationContextFactory).setApplicationContext(applicationContext); + } + this.applicationContextFactories.add(applicationContextFactory); + } + + /** + * Add some factories to the set that will be used to load contexts and jobs. + * + * @param applicationContextFactories the {@link ApplicationContextFactory} values to use */ public void setApplicationContextFactories(ApplicationContextFactory[] applicationContextFactories) { for (ApplicationContextFactory applicationContextFactory : applicationContextFactories) { @@ -115,9 +124,8 @@ public class AutomaticJobRegistrar implements Ordered, Lifecycle, ApplicationLis } /** - * Creates all the application contexts required and set up job registry - * entries with all the instances of {@link Job} found therein. Also closes - * the contexts when the enclosing context is closed. + * Creates all the application contexts required and set up job registry entries with all the instances of + * {@link Job} found therein. Also closes the contexts when the enclosing context is closed. * * @see InitializingBean#afterPropertiesSet() */ @@ -146,8 +154,7 @@ public class AutomaticJobRegistrar implements Ordered, Lifecycle, ApplicationLis } /** - * Take all the contexts from the factories provided and pass them to teh - * {@link JobLoader}. + * Take all the contexts from the factories provided and pass them to the {@link JobLoader}. * * @see Lifecycle#start() */ diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ClassPathXmlApplicationContextFactory.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ClassPathXmlApplicationContextFactory.java index ad369a0de..cda0ca50e 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ClassPathXmlApplicationContextFactory.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ClassPathXmlApplicationContextFactory.java @@ -16,27 +16,8 @@ package org.springframework.batch.core.configuration.support; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.config.BeanFactoryPostProcessor; -import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.beans.factory.config.ConfigurableBeanFactory; -import org.springframework.beans.factory.config.CustomEditorConfigurer; -import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.support.AbstractXmlApplicationContext; import org.springframework.core.io.Resource; -import org.springframework.util.Assert; /** * {@link ApplicationContextFactory} implementation that takes a parent context @@ -46,279 +27,17 @@ import org.springframework.util.Assert; * initialized and cached. Clients should ensure that it is closed when it is no * longer needed. If a path is not set, the parent will always be returned. * + * @deprecated use {@link GenericApplicationContextFactory} instead */ -public class ClassPathXmlApplicationContextFactory implements ApplicationContextFactory, ApplicationContextAware { - - private static final Log logger = LogFactory.getLog(ClassPathXmlApplicationContextFactory.class); - - private Resource resource; - - private ConfigurableApplicationContext parent; - - private boolean copyConfiguration = true; - - private Collection> beanFactoryPostProcessorClasses; - - private Collection> beanPostProcessorExcludeClasses; +public class ClassPathXmlApplicationContextFactory extends GenericApplicationContextFactory { /** - * Convenient constructor for configuration purposes. - */ - public ClassPathXmlApplicationContextFactory() { - this(null); - } - - /** - * Create a factory instance with the resource specified. The resource is a - * Spring XML configuration file. + * Create an application context factory for the resource specified. + * + * @param resource a resource (XML configuration file) */ public ClassPathXmlApplicationContextFactory(Resource resource) { - - this.resource = resource; - beanFactoryPostProcessorClasses = new ArrayList>(); - beanFactoryPostProcessorClasses.add(PropertyPlaceholderConfigurer.class); - beanFactoryPostProcessorClasses.add(CustomEditorConfigurer.class); - beanPostProcessorExcludeClasses = new ArrayList>(); - /* - * Assume that a BeanPostProcessor that is BeanFactoryAware must be - * specific to the parent and remove it from the child (e.g. an - * AutoProxyCreator will not work properly). Unfortunately there might - * still be a a BeanPostProcessor with a dependency that itself is - * BeanFactoryAware, but we can't legislate for that here. - */ - beanPostProcessorExcludeClasses.add(BeanFactoryAware.class); - } - - /** - * Setter for the path to the xml to load to create an - * {@link ApplicationContext}. Use imports to centralise the configuration - * in one file. - * - * @param resource the resource path to the xml to load for the child - * context. - */ - public void setResource(Resource resource) { - this.resource = resource; - } - - /** - * Flag to indicate that configuration such as bean post processors and - * custom editors should be copied from the parent context. Defaults to - * true. - * - * @param copyConfiguration the flag value to set - */ - public void setCopyConfiguration(boolean copyConfiguration) { - this.copyConfiguration = copyConfiguration; - } - - /** - * Protected access for subclasses to the flag determining whether - * configuration should be copied from parent context. - * - * @return the flag value - */ - protected final boolean isCopyConfiguration() { - return copyConfiguration; - } - - /** - * Determines which bean factory post processors (like property - * placeholders) should be copied from the parent context. Defaults to - * {@link PropertyPlaceholderConfigurer} and {@link CustomEditorConfigurer}. - * - * @param copyBeanFactoryPostProcessors the flag value to set - */ - - public void setBeanFactoryPostProcessorClasses( - Class[] beanFactoryPostProcessorClasses) { - this.beanFactoryPostProcessorClasses = new ArrayList>(); - for (int i = 0; i < beanFactoryPostProcessorClasses.length; i++) { - this.beanFactoryPostProcessorClasses.add(beanFactoryPostProcessorClasses[i]); - } - } - - /** - * Determines by exclusion which bean post processors should be copied from - * the parent context. Defaults to {@link BeanFactoryAware} (so any post - * processors that have a reference to the parent bean factory are not - * copied into the child). Note that these classes do not themselves have to - * be {@link BeanPostProcessor} implementations or sub-interfaces. - * - * @param beanPostProcessorExcludeClasses the classes to set - */ - public void setBeanPostProcessorExcludeClasses(Class[] beanPostProcessorExcludeClasses) { - this.beanPostProcessorExcludeClasses = new ArrayList>(); - for (int i = 0; i < beanPostProcessorExcludeClasses.length; i++) { - this.beanPostProcessorExcludeClasses.add(beanPostProcessorExcludeClasses[i]); - } - - } - - /** - * Protected access to the list of bean factory post processor classes that - * should be copied over to the context from the parent. - * - * @return the classes for post processors that were nominated for copying - */ - protected final Collection> getBeanFactoryPostProcessorClasses() { - return beanFactoryPostProcessorClasses; - } - - /** - * Setter for the parent application context. - * - * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) - */ - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - if (applicationContext == null) { - return; - } - Assert.isInstanceOf(ConfigurableApplicationContext.class, applicationContext); - parent = (ConfigurableApplicationContext) applicationContext; - } - - /** - * Creates an {@link ApplicationContext} from the provided path. - * - * @see ApplicationContextFactory#createApplicationContext() - */ - public ConfigurableApplicationContext createApplicationContext() { - - if (resource == null) { - return parent; - } - - return new ResourceXmlApplicationContext(parent); - - } - - /** - * @author Dave Syer - * - */ - private final class ResourceXmlApplicationContext extends AbstractXmlApplicationContext { - - private final DefaultListableBeanFactory parentBeanFactory; - - /** - * @param parent - */ - public ResourceXmlApplicationContext(ConfigurableApplicationContext parent) { - super(parent); - setId(generateId(resource)); - if (parent != null) { - Assert.isTrue(parent.getBeanFactory() instanceof DefaultListableBeanFactory, - "The parent application context must have a bean factory of type DefaultListableBeanFactory"); - parentBeanFactory = (DefaultListableBeanFactory) parent.getBeanFactory(); - refreshBeanFactory(); - prepareContext(parent, this); - } - else { - parentBeanFactory = null; - } - refresh(); - } - - /** - * @param resource - * @return an identifier for the context - */ - private String generateId(Resource resource) { - try { - return resource.getURI().toString(); - } - catch (IOException e) { - return resource.toString(); - } - } - - @Override - protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) { - super.customizeBeanFactory(beanFactory); - if (parentBeanFactory != null) { - ClassPathXmlApplicationContextFactory.this.prepareBeanFactory(parentBeanFactory, beanFactory); - for (Class cls : beanFactoryPostProcessorClasses) { - for (String name : parent.getBeanNamesForType(cls)) { - beanFactory.registerSingleton(name, ((BeanFactoryPostProcessor) parent.getBean(name))); - } - } - } - } - - @Override - protected Resource[] getConfigResources() { - return new Resource[] { resource }; - } - - @Override - public String toString() { - return "ResourceXmlApplicationContext:" + getId(); - } - - } - - /** - * Extension point for special subclasses that want to do more complex - * things with the context prior to refresh. The default implementation - * does nothing. - * - * @param parent the parent for the new application context - * @param context the new application context before it is refreshed, but - * after bean factory is initialized - * - * @see ClassPathXmlApplicationContextFactory#setBeanFactoryPostProcessorClasses(Class[]) - */ - protected void prepareContext(ConfigurableApplicationContext parent, ConfigurableApplicationContext context) { - } - - /** - * Extension point for special subclasses that want to do more complex - * things with the bean factory prior to refresh. The default implementation - * copies all configuration from the parent according to the - * {@link #setCopyConfiguration(boolean) flag} set. - * - * @param parent the parent bean factory for the new context (will never be - * null) - * @param beanFactory the new bean factory before bean definitions are - * loaded - * - * @see ClassPathXmlApplicationContextFactory#setCopyConfiguration(boolean) - * @see DefaultListableBeanFactory#copyConfigurationFrom(ConfigurableBeanFactory) - */ - protected void prepareBeanFactory(DefaultListableBeanFactory parent, DefaultListableBeanFactory beanFactory) { - if (copyConfiguration && parent != null) { - beanFactory.copyConfigurationFrom(parent); - @SuppressWarnings("unchecked") - List beanPostProcessors = beanFactory.getBeanPostProcessors(); - for (BeanPostProcessor beanPostProcessor : new ArrayList(beanPostProcessors)) { - for (Class cls : beanPostProcessorExcludeClasses) { - if (cls.isAssignableFrom(beanPostProcessor.getClass())) { - logger.debug("Removing bean post processor: " + beanPostProcessor + " of type " + cls); - beanPostProcessors.remove(beanPostProcessor); - } - } - } - } - } - - @Override - public String toString() { - return "ClassPathXmlApplicationContextFactory [resource=" + resource + "]"; - } - - @Override - public int hashCode() { - return toString().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - return toString().equals(obj.toString()); + super(resource); } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ClasspathXmlApplicationContextsFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ClasspathXmlApplicationContextsFactoryBean.java index 4ec39fd9c..078584a34 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ClasspathXmlApplicationContextsFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ClasspathXmlApplicationContextsFactoryBean.java @@ -51,7 +51,7 @@ public class ClasspathXmlApplicationContextsFactoryBean implements FactoryBean, /** * A set of resources to load using a - * {@link ClassPathXmlApplicationContextFactory}. Each resource should be a + * {@link GenericApplicationContextFactory}. Each resource should be a * Spring configuration file which is loaded into an application context * whose parent is the current context. In a configuration file the * resources can be given as a pattern (e.g. @@ -116,7 +116,7 @@ public class ClasspathXmlApplicationContextsFactoryBean implements FactoryBean, List applicationContextFactories = new ArrayList(); for (Resource resource : resources) { - ClassPathXmlApplicationContextFactory factory = new ClassPathXmlApplicationContextFactory(); + GenericApplicationContextFactory factory = new GenericApplicationContextFactory(resource); factory.setCopyConfiguration(copyConfiguration); if (beanFactoryPostProcessorClasses != null) { factory.setBeanFactoryPostProcessorClasses(beanFactoryPostProcessorClasses); @@ -124,7 +124,6 @@ public class ClasspathXmlApplicationContextsFactoryBean implements FactoryBean, if (beanPostProcessorExcludeClasses != null) { factory.setBeanPostProcessorExcludeClasses(beanPostProcessorExcludeClasses); } - factory.setResource(resource); factory.setApplicationContext(applicationContext); applicationContextFactories.add(factory); } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/GenericApplicationContextFactory.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/GenericApplicationContextFactory.java new file mode 100644 index 000000000..563007bcc --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/GenericApplicationContextFactory.java @@ -0,0 +1,195 @@ +/* + * Copyright 2006-2007 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.support; + +import java.io.IOException; + +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.context.support.GenericXmlApplicationContext; +import org.springframework.core.io.Resource; +import org.springframework.util.Assert; + +/** + * {@link ApplicationContextFactory} implementation that takes a parent context and a path to the context to create. + * When createApplicationContext method is called, the child {@link ApplicationContext} will be returned. The child + * context is not re-created every time it is requested, it is lazily initialized and cached. Clients should ensure that + * it is closed when it is no longer needed. + * + */ +public class GenericApplicationContextFactory extends AbstractApplicationContextFactory { + + /** + * Create an application context factory for the resource specified. The resource can be an actual {@link Resource}, + * in which case it will be interpreted as an XML file, or it can be a @Configuration class, or a package name. + * + * @param resource a resource (XML configuration file, @Configuration class or java package to scan) + */ + public GenericApplicationContextFactory(Object resource) { + super(resource); + } + + /** + * @see AbstractApplicationContextFactory#createApplicationContext(ConfigurableApplicationContext, Object) + */ + @Override + protected ConfigurableApplicationContext createApplicationContext(ConfigurableApplicationContext parent, + Object resource) { + if (resource instanceof Resource) { + return new ResourceXmlApplicationContext(parent, (Resource) resource); + } + if (resource instanceof Class) { + return new ResourceAnnotationApplicationContext(parent, (Class) resource); + } + if (resource instanceof String) { + return new ResourceAnnotationApplicationContext(parent, (String) resource); + } + throw new IllegalArgumentException("No application context could be created for resource type: " + + resource.getClass()); + } + + private abstract class ApplicationContextHelper { + + private final DefaultListableBeanFactory parentBeanFactory; + + private final ConfigurableApplicationContext parent; + + public ApplicationContextHelper(ConfigurableApplicationContext parent, GenericApplicationContext context, + Object config) { + this.parent = parent; + if (parent != null) { + Assert.isTrue(parent.getBeanFactory() instanceof DefaultListableBeanFactory, + "The parent application context must have a bean factory of type DefaultListableBeanFactory"); + parentBeanFactory = (DefaultListableBeanFactory) parent.getBeanFactory(); + } + else { + parentBeanFactory = null; + } + context.setParent(parent); + context.setId(generateId(config)); + loadConfiguration(config); + prepareContext(parent, context); + } + + protected abstract String generateId(Object config); + + protected abstract void loadConfiguration(Object config); + + protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) { + if (parentBeanFactory != null) { + GenericApplicationContextFactory.this.prepareBeanFactory(parentBeanFactory, beanFactory); + for (Class cls : getBeanFactoryPostProcessorClasses()) { + for (String name : parent.getBeanNamesForType(cls)) { + beanFactory.registerSingleton(name, ((BeanFactoryPostProcessor) parent.getBean(name))); + } + } + } + } + + } + + private final class ResourceXmlApplicationContext extends GenericXmlApplicationContext { + + private final ApplicationContextHelper helper; + + /** + * @param parent + */ + public ResourceXmlApplicationContext(ConfigurableApplicationContext parent, Resource resource) { + helper = new ApplicationContextHelper(parent, this, resource) { + @Override + protected String generateId(Object config) { + Resource resource = (Resource) config; + try { + return resource.getURI().toString(); + } + catch (IOException e) { + return resource.toString(); + } + } + @Override + protected void loadConfiguration(Object config) { + Resource resource = (Resource) config; + load(resource); + } + }; + refresh(); + } + + @Override + protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) { + super.prepareBeanFactory(beanFactory); + helper.prepareBeanFactory(beanFactory); + } + + @Override + public String toString() { + return "ResourceXmlApplicationContext:" + getId(); + } + + } + + private final class ResourceAnnotationApplicationContext extends AnnotationConfigApplicationContext { + + private final ApplicationContextHelper helper; + + public ResourceAnnotationApplicationContext(ConfigurableApplicationContext parent, Object resource) { + helper = new ApplicationContextHelper(parent, this, resource) { + @Override + protected String generateId(Object config) { + if (config instanceof Class) { + Class type = (Class) config; + return type.getName(); + } + else { + return config.toString(); + } + } + @Override + protected void loadConfiguration(Object config) { + if (config instanceof Class) { + Class type = (Class) config; + register(type); + } + else { + String pkg = (String) config; + scan(pkg); + } + } + }; + refresh(); + } + + @Override + protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) { + super.prepareBeanFactory(beanFactory); + helper.prepareBeanFactory(beanFactory); + } + + @Override + public String toString() { + return "ResourceAnnotationApplicationContext:" + getId(); + } + + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/SimpleJobBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/SimpleJobBuilder.java index 10a119922..92b499f60 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/SimpleJobBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/SimpleJobBuilder.java @@ -50,7 +50,7 @@ public class SimpleJobBuilder extends JobBuilderHelper { if (builder != null) { return builder.end().build(); } - SimpleJob job = new SimpleJob(); + SimpleJob job = new SimpleJob(getName()); super.enhance(job); job.setSteps(steps); try { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JobRegistryBackgroundJobRunner.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JobRegistryBackgroundJobRunner.java index 2f4e1bac7..08b6e5293 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JobRegistryBackgroundJobRunner.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JobRegistryBackgroundJobRunner.java @@ -27,8 +27,8 @@ import org.springframework.batch.core.Job; import org.springframework.batch.core.configuration.DuplicateJobException; import org.springframework.batch.core.configuration.JobFactory; import org.springframework.batch.core.configuration.JobRegistry; -import org.springframework.batch.core.configuration.support.ClassPathXmlApplicationContextFactory; import org.springframework.batch.core.configuration.support.DefaultJobLoader; +import org.springframework.batch.core.configuration.support.GenericApplicationContextFactory; import org.springframework.batch.core.configuration.support.JobLoader; import org.springframework.batch.core.launch.JobLauncher; import org.springframework.beans.factory.BeanFactory; @@ -133,9 +133,8 @@ public class JobRegistryBackgroundJobRunner { Resource path = resources[j]; logger.info("Registering Job definitions from " + Arrays.toString(resources)); - ClassPathXmlApplicationContextFactory factory = new ClassPathXmlApplicationContextFactory(); + GenericApplicationContextFactory factory = new GenericApplicationContextFactory(path); factory.setApplicationContext(parentContext); - factory.setResource(path); jobLoader.load(factory); } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/DataSourceConfiguration.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/DataSourceConfiguration.java new file mode 100644 index 000000000..76aa3d805 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/DataSourceConfiguration.java @@ -0,0 +1,54 @@ +/* + * Copyright 2012-2013 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 javax.annotation.PostConstruct; +import javax.sql.DataSource; + +import org.springframework.batch.core.Step; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.core.io.ResourceLoader; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactory; +import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils; +import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; +import org.springframework.util.ClassUtils; + +@Configuration +public class DataSourceConfiguration { + + @Autowired + private Environment environment; + + @Autowired + private ResourceLoader resourceLoader; + + @PostConstruct + protected void initialize() { + ResourceDatabasePopulator populator = new ResourceDatabasePopulator(); + populator.addScript(resourceLoader.getResource(ClassUtils.addResourcePathToPackagePath(Step.class, "schema-hsqldb.sql"))); + populator.setContinueOnError(true); + DatabasePopulatorUtils.execute(populator, dataSource()); + } + + @Bean + public DataSource dataSource() { + return new EmbeddedDatabaseFactory().getDatabase(); + } + +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobBuilderConfigurationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobBuilderConfigurationTests.java new file mode 100644 index 000000000..993d1345d --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobBuilderConfigurationTests.java @@ -0,0 +1,234 @@ +/* + * Copyright 2006-2011 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 static org.junit.Assert.assertEquals; + +import javax.sql.DataSource; + +import org.junit.Test; +import org.springframework.batch.core.BatchStatus; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.builder.SimpleJobBuilder; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.AbstractStep; +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.annotation.Configuration; + +/** + * @author Dave Syer + * + */ +public class JobBuilderConfigurationTests { + + public static boolean fail = false; + + private JobExecution execution; + + @Test + public void testVanillaBatchConfiguration() throws Exception { + testJob(BatchStatus.COMPLETED, 2, TestConfiguration.class); + } + + @Test + public void testConfigurerAsConfiguration() throws Exception { + testJob(BatchStatus.COMPLETED, 1, TestConfigurer.class); + } + + @Test + public void testConfigurerAsBean() throws Exception { + testJob(BatchStatus.COMPLETED, 1, BeansConfigurer.class); + } + + @Test + public void testTwoConfigurations() throws Exception { + testJob("testJob", BatchStatus.COMPLETED, 2, TestConfiguration.class, AnotherConfiguration.class); + } + + @Test + public void testTwoConfigurationsAndConfigurer() throws Exception { + testJob("testJob", BatchStatus.COMPLETED, 2, TestConfiguration.class, TestConfigurer.class); + } + + @Test + public void testTwoConfigurationsAndBeansConfigurer() throws Exception { + testJob("testJob", BatchStatus.COMPLETED, 2, TestConfiguration.class, BeansConfigurer.class); + } + + private void testJob(BatchStatus status, int stepExecutionCount, Class config) throws Exception { + testJob(null, status, stepExecutionCount, config); + } + + private void testJob(String jobName, BatchStatus status, int stepExecutionCount, Class... config) + throws Exception { + + Class[] configs = new Class[config.length + 1]; + System.arraycopy(config, 0, configs, 1, config.length); + configs[0] = DataSourceConfiguration.class; + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(configs); + Job job = jobName == null ? context.getBean(Job.class) : context.getBean(jobName, Job.class); + JobLauncher jobLauncher = context.getBean(JobLauncher.class); + execution = jobLauncher + .run(job, new JobParametersBuilder().addLong("run.id", (long) (Math.random() * Long.MAX_VALUE)) + .toJobParameters()); + assertEquals(status, execution.getStatus()); + assertEquals(stepExecutionCount, execution.getStepExecutions().size()); + context.close(); + + } + + @Configuration + @EnableBatchProcessing + public static class TestConfiguration { + + @Autowired + private JobBuilderFactory jobs; + + @Autowired + private StepBuilderFactory steps; + + @Bean + public Job testJob() throws Exception { + SimpleJobBuilder builder = jobs.get("test").start(step1()).next(step2()); + return builder.build(); + } + + @Bean + protected Step step1() throws Exception { + return steps.get("step1").tasklet(tasklet()).build(); + } + + @Bean + protected Step step2() throws Exception { + return steps.get("step2").tasklet(tasklet()).build(); + } + + @Bean + protected Tasklet tasklet() { + return new Tasklet() { + @Override + public RepeatStatus execute(StepContribution contribution, ChunkContext context) throws Exception { + if (fail) { + throw new RuntimeException("Planned!"); + } + return RepeatStatus.FINISHED; + } + }; + } + } + + @Configuration + @EnableBatchProcessing + public static class AnotherConfiguration { + + @Autowired + private JobBuilderFactory jobs; + + @Autowired + private StepBuilderFactory steps; + + @Autowired + private Tasklet tasklet; + + @Bean + public Job anotherJob() throws Exception { + SimpleJobBuilder builder = jobs.get("another").start(step3()); + return builder.build(); + } + + @Bean + protected Step step3() throws Exception { + return steps.get("step3").tasklet(tasklet).build(); + } + + } + + @Configuration + @EnableBatchProcessing + public static class TestConfigurer extends DefaultBatchConfigurer { + + @Autowired + private SimpleBatchConfiguration jobs; + + @Bean + public Job testConfigererJob() throws Exception { + SimpleJobBuilder builder = jobs.jobBuilders().get("configurer").start(step1()); + return builder.build(); + } + + @Bean + protected Step step1() throws Exception { + AbstractStep step = new AbstractStep("step1") { + @Override + protected void doExecute(StepExecution stepExecution) throws Exception { + stepExecution.setExitStatus(ExitStatus.COMPLETED); + stepExecution.setStatus(BatchStatus.COMPLETED); + } + }; + step.setJobRepository(getJobRepository()); + return step; + } + + } + + @Configuration + @EnableBatchProcessing + public static class BeansConfigurer { + + @Autowired + private JobBuilderFactory jobs; + + @Autowired + private StepBuilderFactory steps; + + @Bean + public Job beansConfigurerJob() throws Exception { + SimpleJobBuilder builder = jobs.get("beans").start(step1()); + return builder.build(); + } + + @Bean + protected Step step1() throws Exception { + return steps.get("step1").tasklet(new Tasklet() { + + @Override + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { + return null; + } + }).build(); + } + + @Bean + @Autowired + protected BatchConfigurer configurer(DataSource dataSource) { + return new DefaultBatchConfigurer(dataSource); + } + + } + +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobLoaderConfigurationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobLoaderConfigurationTests.java new file mode 100644 index 000000000..34d0e0ac4 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobLoaderConfigurationTests.java @@ -0,0 +1,173 @@ +/* + * Copyright 2012-2013 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 static org.junit.Assert.assertEquals; + +import javax.annotation.PostConstruct; + +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.JobParametersBuilder; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.configuration.JobLocator; +import org.springframework.batch.core.configuration.support.ApplicationContextFactory; +import org.springframework.batch.core.configuration.support.AutomaticJobRegistrar; +import org.springframework.batch.core.configuration.support.GenericApplicationContextFactory; +import org.springframework.batch.core.job.builder.SimpleJobBuilder; +import org.springframework.batch.core.launch.JobLauncher; +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.annotation.Configuration; + +/** + * @author Dave Syer + * + */ +public class JobLoaderConfigurationTests { + + private JobExecution execution; + + @Test + public void testJobLoader() throws Exception { + testJob("test", BatchStatus.COMPLETED, 2, LoaderFactoryConfiguration.class); + } + + @Test + public void testJobLoaderWithArray() throws Exception { + testJob("test", BatchStatus.COMPLETED, 2, LoaderRegistrarConfiguration.class); + } + + private void testJob(String jobName, BatchStatus status, int stepExecutionCount, Class... config) + throws Exception { + + Class[] configs = new Class[config.length + 1]; + System.arraycopy(config, 0, configs, 1, config.length); + configs[0] = DataSourceConfiguration.class; + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(configs); + Job job = jobName == null ? context.getBean(Job.class) : context.getBean(JobLocator.class).getJob(jobName); + JobLauncher jobLauncher = context.getBean(JobLauncher.class); + execution = jobLauncher + .run(job, new JobParametersBuilder().addLong("run.id", (long) (Math.random() * Long.MAX_VALUE)) + .toJobParameters()); + assertEquals(status, execution.getStatus()); + assertEquals(stepExecutionCount, execution.getStepExecutions().size()); + context.close(); + + } + + @Configuration + @EnableBatchProcessing(modular=true) + public static class LoaderFactoryConfiguration { + + @Bean + public ApplicationContextFactory jobContextFactory() { + return new GenericApplicationContextFactory(TestConfiguration.class); + + } + + @Bean + public ApplicationContextFactory vanillaContextFactory() { + return new GenericApplicationContextFactory(VanillaConfiguration.class); + } + + } + + @Configuration + @EnableBatchProcessing(modular=true) + public static class LoaderRegistrarConfiguration { + + @Autowired + private AutomaticJobRegistrar registrar; + + @PostConstruct + public void initialize() { + registrar.addApplicationContextFactory(new GenericApplicationContextFactory(TestConfiguration.class)); + registrar.addApplicationContextFactory(new GenericApplicationContextFactory(VanillaConfiguration.class)); + } + + } + + @Configuration + public static class TestConfiguration { + + @Autowired + private JobBuilderFactory jobs; + + @Autowired + private StepBuilderFactory steps; + + @Bean + public Job testJob() throws Exception { + SimpleJobBuilder builder = jobs.get("test").start(step1()).next(step2()); + return builder.build(); + } + + @Bean + protected Step step1() throws Exception { + return steps.get("step1").tasklet(tasklet()).build(); + } + + @Bean + protected Step step2() throws Exception { + return steps.get("step2").tasklet(tasklet()).build(); + } + + @Bean + protected Tasklet tasklet() { + return new Tasklet() { + @Override + public RepeatStatus execute(StepContribution contribution, ChunkContext context) throws Exception { + return RepeatStatus.FINISHED; + } + }; + } + } + + @Configuration + public static class VanillaConfiguration { + + @Autowired + private JobBuilderFactory jobs; + + @Autowired + private StepBuilderFactory steps; + + @Bean + public Job vanillaJob() throws Exception { + SimpleJobBuilder builder = jobs.get("vanilla").start(step3()); + return builder.build(); + } + + @Bean + protected Step step3() throws Exception { + return steps.get("step3").tasklet(new Tasklet() { + @Override + public RepeatStatus execute(StepContribution contribution, ChunkContext context) throws Exception { + return RepeatStatus.FINISHED; + } + }).build(); + } + } + +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/AutomaticJobRegistrarTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/AutomaticJobRegistrarTests.java index a957dfba2..69aaf61a5 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/AutomaticJobRegistrarTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/AutomaticJobRegistrarTests.java @@ -190,8 +190,7 @@ public class AutomaticJobRegistrarTests { private void setUpApplicationContextFactories(Resource[] jobPaths, ApplicationContext parent) { Collection applicationContextFactories = new ArrayList(); for (Resource resource : jobPaths) { - ClassPathXmlApplicationContextFactory factory = new ClassPathXmlApplicationContextFactory(); - factory.setResource(resource); + GenericApplicationContextFactory factory = new GenericApplicationContextFactory(resource); factory.setApplicationContext(parent); applicationContextFactories.add(factory); } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/DefaultJobLoaderTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/DefaultJobLoaderTests.java index 1dce96edc..f898d52a9 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/DefaultJobLoaderTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/DefaultJobLoaderTests.java @@ -38,8 +38,8 @@ public class DefaultJobLoaderTests { @Test public void testLoadWithExplicitName() throws Exception { - ClassPathXmlApplicationContextFactory factory = new ClassPathXmlApplicationContextFactory( - new ByteArrayResource(JOB_XML.getBytes())); + GenericApplicationContextFactory factory = new GenericApplicationContextFactory(new ByteArrayResource( + JOB_XML.getBytes())); jobLoader.load(factory); assertEquals(1, registry.getJobNames().size()); jobLoader.reload(factory); @@ -48,8 +48,8 @@ public class DefaultJobLoaderTests { @Test public void testReload() throws Exception { - ClassPathXmlApplicationContextFactory factory = new ClassPathXmlApplicationContextFactory( - new ClassPathResource("trivial-context.xml", getClass())); + GenericApplicationContextFactory factory = new GenericApplicationContextFactory(new ClassPathResource( + "trivial-context.xml", getClass())); jobLoader.load(factory); assertEquals(1, registry.getJobNames().size()); jobLoader.reload(factory); @@ -58,8 +58,8 @@ public class DefaultJobLoaderTests { @Test public void testReloadWithAutoRegister() throws Exception { - ClassPathXmlApplicationContextFactory factory = new ClassPathXmlApplicationContextFactory( - new ClassPathResource("trivial-context-autoregister.xml", getClass())); + GenericApplicationContextFactory factory = new GenericApplicationContextFactory(new ClassPathResource( + "trivial-context-autoregister.xml", getClass())); jobLoader.load(factory); assertEquals(1, registry.getJobNames().size()); jobLoader.reload(factory); @@ -67,9 +67,8 @@ public class DefaultJobLoaderTests { } private static final String JOB_XML = String - .format( - "", + .format("", DefaultJobLoaderTests.class.getName()); public static class StubJob implements Job { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ClassPathXmlApplicationContextFactoryTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/GenericApplicationContextFactoryTests.java similarity index 75% rename from spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ClassPathXmlApplicationContextFactoryTests.java rename to spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/GenericApplicationContextFactoryTests.java index 8d22a6326..fbe953213 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ClassPathXmlApplicationContextFactoryTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/GenericApplicationContextFactoryTests.java @@ -32,32 +32,30 @@ import org.springframework.util.ClassUtils; * @author Dave Syer * */ -public class ClassPathXmlApplicationContextFactoryTests { - - private ClassPathXmlApplicationContextFactory factory = new ClassPathXmlApplicationContextFactory(); +public class GenericApplicationContextFactoryTests { @Test public void testCreateJob() { - factory.setResource(new ClassPathResource(ClassUtils.addResourcePathToPackagePath(getClass(), - "trivial-context.xml"))); + GenericApplicationContextFactory factory = new GenericApplicationContextFactory( + new ClassPathResource(ClassUtils.addResourcePathToPackagePath(getClass(), "trivial-context.xml"))); ConfigurableApplicationContext context = factory.createApplicationContext(); assertNotNull(context); - assertTrue("Wrong id: "+context, context.getId().contains("trivial-context.xml")); + assertTrue("Wrong id: " + context, context.getId().contains("trivial-context.xml")); } @Test public void testGetJobName() { - factory.setResource(new ClassPathResource(ClassUtils.addResourcePathToPackagePath(getClass(), - "trivial-context.xml"))); + GenericApplicationContextFactory factory = new GenericApplicationContextFactory( + new ClassPathResource(ClassUtils.addResourcePathToPackagePath(getClass(), "trivial-context.xml"))); assertEquals("test-job", factory.createApplicationContext().getBeanNamesForType(Job.class)[0]); } @Test public void testParentConfigurationInherited() { + GenericApplicationContextFactory factory = new GenericApplicationContextFactory( + new ClassPathResource(ClassUtils.addResourcePathToPackagePath(getClass(), "child-context.xml"))); factory.setApplicationContext(new ClassPathXmlApplicationContext(ClassUtils.addResourcePathToPackagePath( getClass(), "parent-context.xml"))); - factory.setResource(new ClassPathResource(ClassUtils.addResourcePathToPackagePath(getClass(), - "child-context.xml"))); ConfigurableApplicationContext context = factory.createApplicationContext(); assertEquals("test-job", context.getBeanNamesForType(Job.class)[0]); assertEquals("bar", ((Job) context.getBean("test-job", Job.class)).getName()); @@ -66,10 +64,10 @@ public class ClassPathXmlApplicationContextFactoryTests { @Test public void testBeanFactoryPostProcessorOrderRespected() { + GenericApplicationContextFactory factory = new GenericApplicationContextFactory( + new ClassPathResource(ClassUtils.addResourcePathToPackagePath(getClass(), "placeholder-context.xml"))); factory.setApplicationContext(new ClassPathXmlApplicationContext(ClassUtils.addResourcePathToPackagePath( getClass(), "parent-context.xml"))); - factory.setResource(new ClassPathResource(ClassUtils.addResourcePathToPackagePath(getClass(), - "placeholder-context.xml"))); ConfigurableApplicationContext context = factory.createApplicationContext(); assertEquals("test-job", context.getBeanNamesForType(Job.class)[0]); assertEquals("spam", ((Job) context.getBean("test-job", Job.class)).getName()); @@ -77,10 +75,10 @@ public class ClassPathXmlApplicationContextFactoryTests { @Test public void testBeanFactoryPostProcessorsNotCopied() { + GenericApplicationContextFactory factory = new GenericApplicationContextFactory( + new ClassPathResource(ClassUtils.addResourcePathToPackagePath(getClass(), "child-context.xml"))); factory.setApplicationContext(new ClassPathXmlApplicationContext(ClassUtils.addResourcePathToPackagePath( getClass(), "parent-context.xml"))); - factory.setResource(new ClassPathResource(ClassUtils.addResourcePathToPackagePath(getClass(), - "child-context.xml"))); @SuppressWarnings("unchecked") Class[] classes = (Class[]) new Class[0]; factory.setBeanFactoryPostProcessorClasses(classes); @@ -92,10 +90,10 @@ public class ClassPathXmlApplicationContextFactoryTests { @Test public void testBeanFactoryConfigurationNotCopied() { + GenericApplicationContextFactory factory = new GenericApplicationContextFactory(new ClassPathResource(ClassUtils.addResourcePathToPackagePath(getClass(), + "child-context.xml"))); factory.setApplicationContext(new ClassPathXmlApplicationContext(ClassUtils.addResourcePathToPackagePath( getClass(), "parent-context.xml"))); - factory.setResource(new ClassPathResource(ClassUtils.addResourcePathToPackagePath(getClass(), - "child-context.xml"))); factory.setCopyConfiguration(false); ConfigurableApplicationContext context = factory.createApplicationContext(); assertEquals("test-job", context.getBeanNamesForType(Job.class)[0]); @@ -109,9 +107,8 @@ public class ClassPathXmlApplicationContextFactoryTests { public void testEquals() throws Exception { Resource resource = new ClassPathResource(ClassUtils.addResourcePathToPackagePath(getClass(), "child-context.xml")); - factory.setResource(resource); - ClassPathXmlApplicationContextFactory other = new ClassPathXmlApplicationContextFactory(); - other.setResource(resource); + GenericApplicationContextFactory factory = new GenericApplicationContextFactory(resource); + GenericApplicationContextFactory other = new GenericApplicationContextFactory(resource); assertEquals(other, factory); assertEquals(other.hashCode(), factory.hashCode()); } diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/child-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/child-context.xml index dd5d639b4..bd2e5e00d 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/child-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/child-context.xml @@ -10,7 +10,7 @@ - + diff --git a/spring-batch-parent/pom.xml b/spring-batch-parent/pom.xml index 34c576b14..d293878fb 100644 --- a/spring-batch-parent/pom.xml +++ b/spring-batch-parent/pom.xml @@ -260,8 +260,8 @@ maven-compiler-plugin 2.5.1 - 1.5 - 1.5 + 1.6 + 1.6 diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/sample/config/DataSourceConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/sample/config/DataSourceConfiguration.java new file mode 100644 index 000000000..2983f9957 --- /dev/null +++ b/spring-batch-samples/src/main/java/org/springframework/batch/sample/config/DataSourceConfiguration.java @@ -0,0 +1,63 @@ +/* + * Copyright 2012-2013 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.sample.config; + +import javax.annotation.PostConstruct; +import javax.sql.DataSource; + +import org.apache.commons.dbcp.BasicDataSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; +import org.springframework.core.env.Environment; +import org.springframework.core.io.ResourceLoader; +import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils; +import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; + +/** + * @author Dave Syer + * + */ +@Configuration +@PropertySource("classpath:/batch-hsql.properties") +public class DataSourceConfiguration { + + @Autowired + private Environment environment; + + @Autowired + private ResourceLoader resourceLoader; + + @PostConstruct + protected void initialize() { + ResourceDatabasePopulator populator = new ResourceDatabasePopulator(); + populator.addScript(resourceLoader.getResource(environment.getProperty("batch.schema.script"))); + populator.setContinueOnError(true); + DatabasePopulatorUtils.execute(populator , dataSource()); + } + + @Bean(destroyMethod="close") + public DataSource dataSource() { + BasicDataSource dataSource = new BasicDataSource(); + dataSource.setDriverClassName(environment.getProperty("batch.jdbc.driver")); + dataSource.setUrl(environment.getProperty("batch.jdbc.url")); + dataSource.setUsername(environment.getProperty("batch.jdbc.user")); + dataSource.setPassword(environment.getProperty("batch.jdbc.password")); + return dataSource; + } + +} \ No newline at end of file diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/sample/config/RetrySampleConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/sample/config/RetrySampleConfiguration.java new file mode 100644 index 000000000..4b40f7f33 --- /dev/null +++ b/spring-batch-samples/src/main/java/org/springframework/batch/sample/config/RetrySampleConfiguration.java @@ -0,0 +1,67 @@ +/* + * Copyright 2012-2013 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.sample.config; + +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; +import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; +import org.springframework.batch.item.ItemReader; +import org.springframework.batch.item.ItemWriter; +import org.springframework.batch.sample.domain.trade.Trade; +import org.springframework.batch.sample.domain.trade.internal.GeneratingTradeItemReader; +import org.springframework.batch.sample.support.RetrySampleItemWriter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Dave Syer + * + */ +@Configuration +public class RetrySampleConfiguration { + + @Autowired + private JobBuilderFactory jobs; + + @Autowired + private StepBuilderFactory steps; + + @Bean + public Job retrySample() throws Exception { + return jobs.get("retrySample").start(step()).build(); + } + + @Bean + protected Step step() throws Exception { + return steps.get("step"). chunk(1).reader(reader()).writer(writer()).faultTolerant() + .retry(Exception.class).retryLimit(3).build(); + } + + @Bean + protected ItemReader reader() { + GeneratingTradeItemReader reader = new GeneratingTradeItemReader(); + reader.setLimit(10); + return reader; + } + + @Bean + protected ItemWriter writer() { + return new RetrySampleItemWriter(); + } + +} diff --git a/spring-batch-samples/src/main/resources/jobs/tradeJob.xml b/spring-batch-samples/src/main/resources/jobs/tradeJob.xml index c8dc38d00..90e569e4c 100644 --- a/spring-batch-samples/src/main/resources/jobs/tradeJob.xml +++ b/spring-batch-samples/src/main/resources/jobs/tradeJob.xml @@ -2,8 +2,11 @@ + + diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/sample/RetrySampleConfigurationTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/sample/RetrySampleConfigurationTests.java new file mode 100644 index 000000000..58a26ff37 --- /dev/null +++ b/spring-batch-samples/src/test/java/org/springframework/batch/sample/RetrySampleConfigurationTests.java @@ -0,0 +1,43 @@ +package org.springframework.batch.sample; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.batch.sample.config.DataSourceConfiguration; +import org.springframework.batch.sample.config.JobRunnerConfiguration; +import org.springframework.batch.sample.config.RetrySampleConfiguration; +import org.springframework.batch.sample.domain.trade.internal.GeneratingTradeItemReader; +import org.springframework.batch.sample.support.RetrySampleItemWriter; +import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * Checks that expected number of items have been processed. + * + * @author Robert Kasanicky + * @author Dave Syer + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = { DataSourceConfiguration.class, RetrySampleConfiguration.class, JobRunnerConfiguration.class}) +public class RetrySampleConfigurationTests { + + @Autowired + private GeneratingTradeItemReader itemGenerator; + + @Autowired + private RetrySampleItemWriter itemProcessor; + + @Autowired + private JobLauncherTestUtils jobLauncherTestUtils; + + @Test + public void testLaunchJob() throws Exception { + jobLauncherTestUtils.launchJob(); + //items processed = items read + 2 exceptions + assertEquals(itemGenerator.getLimit()+2, itemProcessor.getCounter()); + } + +} diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/sample/config/JobRunnerConfiguration.java b/spring-batch-samples/src/test/java/org/springframework/batch/sample/config/JobRunnerConfiguration.java new file mode 100644 index 000000000..b9da8b6c8 --- /dev/null +++ b/spring-batch-samples/src/test/java/org/springframework/batch/sample/config/JobRunnerConfiguration.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-2013 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.sample.config; + +import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Dave Syer + * + */ +@Configuration +@EnableBatchProcessing +public class JobRunnerConfiguration { + + @Bean + public JobLauncherTestUtils utils() throws Exception { + return new JobLauncherTestUtils(); + } + +}