diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersBuilder.java index b8f6e3003..b99152e28 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersBuilder.java @@ -16,10 +16,13 @@ package org.springframework.batch.core; +import org.springframework.batch.core.explore.JobExplorer; import org.springframework.util.Assert; import java.util.Date; +import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Properties; @@ -36,20 +39,21 @@ import java.util.Properties; * * @author Lucas Ward * @author Michael Minella + * @author Glenn Renfro * @since 1.0 * @see JobParameters * @see JobParameter */ public class JobParametersBuilder { - private final Map parameterMap; + private Map parameterMap; /** * Default constructor. Initializes the builder with empty parameters. */ public JobParametersBuilder() { - this.parameterMap = new LinkedHashMap(); + this.parameterMap = new LinkedHashMap<>(); } /** @@ -57,7 +61,7 @@ public class JobParametersBuilder { * @param jobParameters {@link JobParameters} instance used to initialize the builder. */ public JobParametersBuilder(JobParameters jobParameters) { - this.parameterMap = new LinkedHashMap(jobParameters.getParameters()); + this.parameterMap = new LinkedHashMap<>(jobParameters.getParameters()); } /** @@ -67,7 +71,7 @@ public class JobParametersBuilder { * @param properties the job parameters to be used */ public JobParametersBuilder(Properties properties) { - this.parameterMap = new LinkedHashMap(); + this.parameterMap = new LinkedHashMap<>(); if(properties != null) { for (Map.Entry curProperty : properties.entrySet()) { @@ -84,7 +88,7 @@ public class JobParametersBuilder { * @return a reference to this object. */ public JobParametersBuilder addString(String key, String parameter) { - parameterMap.put(key, new JobParameter(parameter, true)); + this.parameterMap.put(key, new JobParameter(parameter, true)); return this; } @@ -97,7 +101,7 @@ public class JobParametersBuilder { * @return a reference to this object. */ public JobParametersBuilder addString(String key, String parameter, boolean identifying) { - parameterMap.put(key, new JobParameter(parameter, identifying)); + this.parameterMap.put(key, new JobParameter(parameter, identifying)); return this; } @@ -109,7 +113,7 @@ public class JobParametersBuilder { * @return a reference to this object. */ public JobParametersBuilder addDate(String key, Date parameter) { - parameterMap.put(key, new JobParameter(parameter, true)); + this.parameterMap.put(key, new JobParameter(parameter, true)); return this; } @@ -122,7 +126,7 @@ public class JobParametersBuilder { * @return a reference to this object. */ public JobParametersBuilder addDate(String key, Date parameter, boolean identifying) { - parameterMap.put(key, new JobParameter(parameter, identifying)); + this.parameterMap.put(key, new JobParameter(parameter, identifying)); return this; } @@ -134,7 +138,7 @@ public class JobParametersBuilder { * @return a reference to this object. */ public JobParametersBuilder addLong(String key, Long parameter) { - parameterMap.put(key, new JobParameter(parameter, true)); + this.parameterMap.put(key, new JobParameter(parameter, true)); return this; } @@ -147,7 +151,7 @@ public class JobParametersBuilder { * @return a reference to this object. */ public JobParametersBuilder addLong(String key, Long parameter, boolean identifying) { - parameterMap.put(key, new JobParameter(parameter, identifying)); + this.parameterMap.put(key, new JobParameter(parameter, identifying)); return this; } @@ -159,7 +163,7 @@ public class JobParametersBuilder { * @return a reference to this object. */ public JobParametersBuilder addDouble(String key, Double parameter) { - parameterMap.put(key, new JobParameter(parameter, true)); + this.parameterMap.put(key, new JobParameter(parameter, true)); return this; } @@ -172,7 +176,7 @@ public class JobParametersBuilder { * @return a reference to this object. */ public JobParametersBuilder addDouble(String key, Double parameter, boolean identifying) { - parameterMap.put(key, new JobParameter(parameter, identifying)); + this.parameterMap.put(key, new JobParameter(parameter, identifying)); return this; } @@ -183,7 +187,7 @@ public class JobParametersBuilder { * @return a valid {@link JobParameters} object. */ public JobParameters toJobParameters() { - return new JobParameters(parameterMap); + return new JobParameters(this.parameterMap); } /** @@ -195,7 +199,75 @@ public class JobParametersBuilder { */ public JobParametersBuilder addParameter(String key, JobParameter jobParameter) { Assert.notNull(jobParameter, "JobParameter must not be null"); - parameterMap.put(key, jobParameter); + this.parameterMap.put(key, jobParameter); return this; } + + /** + * Initializes the {@link JobParameters} based on the state of the {@link Job}. This + * should be called after all parameters have been entered into the builder. + * + * @param job the job for which the {@link JobParameters} are being constructed. + * @param jobExplorer instance to a {@link JobExplorer} + * @return a reference to this object. + * + * @since 4.0 + */ + public JobParametersBuilder getNextJobParameters(Job job, JobExplorer jobExplorer) { + String name = job.getName(); + JobParameters nextParameters = new JobParameters(); + List lastInstances = jobExplorer.getJobInstances(name, 0, 1); + JobParametersIncrementer incrementer = job.getJobParametersIncrementer(); + if (lastInstances.isEmpty()) { + // Start from a completely clean sheet + if (incrementer != null) { + nextParameters = incrementer.getNext(new JobParameters()); + } + } + else { + List previousExecutions = jobExplorer + .getJobExecutions(lastInstances.get(0)); + JobExecution previousExecution = previousExecutions.get(0); + if (previousExecution == null) { + // Normally this will not happen - an instance exists with no executions + if (incrementer != null) { + nextParameters = incrementer.getNext(new JobParameters()); + } + } + else if (isStoppedOrFailed(previousExecution) && job.isRestartable()) { + // Retry a failed or stopped execution + nextParameters = previousExecution.getJobParameters(); + // Non-identifying additional parameters can be removed to a retry + removeNonIdentifying(this.parameterMap); + } + else if (incrementer != null) { + // New instance so increment the parameters if we can + nextParameters = incrementer.getNext(previousExecution.getJobParameters()); + } + } + this.parameterMap = merge(nextParameters, this.parameterMap); + return this; + } + + private void removeNonIdentifying(Map parameters) { + HashMap copy = new HashMap<>(parameters); + for (Map.Entry parameter : copy.entrySet()) { + if (!parameter.getValue().isIdentifying()) { + parameters.remove(parameter.getKey()); + } + } + } + + private LinkedHashMap merge(JobParameters parameters, + Map additionals) { + Map merged = new HashMap<>(); + merged.putAll(parameters.getParameters()); + merged.putAll(additionals); + return new LinkedHashMap<>(merged); + } + + private boolean isStoppedOrFailed(JobExecution execution) { + BatchStatus status = execution.getStatus(); + return (status == BatchStatus.STOPPED || status == BatchStatus.FAILED); + } } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/JobParametersBuilderTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/JobParametersBuilderTests.java index 881884489..663311c08 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/JobParametersBuilderTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/JobParametersBuilderTests.java @@ -15,76 +15,114 @@ */ package org.springframework.batch.core; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; - +import java.util.ArrayList; import java.util.Date; import java.util.Iterator; +import java.util.List; import java.util.Map; +import java.util.Properties; +import org.junit.Before; import org.junit.Test; +import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.job.SimpleJob; +import org.springframework.batch.core.launch.support.RunIdIncrementer; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + /** * @author Lucas Ward * @author Michael Minella + * @author Glenn Renfro * */ public class JobParametersBuilderTests { - JobParametersBuilder parametersBuilder = new JobParametersBuilder(); + private JobParametersBuilder parametersBuilder; - Date date = new Date(System.currentTimeMillis()); + private SimpleJob job; + + private JobExplorer jobExplorer; + + private List jobInstanceList; + + private List jobExecutionList; + + private Date date = new Date(System.currentTimeMillis()); + + @Before + public void initialize() { + this.parametersBuilder = new JobParametersBuilder(); + this.job = new SimpleJob("simpleJob"); + this.jobExplorer = mock(JobExplorer.class); + this.jobInstanceList = new ArrayList<>(1); + this.jobExecutionList = new ArrayList<>(1); + } @Test public void testNonIdentifyingParameters() { - parametersBuilder.addDate("SCHEDULE_DATE", date, false); - parametersBuilder.addLong("LONG", new Long(1), false); - parametersBuilder.addString("STRING", "string value", false); - JobParameters parameters = parametersBuilder.toJobParameters(); + this.parametersBuilder.addDate("SCHEDULE_DATE", date, false); + this.parametersBuilder.addLong("LONG", new Long(1), false); + this.parametersBuilder.addString("STRING", "string value", false); + this.parametersBuilder.addDouble("DOUBLE", new Double(1), false); + + JobParameters parameters = this.parametersBuilder.toJobParameters(); assertEquals(date, parameters.getDate("SCHEDULE_DATE")); assertEquals(1L, parameters.getLong("LONG").longValue()); assertEquals("string value", parameters.getString("STRING")); + assertEquals(1, parameters.getDouble("DOUBLE").doubleValue(), 1e-15); assertFalse(parameters.getParameters().get("SCHEDULE_DATE").isIdentifying()); assertFalse(parameters.getParameters().get("LONG").isIdentifying()); assertFalse(parameters.getParameters().get("STRING").isIdentifying()); + assertFalse(parameters.getParameters().get("DOUBLE").isIdentifying()); } @Test - public void testToJobRuntimeParamters(){ - parametersBuilder.addDate("SCHEDULE_DATE", date); - parametersBuilder.addLong("LONG", new Long(1)); - parametersBuilder.addString("STRING", "string value"); - JobParameters parameters = parametersBuilder.toJobParameters(); + public void testToJobRuntimeParameters(){ + this.parametersBuilder.addDate("SCHEDULE_DATE", date); + this.parametersBuilder.addLong("LONG", new Long(1)); + this.parametersBuilder.addString("STRING", "string value"); + this.parametersBuilder.addDouble("DOUBLE", new Double(1)); + JobParameters parameters = this.parametersBuilder.toJobParameters(); assertEquals(date, parameters.getDate("SCHEDULE_DATE")); assertEquals(1L, parameters.getLong("LONG").longValue()); + assertEquals(1, parameters.getDouble("DOUBLE").doubleValue(), 1e-15); assertEquals("string value", parameters.getString("STRING")); } @Test - public void testNullRuntimeParamters(){ - parametersBuilder.addDate("SCHEDULE_DATE", null); - parametersBuilder.addLong("LONG", null); - parametersBuilder.addString("STRING", null); - JobParameters parameters = parametersBuilder.toJobParameters(); + public void testNullRuntimeParameters(){ + this.parametersBuilder.addDate("SCHEDULE_DATE", null); + this.parametersBuilder.addLong("LONG", null); + this.parametersBuilder.addString("STRING", null); + this.parametersBuilder.addDouble("DOUBLE", null); + + JobParameters parameters = this.parametersBuilder.toJobParameters(); assertEquals(null, parameters.getDate("SCHEDULE_DATE")); assertEquals(0L, parameters.getLong("LONG").longValue()); assertEquals(null, parameters.getString("STRING")); + assertEquals(0, parameters.getLong("DOUBLE").doubleValue(), 1e-15); } @Test public void testCopy(){ - parametersBuilder.addString("STRING", "string value"); - parametersBuilder = new JobParametersBuilder(parametersBuilder.toJobParameters()); - Iterator parameters = parametersBuilder.toJobParameters().getParameters().keySet().iterator(); + this.parametersBuilder.addString("STRING", "string value"); + this.parametersBuilder = new JobParametersBuilder(this.parametersBuilder.toJobParameters()); + Iterator parameters = this.parametersBuilder.toJobParameters().getParameters().keySet().iterator(); assertEquals("STRING", parameters.next()); } @Test public void testOrderedTypes(){ - parametersBuilder.addDate("SCHEDULE_DATE", date); - parametersBuilder.addLong("LONG", new Long(1)); - parametersBuilder.addString("STRING", "string value"); - Iterator parameters = parametersBuilder.toJobParameters().getParameters().keySet().iterator(); + this.parametersBuilder.addDate("SCHEDULE_DATE", date); + this.parametersBuilder.addLong("LONG", new Long(1)); + this.parametersBuilder.addString("STRING", "string value"); + Iterator parameters = this.parametersBuilder.toJobParameters().getParameters().keySet().iterator(); assertEquals("SCHEDULE_DATE", parameters.next()); assertEquals("LONG", parameters.next()); assertEquals("STRING", parameters.next()); @@ -92,10 +130,10 @@ public class JobParametersBuilderTests { @Test public void testOrderedStrings(){ - parametersBuilder.addString("foo", "value foo"); - parametersBuilder.addString("bar", "value bar"); - parametersBuilder.addString("spam", "value spam"); - Iterator parameters = parametersBuilder.toJobParameters().getParameters().keySet().iterator(); + this.parametersBuilder.addString("foo", "value foo"); + this.parametersBuilder.addString("bar", "value bar"); + this.parametersBuilder.addString("spam", "value spam"); + Iterator parameters = this.parametersBuilder.toJobParameters().getParameters().keySet().iterator(); assertEquals("foo", parameters.next()); assertEquals("bar", parameters.next()); assertEquals("spam", parameters.next()); @@ -104,9 +142,105 @@ public class JobParametersBuilderTests { @Test public void testAddJobParameter(){ JobParameter jobParameter = new JobParameter("bar"); - parametersBuilder.addParameter("foo", jobParameter); - Map parameters = parametersBuilder.toJobParameters().getParameters(); + this.parametersBuilder.addParameter("foo", jobParameter); + Map parameters = this.parametersBuilder.toJobParameters().getParameters(); assertEquals(1, parameters.size()); assertEquals("bar", parameters.get("foo").getValue()); } + + @Test + public void testPropreties() { + Properties props = new Properties(); + props.setProperty("SCHEDULE_DATE", "A DATE"); + props.setProperty("LONG", "1"); + props.setProperty("STRING", "string value"); + this.parametersBuilder = new JobParametersBuilder(props); + JobParameters parameters = this.parametersBuilder.toJobParameters(); + assertEquals("A DATE", parameters.getString("SCHEDULE_DATE")); + assertEquals("1", parameters.getString("LONG")); + assertEquals("string value", parameters.getString("STRING")); + assertFalse(parameters.getParameters().get("SCHEDULE_DATE").isIdentifying()); + assertFalse(parameters.getParameters().get("LONG").isIdentifying()); + assertFalse(parameters.getParameters().get("STRING").isIdentifying()); + } + + + @Test + public void testGetNextJobParametersFirstRun(){ + job.setJobParametersIncrementer(new RunIdIncrementer()); + initializeForNextJobParameters(); + this.parametersBuilder.getNextJobParameters(this.job, this.jobExplorer); + defaultNextJobParametersVerify(this.parametersBuilder.toJobParameters(), 4); + } + + @Test + public void testGetNextJobParametersNoIncrementer(){ + initializeForNextJobParameters(); + this.parametersBuilder.getNextJobParameters(this.job, this.jobExplorer); + baseJobParametersVerify(this.parametersBuilder.toJobParameters(), 3); + } + + @Test + public void testGetNextJobParameters(){ + this.job.setJobParametersIncrementer(new RunIdIncrementer()); + this.jobInstanceList.add(new JobInstance(1L, "simpleJobInstance")); + this.jobExecutionList.add(getJobExecution(this.jobInstanceList.get(0), null)); + when(this.jobExplorer.getJobInstances("simpleJob",0,1)).thenReturn(this.jobInstanceList); + when(this.jobExplorer.getJobExecutions(any())).thenReturn(this.jobExecutionList); + initializeForNextJobParameters(); + this.parametersBuilder.getNextJobParameters(this.job, this.jobExplorer); + defaultNextJobParametersVerify(this.parametersBuilder.toJobParameters(), 4); + } + + @Test + public void testGetNextJobParametersRestartable(){ + this.job.setRestartable(true); + this.job.setJobParametersIncrementer(new RunIdIncrementer()); + this.jobInstanceList.add(new JobInstance(1L, "simpleJobInstance")); + this.jobExecutionList.add(getJobExecution(this.jobInstanceList.get(0), BatchStatus.FAILED)); + when(this.jobExplorer.getJobInstances("simpleJob",0,1)).thenReturn(this.jobInstanceList); + when(this.jobExplorer.getJobExecutions(any())).thenReturn(this.jobExecutionList); + initializeForNextJobParameters(); + this.parametersBuilder.addLong("NON_IDENTIFYING_LONG", new Long(1), false); + this.parametersBuilder.getNextJobParameters(this.job, this.jobExplorer); + baseJobParametersVerify(this.parametersBuilder.toJobParameters(), 3); + } + + @Test + public void testGetNextJobParametersNoPreviousExecution(){ + this.job.setJobParametersIncrementer(new RunIdIncrementer()); + this.jobInstanceList.add(new JobInstance(1L, "simpleJobInstance")); + this.jobExecutionList.add(null); + when(this.jobExplorer.getJobInstances("simpleJob",0,1)).thenReturn(this.jobInstanceList); + when(this.jobExplorer.getJobExecutions(any())).thenReturn(this.jobExecutionList); + initializeForNextJobParameters(); + this.parametersBuilder.getNextJobParameters(this.job, this.jobExplorer); + baseJobParametersVerify(this.parametersBuilder.toJobParameters(), 4); + } + + private void initializeForNextJobParameters() { + this.parametersBuilder.addDate("SCHEDULE_DATE", date); + this.parametersBuilder.addLong("LONG", new Long(1)); + this.parametersBuilder.addString("STRING", "string value"); + } + + private void defaultNextJobParametersVerify(JobParameters parameters, int paramCount) { + baseJobParametersVerify(parameters, paramCount); + assertEquals("1", parameters.getString("run.id")); + } + private void baseJobParametersVerify(JobParameters parameters, int paramCount) { + assertEquals(date, parameters.getDate("SCHEDULE_DATE")); + assertEquals(1L, parameters.getLong("LONG").longValue()); + assertEquals("string value", parameters.getString("STRING")); + assertEquals(paramCount, parameters.getParameters().size()); + } + + private JobExecution getJobExecution(JobInstance jobInstance, BatchStatus batchStatus) { + JobExecution jobExecution = new JobExecution(jobInstance, 1L, null, "TestConfig"); + if(batchStatus != null) { + jobExecution.setStatus(batchStatus); + } + return jobExecution; + + } }