IN PROGRESS - issue BATCH-24: Provide exit code for job executed as a main method

http://opensource.atlassian.com/projects/spring/browse/BATCH-24
This commit is contained in:
lucasward
2007-09-20 01:16:57 +00:00
parent 464b1393b6
commit 7e83c33632
10 changed files with 417 additions and 79 deletions

View File

@@ -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
* <ol>
* <li> path to resource to load job configuration context
* (default "job-configuration.xml");</li>
* <li>runtime name for job execution (default
* "job-execution-id").</li>
* <li> classpath location of resource to load job configuration
* context (default "job-configuration.xml");</li>
* <li>runtime name of Job (default "job-execution-id").</li>
* <li> parent context key for use in pulling the correct
* beanFactory from the beanRefContext.xml (@see
* ContextSingletonBeanFactoryLocator)</li>
* </ol>
* @throws NoSuchJobConfigurationException
*/
public static void main(String[] args) {
String path = "job-configuration.xml";
String name = null;
if (args.length > 0) {
path = args[0];
}
if (args.length > 1) {
name = args[1];
}
String path = System.getProperty(JOB_CONFIGURATIN_PATH_KEY,
DEFAULT_JOB_CONFIGURATION_PATH);
String name = System.getProperty(JOB_NAME_KEY);
String parentKey = System.getProperty(BATCH_EXECUTION_ENVIRONMENT_KEY,
DEFAULT_PARENT_KEY);
BatchCommandLineLauncher command = new BatchCommandLineLauncher();
int result = command.start(path, name);
System.exit(result);
int result = command.start(path, name, parentKey);
command.getSystemExiter().exit(result);
}
}

View File

@@ -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;
/**
* Interface for exiting the JVM. This abstraction is only
* useful in order to allow classes that make System.exit calls
* to be testable, since calling System.exit during a unit
* test would cause the entire jvm to finish.
*
* @author Lucas Ward
*
*/
public interface SystemExiter {
/**
* Terminate the currently running Java Virtual Machine.
*
* @param status exit status.
* @throws SecurityException
* if a security manager exists and its <code>checkExit</code>
* method doesn't allow exit with the specified status.
* @see System.exit
*/
void exit(int status);
}

View File

@@ -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]);*/
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -2,10 +2,22 @@
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean id="simple-container" class="org.springframework.context.support.ClassPathXmlApplicationContext">
<bean id="batchExecutionEnvironment" class="org.springframework.context.support.ClassPathXmlApplicationContext">
<constructor-arg>
<value>simple-container-definition.xml</value>
</constructor-arg>
</bean>
<bean id="testBatchEnvironment" class="org.springframework.context.support.ClassPathXmlApplicationContext">
<constructor-arg>
<value>org/springframework/batch/execution/bootstrap/test-batch-environment.xml</value>
</constructor-arg>
</bean>
<bean id="testBatchEnvironmentNoLauncher" class="org.springframework.context.support.ClassPathXmlApplicationContext">
<constructor-arg>
<value>org/springframework/batch/execution/bootstrap/test-batch-environment-no-launcher.xml</value>
</constructor-arg>
</bean>
</beans>

View File

@@ -11,10 +11,10 @@
<property name="jobConfigurationRegistry" ref="jobConfigurationRegistry"/>
</bean>
<bean id="test-job" parent="simpleJob">
<bean id="test-job" class="org.springframework.batch.core.configuration.JobConfiguration">
<property name="name" value="test-job"/>
<property name="steps">
<bean id="step1" parent="simpleStep">
<bean id="step1" class="org.springframework.batch.execution.step.simple.SimpleStepConfiguration">
<constructor-arg>
<bean
class="org.springframework.batch.execution.tasklet.ItemProviderProcessTasklet">

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
<bean id="jobConfigurationRegistry" class="org.springframework.batch.execution.configuration.MapJobConfigurationRegistry"/>
<bean class="org.springframework.batch.execution.bootstrap.support.SimpleJvmExitCodeMapper" />
<bean class="org.springframework.batch.execution.bootstrap.MockSystemExiter" />
</beans>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
<bean id="simpleContainerLauncher" class="org.springframework.batch.execution.bootstrap.MockJobLauncher" />
<bean id="jobConfigurationRegistry" class="org.springframework.batch.execution.configuration.MapJobConfigurationRegistry"/>
<bean class="org.springframework.batch.execution.bootstrap.support.SimpleJvmExitCodeMapper" />
<bean class="org.springframework.batch.execution.bootstrap.MockSystemExiter" />
</beans>