diff --git a/execution/src/main/java/org/springframework/batch/execution/bootstrap/BatchCommandLineLauncher.java b/execution/src/main/java/org/springframework/batch/execution/bootstrap/BatchCommandLineLauncher.java index e2ef62d38..929e2f2fd 100644 --- a/execution/src/main/java/org/springframework/batch/execution/bootstrap/BatchCommandLineLauncher.java +++ b/execution/src/main/java/org/springframework/batch/execution/bootstrap/BatchCommandLineLauncher.java @@ -16,36 +16,55 @@ package org.springframework.batch.execution.bootstrap; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.batch.core.configuration.NoSuchJobConfigurationException; +import org.springframework.batch.core.executor.ExitCodeExceptionClassifier; +import org.springframework.batch.execution.bootstrap.support.JvmSystemExiter; +import org.springframework.batch.execution.bootstrap.support.SimpleJvmExitCodeMapper; +import org.springframework.batch.execution.step.simple.SimpleExitCodeExceptionClassifier; import org.springframework.batch.repeat.ExitStatus; +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.access.BeanFactoryLocator; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.access.ContextSingletonBeanFactoryLocator; import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.util.Assert; /** * @author Dave Syer + * @author Lucas Ward * @since 2.1 */ public class BatchCommandLineLauncher { + protected static final Log logger = LogFactory + .getLog(BatchCommandLineLauncher.class); + /** * The key for the parent context. */ - public static final String PARENT_KEY = "simple-container"; + private static final String DEFAULT_PARENT_KEY = "batchExecutionEnvironment"; + private static final String DEFAULT_JOB_CONFIGURATION_PATH = "job-configuration.xml"; - private ConfigurableApplicationContext parent; - private JvmExitCodeMapper exitCodeMapper; + private static final String JOB_CONFIGURATIN_PATH_KEY = "job.configuration.path"; + private static final String JOB_NAME_KEY = "job.name"; + private static final String BATCH_EXECUTION_ENVIRONMENT_KEY = "batch.execution.environment.key"; + + private BeanFactoryLocator beanFactoryLocator; + + private JvmExitCodeMapper exitCodeMapper = new SimpleJvmExitCodeMapper(); + + private ExitCodeExceptionClassifier exceptionClassifier = new SimpleExitCodeExceptionClassifier(); private JobLauncher launcher; - /** - * Default constructor for the launcher. Sets up the parent context to use - * for all job executions using a context key {@link #PARENT_KEY}. - */ + private SystemExiter systemExiter = new JvmSystemExiter(); + public BatchCommandLineLauncher() { - parent = (ConfigurableApplicationContext) ContextSingletonBeanFactoryLocator - .getInstance().useBeanFactory(PARENT_KEY).getFactory(); + beanFactoryLocator = ContextSingletonBeanFactoryLocator.getInstance(); } /** @@ -58,6 +77,16 @@ public class BatchCommandLineLauncher { this.launcher = launcher; } + /** + * Injection setter for the {@link ExitCodeExceptionClassifier} + * + * @param exceptionClassifier + */ + public void setExceptionClassifier( + ExitCodeExceptionClassifier exceptionClassifier) { + this.exceptionClassifier = exceptionClassifier; + } + /** * Injection setter for the {@link JvmExitCodeMapper}. * @@ -68,25 +97,52 @@ public class BatchCommandLineLauncher { this.exitCodeMapper = exitCodeMapper; } + /** + * Injection setter for the {@link SystemExiter}. + * + * @param systemExitor + */ + public void setSystemExiter(SystemExiter systemExitor) { + this.systemExiter = systemExitor; + } + + public SystemExiter getSystemExiter() { + return systemExiter; + } + /** * @param path * the path to a Spring context configuration for this job * @param jobName * the name of the job execution to use * @throws NoSuchJobConfigurationException + * @throws IllegalStateException + * if JobLauncher is not autowired by the ApplicationContext */ - int start(String path, String jobName) { - if (!path.endsWith(".xml")) { - path = path + ".xml"; - } - ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( - new String[] { path }, parent); - context.getAutowireCapableBeanFactory().autowireBeanProperties(this, - AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true); - - ExitStatus status = null; + int start(String path, String jobName, String parentKey) { + ExitStatus status = ExitStatus.FAILED; + ClassPathXmlApplicationContext context = null; + try { + ClassPathXmlApplicationContext parent = (ClassPathXmlApplicationContext) beanFactoryLocator + .useBeanFactory(parentKey).getFactory(); + + if (!path.endsWith(".xml")) { + path = path + ".xml"; + } + + context = new ClassPathXmlApplicationContext( + new String[] { path }, parent); + + context.getAutowireCapableBeanFactory().autowireBeanProperties( + this, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, false); + + Assert.state(launcher != null, + "JobLauncher must be provided in the parent ApplicationContext" + + ", check the context created within classpath*:beanRefContext.xml to ensure a JobLauncher" + + " is declared"); + if (!launcher.isRunning()) { if (jobName == null) { status = launcher.run(); @@ -94,15 +150,21 @@ public class BatchCommandLineLauncher { status = launcher.run(jobName); } } - } catch (NoSuchJobConfigurationException e) { - status = new ExitStatus(false, - JvmExitCodeMapper.BATCH_EXITCODE_NO_SUCH_JOBCONFIGURATION, - "Could not locate JobConfiguration \"" + jobName + "\"" ); + } catch (NoSuchJobConfigurationException e) { + logger.fatal("Could not locate JobConfiguration \"" + jobName + + "\"", e); + status = new ExitStatus(false, + JvmExitCodeMapper.BATCH_EXITCODE_NO_SUCH_JOBCONFIGURATION); + } catch (Throwable t) { + logger.fatal(t); + status = exceptionClassifier.classifyForExitCode(t); } finally { - try { - context.stop(); - } finally { - context.close(); + if(context != null){ + try { + context.stop(); + } finally { + context.close(); + } } } return exitCodeMapper.getExitCode(status.getExitCode()); @@ -115,27 +177,26 @@ public class BatchCommandLineLauncher { * * @param args *
checkExit
+ * method doesn't allow exit with the specified status.
+ * @see System.exit
+ */
+ void exit(int status);
+}
diff --git a/execution/src/test/java/org/springframework/batch/execution/bootstrap/BatchCommandLineLauncherTests.java b/execution/src/test/java/org/springframework/batch/execution/bootstrap/BatchCommandLineLauncherTests.java
index f9372f2ac..cebc51965 100644
--- a/execution/src/test/java/org/springframework/batch/execution/bootstrap/BatchCommandLineLauncherTests.java
+++ b/execution/src/test/java/org/springframework/batch/execution/bootstrap/BatchCommandLineLauncherTests.java
@@ -15,8 +15,15 @@
*/
package org.springframework.batch.execution.bootstrap;
+import org.easymock.MockControl;
import org.springframework.batch.core.configuration.NoSuchJobConfigurationException;
import org.springframework.batch.execution.bootstrap.BatchCommandLineLauncher;
+import org.springframework.batch.repeat.ExitStatus;
+import org.springframework.beans.factory.access.BeanFactoryLocator;
+import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.access.ContextSingletonBeanFactoryLocator;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
import junit.framework.TestCase;
@@ -25,48 +32,98 @@ import junit.framework.TestCase;
*
*/
public class BatchCommandLineLauncherTests extends TestCase {
+
+ private static final String DEFAULT_PARENT_KEY = "batchExecutionEnvironment";
+ private static final String DEFAULT_JOB_CONFIGURATION_PATH = "job-configuration.xml";
+
+ private static final String JOB_CONFIGURATION_PATH_KEY = "job.configuration.path";
+ private static final String JOB_NAME_KEY = "job.name";
+ private static final String BATCH_EXECUTION_ENVIRONMENT_KEY = "batch.execution.environment.key";
+
+ private static final String TEST_BATCH_ENVIRONMENT_KEY = "testBatchEnvironment";
+ private static final String TEST_BATCH_ENVIRONMENT_NO_LAUNCHER_KEY = "testBatchEnvironmentNoLauncher";
+
+ BeanFactoryLocator beanFactoryLocator = ContextSingletonBeanFactoryLocator.getInstance();
+
+ ClassPathXmlApplicationContext context;
+
+ MockJobLauncher mockJobLauncher;
+ MockSystemExiter mockSystemExiter;
+
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ context = (ClassPathXmlApplicationContext)beanFactoryLocator.useBeanFactory(TEST_BATCH_ENVIRONMENT_KEY).getFactory();
+ context.getAutowireCapableBeanFactory().autowireBeanProperties(this, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true);
+ System.setProperty(BATCH_EXECUTION_ENVIRONMENT_KEY, TEST_BATCH_ENVIRONMENT_KEY);
+ }
+
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ System.clearProperty(JOB_CONFIGURATION_PATH_KEY);
+ System.clearProperty(JOB_NAME_KEY);
+ System.clearProperty(BATCH_EXECUTION_ENVIRONMENT_KEY);
+ }
+
+ public void setMockJobLauncher(MockJobLauncher mockJobLauncher){
+ this.mockJobLauncher = mockJobLauncher;
+ }
+
+ public void setMockSystemExiter(MockSystemExiter mockSystemExiter) {
+ this.mockSystemExiter = mockSystemExiter;
+ }
- BatchCommandLineLauncher commandLine = new BatchCommandLineLauncher();
+ public void testParentWithNoLauncher(){
+ context = (ClassPathXmlApplicationContext)beanFactoryLocator.useBeanFactory(TEST_BATCH_ENVIRONMENT_NO_LAUNCHER_KEY).getFactory();
+ context.getAutowireCapableBeanFactory().autowireBeanProperties(this, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, false);
+ System.setProperty(BATCH_EXECUTION_ENVIRONMENT_KEY, TEST_BATCH_ENVIRONMENT_NO_LAUNCHER_KEY);
+
+ BatchCommandLineLauncher.main(new String[0]);
+
+ assertEquals(JvmExitCodeMapper.JVM_EXITCODE_GENERIC_ERROR, mockSystemExiter.getStatus());
+ }
- int count = 0;
+ /**
+ * Test method for {@link org.springframework.batch.execution.bootstrap.BatchCommandLineLauncher#main(java.lang.String[])}.
+ * @throws Exception
+ */
+ public void testDefaultNameAndPath() throws Exception {
+
+ mockJobLauncher.setReturnValue(ExitStatus.FINISHED);
+
+ BatchCommandLineLauncher.main(new String[0]);
+
+ assertEquals(JvmExitCodeMapper.JVM_EXITCODE_COMPLETED, mockSystemExiter.getStatus());
+ assertEquals(mockJobLauncher.getLastRunCalled(), MockJobLauncher.RUN_NO_ARGS);
+ }
+ public void testCustomJobName(){
+
+ mockJobLauncher.setReturnValue(ExitStatus.FINISHED);
+
+ System.setProperty(JOB_NAME_KEY, "foo");
+ BatchCommandLineLauncher.main(new String[0]);
+
+ assertEquals(JvmExitCodeMapper.JVM_EXITCODE_COMPLETED, mockSystemExiter.getStatus());
+ assertEquals(mockJobLauncher.getLastRunCalled(), MockJobLauncher.RUN_JOB_NAME);
+ }
+
/**
* Test method for {@link org.springframework.batch.execution.bootstrap.BatchCommandLineLauncher#main(java.lang.String[])}.
* @throws Exception
*/
public void testMainWithDefaultArguments() throws Exception {
- // BatchCommandLineLauncher.main(new String[0]);
- // TODO: find a way to assert something. No error actually
- // means the test was successful...
- }
-
- /**
- * Test method for {@link org.springframework.batch.execution.bootstrap.BatchCommandLineLauncher#main(java.lang.String[])}.
- * @throws Exception
- */
- public void testMainWithParentContext() throws Exception {
- // Try an XML file name for the parent context with no suffix
- //BatchCommandLineLauncher.main(new String[]{"job-configuration"});
- //assertEquals(commandLine.start("job-configuration.xml", null), 0);
+ //can't test this without running the whole test in another jvm.
+ //BatchCommandLineLauncher.main(new String[0]);
}
- /**
- * Test method for {@link org.springframework.batch.execution.bootstrap.BatchCommandLineLauncher#main(java.lang.String[])}.
- * @throws Exception
- */
- public void testMainWithParentContextAndValidJobId() throws Exception {
- // Try a job id as the second argument
- //BatchCommandLineLauncher.main(new String[]{"job-configuration", "test-job"});
- //assertEquals(commandLine.start("job-configuration.xml", "test-job"), 0);
- }
-
- /**
- * Test method for {@link org.springframework.batch.execution.bootstrap.BatchCommandLineLauncher#main(java.lang.String[])}.
- * @throws Exception
- */
- public void testMainWithParentContextAndInvalidJobId() throws Exception {
- // Try a job id as the second argument test-job
- //BatchCommandLineLauncher.main(new String[]{"job-configuration", "foo-bar-spam"});
- //assertEquals(commandLine.start("job-configuration", "foo-bar-spam"), 2);
+
+
+ public void testInvalidJobConfig(){
+ //also not testable without kicking off in a new jvm, since autowiring happens
+ //after the context is loaded.
+/* System.setProperty(JOB_CONFIGURATION_PATH_KEY, "foo");
+
+ BatchCommandLineLauncher.main(new String[0]);*/
}
}
diff --git a/execution/src/test/java/org/springframework/batch/execution/bootstrap/MockJobLauncher.java b/execution/src/test/java/org/springframework/batch/execution/bootstrap/MockJobLauncher.java
new file mode 100644
index 000000000..4beeb194e
--- /dev/null
+++ b/execution/src/test/java/org/springframework/batch/execution/bootstrap/MockJobLauncher.java
@@ -0,0 +1,63 @@
+package org.springframework.batch.execution.bootstrap;
+
+import org.springframework.batch.core.configuration.NoSuchJobConfigurationException;
+import org.springframework.batch.core.runtime.JobIdentifier;
+import org.springframework.batch.repeat.ExitStatus;
+
+/**
+ * Mock Job Launcher. Normally, something like EasyMock would
+ * be used to mock an interface, however, because of the nature
+ * of launching a batch job from the command line, the mocked
+ * class cannot be injected.
+ *
+ * @author Lucas Ward
+ *
+ */
+public class MockJobLauncher implements JobLauncher {
+
+ public static final int RUN_NO_ARGS = 0;
+ public static final int RUN_JOB_NAME = 1;
+ public static final int RUN_JOB_IDENTIFIER =2 ;
+
+ private int lastRunCalled = RUN_NO_ARGS;
+ private ExitStatus returnValue = ExitStatus.FINISHED;
+
+ private boolean isRunning = false;
+
+ public boolean isRunning() {
+ return isRunning;
+ }
+
+ public ExitStatus run() throws NoSuchJobConfigurationException {
+ lastRunCalled = RUN_NO_ARGS;
+ return returnValue;
+ }
+
+ public ExitStatus run(String jobName)
+ throws NoSuchJobConfigurationException {
+ lastRunCalled = RUN_JOB_NAME;
+ return returnValue;
+ }
+
+ public ExitStatus run(JobIdentifier jobIdentifier)
+ throws NoSuchJobConfigurationException {
+ lastRunCalled = RUN_JOB_IDENTIFIER;
+ return returnValue;
+ }
+
+ public void stop() {
+
+ }
+
+ public void setReturnValue(ExitStatus returnValue){
+ this.returnValue = returnValue;
+ }
+
+ public void setIsRunning(boolean isRunning){
+ this.isRunning = isRunning;
+ }
+
+ public int getLastRunCalled(){
+ return lastRunCalled;
+ }
+}
diff --git a/execution/src/test/java/org/springframework/batch/execution/bootstrap/MockSystemExiter.java b/execution/src/test/java/org/springframework/batch/execution/bootstrap/MockSystemExiter.java
new file mode 100644
index 000000000..6dc0f3638
--- /dev/null
+++ b/execution/src/test/java/org/springframework/batch/execution/bootstrap/MockSystemExiter.java
@@ -0,0 +1,33 @@
+/*
+ * 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.bootstrap;
+
+/**
+ * @author Lucas Ward
+ *
+ */
+class MockSystemExiter implements SystemExiter{
+
+ private int status;
+
+ public void exit(int status) {
+ this.status = status;
+ }
+
+ public int getStatus() {
+ return status;
+ }
+}
diff --git a/execution/src/test/java/org/springframework/batch/execution/bootstrap/support/JvmSystemExiter.java b/execution/src/test/java/org/springframework/batch/execution/bootstrap/support/JvmSystemExiter.java
new file mode 100644
index 000000000..1f2d4fc04
--- /dev/null
+++ b/execution/src/test/java/org/springframework/batch/execution/bootstrap/support/JvmSystemExiter.java
@@ -0,0 +1,39 @@
+/*
+ * 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.bootstrap.support;
+
+import org.springframework.batch.execution.bootstrap.SystemExiter;
+
+/**
+ * Implementation of the {@link SystemExiter} interface
+ * that calls the standards System.exit method. It should
+ * be noted that there will be no unit tests for this class,
+ * since there is only one line of actual code, that would only
+ * be testable by mocking System or Runtime.
+ *
+ * @author Lucas Ward
+ *
+ */
+public class JvmSystemExiter implements SystemExiter {
+
+ /* (non-Javadoc)
+ * @see org.springframework.batch.execution.bootstrap.SystemExiter#exit(int)
+ */
+ public void exit(int status) {
+ System.exit(status);
+ }
+
+}
diff --git a/execution/src/test/resources/beanRefContext.xml b/execution/src/test/resources/beanRefContext.xml
index 1e8b9b7a5..784d115a2 100644
--- a/execution/src/test/resources/beanRefContext.xml
+++ b/execution/src/test/resources/beanRefContext.xml
@@ -2,10 +2,22 @@