diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactory.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactory.java index 86d87eeda..1618e8962 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactory.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactory.java @@ -32,14 +32,28 @@ public class ApplicationContextJobFactory extends AbstractGroupAwareJobFactory { final ApplicationContextFactory applicationContextFactory; /** + * @param groupName the name of the group that the job belongs to * @param jobName the id of the {@link Job} in the application context to be * created + * @param applicationContextFactory a factory for an application context + * containing a job with the job name provided */ - public ApplicationContextJobFactory(ApplicationContextFactory applicationContextFactory, String jobName) { - super(jobName); + public ApplicationContextJobFactory(String groupName, String jobName, + ApplicationContextFactory applicationContextFactory) { + super(groupName, jobName); this.applicationContextFactory = applicationContextFactory; } + /** + * @param jobName the id of the {@link Job} in the application context to be + * created + * @param applicationContextFactory a factory for an application context + * containing a job with the job name provided + */ + public ApplicationContextJobFactory(String jobName, ApplicationContextFactory applicationContextFactory) { + this(null, jobName, applicationContextFactory); + } + /** * Create an {@link ApplicationContext} from the factory provided and pull * out a bean with the name given during initialization. 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 36ce1791f..6b56014c2 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 @@ -44,7 +44,10 @@ import org.springframework.core.io.Resource; * * @author Lucas Ward * @author Dave Syer + * * @since 2.0 + * @since 2.1 this class does not implement {@link JobRegistry}: it is a + * {@link ListableJobLocator} */ public class ClassPathXmlJobRegistry implements ListableJobLocator, ApplicationContextAware, InitializingBean, DisposableBean { @@ -114,8 +117,9 @@ public class ClassPathXmlJobRegistry implements ListableJobLocator, ApplicationC for (String name : names) { logger.debug("Registering job: " + name + " from context: " + resource); - ApplicationContextJobFactory jobFactory = new ApplicationContextJobFactory(applicationContextFactory, - name); + String groupName = getGroupName(context, resource, name); + ApplicationContextJobFactory jobFactory = new ApplicationContextJobFactory(groupName, name, + applicationContextFactory); jobRegistry.register(jobFactory); } } @@ -126,6 +130,20 @@ public class ClassPathXmlJobRegistry implements ListableJobLocator, ApplicationC } + /** + * Determine a group name for the job to be registered. Default + * implementation does nothing, but provides an extension point for + * specialised subclasses. + * + * @param context the application context containing the job + * @param resource the resource that was used to create the context + * @param jobName the jobName + * @return a group name for the job (or null if not needed) + */ + protected String getGroupName(ApplicationContext context, Resource resource, String jobName) { + return null; + } + /** * Close the contexts that were created in {@link #afterPropertiesSet()}. * diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistryBeanPostProcessor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistryBeanPostProcessor.java index c541a9880..533338011 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistryBeanPostProcessor.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistryBeanPostProcessor.java @@ -24,28 +24,50 @@ import org.springframework.batch.core.configuration.JobLocator; import org.springframework.batch.core.configuration.JobRegistry; import org.springframework.beans.BeansException; import org.springframework.beans.FatalBeanException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.util.Assert; /** - * A {@link BeanPostProcessor} that registers {@link Job} beans - * with a {@link JobRegistry}. Include a bean of this type along - * with your job configuration, and use the same - * {@link JobRegistry} as a {@link JobLocator} when - * you need to locate a {@link JobLocator} to launch. + * A {@link BeanPostProcessor} that registers {@link Job} beans with a + * {@link JobRegistry}. Include a bean of this type along with your job + * configuration, and use the same {@link JobRegistry} as a {@link JobLocator} + * when you need to locate a {@link Job} to launch. * * @author Dave Syer * */ -public class JobRegistryBeanPostProcessor implements BeanPostProcessor, InitializingBean, DisposableBean { +public class JobRegistryBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware, InitializingBean, + DisposableBean { // It doesn't make sense for this to have a default value... private JobRegistry jobRegistry = null; private Collection jobNames = new HashSet(); + private String groupName = null; + + private DefaultListableBeanFactory beanFactory; + + /** + * The group name for jobs registered by this component. Optional (defaults + * to null, which means that jobs are registered with their bean names). + * Useful where there is a hierarchy of application contexts all + * contributing to the same {@link JobRegistry}: child contexts can then + * define an instance with a unique group name to avoid clashes between job + * names. + * + * @param groupName the groupName to set + */ + public void setGroupName(String groupName) { + this.groupName = groupName; + } + /** * Injection setter for {@link JobRegistry}. * @@ -55,6 +77,19 @@ public class JobRegistryBeanPostProcessor implements BeanPostProcessor, Initiali this.jobRegistry = jobRegistry; } + /* + * (non-Javadoc) + * + * @see + * org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(org + * .springframework.beans.factory.BeanFactory) + */ + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + if (beanFactory instanceof DefaultListableBeanFactory) { + this.beanFactory = (DefaultListableBeanFactory) beanFactory; + } + } + /** * Make sure the registry is set before use. * @@ -65,8 +100,8 @@ public class JobRegistryBeanPostProcessor implements BeanPostProcessor, Initiali } /** - * De-register all the {@link Job} instances that were - * regsistered by this post processor. + * De-register all the {@link Job} instances that were regsistered by this + * post processor. * @see org.springframework.beans.factory.DisposableBean#destroy() */ public void destroy() throws Exception { @@ -78,8 +113,7 @@ public class JobRegistryBeanPostProcessor implements BeanPostProcessor, Initiali /** * If the bean is an instance of {@link Job} then register it. - * @throws FatalBeanException if there is a - * {@link DuplicateJobException}. + * @throws FatalBeanException if there is a {@link DuplicateJobException}. * * @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization(java.lang.Object, * java.lang.String) @@ -88,8 +122,13 @@ public class JobRegistryBeanPostProcessor implements BeanPostProcessor, Initiali if (bean instanceof Job) { Job job = (Job) bean; try { - jobRegistry.register(new ReferenceJobFactory(job)); - jobNames.add(job.getName()); + String groupName = this.groupName; + if (beanFactory != null) { + groupName = getGroupName(beanFactory.getBeanDefinition(beanName), job); + } + ReferenceJobFactory jobFactory = new ReferenceJobFactory(groupName, job); + jobRegistry.register(jobFactory); + jobNames.add(jobFactory.getJobName()); } catch (DuplicateJobException e) { throw new FatalBeanException("Cannot register job configuration", e); @@ -98,6 +137,19 @@ public class JobRegistryBeanPostProcessor implements BeanPostProcessor, Initiali return bean; } + /** + * Determine a group name for the job to be registered. Default + * implementation just returns the {@link #setGroupName(String) groupName} + * configured. Provides an extension point for specialised subclasses. + * + * @param beanDefinition the bean definition for the job + * @param job the job + * @return a group name for the job (or null if not needed) + */ + protected String getGroupName(BeanDefinition beanDefinition, Job job) { + return groupName; + } + /** * Do nothing. * @@ -107,5 +159,4 @@ public class JobRegistryBeanPostProcessor implements BeanPostProcessor, Initiali public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } - } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ReferenceJobFactory.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ReferenceJobFactory.java index 56f5e0b34..faa7b7a3f 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ReferenceJobFactory.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ReferenceJobFactory.java @@ -29,12 +29,20 @@ public class ReferenceJobFactory extends AbstractGroupAwareJobFactory { private Job job; + /** + * @param groupName the name of the group that this job belongs to + * @param job the {@link Job} to return from {@link #createJob()}. + */ + public ReferenceJobFactory(String groupName, Job job) { + super(groupName, job.getName()); + this.job = job; + } + /** * @param job the {@link Job} to return from {@link #createJob()}. */ public ReferenceJobFactory(Job job) { - super(job.getName()); - this.job = job; + this(null, job); } /** 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 35e8897fe..e292d9f86 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 @@ -129,7 +129,7 @@ public class JobRegistryBackgroundJobRunner { factory.setApplicationContext(parentContext); factory.setPath(path); logger.info("Registering Job definition: " + names[k]); - registry.register(new ApplicationContextJobFactory(factory, names[k])); + registry.register(new ApplicationContextJobFactory(names[k], factory)); } } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactoryTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactoryTests.java index 74dfb69f8..9c035fcf2 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactoryTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactoryTests.java @@ -1,5 +1,6 @@ package org.springframework.batch.core.configuration.support; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import org.junit.Test; @@ -9,20 +10,25 @@ import org.springframework.context.support.StaticApplicationContext; public class ApplicationContextJobFactoryTests { - private AbstractGroupAwareJobFactory factory = new ApplicationContextJobFactory( - new StubApplicationContextFactory(), "job"); - @Test public void testFactoryContext() throws Exception { + ApplicationContextJobFactory factory = new ApplicationContextJobFactory("job", + new StubApplicationContextFactory()); assertNotNull(factory.createJob()); } - private static class StubApplicationContextFactory implements - ApplicationContextFactory { + @Test + public void testGroupName() throws Exception { + ApplicationContextJobFactory factory = new ApplicationContextJobFactory("jobs", "job", + new StubApplicationContextFactory()); + assertEquals("jobs$job", factory.getJobName()); + } + + private static class StubApplicationContextFactory implements ApplicationContextFactory { public ConfigurableApplicationContext createApplicationContext() { StaticApplicationContext context = new StaticApplicationContext(); context.registerSingleton("job", JobSupport.class); - return context ; + return context; } } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/JobRegistryBeanPostProcessorTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/JobRegistryBeanPostProcessorTests.java index 47415ae14..c21c19e8e 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/JobRegistryBeanPostProcessorTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/JobRegistryBeanPostProcessorTests.java @@ -15,10 +15,14 @@ */ package org.springframework.batch.core.configuration.support; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import java.util.Collection; -import junit.framework.TestCase; - +import org.junit.Test; import org.springframework.batch.core.configuration.DuplicateJobException; import org.springframework.batch.core.job.JobSupport; import org.springframework.batch.core.launch.NoSuchJobException; @@ -29,11 +33,12 @@ import org.springframework.context.support.ClassPathXmlApplicationContext; * @author Dave Syer * */ -public class JobRegistryBeanPostProcessorTests extends TestCase { +public class JobRegistryBeanPostProcessorTests { private JobRegistryBeanPostProcessor processor = new JobRegistryBeanPostProcessor(); - public void testInitialization() throws Exception { + @Test + public void testInitializationFails() throws Exception { try { processor.afterPropertiesSet(); fail("Expected IllegalArgumentException"); @@ -44,33 +49,48 @@ public class JobRegistryBeanPostProcessorTests extends TestCase { } } + @Test public void testBeforeInitialization() throws Exception { // should be a no-op - assertEquals("foo", processor.postProcessAfterInitialization("foo", "bar")); + assertEquals("foo", processor.postProcessBeforeInitialization("foo", "bar")); } + @Test public void testAfterInitializationWithWrongType() throws Exception { // should be a no-op assertEquals("foo", processor.postProcessAfterInitialization("foo", "bar")); } + @Test public void testAfterInitializationWithCorrectType() throws Exception { MapJobRegistry registry = new MapJobRegistry(); processor.setJobRegistry(registry); - JobSupport configuration = new JobSupport(); - configuration.setBeanName("foo"); - assertEquals(configuration, processor.postProcessAfterInitialization(configuration, "bar")); - assertEquals(configuration.getName(), registry.getJob("foo").getName()); + JobSupport job = new JobSupport(); + job.setBeanName("foo"); + assertEquals(job, processor.postProcessAfterInitialization(job, "bar")); + assertEquals(job.getName(), registry.getJob("foo").getName()); } + @Test + public void testAfterInitializationWithGroupName() throws Exception { + MapJobRegistry registry = new MapJobRegistry(); + processor.setJobRegistry(registry); + processor.setGroupName("jobs"); + JobSupport job = new JobSupport(); + job.setBeanName("foo"); + assertEquals(job, processor.postProcessAfterInitialization(job, "bar")); + assertEquals("[jobs$foo]", registry.getJobNames().toString()); + } + + @Test public void testAfterInitializationWithDuplicate() throws Exception { MapJobRegistry registry = new MapJobRegistry(); processor.setJobRegistry(registry); - JobSupport configuration = new JobSupport(); - configuration.setBeanName("foo"); - processor.postProcessAfterInitialization(configuration, "bar"); + JobSupport job = new JobSupport(); + job.setBeanName("foo"); + processor.postProcessAfterInitialization(job, "bar"); try { - processor.postProcessAfterInitialization(configuration, "spam"); + processor.postProcessAfterInitialization(job, "spam"); fail("Expected FatalBeanException"); } catch (FatalBeanException e) { @@ -79,12 +99,13 @@ public class JobRegistryBeanPostProcessorTests extends TestCase { } } + @Test public void testUnregisterOnDestroy() throws Exception { MapJobRegistry registry = new MapJobRegistry(); processor.setJobRegistry(registry); - JobSupport configuration = new JobSupport(); - configuration.setBeanName("foo"); - assertEquals(configuration, processor.postProcessAfterInitialization(configuration, "bar")); + JobSupport job = new JobSupport(); + job.setBeanName("foo"); + assertEquals(job, processor.postProcessAfterInitialization(job, "bar")); processor.destroy(); try { assertEquals(null, registry.getJob("foo")); @@ -95,6 +116,7 @@ public class JobRegistryBeanPostProcessorTests extends TestCase { } } + @Test public void testExecutionWithApplicationContext() throws Exception { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("test-context.xml", getClass()); MapJobRegistry registry = (MapJobRegistry) context.getBean("registry"); @@ -117,4 +139,5 @@ public class JobRegistryBeanPostProcessorTests extends TestCase { assertEquals(context.getBean("test-job-with-concrete-parent-and-name"), registry.getJob("oof")); assertEquals(context.getBean("test-job-with-concrete-parent-and-bean-name"), registry.getJob("rab")); } + } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ReferenceJobFactoryTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ReferenceJobFactoryTests.java new file mode 100644 index 000000000..6b1b3c458 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ReferenceJobFactoryTests.java @@ -0,0 +1,35 @@ +/* + * 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 static org.junit.Assert.*; + +import org.junit.Test; +import org.springframework.batch.core.job.JobSupport; + +/** + * @author Dave Syer + * + */ +public class ReferenceJobFactoryTests { + + @Test + public void testGroupName() throws Exception { + ReferenceJobFactory factory = new ReferenceJobFactory("jobs", new JobSupport("foo")); + assertEquals("jobs$foo", factory.getJobName()); + } + +}