IN PROGRESS - issue BATCH-264: Dependencies among jobs

Implement ConditionalJob (branching but not concurrent execution) and tidy up Abstract and SimpleJob a bit.
This commit is contained in:
dsyer
2008-10-15 14:23:15 +00:00
parent fa2b04a1db
commit c1a05e71a4
9 changed files with 892 additions and 35 deletions

View File

@@ -21,8 +21,10 @@ import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.repeat.ExitStatus;
@@ -273,12 +275,12 @@ public class JobExecution extends Entity {
*/
public List<Throwable> getAllFailureExceptions(){
List<Throwable> allExceptions = new ArrayList<Throwable>(failureExceptions);
Set<Throwable> allExceptions = new HashSet<Throwable>(failureExceptions);
for(StepExecution stepExecution: stepExecutions){
allExceptions.addAll(stepExecution.getFailureExceptions());
}
return allExceptions;
return new ArrayList<Throwable>(allExceptions);
}
/**

View File

@@ -52,7 +52,7 @@ import org.springframework.util.ClassUtils;
*/
public abstract class AbstractJob implements Job, BeanNameAware, InitializingBean {
private static final Log logger = LogFactory.getLog(AbstractJob.class);
protected static final Log logger = LogFactory.getLog(AbstractJob.class);
private String name;
@@ -239,11 +239,13 @@ public abstract class AbstractJob implements Job, BeanNameAware, InitializingBea
}
catch (JobInterruptedException e) {
logger.error(e);
execution.setExitStatus(ExitStatus.FAILED);
execution.setStatus(BatchStatus.STOPPED);
execution.addFailureException(e);
}
catch (Throwable t) {
logger.error(t);
execution.setExitStatus(ExitStatus.FAILED);
execution.setStatus(BatchStatus.FAILED);
execution.addFailureException(t);
}
@@ -271,14 +273,18 @@ public abstract class AbstractJob implements Job, BeanNameAware, InitializingBea
/**
* Convenience method for subclasses to delegate the handling of a specific
* step in the context of the current {@link JobExecution}.
* step in the context of the current {@link JobExecution}. Clients of this
* method do not need access to the {@link JobRepository}, nor do they need
* to worry about populating the execution context on a restart, nor
* detecting the interrupted state (in job or step execution).
*
* @param step the {@link Step} to execute
* @param execution the currect {@link JobExecution}
* @param execution the current {@link JobExecution}
* @return the {@link StepExecution} corresponding to this step
*
* @throws JobInterruptedException if the {@link JobExecution} has been
* interrupted
* interrupted, and in particular if {@link BatchStatus#STOPPED} or
* {@link BatchStatus#STOPPING} is detected
* @throws StartLimitExceededException if the start limit has been exceeded
* for this step
* @throws JobRestartException if the job is in an inconsistent state from
@@ -286,7 +292,7 @@ public abstract class AbstractJob implements Job, BeanNameAware, InitializingBea
*/
protected final StepExecution handleStep(Step step, JobExecution execution) throws JobInterruptedException,
JobRestartException, StartLimitExceededException {
if (execution.getStatus() == BatchStatus.STOPPING) {
if (execution.getStatus() == BatchStatus.STOPPING || execution.getStatus() == BatchStatus.STOPPED) {
throw new JobInterruptedException("JobExecution interrupted.");
}
@@ -313,6 +319,11 @@ public abstract class AbstractJob implements Job, BeanNameAware, InitializingBea
step.execute(currentStepExecution);
if (currentStepExecution.getStatus() == BatchStatus.STOPPED
|| currentStepExecution.getStatus() == BatchStatus.STOPPING) {
throw new JobInterruptedException("Job interrupted by step execution");
}
}
return currentStepExecution;

View File

@@ -15,23 +15,230 @@
*/
package org.springframework.batch.core.job;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionException;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.repeat.ExitStatus;
/**
* A {@link Job} that branches conditionally depending on the exit status of the
* last step execution. The input parameters are the step transitions (in no
* particular order). The start step name must also be specified (and must exist
* in the set of transitions).
*
* @author Dave Syer
*
*
*/
public class ConditionalJob extends AbstractJob {
private Step startStep;
private Map<String, SortedSet<StepTransition>> transitionMap = new HashMap<String, SortedSet<StepTransition>>();
private Map<String, Step> stepMap = new HashMap<String, Step>();
private String startStepName;
private Collection<StepTransition> stepTransitions;
/**
* @see AbstractJob#doExecute(JobExecution)
* Create a {@link Job} with the given name.
* @param name
*/
public ConditionalJob(String name) {
super(name);
}
/**
* Create a {@link Job} with no name.
*/
public ConditionalJob() {
super();
}
/**
* Public setter for the start step name.
* @param startStepName the name of the start step
*/
public void setStartStepName(String startStepName) {
this.startStepName = startStepName;
}
/**
* Public setter for the stepTransitions.
* @param stepTransitions the stepTransitions to set
*/
public void setStepTransitions(Collection<StepTransition> stepTransitions) {
this.stepTransitions = stepTransitions;
}
/**
* Locate start step and pre-populate data structures needed for execution.
*
* @see AbstractJob#afterPropertiesSet()
*/
@Override
protected StepExecution doExecute(JobExecution execution) throws JobExecutionException {
// TODO Auto-generated method stub
return null;
public void afterPropertiesSet() throws Exception {
super.afterPropertiesSet();
startStep = null;
transitionMap.clear();
stepMap.clear();
boolean hasEndStep = false;
for (StepTransition stepTransition : stepTransitions) {
Step step = stepTransition.getStep();
stepMap.put(step.getName(), step);
}
for (StepTransition stepTransition : stepTransitions) {
Step step = stepTransition.getStep();
if (!stepTransition.isEnd()) {
String next = stepTransition.getNext();
if (!stepMap.containsKey(next)) {
throw new IllegalArgumentException("Missing step for [" + stepTransition + "]");
}
}
else {
hasEndStep = true;
}
String name = step.getName();
SortedSet<StepTransition> set = transitionMap.get(name);
if (set == null) {
set = new TreeSet<StepTransition>();
transitionMap.put(name, set);
}
set.add(stepTransition);
}
if (!hasEndStep) {
throw new IllegalArgumentException(
"No end step was found. You must specify at least one transition with no next step.");
}
if (startStepName != null) {
startStep = stepMap.get(startStepName);
if (startStep == null) {
throw new IllegalArgumentException(
"Start step does not exist (if you specify a startStepName make sure "
+ "a step with that name is in one of the transitions): [" + startStepName + "]");
}
}
else {
// Try and locate a transition with no incoming links
Set<String> nextStepNames = new HashSet<String>();
for (StepTransition stepTransition : stepTransitions) {
nextStepNames.add(stepTransition.getNext());
}
for (StepTransition stepTransition : stepTransitions) {
Step step = stepTransition.getStep();
if (!nextStepNames.contains(step.getName())) {
if (startStep != null && !startStep.getName().equals(step.getName())) {
throw new IllegalArgumentException(String.format(
"Multiple possible start steps found: [%s, %s]. "
+ "Please specify one explicitly with the startStepName property.", startStep
.getName(), step.getName()));
}
startStep = step;
}
}
if (startStep == null) {
throw new IllegalArgumentException(
"No start step could be located (no transition without incoming links)");
}
}
}
/**
* @see AbstractJob#doExecute(JobExecution)
* @throws JobExecutionException if the next step cannot be located at any
* point
*/
@Override
protected StepExecution doExecute(JobExecution jobExecution) throws JobExecutionException {
StepExecution stepExecution = null;
Step step = nextStep(null);
while (step != null) {
stepExecution = handleStep(step, jobExecution);
step = nextStep(stepExecution);
}
return stepExecution;
}
/**
* @param stepExecution the last {@link StepExecution} (or null if this is
* the start)
* @return the next {@link Step} (or null if this is the end)
* @throws JobExecutionException
*/
private Step nextStep(StepExecution stepExecution) throws JobExecutionException {
if (stepExecution == null) {
return startStep;
}
String stepName = stepExecution.getStepName();
Set<StepTransition> set = transitionMap.get(stepName);
if (set == null) {
throw new JobExecutionException(String.format("No transitions found in job=%s for step=%s", getName(),
stepName));
}
ExitStatus status = stepExecution.getExitStatus();
String next = null;
for (StepTransition stepTransition : set) {
if (stepTransition.matches(status)) {
if (stepTransition.isEnd()) {
// End of job
return null;
}
next = stepTransition.getNext();
break;
}
}
if (next == null) {
throw new JobExecutionException(String.format(
"Next step not found in job=%s for step=%s with exit status=%s", getName(), stepName, status));
}
if (!stepMap.containsKey(next)) {
throw new JobExecutionException(String.format("Next step not specified in job=%s for next=%s", getName(),
next));
}
return stepMap.get(next);
}
}

View File

@@ -76,7 +76,7 @@ public class SimpleJob extends AbstractJob {
StepExecution stepExecution = null;
for (Step step : steps) {
stepExecution = handleStep(step, execution);
if (stepExecution.getStatus() == BatchStatus.FAILED || stepExecution.getStatus() == BatchStatus.STOPPED) {
if (stepExecution.getStatus() != BatchStatus.COMPLETED) {
return stepExecution;
}
}

View File

@@ -0,0 +1,284 @@
/*
* 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.job;
import org.springframework.batch.core.Step;
import org.springframework.batch.repeat.ExitStatus;
import org.springframework.util.StringUtils;
/**
* Value object representing a potential transition from one {@link Step} to
* another. The originating step name and the next {@link Step} to execute are
* linked by a pattern for the {@link ExitStatus#getExitCode() exit code} of an
* execution of the originating step.
*
* @author Dave Syer
*
*/
public class StepTransition implements Comparable<StepTransition> {
private final String pattern;
private final String next;
private final Step step;
/**
* Create a new end state {@link StepTransition} specification. This
* transition explicitly goes to an end state (i.e. no more executions).
*
* @see StepTransition#StepTransition(Step, String, String)
*/
public StepTransition(Step step, String pattern) {
this(step, pattern, null);
}
/**
* Create a new {@link StepTransition} specification from one step to
* another (by name).
*
* @param step the step to be executed
* @param pattern the pattern to match in the {@link ExitStatus} of the step
* @param next the name of the next step to execute
*/
public StepTransition(Step step, String pattern, String next) {
super();
this.step = step;
if (!StringUtils.hasText(pattern)) {
this.pattern = "*";
}
else {
this.pattern = pattern;
}
this.next = next;
}
/**
* Public getter for the next step name.
* @return the next
*/
public String getNext() {
return next;
}
/**
* Public getter for the step.
* @return the step
*/
public Step getStep() {
return step;
}
/**
* Check if the provided {@link ExitStatus} matches the pattern, signalling
* that the next step should be executed.
*
* @param status the {@link ExitStatus} to compare
* @return true if the pattern matches this status
*/
public boolean matches(ExitStatus status) {
return matchStrings(pattern, status.getExitCode());
}
/**
* Check for a special next step signalling the end of a job.
*
* @return true if this transition goes nowhere (there is no next)
*/
public boolean isEnd() {
return next == null;
}
/**
* Lifted from AntPathMatcher in Spring Core. Tests whether or not a string
* matches against a pattern. The pattern may contain two special
* characters:<br>
* '*' means zero or more characters<br>
* '?' means one and only one character
* @param pattern pattern to match against. Must not be <code>null</code>.
* @param str string which must be matched against the pattern. Must not be
* <code>null</code>.
* @return <code>true</code> if the string matches against the pattern, or
* <code>false</code> otherwise.
*/
private boolean matchStrings(String pattern, String str) {
char[] patArr = pattern.toCharArray();
char[] strArr = str.toCharArray();
int patIdxStart = 0;
int patIdxEnd = patArr.length - 1;
int strIdxStart = 0;
int strIdxEnd = strArr.length - 1;
char ch;
boolean containsStar = pattern.contains("*");
if (!containsStar) {
// No '*'s, so we make a shortcut
if (patIdxEnd != strIdxEnd) {
return false; // Pattern and string do not have the same size
}
for (int i = 0; i <= patIdxEnd; i++) {
ch = patArr[i];
if (ch != '?') {
if (ch != strArr[i]) {
return false;// Character mismatch
}
}
}
return true; // String matches against pattern
}
if (patIdxEnd == 0) {
return true; // Pattern contains only '*', which matches anything
}
// Process characters before first star
while ((ch = patArr[patIdxStart]) != '*' && strIdxStart <= strIdxEnd) {
if (ch != '?') {
if (ch != strArr[strIdxStart]) {
return false;// Character mismatch
}
}
patIdxStart++;
strIdxStart++;
}
if (strIdxStart > strIdxEnd) {
// All characters in the string are used. Check if only '*'s are
// left in the pattern. If so, we succeeded. Otherwise failure.
for (int i = patIdxStart; i <= patIdxEnd; i++) {
if (patArr[i] != '*') {
return false;
}
}
return true;
}
// Process characters after last star
while ((ch = patArr[patIdxEnd]) != '*' && strIdxStart <= strIdxEnd) {
if (ch != '?') {
if (ch != strArr[strIdxEnd]) {
return false;// Character mismatch
}
}
patIdxEnd--;
strIdxEnd--;
}
if (strIdxStart > strIdxEnd) {
// All characters in the string are used. Check if only '*'s are
// left in the pattern. If so, we succeeded. Otherwise failure.
for (int i = patIdxStart; i <= patIdxEnd; i++) {
if (patArr[i] != '*') {
return false;
}
}
return true;
}
// process pattern between stars. padIdxStart and patIdxEnd point
// always to a '*'.
while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
int patIdxTmp = -1;
for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
if (patArr[i] == '*') {
patIdxTmp = i;
break;
}
}
if (patIdxTmp == patIdxStart + 1) {
// Two stars next to each other, skip the first one.
patIdxStart++;
continue;
}
// Find the pattern between padIdxStart & padIdxTmp in str between
// strIdxStart & strIdxEnd
int patLength = (patIdxTmp - patIdxStart - 1);
int strLength = (strIdxEnd - strIdxStart + 1);
int foundIdx = -1;
strLoop: for (int i = 0; i <= strLength - patLength; i++) {
for (int j = 0; j < patLength; j++) {
ch = patArr[patIdxStart + j + 1];
if (ch != '?') {
if (ch != strArr[strIdxStart + i + j]) {
continue strLoop;
}
}
}
foundIdx = strIdxStart + i;
break;
}
if (foundIdx == -1) {
return false;
}
patIdxStart = patIdxTmp;
strIdxStart = foundIdx + patLength;
}
// All characters in the string are used. Check if only '*'s are left
// in the pattern. If so, we succeeded. Otherwise failure.
for (int i = patIdxStart; i <= patIdxEnd; i++) {
if (patArr[i] != '*') {
return false;
}
}
return true;
}
/**
* Sorts by decreasing specificity of pattern, based on just counting
* wildcards (with * taking precedence over ?). If wodlcard counts are equal
* then falls back to alphabetic comparison. Hence * &gt; foo* &gt; ??? &gt;
* fo? > foo.
* @see Comparable#compareTo(Object)
*/
public int compareTo(StepTransition other) {
String value = other.pattern;
if (pattern.equals(value)) {
return 0;
}
int patternCount = StringUtils.countOccurrencesOf(pattern, "*");
int valueCount = StringUtils.countOccurrencesOf(value, "*");
if (patternCount > valueCount) {
return 1;
}
if (patternCount < valueCount) {
return -1;
}
patternCount = StringUtils.countOccurrencesOf(pattern, "?");
valueCount = StringUtils.countOccurrencesOf(value, "?");
if (patternCount > valueCount) {
return 1;
}
if (patternCount < valueCount) {
return -1;
}
return pattern.compareTo(value);
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return String.format("StepTransition: step=%s, pattern=%s, next=%s", step.getName(), pattern, next);
}
}

View File

@@ -19,6 +19,17 @@ import org.springframework.beans.factory.FactoryBean;
*/
public class MapJobRepositoryFactoryBean extends AbstractJobRepositoryFactoryBean {
/**
* Convenience method to clear all the map daos globally, removing all
* entities.
*/
public static void clear() {
MapJobInstanceDao.clear();
MapJobExecutionDao.clear();
MapStepExecutionDao.clear();
MapExecutionContextDao.clear();
}
@Override
protected JobExecutionDao createJobExecutionDao() throws Exception {
return new MapJobExecutionDao();

View File

@@ -0,0 +1,221 @@
/*
* 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.job;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Before;
import org.junit.Test;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionException;
import org.springframework.batch.core.JobInterruptedException;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.UnexpectedJobExecutionException;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean;
import org.springframework.batch.core.step.StepSupport;
import org.springframework.batch.repeat.ExitStatus;
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
/**
* @author Dave Syer
*
*/
public class ConditionalJobTests {
private ConditionalJob job = new ConditionalJob("job");
private JobExecution jobExecution;
@Before
public void setUp() throws Exception {
MapJobRepositoryFactoryBean.clear();
MapJobRepositoryFactoryBean factory = new MapJobRepositoryFactoryBean();
factory.setTransactionManager(new ResourcelessTransactionManager());
factory.afterPropertiesSet();
JobRepository jobRepository = (JobRepository) factory.getObject();
job.setJobRepository(jobRepository);
jobExecution = jobRepository.createJobExecution("job", new JobParameters());
}
@Test(expected = IllegalArgumentException.class)
public void testEmptySteps() throws Exception {
job.setStepTransitions(Collections.<StepTransition> emptySet());
job.afterPropertiesSet();
}
@Test(expected = IllegalArgumentException.class)
public void testNoNextStepSpecified() throws Exception {
job.setStepTransitions(Collections.singleton(new StepTransition(new StepSupport("step"), "*", "foo")));
job.afterPropertiesSet();
}
@Test(expected = IllegalArgumentException.class)
public void testNoStartStep() throws Exception {
job.setStepTransitions(Arrays.asList(new StepTransition(new StepSupport("step"), "FAILED", "step"),
new StepTransition(new StepSupport("step"), "*")));
job.afterPropertiesSet();
}
@Test(expected = IllegalArgumentException.class)
public void testNoEndStep() throws Exception {
job.setStepTransitions(Collections.singleton(new StepTransition(new StepSupport("step"), "FAILED", "step")));
job.setStartStepName("step");
job.afterPropertiesSet();
}
@Test(expected = IllegalArgumentException.class)
public void testMultipleStartSteps() throws Exception {
job.setStepTransitions(Arrays.asList(new StepTransition(new StubStep("step1"), "*"), new StepTransition(
new StubStep("step2"), "*")));
job.afterPropertiesSet();
}
@Test
public void testNoMatchForNextStep() throws Exception {
job.setStepTransitions(Arrays.asList(new StepTransition(new StubStep("step1"), "FOO", "step2"),
new StepTransition(new StubStep("step2"), "*")));
job.afterPropertiesSet();
try {
job.doExecute(jobExecution);
fail("Expected JobExecutionException");
}
catch (JobExecutionException e) {
// expected
String message = e.getMessage();
assertTrue("Wrong message: " + message, message.toLowerCase().contains("next step not found"));
}
}
@Test
public void testOneStep() throws Exception {
job.setStepTransitions(Arrays.asList(new StepTransition(new StubStep("step1"), "*")));
job.afterPropertiesSet();
StepExecution stepExecution = job.doExecute(jobExecution);
assertEquals(ExitStatus.FINISHED, stepExecution.getExitStatus());
assertEquals(1, jobExecution.getStepExecutions().size());
}
@Test
public void testExplicitStartStep() throws Exception {
job.setStepTransitions(Arrays.asList(new StepTransition(new StubStep("step"), "FAILED", "step"),
new StepTransition(new StubStep("step"), "*")));
job.setStartStepName("step");
job.afterPropertiesSet();
StepExecution stepExecution = job.doExecute(jobExecution);
assertEquals(ExitStatus.FINISHED, stepExecution.getExitStatus());
assertEquals(1, jobExecution.getStepExecutions().size());
}
@Test
public void testTwoSteps() throws Exception {
job.setStepTransitions(Arrays.asList(new StepTransition(new StubStep("step1"), "*", "step2"),
new StepTransition(new StubStep("step2"), "*")));
job.afterPropertiesSet();
StepExecution stepExecution = job.doExecute(jobExecution);
assertEquals(ExitStatus.FINISHED, stepExecution.getExitStatus());
assertEquals(2, jobExecution.getStepExecutions().size());
}
@Test
public void testFailedStep() throws Exception {
job.setStepTransitions(Arrays.asList(new StepTransition(new StubStep("step1") {
@Override
public void execute(StepExecution stepExecution) throws JobInterruptedException,
UnexpectedJobExecutionException {
stepExecution.setStatus(BatchStatus.FAILED);
stepExecution.setExitStatus(ExitStatus.FAILED);
}
}, "*", "step2"), new StepTransition(new StubStep("step2"), "*")));
job.afterPropertiesSet();
StepExecution stepExecution = job.doExecute(jobExecution);
assertEquals(ExitStatus.FINISHED, stepExecution.getExitStatus());
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
assertEquals(2, jobExecution.getStepExecutions().size());
}
@Test
public void testStoppingStep() throws Exception {
job.setStepTransitions(Arrays.asList(new StepTransition(new StubStep("step1") {
@Override
public void execute(StepExecution stepExecution) throws JobInterruptedException,
UnexpectedJobExecutionException {
stepExecution.setStatus(BatchStatus.STOPPED);
}
}, "*", "step2"),
new StepTransition(new StubStep("step2"), "*")));
job.afterPropertiesSet();
try {
job.doExecute(jobExecution);
fail("Expected JobInterruptedException");
} catch (JobInterruptedException e) {
// expected
}
assertEquals(1, jobExecution.getStepExecutions().size());
}
@Test
public void testBranching() throws Exception {
job.setStepTransitions(Arrays.asList(new StepTransition(new StubStep("step1"), "*", "step2"),
new StepTransition(new StubStep("step1"), "COMPLETED", "step3"), new StepTransition(new StubStep(
"step2"), "*"), new StepTransition(new StubStep("step3"), "*")));
job.afterPropertiesSet();
StepExecution stepExecution = job.doExecute(jobExecution);
assertEquals(ExitStatus.FINISHED, stepExecution.getExitStatus());
assertEquals(2, jobExecution.getStepExecutions().size());
assertEquals("step3", stepExecution.getStepName());
}
/**
* @author Dave Syer
*
*/
private static class StubStep extends StepSupport {
/**
*
*/
public StubStep() {
super();
}
/**
* @param string
*/
public StubStep(String string) {
super(string);
}
/**
* @see StepSupport#execute(StepExecution)
*/
@Override
public void execute(StepExecution stepExecution) throws JobInterruptedException,
UnexpectedJobExecutionException {
stepExecution.setStatus(BatchStatus.COMPLETED);
stepExecution.setExitStatus(ExitStatus.FINISHED);
}
}
}

View File

@@ -110,20 +110,18 @@ public class SimpleJobTests {
job = new SimpleJob();
job.setJobRepository(jobRepository);
step1 = new StubStep("TestStep1");
step1 = new StubStep("TestStep1", jobRepository);
step1.setCallback(new Runnable() {
public void run() {
list.add("default");
}
});
step2 = new StubStep("TestStep2");
step2 = new StubStep("TestStep2", jobRepository);
step2.setCallback(new Runnable() {
public void run() {
list.add("default");
}
});
step1.setJobRepository(jobRepository);
step2.setJobRepository(jobRepository);
List<Step> steps = new ArrayList<Step>();
steps.add(step1);
@@ -250,8 +248,8 @@ public class SimpleJobTests {
final JobInterruptedException exception = new JobInterruptedException("Interrupt!");
step1.setProcessException(exception);
job.execute(jobExecution);
assertEquals(1, jobExecution.getAllFailureExceptions().size());
assertEquals(exception, jobExecution.getAllFailureExceptions().get(0));
assertEquals(2, jobExecution.getAllFailureExceptions().size());
assertEquals(exception, jobExecution.getStepExecutions().iterator().next().getFailureExceptions().get(0));
assertEquals(0, list.size());
checkRepository(BatchStatus.STOPPED, ExitStatus.FAILED);
}
@@ -418,22 +416,24 @@ public class SimpleJobTests {
@Test
public void testInterruptJob() throws Exception {
step1 = new StubStep("interruptStep") {
step1 = new StubStep("interruptStep", jobRepository) {
public void execute(StepExecution stepExecution) throws JobInterruptedException,
UnexpectedJobExecutionException {
stepExecution.getJobExecution().stop();
super.execute(stepExecution);
}
};
job.setSteps(Arrays.asList(new Step[] { step1, step2 }));
job.execute(jobExecution);
Throwable expected = jobExecution.getAllFailureExceptions().get(0);
assertTrue(expected instanceof JobInterruptedException);
assertEquals("JobExecution interrupted.", expected.getMessage());
assertEquals(1, jobExecution.getAllFailureExceptions().size());
Throwable expected = jobExecution.getAllFailureExceptions().get(0);
assertTrue("Wrong exception "+expected, expected instanceof JobInterruptedException);
assertEquals("JobExecution interrupted.", expected.getMessage());
assertNull("Second step was not executed", step2.passedInStepContext);
assertNull("Second step was not supposed to be executed", step2.passedInStepContext);
}
/*
@@ -446,7 +446,7 @@ public class SimpleJobTests {
assertEquals(jobInstance.getId(), jobExecution.getJobId());
assertEquals(status, jobExecution.getStatus());
if (exitStatus != null) {
assertEquals(jobExecution.getExitStatus().getExitCode(), exitStatus.getExitCode());
assertEquals(exitStatus.getExitCode(), jobExecution.getExitStatus().getExitCode());
}
}
@@ -469,8 +469,9 @@ public class SimpleJobTests {
/**
* @param string
*/
public StubStep(String string) {
public StubStep(String string, JobRepository jobRepository) {
super(string);
this.jobRepository = jobRepository;
}
/**
@@ -529,14 +530,5 @@ public class SimpleJobTests {
}
/**
* Public setter for {@link JobRepository}.
*
* @param jobRepository is a mandatory dependence (no default).
*/
public void setJobRepository(JobRepository jobRepository) {
this.jobRepository = jobRepository;
}
}
}

View File

@@ -0,0 +1,129 @@
/*
* 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.job;
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.batch.core.step.StepSupport;
import org.springframework.batch.repeat.ExitStatus;
/**
* @author Dave Syer
*
*/
public class StepTransitionTests {
@Test
public void testIsEnd() {
StepTransition transition = new StepTransition(new StepSupport(), "");
assertTrue(transition.isEnd());
assertNull(transition.getNext());
}
@Test
public void testMatchesStar() {
StepTransition transition = new StepTransition(new StepSupport(), "*", "start");
assertTrue(transition.matches(ExitStatus.CONTINUABLE));
}
@Test
public void testMatchesNull() {
StepTransition transition = new StepTransition(new StepSupport(), null, "start");
assertTrue(transition.matches(ExitStatus.CONTINUABLE));
}
@Test
public void testMatchesEmpty() {
StepTransition transition = new StepTransition(new StepSupport(), "", "start");
assertTrue(transition.matches(ExitStatus.CONTINUABLE));
}
@Test
public void testMatchesExact() {
StepTransition transition = new StepTransition(new StepSupport(), "CONTINUABLE", "start");
assertTrue(transition.matches(ExitStatus.CONTINUABLE));
}
@Test
public void testMatchesWildcard() {
StepTransition transition = new StepTransition(new StepSupport(), "CONTIN*", "start" );
assertTrue(transition.matches(ExitStatus.CONTINUABLE));
}
@Test
public void testMatchesPlaceholder() {
StepTransition transition = new StepTransition(new StepSupport(), "CONTIN???LE", "start");
assertTrue(transition.matches(ExitStatus.CONTINUABLE));
}
@Test
public void testSimpleOrderingEqual() {
StepTransition transition = new StepTransition(new StepSupport(), "CONTIN???LE", "start");
assertEquals(0, transition.compareTo(transition));
}
@Test
public void testSimpleOrderingMoreGeneral() {
StepTransition transition = new StepTransition(new StepSupport(), "CONTIN???LE", "start");
StepTransition other = new StepTransition(new StepSupport(), "CONTINUABLE", "start");
assertEquals(1, transition.compareTo(other));
assertEquals(-1, other.compareTo(transition));
}
@Test
public void testSimpleOrderingMostGeneral() {
StepTransition transition = new StepTransition(new StepSupport(), "*", "start");
StepTransition other = new StepTransition(new StepSupport(), "CONTINUABLE", "start");
assertEquals(1, transition.compareTo(other));
assertEquals(-1, other.compareTo(transition));
}
@Test
public void testSubstringAndWildcard() {
StepTransition transition = new StepTransition(new StepSupport(), "CONTIN*", "start");
StepTransition other = new StepTransition(new StepSupport(), "CONTINUABLE", "start");
assertEquals(1, transition.compareTo(other));
assertEquals(-1, other.compareTo(transition));
}
@Test
public void testSimpleOrderingMostToNextGeneral() {
StepTransition transition = new StepTransition(new StepSupport(), "*", "start");
StepTransition other = new StepTransition(new StepSupport(), "C?", "start");
assertEquals(1, transition.compareTo(other));
assertEquals(-1, other.compareTo(transition));
}
@Test
public void testSimpleOrderingAdjacent() {
StepTransition transition = new StepTransition(new StepSupport(), "CON*", "start");
StepTransition other = new StepTransition(new StepSupport(), "CON?", "start");
assertEquals(1, transition.compareTo(other));
assertEquals(-1, other.compareTo(transition));
}
@Test
public void testToString() {
StepTransition transition = new StepTransition(new StepSupport(), "CONTIN???LE", "start");
String string = transition.toString();
assertTrue("Wrong string: " + string, string.contains("StepTransition"));
assertTrue("Wrong string: " + string, string.contains("start"));
assertTrue("Wrong string: " + string, string.contains("CONTIN???LE"));
assertTrue("Wrong string: " + string, string.contains("next="));
}
}