diff --git a/spring-batch-core/pom.xml b/spring-batch-core/pom.xml index 72a5a3a7c..abceb52c9 100644 --- a/spring-batch-core/pom.xml +++ b/spring-batch-core/pom.xml @@ -96,6 +96,17 @@ spring-jdbc true + + org.springframework.osgi + spring-osgi-core + true + + + org.osgi + osgi_R4_core + 1.0 + true + org.springframework spring-test diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextFactory.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextFactory.java new file mode 100644 index 000000000..624cde579 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextFactory.java @@ -0,0 +1,9 @@ +package org.springframework.batch.core.configuration.support; + +import org.springframework.context.ConfigurableApplicationContext; + +public interface ApplicationContextFactory { + + ConfigurableApplicationContext createApplicationContext(); + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ClassPathXmlApplicationContextJobFactory.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactory.java similarity index 65% rename from spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ClassPathXmlApplicationContextJobFactory.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactory.java index 922963e54..71ae91804 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ClassPathXmlApplicationContextJobFactory.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactory.java @@ -19,9 +19,7 @@ import org.springframework.batch.core.Job; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobExecutionException; import org.springframework.batch.core.configuration.JobFactory; -import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; @@ -32,44 +30,22 @@ import org.springframework.context.support.ClassPathXmlApplicationContext; * @author Dave Syer * */ -public class ClassPathXmlApplicationContextJobFactory implements JobFactory, ApplicationContextAware { +public class ApplicationContextJobFactory implements JobFactory { - final private String beanName; + final private String jobName; - final private String path; - - private ApplicationContext parent; + final private ApplicationContextFactory applicationContextFactory; /** - * Setter for the parent application context. - * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) - */ - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - parent = applicationContext; - } - - /** - * @param beanName the id of the {@link Job} in the application context to + * @param jobName the id of the {@link Job} in the application context to * be created - * @param path the path to the XML configuration containing the {@link Job} */ - public ClassPathXmlApplicationContextJobFactory(String beanName, String path) { - this(beanName, path, null); - } - - /** - * @param beanName the id of the {@link Job} in the application context to - * be created - * @param path the path to the XML configuration containing the {@link Job} - * @param parent the application context to use as a parent (or null) - */ - public ClassPathXmlApplicationContextJobFactory(String beanName, String path, ApplicationContext parent) { + public ApplicationContextJobFactory(ApplicationContextFactory applicationContextFactory, String jobName) { super(); - this.beanName = beanName; - this.path = path; - this.parent = parent; + this.jobName = jobName; + this.applicationContextFactory = applicationContextFactory; } - + /** * Create a {@link ClassPathXmlApplicationContext} from the path provided * and pull out a bean with the name given during initialization. @@ -77,10 +53,8 @@ public class ClassPathXmlApplicationContextJobFactory implements JobFactory, App * @see org.springframework.batch.core.configuration.JobFactory#createJob() */ public Job createJob() { - ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[] { path }, false, parent); - context.setDisplayName("Job ApplicationContext "+beanName); - context.refresh(); - Job job = (Job) context.getBean(beanName, Job.class); + ConfigurableApplicationContext context = applicationContextFactory.createApplicationContext(); + Job job = (Job) context.getBean(jobName, Job.class); return new ContextClosingJob(job, context); } @@ -92,7 +66,7 @@ public class ClassPathXmlApplicationContextJobFactory implements JobFactory, App * @see org.springframework.batch.core.configuration.JobFactory#getJobName() */ public String getJobName() { - return beanName; + return jobName; } /** 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 new file mode 100644 index 000000000..e56897824 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ClassPathXmlApplicationContextFactory.java @@ -0,0 +1,46 @@ +package org.springframework.batch.core.configuration.support; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.util.Assert; + +public class ClassPathXmlApplicationContextFactory implements + ApplicationContextFactory, ApplicationContextAware { + + private ApplicationContext parent; + + private String path; + + /** + * @param path + * the resource path to the xml to load for the child context. + */ + public void setPath(String path) { + this.path = path; + } + + /** + * Setter for the parent application context. + * + * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) + */ + public void setApplicationContext(ApplicationContext applicationContext) + throws BeansException { + Assert.isInstanceOf(ConfigurableApplicationContext.class, + applicationContext); + parent = applicationContext; + } + + /** + * Creates an {@link ApplicationContext} from the provided path. + * + * @see ApplicationContextFactory#createApplicationContext() + */ + public ConfigurableApplicationContext createApplicationContext() { + return new ClassPathXmlApplicationContext(new String[] { path }, parent); + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobFactoryRegistrationListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobFactoryRegistrationListener.java new file mode 100644 index 000000000..01c730b22 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobFactoryRegistrationListener.java @@ -0,0 +1,57 @@ +package org.springframework.batch.core.configuration.support; + +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.batch.core.configuration.JobFactory; +import org.springframework.batch.core.configuration.JobRegistry; + +/** + * Generic service that can bind and unbind a {@link JobFactory} in a + * {@link JobRegistry}. + * + * @author Dave Syer + * + */ +public class JobFactoryRegistrationListener { + + private Log logger = LogFactory.getLog(getClass()); + + private JobRegistry jobRegistry; + + /** + * Public setter for a {@link JobRegistry} to use for all the bind and + * unbind events. + * + * @param jobRegistry {@link JobRegistry} + */ + public void setJobRegistry(JobRegistry jobRegistry) { + this.jobRegistry = jobRegistry; + } + + /** + * Take the {@link JobFactory} provided and register it with the + * {@link JobRegistry}. + * @param jobFactory a {@link JobFactory} + * @param params not needed by this listener. + * @throws Exception if there is a problem + */ + public void bind(JobFactory jobFactory, Map params) throws Exception { + logger.info("Binding JobFactory: " + jobFactory.getJobName()); + jobRegistry.register(jobFactory); + } + + /** + * Take the {@link JobFactory} provided and unregister it with the + * {@link JobRegistry}. + * @param jobFactory a {@link JobFactory} + * @param params not needed by this listener. + * @throws Exception if there is a problem + */ + public void unbind(JobFactory jobFactory, Map params) throws Exception { + logger.info("Unbinding JobFactory: " + jobFactory.getJobName()); + jobRegistry.unregister(jobFactory.getJobName()); + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/OsgiBundleXmlApplicationContextFactory.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/OsgiBundleXmlApplicationContextFactory.java new file mode 100644 index 000000000..9a927d845 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/OsgiBundleXmlApplicationContextFactory.java @@ -0,0 +1,74 @@ +package org.springframework.batch.core.configuration.support; + +import org.osgi.framework.BundleContext; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.osgi.context.BundleContextAware; +import org.springframework.osgi.context.support.OsgiBundleXmlApplicationContext; + +public class OsgiBundleXmlApplicationContextFactory implements BundleContextAware, + ApplicationContextFactory, ApplicationContextAware { + + private BundleContext bundleContext; + + private ApplicationContext parent; + + private String path; + + private String displayName; + + /** + * @param path + * the resource path to the xml to load for the child context. + */ + public void setPath(String path) { + this.path = path; + } + + /** + * @param displayName the display name for the application context created. + */ + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + /** + * Setter for the parent application context. + * + * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) + */ + public void setApplicationContext(ApplicationContext applicationContext) + throws BeansException { + parent = applicationContext; + } + + /** + * Stash the {@link BundleContext} for creating a job application context + * later. + * + * @see org.springframework.osgi.context.BundleContextAware#setBundleContext(org.osgi.framework.BundleContext) + */ + public void setBundleContext(BundleContext context) { + this.bundleContext = context; + } + + /** + * Create an application context from the provided path, using the current + * OSGi {@link BundleContext} and the enclosing Spring + * {@link ApplicationContext} as a parent context. + * + * @see ApplicationContextFactory#createApplicationContext() + */ + public ConfigurableApplicationContext createApplicationContext() { + OsgiBundleXmlApplicationContext context = new OsgiBundleXmlApplicationContext( + new String[] { path }, parent); + String displayName = bundleContext.getBundle().getSymbolicName() + ":" + this.displayName; + context.setDisplayName(displayName); + context.setBundleContext(bundleContext); + context.refresh(); + return context; + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/CommandLineJobRunner.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/CommandLineJobRunner.java index 7d0648e1c..5b2d009cc 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/CommandLineJobRunner.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/CommandLineJobRunner.java @@ -48,17 +48,16 @@ import org.springframework.util.StringUtils; * *

* With any launch of a batch job within Spring Batch, a Spring context - * containing the Job and the 'Execution Environment' has to be created. This - * command line launcher can be used to load that context from a single - * location. It can also load the job as well All dependencies of the launcher - * will then be satisfied by autowiring by type from the combined application - * context. Default values are provided for all fields except the - * {@link JobLauncher} and {@link JobLocator}. Therefore, if autowiring fails - * to set it (it should be noted that dependency checking is disabled because - * most of the fields have default values and thus don't require dependencies to - * be fulfilled via autowiring) then an exception will be thrown. It should also - * be noted that even if an exception is thrown by this class, it will be mapped - * to an integer and returned. + * containing the {@link Job} and some execution context has to be created. This + * command line launcher can be used to load the job and its context from a + * single location. All dependencies of the launcher will then be satisfied by + * autowiring by type from the combined application context. Default values are + * provided for all fields except the {@link JobLauncher} and {@link JobLocator}. + * Therefore, if autowiring fails to set it (it should be noted that dependency + * checking is disabled because most of the fields have default values and thus + * don't require dependencies to be fulfilled via autowiring) then an exception + * will be thrown. It should also be noted that even if an exception is thrown + * by this class, it will be mapped to an integer and returned. *

* *

@@ -89,7 +88,7 @@ import org.springframework.util.StringUtils; *

* *

- * The combined application context must only contain one instance of a + * The combined application context must contain only one instance of * {@link JobLauncher}. The job parameters passed in to the command line will * be converted to {@link Properties} by assuming that each individual element * is one parameter that is separated by an equals sign. For example, 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 new file mode 100644 index 000000000..038b2a1a3 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JobRegistryBackgroundJobRunner.java @@ -0,0 +1,211 @@ +/* + * 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.launch.support; + +import java.util.ArrayList; +import java.util.Arrays; +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.JobFactory; +import org.springframework.batch.core.configuration.JobRegistry; +import org.springframework.batch.core.configuration.support.ApplicationContextJobFactory; +import org.springframework.batch.core.configuration.support.ClassPathXmlApplicationContextFactory; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.DuplicateJobException; +import org.springframework.beans.factory.BeanFactory; +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; +import org.springframework.util.Assert; + +/** + *

+ * Command line launcher for registering jobs with a {@link JobRegistry}. + * Normally this will be used in conjunction with an external trigger for the + * jobs registered, e.g. a JMX MBean wrapper for a {@link JobLauncher}, or a + * Quartz trigger. + *

+ * + *

+ * With any launch of a batch job within Spring Batch, a Spring context + * containing the {@link Job} has to be created. Using this launcher, the jobs + * are all registered with a {@link JobRegistry} defined in a parent application + * context. The jobs are then set up in child contexts. All dependencies of the + * runner will then be satisfied by autowiring by type from the parent + * application context. Default values are provided for all fields except the + * {@link JobRegistry}. Therefore, if autowiring fails to set it then an + * exception will be thrown. + *

+ * + * @author Dave Syer + * + */ +public class JobRegistryBackgroundJobRunner implements ResourceLoaderAware { + + /** + * System property key that switches the runner to "embedded" mode + * (returning immediately from the main method). Useful for testing + * purposes. + */ + public static final String EMBEDDED = JobRegistryBackgroundJobRunner.class.getSimpleName() + ".EMBEDDED"; + + private static Log logger = LogFactory.getLog(JobRegistryBackgroundJobRunner.class); + + private JobRegistry registry; + + private ResourceLoader resourceLoader; + + private ApplicationContext parentContext = null; + + final private String parentContextPath; + + private static List errors = new ArrayList(); + + /** + * @param parentContextPath + */ + public JobRegistryBackgroundJobRunner(String parentContextPath) { + super(); + this.parentContextPath = parentContextPath; + } + + /** + * 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; + } + + /** + * Public getter for the startup errors encountered during parent context + * creation. + * @return the errors + */ + public static List getErrors() { + return errors; + } + + private void register(String[] paths) throws DuplicateJobException { + for (int i = 0; i < paths.length; i++) { + String path = paths[i]; + logger.info("Registering Job definitions from " + path); + ConfigurableListableBeanFactory beanFactory = new XmlBeanFactory(resourceLoader.getResource(path), + parentContext.getAutowireCapableBeanFactory()); + String[] names = beanFactory.getBeanNamesForType(Job.class); + for (int j = 0; j < names.length; j++) { + ClassPathXmlApplicationContextFactory factory = new ClassPathXmlApplicationContextFactory(); + factory.setApplicationContext(parentContext); + factory.setPath(path); + logger.info("Registering Job definition: " + names[j]); + registry.register(new ApplicationContextJobFactory(factory, names[j])); + } + } + } + + /** + * Supply a list of application context locations, starting with the parent + * context, and followed by the children. The parent must contain a + * {@link JobRegistry} and the child contexts are expected to contain + * {@link Job} definitions, each of which will be registered wit the + * registry. + * + * Example usage: + * + *
+	 * $ java -classpath ... JobRegistryBackgroundJobRunner job-registry-context.xml job1.xml job2.xml ...
+	 * 
+ * + * The child contexts are created only when needed though the + * {@link JobFactory} interface (but the XML is validated on startup by + * using it to create a {@link BeanFactory} which is then discarded). + * + * The parent context is created in a separate thread, and the program will + * pause for input in an infinite loop until the user hits any key. + * + * @param args the context locations to use (first one is for parent) + * @throws Exception if anything goes wrong with the context creation + */ + public static void main(String... args) throws Exception { + + Assert.state(args.length >= 1, "At least one argument (the parent context path) must be provided."); + + final JobRegistryBackgroundJobRunner launcher = new JobRegistryBackgroundJobRunner(args[0]); + errors.clear(); + + logger.info("Starting job registry in parent context from XML at: [" + args[0] + "]"); + + new Thread(new Runnable() { + public void run() { + try { + launcher.run(); + } + catch (RuntimeException e) { + errors.add(e); + throw e; + } + }; + }).start(); + + logger.info("Waiting for parent context to start."); + while (launcher.parentContext == null && errors.isEmpty()) { + Thread.sleep(100L); + } + if (!errors.isEmpty()) { + logger.info(errors.size() + " errors detected on startup of parent context. Rethrowing."); + throw errors.get(0); + } + + // Paths to individual job configurations. + final String[] paths = new String[args.length - 1]; + System.arraycopy(args, 1, paths, 0, paths.length); + + logger.info("Parent context started. Registering jobs from paths: " + Arrays.asList(paths)); + launcher.register(paths); + + if (System.getProperty(EMBEDDED) != null) { + return; + } + + System.out.println("Started application. Hit any key to exit."); + System.in.read(); + + } + + private void run() { + final ApplicationContext parent = new ClassPathXmlApplicationContext(parentContextPath); + parent.getAutowireCapableBeanFactory().autowireBeanProperties(this, + AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, false); + parent.getAutowireCapableBeanFactory().initializeBean(this, getClass().getSimpleName()); + this.parentContext = parent; + } + +} 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 new file mode 100644 index 000000000..0a8a1d0cf --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactoryTests.java @@ -0,0 +1,30 @@ +package org.springframework.batch.core.configuration.support; + +import static org.junit.Assert.assertNotNull; + +import org.junit.Test; +import org.springframework.batch.core.job.JobSupport; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.StaticApplicationContext; + +public class ApplicationContextJobFactoryTests { + + private ApplicationContextJobFactory factory = new ApplicationContextJobFactory( + new StubApplicationContextFactory(), "job"); + + @Test + public void testFactoryContext() throws Exception { + assertNotNull(factory.createJob()); + } + + private static class StubApplicationContextFactory implements + ApplicationContextFactory { + public ConfigurableApplicationContext createApplicationContext() { + StaticApplicationContext context = new StaticApplicationContext(); + context.registerSingleton("job", JobSupport.class); + return context ; + } + + } + +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ClassPathXmlApplicationContextJobFactoryTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ClassPathXmlApplicationContextFactoryTests.java similarity index 56% rename from spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ClassPathXmlApplicationContextJobFactoryTests.java rename to spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ClassPathXmlApplicationContextFactoryTests.java index ddbd02469..a69375672 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ClassPathXmlApplicationContextJobFactoryTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ClassPathXmlApplicationContextFactoryTests.java @@ -17,28 +17,25 @@ package org.springframework.batch.core.configuration.support; import junit.framework.TestCase; +import org.springframework.batch.core.Job; import org.springframework.util.ClassUtils; /** * @author Dave Syer - * + * */ -public class ClassPathXmlApplicationContextJobFactoryTests extends TestCase { - - private ClassPathXmlApplicationContextJobFactory factory = new ClassPathXmlApplicationContextJobFactory("test-job", ClassUtils.addResourcePathToPackagePath(getClass(), "trivial-context.xml")); +public class ClassPathXmlApplicationContextFactoryTests extends TestCase { + + private ClassPathXmlApplicationContextFactory factory = new ClassPathXmlApplicationContextFactory(); - /** - * Test method for {@link org.springframework.batch.core.configuration.support.ClassPathXmlApplicationContextJobFactory#createJob()}. - */ public void testCreateJob() { - assertNotNull(factory.createJob()); + factory.setPath(ClassUtils.addResourcePathToPackagePath(getClass(), "trivial-context.xml")); + assertNotNull(factory.createApplicationContext()); } - /** - * Test method for {@link org.springframework.batch.core.configuration.support.ClassPathXmlApplicationContextJobFactory#getJobName()}. - */ public void testGetJobName() { - assertEquals("test-job", factory.getJobName()); + factory.setPath(ClassUtils.addResourcePathToPackagePath(getClass(), "trivial-context.xml")); + assertEquals("test-job", factory.createApplicationContext().getBeanNamesForType(Job.class)[0]); } } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/JobRegistryBackgroundJobRunnerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/JobRegistryBackgroundJobRunnerTests.java new file mode 100644 index 000000000..4f907e081 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/JobRegistryBackgroundJobRunnerTests.java @@ -0,0 +1,54 @@ +/* + * 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.launch.support; + +import static org.junit.Assert.assertEquals; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.util.ClassUtils; + +/** + * @author Dave Syer + * + */ +public class JobRegistryBackgroundJobRunnerTests { + + /** + * Test method for + * {@link org.springframework.batch.core.launch.support.JobRegistryBackgroundJobRunner#main(java.lang.String[])}. + */ + @Test + public void testMain() { + assertEquals(0, JobRegistryBackgroundJobRunner.getErrors().size()); + } + + @Before + public void setUp() throws Exception { + JobRegistryBackgroundJobRunner.getErrors().clear(); + System.setProperty(JobRegistryBackgroundJobRunner.EMBEDDED, ""); + JobRegistryBackgroundJobRunner.main( + ClassUtils.addResourcePathToPackagePath(getClass(), "test-environment.xml"), ClassUtils + .addResourcePathToPackagePath(getClass(), "job.xml")); + } + + @After + public void tearDown() throws Exception { + System.clearProperty(JobRegistryBackgroundJobRunner.EMBEDDED); + JobRegistryBackgroundJobRunner.getErrors().clear(); + } +} diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/launch/support/test-environment.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/launch/support/test-environment.xml index 15e44a16b..d442bb315 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/launch/support/test-environment.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/launch/support/test-environment.xml @@ -1,25 +1,22 @@ - - + - - - - - - - - - + + + + + + + + + + + diff --git a/spring-batch-samples/.settings/jmxLauncher.launch b/spring-batch-samples/.settings/jmxLauncher.launch index 937a244c2..6016abc33 100644 --- a/spring-batch-samples/.settings/jmxLauncher.launch +++ b/spring-batch-samples/.settings/jmxLauncher.launch @@ -2,7 +2,7 @@ - + @@ -10,7 +10,8 @@ - + + diff --git a/spring-batch-samples/.settings/quartzLauncher.launch b/spring-batch-samples/.settings/quartzLauncher.launch index bffcd4dbe..82735f801 100644 --- a/spring-batch-samples/.settings/quartzLauncher.launch +++ b/spring-batch-samples/.settings/quartzLauncher.launch @@ -1,8 +1,8 @@ - + - + @@ -10,6 +10,7 @@ - + + diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/sample/launch/TaskExecutorLauncher.java b/spring-batch-samples/src/main/java/org/springframework/batch/sample/launch/TaskExecutorLauncher.java deleted file mode 100644 index 9aba00d09..000000000 --- a/spring-batch-samples/src/main/java/org/springframework/batch/sample/launch/TaskExecutorLauncher.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * 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.launch; - -import java.util.ArrayList; -import java.util.List; - -import org.springframework.batch.core.Job; -import org.springframework.batch.core.configuration.JobRegistry; -import org.springframework.batch.core.configuration.support.ClassPathXmlApplicationContextJobFactory; -import org.springframework.batch.core.repository.DuplicateJobException; -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 implements ResourceLoaderAware { - - private JobRegistry registry; - - private ResourceLoader resourceLoader; - - private ApplicationContext parentContext = null; - - private static List errors = new ArrayList(); - - /** - * 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; - } - - /** - * Public getter for the errors. - * @return the errors - */ - public static List getErrors() { - return errors; - } - - 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 { - - final TaskExecutorLauncher launcher = new TaskExecutorLauncher(); - errors.clear(); - - new Thread(new Runnable() { - public void run() { - try { - launcher.run(); - } - catch (RuntimeException e) { - errors.add(e); - throw e; - } - }; - }).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; - - } - -} diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/sample/quartz/QuartzBatchLauncher.java b/spring-batch-samples/src/main/java/org/springframework/batch/sample/quartz/QuartzBatchLauncher.java deleted file mode 100644 index 439f574b1..000000000 --- a/spring-batch-samples/src/main/java/org/springframework/batch/sample/quartz/QuartzBatchLauncher.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * 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.quartz; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.configuration.JobRegistry; -import org.springframework.batch.core.configuration.support.ClassPathXmlApplicationContextJobFactory; -import org.springframework.batch.core.repository.DuplicateJobException; -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.support.ClassPathXmlApplicationContext; -import org.springframework.core.io.ResourceLoader; - -public class QuartzBatchLauncher { - - private static Log log = LogFactory.getLog(QuartzBatchLauncher.class); - - private JobRegistry registry; - - 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 { - - final QuartzBatchLauncher launcher = new QuartzBatchLauncher(); - - new Thread(new Runnable() { - public void run() { - 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); - - log.info("Started Quartz scheduler."); - System.in.read(); - } - - private void run() { - - /* - * A simple execution environment with a Quartz scheduler. This will be - * used as the parent context for loading job configurations. - */ - final ApplicationContext parent = new ClassPathXmlApplicationContext("quartz-job-launcher-context.xml"); - parent.getAutowireCapableBeanFactory().autowireBeanProperties(this, - AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, false); - parent.getAutowireCapableBeanFactory().initializeBean(this, "quartzLauncher"); - this.parentContext = parent; - - } -} diff --git a/spring-batch-samples/src/main/resources/log4j.properties b/spring-batch-samples/src/main/resources/log4j.properties index 79d0cd588..fcc475b7a 100644 --- a/spring-batch-samples/src/main/resources/log4j.properties +++ b/spring-batch-samples/src/main/resources/log4j.properties @@ -22,7 +22,7 @@ log4j.rootLogger=info, stdout #log4j.logger.org.hibernate.connection.DriverManagerConnectionProvider=trace ### enable spring -log4j.logger.org.springframework=error +log4j.logger.org.springframework=info #log4j.logger.org.springframework.transaction=debug #log4j.logger.org.springframework.jdbc.core=debug #log4j.logger.org.springframework.orm=debug diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/sample/launch/RemoteLauncherTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/sample/launch/RemoteLauncherTests.java index 32a3cc592..ef20cbcf8 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/sample/launch/RemoteLauncherTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/sample/launch/RemoteLauncherTests.java @@ -15,17 +15,22 @@ */ package org.springframework.batch.sample.launch; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + import java.util.ArrayList; import java.util.List; import javax.management.MBeanServerConnection; import javax.management.MalformedObjectNameException; -import junit.framework.TestCase; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.junit.Before; +import org.junit.Test; import org.springframework.batch.core.launch.support.ExportedJobLauncher; +import org.springframework.batch.core.launch.support.JobRegistryBackgroundJobRunner; import org.springframework.jmx.MBeanServerNotFoundException; import org.springframework.jmx.access.InvalidInvocationException; import org.springframework.jmx.access.MBeanProxyFactoryBean; @@ -35,7 +40,7 @@ import org.springframework.jmx.support.MBeanServerConnectionFactoryBean; * @author Dave Syer * */ -public class RemoteLauncherTests extends TestCase { +public class RemoteLauncherTests { private static Log logger = LogFactory.getLog(RemoteLauncherTests.class); @@ -47,11 +52,14 @@ public class RemoteLauncherTests extends TestCase { private static JobLoader loader; + @Test public void testConnect() throws Exception { - assertEquals(0, errors.size()); + String message = errors.isEmpty() ? "" : errors.get(0).getMessage(); + assertEquals(message, 0, errors.size()); assertTrue(isConnected()); } + @Test public void testLaunchBadJob() throws Exception { assertEquals(0, errors.size()); assertTrue(isConnected()); @@ -59,6 +67,7 @@ public class RemoteLauncherTests extends TestCase { assertTrue("Should contain 'NoSuchJobException': " + result, result.indexOf("NoSuchJobException") >= 0); } + @Test public void testLaunchAndStopRealJob() throws Exception { assertEquals(0, errors.size()); assertTrue(isConnected()); @@ -74,7 +83,8 @@ public class RemoteLauncherTests extends TestCase { * (non-Javadoc) * @see junit.framework.TestCase#setUp() */ - protected void setUp() throws Exception { + @Before + public void setUp() throws Exception { if (launcher != null) { return; } @@ -82,7 +92,7 @@ public class RemoteLauncherTests extends TestCase { thread = new Thread(new Runnable() { public void run() { try { - TaskExecutorLauncher.main(new String[0]); + JobRegistryBackgroundJobRunner.main("adhoc-job-launcher-context.xml", "jobs/adhocLoopJob.xml"); } catch (Exception e) { errors.add(e); @@ -102,8 +112,8 @@ public class RemoteLauncherTests extends TestCase { */ private static boolean isConnected() throws Exception { boolean connected = false; - if (!TaskExecutorLauncher.getErrors().isEmpty()) { - throw (RuntimeException) TaskExecutorLauncher.getErrors().get(0); + if (!JobRegistryBackgroundJobRunner.getErrors().isEmpty()) { + throw (RuntimeException) JobRegistryBackgroundJobRunner.getErrors().get(0); } if (launcher == null) { MBeanServerConnectionFactoryBean connectionFactory = new MBeanServerConnectionFactoryBean();