diff --git a/spring-batch-core/.springBeans b/spring-batch-core/.springBeans index e3950f15b..7e376f2b9 100644 --- a/spring-batch-core/.springBeans +++ b/spring-batch-core/.springBeans @@ -54,7 +54,6 @@ src/test/resources/org/springframework/batch/core/configuration/xml/StopIncompleteJobParserTests-context.xml src/test/resources/org/springframework/batch/core/configuration/xml/AutoRegisteringStepScopeForJobElementTests-context.xml src/test/resources/org/springframework/batch/core/configuration/xml/AutoRegisteringStepScopeForStepElementTests-context.xml - src/test/resources/org/springframework/batch/core/configuration/support/ClassPathXmlJobRegistryContextTests-context.xml src/test/resources/org/springframework/batch/core/configuration/xml/StopAndRestartJobParserTests-context.xml src/test/resources/org/springframework/batch/core/configuration/xml/StopRestartOnCompletedStepJobParserTests-context.xml src/test/resources/org/springframework/batch/core/configuration/xml/StopRestartOnFailedStepJobParserTests-context.xml diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ClassPathXmlJobLoader.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ClassPathXmlJobLoader.java new file mode 100644 index 000000000..c088194cc --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ClassPathXmlJobLoader.java @@ -0,0 +1,215 @@ +/* + * 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.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +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.JobLocator; +import org.springframework.batch.core.configuration.JobRegistry; +import org.springframework.batch.core.configuration.ListableJobLocator; +import org.springframework.batch.core.launch.NoSuchJobException; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.core.io.Resource; +import org.springframework.util.Assert; + +/** + * Implementation of the {@link ListableJobLocator} interface that assumes all + * Jobs will be loaded from class path xml resources. 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, but if there is a unique one + * available in the current application context, then that will be used by + * default. + * + * @author Lucas Ward + * @author Dave Syer + * + * @since 2.0 + * @since 2.1 this class does not implement {@link JobRegistry} + */ +public class ClassPathXmlJobLoader implements ApplicationContextAware, InitializingBean, DisposableBean, + ApplicationListener { + + private static Log logger = LogFactory.getLog(ClassPathXmlJobLoader.class); + + private List jobPaths; + + private ApplicationContext parent; + + private JobRegistry jobRegistry; + + private Collection contexts = new HashSet(); + + /** + * A set of resources to load. 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. + * classpath*:/config/*-job-context.xml). + * + * @param jobPaths + */ + public void setJobPaths(Resource[] jobPaths) { + this.jobPaths = Arrays.asList(jobPaths); + } + + /** + * The {@link JobRegistry} to use for jobs created. If not provided an + * instance will be discovered from the application context. + * + * @param jobRegistry + */ + public void setJobRegistry(JobRegistry jobRegistry) { + this.jobRegistry = jobRegistry; + } + + /* + * (non-Javadoc) + * + * @see + * org.springframework.context.ApplicationContextAware#setApplicationContext + * (org.springframework.context.ApplicationContext) + */ + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + parent = applicationContext; + } + + /** + * Initialize the {@link JobRegistry} if not already injected. Attempts to + * discover a registry from the application context, searching for a unique + * bean of type {@link JobRegistry}. + * + * @throws Exception + */ + public void afterPropertiesSet() throws Exception { + + if (jobRegistry == null) { + String[] names = parent.getBeanNamesForType(JobRegistry.class); + Assert.state(names.length == 1, "Precisely one bean of type JobRegistry is required. Found = " + + names.length); + jobRegistry = (JobRegistry) parent.getBean(names[0]); + } + + } + + /** + * Create all the application contexts required and set up job registry + * entries with all the instances of {@link Job} found therein. + * + * @see InitializingBean#afterPropertiesSet() + */ + public final void onApplicationEvent(ApplicationEvent event) { + if (event instanceof ContextRefreshedEvent && event.getSource() == parent) { + try { + initialize(); + } + catch (DuplicateJobException e) { + throw new IllegalStateException(e); + } + catch (NoSuchJobException e) { + throw new IllegalStateException(e); + } + } + } + + /** + * Create jobs as instructed and register them so they can be accessed via + * the {@link JobLocator} interface. Normally called from + * {@link #onApplicationEvent(ApplicationEvent)} when the parent context is + * refreshed. + * + * @throws DuplicateJobException if the job registry detects a duplicate job + * @throws NoSuchJobException if no jobs are registered, since this is + * usually an error + */ + protected void initialize() throws DuplicateJobException, NoSuchJobException { + + for (Resource resource : jobPaths) { + + ConfigurableApplicationContext context = createApplicationContext(parent, resource); + contexts.add(context); + String[] names = context.getBeanNamesForType(Job.class); + + for (String name : names) { + logger.debug("Registering job: " + name + " from context: " + resource); + JobFactory jobFactory = new ReferenceJobFactory((Job) context.getBean(name)); + jobRegistry.register(jobFactory); + } + + } + + if (jobRegistry.getJobNames().isEmpty()) { + throw new NoSuchJobException("Could not locate any jobs in resources provided."); + } + + } + + /** + * Create an application context from the resource provided. Extension point + * for subclasses if they need to customize the context in any way. The + * default uses a {@link ClassPathXmlApplicationContextFactory}. + * + * @param parent the parent application context (or null if there is none) + * @param resource the location of the XML configuration + * + * @return an application context containing jobs + */ + protected ConfigurableApplicationContext createApplicationContext(ApplicationContext parent, Resource resource) { + ClassPathXmlApplicationContextFactory applicationContextFactory = new ClassPathXmlApplicationContextFactory(); + applicationContextFactory.setPath(resource); + if (parent != null) { + applicationContextFactory.setApplicationContext(parent); + } + return applicationContextFactory.createApplicationContext(); + } + + /** + * Close the contexts that were created in {@link #afterPropertiesSet()}. + * + * @see DisposableBean#destroy() + */ + public void destroy() throws Exception { + + for (ConfigurableApplicationContext context : contexts) { + if (context.isActive()) { + context.close(); + } + } + for (String jobName : jobRegistry.getJobNames()) { + jobRegistry.unregister(jobName); + } + contexts.clear(); + + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ClassPathXmlJobRegistry.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ClassPathXmlJobRegistry.java index 70648b83f..d215727ff 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ClassPathXmlJobRegistry.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ClassPathXmlJobRegistry.java @@ -1,200 +1,12 @@ -/* - * 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.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -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.JobLocator; -import org.springframework.batch.core.configuration.JobRegistry; -import org.springframework.batch.core.configuration.ListableJobLocator; -import org.springframework.batch.core.launch.NoSuchJobException; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.context.ApplicationEvent; -import org.springframework.context.ApplicationListener; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.event.ContextRefreshedEvent; -import org.springframework.core.io.Resource; - /** - * Implementation of the {@link ListableJobLocator} interface that assumes all - * Jobs will be loaded from class path xml resources. 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. Care must be taken to avoid duplicate names. + * Placeholder for deprecation warning. * - * @author Lucas Ward * @author Dave Syer - * - * @since 2.0 - * @since 2.1 this class does not implement {@link JobRegistry}: it is a - * {@link ListableJobLocator} + * + * @deprecated in version 2.1, please us {@link ClassPathXmlJobLoader} instead */ -public class ClassPathXmlJobRegistry implements ListableJobLocator, ApplicationContextAware, DisposableBean, - ApplicationListener { - - private static Log logger = LogFactory.getLog(ClassPathXmlJobRegistry.class); - - private List jobPaths; - - private ApplicationContext parent; - - private JobRegistry jobRegistry = new MapJobRegistry(); - - private Collection contexts = new HashSet(); - - /** - * A set of resources to load. 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. - * classpath*:/config/*-job-context.xml). - * - * @param jobPaths - */ - public void setJobPaths(Resource[] jobPaths) { - this.jobPaths = Arrays.asList(jobPaths); - } - - /* - * (non-Javadoc) - * - * @see - * org.springframework.context.ApplicationContextAware#setApplicationContext - * (org.springframework.context.ApplicationContext) - */ - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - parent = applicationContext; - } - - /* - * (non-Javadoc) - * - * @see - * org.springframework.batch.core.configuration.JobLocator#getJob(java.lang - * .String) - */ - public Job getJob(String name) throws NoSuchJobException { - return jobRegistry.getJob(name); - } - - public Collection getJobNames() { - return jobRegistry.getJobNames(); - } - - /** - * Create all the application contexts required and set up job registry - * entries with all the instances of {@link Job} found therein. - * - * @see InitializingBean#afterPropertiesSet() - */ - public final void onApplicationEvent(ApplicationEvent event) { - if (event instanceof ContextRefreshedEvent && event.getSource() == parent) { - try { - initialize(); - } - catch (DuplicateJobException e) { - throw new IllegalStateException(e); - } - catch (NoSuchJobException e) { - throw new IllegalStateException(e); - } - } - } - - /** - * Create jobs as instructed and register them so they can be accessed via - * the {@link JobLocator} interface. Normally called from - * {@link #onApplicationEvent(ApplicationEvent)} when the parent context is - * refreshed. - * - * @throws DuplicateJobException if the job registry detects a duplicate job - * @throws NoSuchJobException if no jobs are registered, since this is - * usually an error - */ - protected void initialize() throws DuplicateJobException, NoSuchJobException { - - for (Resource resource : jobPaths) { - - ConfigurableApplicationContext context = createApplicationContext(parent, resource); - contexts.add(context); - String[] names = context.getBeanNamesForType(Job.class); - - for (String name : names) { - logger.debug("Registering job: " + name + " from context: " + resource); - JobFactory jobFactory = new ReferenceJobFactory((Job) context.getBean(name)); - jobRegistry.register(jobFactory); - } - - } - - if (jobRegistry.getJobNames().isEmpty()) { - throw new NoSuchJobException("Could not locate any jobs in resources provided."); - } - - } - - /** - * Create an application context from the resource provided. Extension point - * for subclasses if they need to customize the context in any way. The - * default uses a {@link ClassPathXmlApplicationContextFactory}. - * - * @param parent the parent application context (or null if there is none) - * @param resource the location of the XML configuration - * - * @return an application context containing jobs - */ - protected ConfigurableApplicationContext createApplicationContext(ApplicationContext parent, Resource resource) { - ClassPathXmlApplicationContextFactory applicationContextFactory = new ClassPathXmlApplicationContextFactory(); - applicationContextFactory.setPath(resource); - if (parent != null) { - applicationContextFactory.setApplicationContext(parent); - } - return applicationContextFactory.createApplicationContext(); - } - - /** - * Close the contexts that were created in {@link #afterPropertiesSet()}. - * - * @see DisposableBean#destroy() - */ - public void destroy() throws Exception { - - for (ConfigurableApplicationContext context : contexts) { - if (context.isActive()) { - context.close(); - } - } - for (String jobName : jobRegistry.getJobNames()) { - jobRegistry.unregister(jobName); - } - contexts.clear(); - - } +public abstract class ClassPathXmlJobRegistry { } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ClassPathXmlJobRegistryContextTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ClassPathXmlJobLoaderContextTests.java similarity index 85% rename from spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ClassPathXmlJobRegistryContextTests.java rename to spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ClassPathXmlJobLoaderContextTests.java index b45cac070..3b5c52b4b 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ClassPathXmlJobRegistryContextTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ClassPathXmlJobLoaderContextTests.java @@ -8,6 +8,7 @@ import java.util.Collection; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.batch.core.Job; +import org.springframework.batch.core.configuration.JobRegistry; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -20,10 +21,10 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration -public class ClassPathXmlJobRegistryContextTests { +public class ClassPathXmlJobLoaderContextTests { @Autowired - private ClassPathXmlJobRegistry registry; + private JobRegistry registry; @Test public void testLocateJob() throws Exception{ diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ClassPathXmlJobRegistryTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ClassPathXmlJobLoaderTests.java similarity index 67% rename from spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ClassPathXmlJobRegistryTests.java rename to spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ClassPathXmlJobLoaderTests.java index 00e13a622..528f2803c 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ClassPathXmlJobRegistryTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ClassPathXmlJobLoaderTests.java @@ -6,10 +6,12 @@ import static org.junit.Assert.fail; import java.util.Collection; +import org.junit.Before; import org.junit.Test; import org.springframework.batch.core.Job; import org.springframework.batch.core.launch.NoSuchJobException; import org.springframework.beans.factory.BeanCreationException; +import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; @@ -20,9 +22,15 @@ import org.springframework.core.io.Resource; * @author Lucas Ward * */ -public class ClassPathXmlJobRegistryTests { +public class ClassPathXmlJobLoaderTests { - private ClassPathXmlJobRegistry registry = new ClassPathXmlJobRegistry(); + private ClassPathXmlJobLoader loader = new ClassPathXmlJobLoader(); + private MapJobRegistry registry = new MapJobRegistry(); + + @Before + public void setUp() { + loader.setJobRegistry(registry); + } @Test public void testLocateJob() throws Exception { @@ -31,11 +39,11 @@ public class ClassPathXmlJobRegistryTests { new ClassPathResource("org/springframework/batch/core/launch/support/job.xml"), new ClassPathResource("org/springframework/batch/core/launch/support/job2.xml") }; - registry.setJobPaths(jobPaths); + loader.setJobPaths(jobPaths); GenericApplicationContext applicationContext = new GenericApplicationContext(); applicationContext.refresh(); - registry.setApplicationContext(applicationContext); - registry.initialize(); + loader.setApplicationContext(applicationContext); + loader.initialize(); Collection names = registry.getJobNames(); assertEquals(2, names.size()); @@ -53,11 +61,11 @@ public class ClassPathXmlJobRegistryTests { Resource[] jobPaths = new Resource[] { new ClassPathResource( "org/springframework/batch/core/launch/support/test-environment.xml") }; - registry.setJobPaths(jobPaths); + loader.setJobPaths(jobPaths); GenericApplicationContext applicationContext = new GenericApplicationContext(); applicationContext.refresh(); - registry.setApplicationContext(applicationContext); - registry.initialize(); + loader.setApplicationContext(applicationContext); + loader.initialize(); } @Test @@ -65,11 +73,23 @@ public class ClassPathXmlJobRegistryTests { Resource[] jobPaths = new Resource[] { new ClassPathResource( "org/springframework/batch/core/launch/support/2jobs.xml") }; - registry.setJobPaths(jobPaths); + loader.setJobPaths(jobPaths); GenericApplicationContext applicationContext = new GenericApplicationContext(); applicationContext.refresh(); - registry.setApplicationContext(applicationContext); - registry.initialize(); + loader.setApplicationContext(applicationContext); + loader.initialize(); + assertEquals(2, registry.getJobNames().size()); + } + + @Test + public void testChildContextOverridesBeanPostProcessor() throws Exception { + + Resource[] jobPaths = new Resource[] { new ClassPathResource( + "org/springframework/batch/core/launch/support/2jobs.xml") }; + loader.setApplicationContext(new ClassPathXmlApplicationContext( + "/org/springframework/batch/core/launch/support/test-environment-with-registry-and-auto-register.xml")); + loader.setJobPaths(jobPaths); + loader.initialize(); assertEquals(2, registry.getJobNames().size()); } @@ -79,9 +99,9 @@ public class ClassPathXmlJobRegistryTests { Resource[] jobPaths = new Resource[] { new ClassPathResource("org/springframework/batch/core/launch/support/2jobs.xml"), new ClassPathResource("org/springframework/batch/core/launch/support/error.xml") }; - registry.setJobPaths(jobPaths); + loader.setJobPaths(jobPaths); try { - registry.initialize(); + loader.initialize(); fail("Expected BeanCreationException"); } catch (BeanCreationException e) { @@ -94,10 +114,10 @@ public class ClassPathXmlJobRegistryTests { Resource[] jobPaths = new Resource[] { new ClassPathResource( "org/springframework/batch/core/launch/support/2jobs.xml") }; - registry.setJobPaths(jobPaths); - registry.initialize(); + loader.setJobPaths(jobPaths); + loader.initialize(); assertEquals(2, registry.getJobNames().size()); - registry.destroy(); + loader.destroy(); assertEquals(0, registry.getJobNames().size()); } diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/ClassPathXmlJobLoaderContextTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/ClassPathXmlJobLoaderContextTests-context.xml new file mode 100644 index 000000000..f9b49e6d2 --- /dev/null +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/ClassPathXmlJobLoaderContextTests-context.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/ClassPathXmlJobRegistryContextTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/ClassPathXmlJobRegistryContextTests-context.xml deleted file mode 100644 index 1c3eabbf3..000000000 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/ClassPathXmlJobRegistryContextTests-context.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - diff --git a/spring-batch-samples/src/main/resources/skipSample-job-launcher-context.xml b/spring-batch-samples/src/main/resources/skipSample-job-launcher-context.xml index e18d2513f..fd0a90080 100644 --- a/spring-batch-samples/src/main/resources/skipSample-job-launcher-context.xml +++ b/spring-batch-samples/src/main/resources/skipSample-job-launcher-context.xml @@ -30,9 +30,11 @@ - + + +