diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobRegistry.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobRegistry.java index 3d2a909ee..be24eaac4 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobRegistry.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobRegistry.java @@ -29,18 +29,18 @@ public interface JobRegistry extends JobLocator { /** * Registers a {@link Job} at runtime. * - * @param job the {@link Job} to be registered + * @param jobFactory the {@link Job} to be registered * - * @throws DuplicateJobException if a configuration with the - * same name has already been registered. + * @throws DuplicateJobException if a factory with the same job name has + * already been registered. */ - void register(Job job) throws DuplicateJobException; + void register(JobFactory jobFactory) throws DuplicateJobException; /** - * Unregisters a previously registered {@link Job}. If it was - * not previously registered there is no error. + * Unregisters a previously registered {@link Job}. If it was not + * previously registered there is no error. * - * @param job the {@link Job} to unregister. + * @param jobName the {@link Job} to unregister. */ - void unregister(Job job); + void unregister(String jobName); } diff --git a/spring-batch-execution/src/main/java/org/springframework/batch/execution/configuration/JobRegistryBeanPostProcessor.java b/spring-batch-execution/src/main/java/org/springframework/batch/execution/configuration/JobRegistryBeanPostProcessor.java index 4d562881b..e246c417f 100644 --- a/spring-batch-execution/src/main/java/org/springframework/batch/execution/configuration/JobRegistryBeanPostProcessor.java +++ b/spring-batch-execution/src/main/java/org/springframework/batch/execution/configuration/JobRegistryBeanPostProcessor.java @@ -45,7 +45,7 @@ public class JobRegistryBeanPostProcessor implements BeanPostProcessor, Initiali // It doesn't make sense for this to have a default value... private JobRegistry jobConfigurationRegistry = null; - private Collection jobConfigurations = new HashSet(); + private Collection jobNames = new HashSet(); /** * Injection setter for {@link JobRegistry}. @@ -71,11 +71,11 @@ public class JobRegistryBeanPostProcessor implements BeanPostProcessor, Initiali * @see org.springframework.beans.factory.DisposableBean#destroy() */ public void destroy() throws Exception { - for (Iterator iter = jobConfigurations.iterator(); iter.hasNext();) { - Job jobConfiguration = (Job) iter.next(); - jobConfigurationRegistry.unregister(jobConfiguration); + for (Iterator iter = jobNames.iterator(); iter.hasNext();) { + String name = (String) iter.next(); + jobConfigurationRegistry.unregister(name); } - jobConfigurations.clear(); + jobNames.clear(); } /** @@ -88,10 +88,10 @@ public class JobRegistryBeanPostProcessor implements BeanPostProcessor, Initiali */ public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof Job) { - Job jobConfiguration = (Job) bean; + Job job = (Job) bean; try { - jobConfigurationRegistry.register(jobConfiguration); - jobConfigurations.add(jobConfiguration); + jobConfigurationRegistry.register(new ReferenceJobFactory(job)); + jobNames.add(job.getName()); } catch (DuplicateJobException e) { throw new FatalBeanException("Cannot register job configuration", e); diff --git a/spring-batch-execution/src/main/java/org/springframework/batch/execution/configuration/MapJobRegistry.java b/spring-batch-execution/src/main/java/org/springframework/batch/execution/configuration/MapJobRegistry.java index e43ad882a..e577dbdc9 100644 --- a/spring-batch-execution/src/main/java/org/springframework/batch/execution/configuration/MapJobRegistry.java +++ b/spring-batch-execution/src/main/java/org/springframework/batch/execution/configuration/MapJobRegistry.java @@ -23,14 +23,15 @@ import java.util.Map; import org.springframework.batch.core.domain.Job; import org.springframework.batch.core.repository.DuplicateJobException; +import org.springframework.batch.core.repository.JobFactory; import org.springframework.batch.core.repository.JobRegistry; import org.springframework.batch.core.repository.ListableJobRegistry; import org.springframework.batch.core.repository.NoSuchJobException; import org.springframework.util.Assert; /** - * Simple map-based implementation of {@link JobRegistry}. Access - * to the map is synchronized, guarded by an internal lock. + * Simple map-based implementation of {@link JobRegistry}. Access to the map is + * synchronized, guarded by an internal lock. * * @author Dave Syer * @@ -43,17 +44,16 @@ public class MapJobRegistry implements ListableJobRegistry { * (non-Javadoc) * @see org.springframework.batch.container.common.configuration.JobConfigurationRegistry#registerJobConfiguration(org.springframework.batch.container.common.configuration.JobConfiguration) */ - public void register(Job jobConfiguration) throws DuplicateJobException { - Assert.notNull(jobConfiguration); - String name = jobConfiguration.getName(); + public void register(JobFactory jobFactory) throws DuplicateJobException { + Assert.notNull(jobFactory); + String name = jobFactory.getJobName(); Assert.notNull(name, "Job configuration must have a name."); synchronized (map) { - if (map.containsKey(name) && jobConfiguration.equals(map.get(name))) { + if (map.containsKey(name)) { throw new DuplicateJobException("A job configuration with this name [" + name + "] was already registered"); } - // allow replacing job configuration with new instance - map.put(name, jobConfiguration); + map.put(name, jobFactory); } } @@ -61,8 +61,7 @@ public class MapJobRegistry implements ListableJobRegistry { * (non-Javadoc) * @see org.springframework.batch.container.common.configuration.JobConfigurationRegistry#unregister(org.springframework.batch.container.common.configuration.JobConfiguration) */ - public void unregister(Job jobConfiguration) { - String name = jobConfiguration.getName(); + public void unregister(String name) { Assert.notNull(name, "Job configuration must have a name."); synchronized (map) { map.remove(name); @@ -77,15 +76,14 @@ public class MapJobRegistry implements ListableJobRegistry { public Job getJob(String name) throws NoSuchJobException { synchronized (map) { if (!map.containsKey(name)) { - throw new NoSuchJobException("No job configuration with the name [" + name - + "] was registered"); + throw new NoSuchJobException("No job configuration with the name [" + name + "] was registered"); } - return (Job) map.get(name); + return (Job) ((JobFactory) map.get(name)).createJob(); } } - - /* (non-Javadoc) + /* + * (non-Javadoc) * @see org.springframework.batch.container.common.configuration.ListableJobConfigurationRegistry#getJobConfigurations() */ public Collection getJobNames() { diff --git a/spring-batch-execution/src/main/java/org/springframework/batch/execution/configuration/ReferenceJobFactory.java b/spring-batch-execution/src/main/java/org/springframework/batch/execution/configuration/ReferenceJobFactory.java new file mode 100644 index 000000000..a78721b83 --- /dev/null +++ b/spring-batch-execution/src/main/java/org/springframework/batch/execution/configuration/ReferenceJobFactory.java @@ -0,0 +1,61 @@ +/* + * 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.execution.configuration; + +import org.springframework.batch.core.domain.Job; +import org.springframework.batch.core.repository.JobFactory; + +/** + * A {@link JobFactory} that just keeps a reference to a {@link Job}. It never + * modifies its {@link Job}. + * + * @author Dave Syer + * + */ +public class ReferenceJobFactory implements JobFactory { + + private Job job; + + private String name; + + /** + * @param job the {@link Job} to return from {@link #createJob()}. + */ + public ReferenceJobFactory(Job job) { + super(); + this.job = job; + this.name = job.getName(); + } + + /** + * Just return the instance passed in on initialization. + * + * @see org.springframework.batch.core.repository.JobFactory#createJob() + */ + public Job createJob() { + return job; + } + + /** + * Returns the job name as passed in on initialization. + * + * @see org.springframework.batch.core.repository.JobFactory#getJobName() + */ + public String getJobName() { + return name; + } + +} diff --git a/spring-batch-execution/src/test/java/org/springframework/batch/execution/bootstrap/support/SimpleExportedJobLauncherTests.java b/spring-batch-execution/src/test/java/org/springframework/batch/execution/bootstrap/support/SimpleExportedJobLauncherTests.java index c56473475..72bd9de1d 100644 --- a/spring-batch-execution/src/test/java/org/springframework/batch/execution/bootstrap/support/SimpleExportedJobLauncherTests.java +++ b/spring-batch-execution/src/test/java/org/springframework/batch/execution/bootstrap/support/SimpleExportedJobLauncherTests.java @@ -29,6 +29,7 @@ import org.springframework.batch.core.domain.StepExecution; import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; import org.springframework.batch.core.runtime.JobParametersFactory; import org.springframework.batch.execution.configuration.MapJobRegistry; +import org.springframework.batch.execution.configuration.ReferenceJobFactory; import org.springframework.batch.execution.job.JobSupport; import org.springframework.batch.execution.launch.JobLauncher; import org.springframework.batch.execution.step.StepSupport; @@ -117,7 +118,7 @@ public class SimpleExportedJobLauncherTests extends TestCase { * @throws Exception */ public void testGetStatisticsWithContent() throws Exception { - jobLocator.register(new JobSupport("foo")); + jobLocator.register(new ReferenceJobFactory(new JobSupport("foo"))); launcher.run("foo"); Properties props = launcher.getStatistics(); assertNotNull(props); @@ -130,7 +131,7 @@ public class SimpleExportedJobLauncherTests extends TestCase { * @throws Exception */ public void testIsRunning() throws Exception { - jobLocator.register(new JobSupport("foo")); + jobLocator.register(new ReferenceJobFactory(new JobSupport("foo"))); launcher.run("foo"); assertTrue(launcher.isRunning()); } @@ -141,7 +142,7 @@ public class SimpleExportedJobLauncherTests extends TestCase { * @throws Exception */ public void testAlreadyRunning() throws Exception { - jobLocator.register(new JobSupport("foo")); + jobLocator.register(new ReferenceJobFactory(new JobSupport("foo"))); launcher.setLauncher(new JobLauncher() { public JobExecution run(Job job, JobParameters jobParameters) throws JobExecutionAlreadyRunningException { throw new JobExecutionAlreadyRunningException("Bad!"); @@ -166,7 +167,7 @@ public class SimpleExportedJobLauncherTests extends TestCase { * @throws Exception */ public void testRunJobWithParameters() throws Exception { - jobLocator.register(new JobSupport("foo")); + jobLocator.register(new ReferenceJobFactory(new JobSupport("foo"))); String value = launcher.run("foo", "bar=spam,bucket=crap"); assertTrue(launcher.isRunning()); assertTrue("Return value was not a JobExecution: " + value, value.contains("JobExecution")); @@ -178,7 +179,7 @@ public class SimpleExportedJobLauncherTests extends TestCase { * @throws Exception */ public void testRunJobWithParametersAndFactory() throws Exception { - jobLocator.register(new JobSupport("foo")); + jobLocator.register(new ReferenceJobFactory(new JobSupport("foo"))); launcher.setJobParametersFactory(new JobParametersFactory() { public JobParameters getJobParameters(Properties properties) { return new JobParametersBuilder().addString("foo", "spam").toJobParameters(); @@ -198,7 +199,7 @@ public class SimpleExportedJobLauncherTests extends TestCase { * @throws Exception */ public void testStop() throws Exception { - jobLocator.register(new JobSupport("foo")); + jobLocator.register(new ReferenceJobFactory(new JobSupport("foo"))); launcher.run("foo"); assertTrue(launcher.isRunning()); launcher.stop(); diff --git a/spring-batch-execution/src/test/java/org/springframework/batch/execution/configuration/MapJobRegistryTests.java b/spring-batch-execution/src/test/java/org/springframework/batch/execution/configuration/MapJobRegistryTests.java index 828605644..628c4b24b 100644 --- a/spring-batch-execution/src/test/java/org/springframework/batch/execution/configuration/MapJobRegistryTests.java +++ b/spring-batch-execution/src/test/java/org/springframework/batch/execution/configuration/MapJobRegistryTests.java @@ -19,10 +19,9 @@ import java.util.Collection; import junit.framework.TestCase; -import org.springframework.batch.core.domain.Job; import org.springframework.batch.core.repository.DuplicateJobException; +import org.springframework.batch.core.repository.JobFactory; import org.springframework.batch.core.repository.NoSuchJobException; -import org.springframework.batch.execution.configuration.MapJobRegistry; import org.springframework.batch.execution.job.JobSupport; /** @@ -34,13 +33,13 @@ public class MapJobRegistryTests extends TestCase { private MapJobRegistry registry = new MapJobRegistry(); /** - * Test method for {@link org.springframework.batch.execution.configuration.MapJobRegistry#unregister(org.springframework.batch.execution.job.JobSupport)}. + * Test method for {@link org.springframework.batch.execution.configuration.MapJobRegistry#unregister(String)}. * @throws Exception */ public void testUnregister() throws Exception { - registry.register(new JobSupport("foo")); + registry.register(new ReferenceJobFactory(new JobSupport("foo"))); assertNotNull(registry.getJob("foo")); - registry.unregister(new JobSupport("foo")); + registry.unregister("foo"); try { assertNull(registry.getJob("foo")); fail("Expected NoSuchJobConfigurationException"); @@ -55,12 +54,12 @@ public class MapJobRegistryTests extends TestCase { * Test method for {@link org.springframework.batch.execution.configuration.MapJobRegistry#getJob(java.lang.String)}. */ public void testReplaceDuplicateConfiguration() throws Exception { - registry.register(new JobSupport("foo")); + registry.register(new ReferenceJobFactory(new JobSupport("foo"))); try { - registry.register(new JobSupport("foo")); + registry.register(new ReferenceJobFactory(new JobSupport("foo"))); + fail("Expected DuplicateJobConfigurationException"); } catch (DuplicateJobException e) { - fail("Unexpected DuplicateJobConfigurationException"); - // expected + // unexpected: even if the job is different we want a DuplicateJobException assertTrue(e.getMessage().indexOf("foo")>=0); } } @@ -69,10 +68,10 @@ public class MapJobRegistryTests extends TestCase { * Test method for {@link org.springframework.batch.execution.configuration.MapJobRegistry#getJob(java.lang.String)}. */ public void testRealDuplicateConfiguration() throws Exception { - Job jobConfiguration = new JobSupport("foo"); - registry.register(jobConfiguration); + JobFactory jobFactory = new ReferenceJobFactory(new JobSupport("foo")); + registry.register(jobFactory); try { - registry.register(jobConfiguration); + registry.register(jobFactory); fail("Unexpected DuplicateJobConfigurationException"); } catch (DuplicateJobException e) { // expected @@ -85,12 +84,12 @@ public class MapJobRegistryTests extends TestCase { * @throws Exception */ public void testGetJobConfigurations() throws Exception { - Job configuration = new JobSupport("foo"); - registry.register(configuration); - registry.register(new JobSupport("bar")); + JobFactory jobFactory = new ReferenceJobFactory(new JobSupport("foo")); + registry.register(jobFactory); + registry.register(new ReferenceJobFactory(new JobSupport("bar"))); Collection configurations = registry.getJobNames(); assertEquals(2, configurations.size()); - assertTrue(configurations.contains(configuration.getName())); + assertTrue(configurations.contains(jobFactory.getJobName())); } } diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/sample/tasklet/InfiniteLoopTasklet.java b/spring-batch-samples/src/main/java/org/springframework/batch/sample/tasklet/InfiniteLoopTasklet.java index 19625c6aa..7b3712086 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/sample/tasklet/InfiniteLoopTasklet.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/sample/tasklet/InfiniteLoopTasklet.java @@ -18,6 +18,8 @@ package org.springframework.batch.sample.tasklet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.batch.core.domain.StepExecution; +import org.springframework.batch.core.listener.StepListenerSupport; import org.springframework.batch.core.tasklet.Tasklet; import org.springframework.batch.repeat.ExitStatus; @@ -29,8 +31,9 @@ import org.springframework.batch.repeat.ExitStatus; * @author Lucas Ward * */ -public class InfiniteLoopTasklet implements Tasklet { +public class InfiniteLoopTasklet extends StepListenerSupport implements Tasklet { + private StepExecution stepExecution; private int count = 0; private static final Log logger = LogFactory.getLog(InfiniteLoopTasklet.class); @@ -42,7 +45,7 @@ public class InfiniteLoopTasklet implements Tasklet { } public ExitStatus execute() throws Exception { - while(true) { + while(!stepExecution.isTerminateOnly()) { try { Thread.sleep(500); } catch (InterruptedException e) { @@ -52,6 +55,14 @@ public class InfiniteLoopTasklet implements Tasklet { count++; logger.info("Executing infinite loop, at count="+count); } + return ExitStatus.FAILED; + } + + /* (non-Javadoc) + * @see org.springframework.batch.core.listener.StepListenerSupport#beforeStep(org.springframework.batch.core.domain.StepExecution) + */ + public void beforeStep(StepExecution stepExecution) { + this.stepExecution = stepExecution; } } diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/sample/ClassPathXmlApplicationContextJobFactory.java b/spring-batch-samples/src/test/java/org/springframework/batch/sample/ClassPathXmlApplicationContextJobFactory.java new file mode 100644 index 000000000..f2e7f747e --- /dev/null +++ b/spring-batch-samples/src/test/java/org/springframework/batch/sample/ClassPathXmlApplicationContextJobFactory.java @@ -0,0 +1,71 @@ +/* + * 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.sample; + +import org.springframework.batch.core.domain.Job; +import org.springframework.batch.core.repository.JobFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * A {@link JobFactory} that creates its own {@link ApplicationContext} from a + * path supplied, and pulls a bean out when asked to create a {@link Job}. + * + * @author Dave Syer + * + */ +public class ClassPathXmlApplicationContextJobFactory implements JobFactory { + + private String beanName; + + private String path; + + private ApplicationContext parent; + + /** + * @param beanName + * @param path + */ + public ClassPathXmlApplicationContextJobFactory(String beanName, String path, ApplicationContext parent) { + super(); + this.beanName = beanName; + this.path = path; + this.parent = parent; + } + + /** + * Create a {@link ClassPathXmlApplicationContext} from the path provided + * and pull out a bean with the name given during initialization. + * + * @see org.springframework.batch.core.repository.JobFactory#createJob() + */ + public Job createJob() { + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[] { path }, parent); + return (Job) context.getBean(beanName, Job.class); + } + + /** + * Return the bean name of the job in the application context. N.B. this is + * usually the name of the job as well, but it needn't be. The important + * thing is that the job can be located by this name. + * + * @see org.springframework.batch.core.repository.JobFactory#getJobName() + */ + public String getJobName() { + return beanName; + } + +} diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/sample/TaskExecutorLauncher.java b/spring-batch-samples/src/test/java/org/springframework/batch/sample/TaskExecutorLauncher.java index aaa6a7c3c..b38fa1f90 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/sample/TaskExecutorLauncher.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/sample/TaskExecutorLauncher.java @@ -15,53 +15,96 @@ */ package org.springframework.batch.sample; +import org.springframework.batch.core.domain.Job; +import org.springframework.batch.core.repository.DuplicateJobException; import org.springframework.batch.core.repository.JobRegistry; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.context.ApplicationContext; +import org.springframework.context.ResourceLoaderAware; import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.core.io.ResourceLoader; /** * @author Dave Syer * */ -public class TaskExecutorLauncher { - +public class TaskExecutorLauncher implements ResourceLoaderAware { + private JobRegistry registry; - - private void register(String[] paths) { - // registry.register(jobConfiguration) + + private ResourceLoader resourceLoader; + + private ApplicationContext parentContext = null; + + /** + * Public setter for the {@link JobRegistry}. + * @param registry the registry to set + */ + public void setRegistry(JobRegistry registry) { + this.registry = registry; + } + + /* + * (non-Javadoc) + * @see org.springframework.context.ResourceLoaderAware#setResourceLoader(org.springframework.core.io.ResourceLoader) + */ + public void setResourceLoader(ResourceLoader resourceLoader) { + this.resourceLoader = resourceLoader; + } + + private void register(String[] paths) throws DuplicateJobException { + for (int i = 0; i < paths.length; i++) { + String path = paths[i]; + ConfigurableListableBeanFactory beanFactory = new XmlBeanFactory(resourceLoader.getResource(path), + parentContext.getAutowireCapableBeanFactory()); + String[] names = beanFactory.getBeanNamesForType(Job.class); + for (int j = 0; j < names.length; j++) { + registry.register(new ClassPathXmlApplicationContextJobFactory(names[j], path, parentContext)); + } + } } public static void main(String[] args) throws Exception { - // Paths to individual job configurations. - final String[] paths = new String[] { "jobs/adhocLoopJob.xml", - "jobs/footballJob.xml" }; - - // The simple execution environment will be used as a parent - // context for each of the job contexts. The standard version of this - // from the Spring Batch samples does not have an MBean for the - // JobLauncher, nor does the JobLauncher have an asynchronous - // TaskExecutor. The adhocLoopJob has both, which is why it has to be - // included in the paths above. - final ApplicationContext parent = new ClassPathXmlApplicationContext( - "adhoc-job-launcher-context.xml"); -// parent.getAutowireCapableBeanFactory().autowireBeanProperties( -// this, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, false); + final TaskExecutorLauncher launcher = new TaskExecutorLauncher(); new Thread(new Runnable() { public void run() { - for (int i = 0; i < paths.length; i++) { - String path = paths[i]; - new ClassPathXmlApplicationContext(new String[] { path }, - parent); - } + launcher.run(); }; }).start(); + while (launcher.parentContext == null) { + Thread.sleep(100L); + } + + // Paths to individual job configurations. + final String[] paths = new String[] { "jobs/adhocLoopJob.xml", "jobs/footballJob.xml" }; + + launcher.register(paths); + System.out .println("Started application. " + "Please connect using JMX (remember to use -Dcom.sun.management.jmxremote if you can't see anything in Jconsole)."); System.in.read(); } + + private void run() { + + /* + * A simple execution environment with an MBean for the JobLauncher, + * which has an asynchronous TaskExecutor. This will be used as the + * parent context for loading job configurations. + */ + final ApplicationContext parent = new ClassPathXmlApplicationContext("adhoc-job-launcher-context.xml"); + parent.getAutowireCapableBeanFactory().autowireBeanProperties(this, + AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, false); + parent.getAutowireCapableBeanFactory().initializeBean(this, "taskExecutorLauncher"); + this.parentContext = parent; + + } + }