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:
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 * > foo* > ??? >
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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="));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user