[BAEL-3646] Addressing PR comments

This commit is contained in:
mike b 2020-03-17 10:06:34 -04:00
parent 2c0cf3e2e0
commit e58683f48b
16 changed files with 208 additions and 121 deletions

Binary file not shown.

View File

@ -1,11 +1,14 @@
package org.baeldung.conditionalflow; package org.baeldung.conditionalflow;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner; import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication @SpringBootApplication
public class ConditionalFlowApplication implements CommandLineRunner { public class ConditionalFlowApplication implements CommandLineRunner {
private static Logger logger = LoggerFactory.getLogger(ConditionalFlowApplication.class);
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(ConditionalFlowApplication.class, args); SpringApplication.run(ConditionalFlowApplication.class, args);
@ -13,6 +16,6 @@ public class ConditionalFlowApplication implements CommandLineRunner {
@Override @Override
public void run(String... args) throws Exception { public void run(String... args) throws Exception {
System.out.println("Running and exiting"); logger.info("Running conditional flow application...");
} }
} }

View File

@ -1,6 +1,5 @@
package org.baeldung.conditionalflow; package org.baeldung.conditionalflow;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.StepExecution; import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.job.flow.FlowExecutionStatus; import org.springframework.batch.core.job.flow.FlowExecutionStatus;
@ -8,12 +7,23 @@ import org.springframework.batch.core.job.flow.JobExecutionDecider;
public class NumberInfoDecider implements JobExecutionDecider { public class NumberInfoDecider implements JobExecutionDecider {
public static final String NOTIFY = "NOTIFY";
public static final String QUIET = "QUIET";
/**
* Method that determines notification status of job
* @return true if notifications should be sent.
*/
private boolean shouldNotify() {
return true;
}
@Override @Override
public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) { public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) {
if(jobExecution.getExitStatus().equals("UNKNOWN")) { if (shouldNotify()) {
return new FlowExecutionStatus("NOTIFY"); return new FlowExecutionStatus(NOTIFY);
} else { } else {
return null; return new FlowExecutionStatus(QUIET);
} }
} }
} }

View File

@ -11,6 +11,9 @@ import org.springframework.batch.core.configuration.annotation.StepBuilderFactor
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import static org.baeldung.conditionalflow.NumberInfoDecider.NOTIFY;
@Configuration @Configuration
@EnableBatchProcessing @EnableBatchProcessing
@ -19,67 +22,70 @@ public class NumberInfoConfig {
@Bean @Bean
@Qualifier("NotificationStep") @Qualifier("NotificationStep")
public Step notificationStep(StepBuilderFactory sbf) { public Step notificationStep(StepBuilderFactory sbf) {
return sbf.get("Billing step").tasklet(new NotifierTasklet()).build(); return sbf.get("Notify step")
.tasklet(new NotifierTasklet())
.build();
} }
public Step numberGeneratorStep(StepBuilderFactory sbf, int[] values, String prepend) { public Step numberGeneratorStep(StepBuilderFactory sbf, int[] values, String prepend) {
return sbf.get("Number generator") return sbf.get("Number generator")
.<NumberInfo, Integer>chunk(1) .<NumberInfo, Integer> chunk(1)
.reader(new NumberInfoGenerator(values)) .reader(new NumberInfoGenerator(values))
.processor(new NumberInfoClassifier()) .processor(new NumberInfoClassifier())
.writer(new PrependingStdoutWriter<>(prepend)) .writer(new PrependingStdoutWriter<>(prepend))
.build(); .build();
} }
public Step numberGeneratorStepDecider(StepBuilderFactory sbf, int[] values, String prepend) { public Step numberGeneratorStepDecider(StepBuilderFactory sbf, int[] values, String prepend) {
return sbf.get("Number generator") return sbf.get("Number generator decider")
.<NumberInfo, Integer>chunk(1) .<NumberInfo, Integer> chunk(1)
.reader(new NumberInfoGenerator(values)) .reader(new NumberInfoGenerator(values))
.processor(new NumberInfoClassifierWithDecider()) .processor(new NumberInfoClassifierWithDecider())
.writer(new PrependingStdoutWriter<>(prepend)) .writer(new PrependingStdoutWriter<>(prepend))
.build(); .build();
} }
@Bean @Bean
public Job numberGeneratorNonNotifierJob(JobBuilderFactory jobBuilderFactory, @Qualifier("first_job")
StepBuilderFactory stepBuilderFactory, public Job numberGeneratorNonNotifierJob(JobBuilderFactory jobBuilderFactory, StepBuilderFactory stepBuilderFactory, @Qualifier("NotificationStep") Step notificationStep) {
@Qualifier("NotificationStep") Step notificationStep int[] nonNotifierData = { -1, -2, -3 };
) {
int[] nonNotifierData = {-1, -2, -3};
Step step = numberGeneratorStep(stepBuilderFactory, nonNotifierData, "First Dataset Processor"); Step step = numberGeneratorStep(stepBuilderFactory, nonNotifierData, "First Dataset Processor");
return jobBuilderFactory.get("Number generator - first dataset") return jobBuilderFactory.get("Number generator - first dataset")
.start(step) .start(step)
.on("NOTIFY").to(notificationStep) .on(NOTIFY)
.from(step).on("*").stop() .to(notificationStep)
.end() .from(step)
.build(); .on("*")
.stop()
.end()
.build();
} }
@Bean @Bean
public Job numberGeneratorNotifierJob(JobBuilderFactory jobBuilderFactory, @Qualifier("second_job")
StepBuilderFactory stepBuilderFactory, public Job numberGeneratorNotifierJob(JobBuilderFactory jobBuilderFactory, StepBuilderFactory stepBuilderFactory, @Qualifier("NotificationStep") Step notificationStep) {
@Qualifier("NotificationStep") Step notificationStep int[] billableData = { 11, -2, -3 };
) {
int[] billableData = {11, -2, -3};
Step dataProviderStep = numberGeneratorStep(stepBuilderFactory, billableData, "Second Dataset Processor"); Step dataProviderStep = numberGeneratorStep(stepBuilderFactory, billableData, "Second Dataset Processor");
return jobBuilderFactory.get("Number generator - second dataset") return jobBuilderFactory.get("Number generator - second dataset")
.start(dataProviderStep) .start(dataProviderStep)
.on("NOTIFY").to(notificationStep) .on(NOTIFY)
.end() .to(notificationStep)
.build(); .end()
.build();
} }
@Bean @Bean
public Job numberGeneratorNotifierJobWithDecider(JobBuilderFactory jobBuilderFactory, @Qualifier("third_job")
StepBuilderFactory stepBuilderFactory, @Primary
@Qualifier("NotificationStep") Step notificationStep public Job numberGeneratorNotifierJobWithDecider(JobBuilderFactory jobBuilderFactory, StepBuilderFactory stepBuilderFactory, @Qualifier("NotificationStep") Step notificationStep) {
) { int[] billableData = { 11, -2, -3 };
int[] billableData = {11, -2, -3};
Step dataProviderStep = numberGeneratorStepDecider(stepBuilderFactory, billableData, "Third Dataset Processor"); Step dataProviderStep = numberGeneratorStepDecider(stepBuilderFactory, billableData, "Third Dataset Processor");
return jobBuilderFactory.get("Number generator - third dataset") return jobBuilderFactory.get("Number generator - third dataset")
.start(dataProviderStep) .start(dataProviderStep)
.next(new NumberInfoDecider()).on("NOTIFY").to(notificationStep) .next(new NumberInfoDecider())
.end() .on(NOTIFY)
.build(); .to(notificationStep)
.end()
.build();
} }
} }

View File

@ -5,14 +5,14 @@ import java.util.Objects;
public class NumberInfo { public class NumberInfo {
private int number; private int number;
public static NumberInfo from(int number){
return new NumberInfo(number);
}
public NumberInfo(int number) { public NumberInfo(int number) {
this.number = number; this.number = number;
} }
public static NumberInfo from(int number) {
return new NumberInfo(number);
}
public boolean isPositive() { public boolean isPositive() {
return number > 0; return number > 0;
} }
@ -27,8 +27,10 @@ public class NumberInfo {
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o)
if (o == null || getClass() != o.getClass()) return false; return true;
if (o == null || getClass() != o.getClass())
return false;
NumberInfo that = (NumberInfo) o; NumberInfo that = (NumberInfo) o;
return number == that.number; return number == that.number;
} }
@ -40,8 +42,6 @@ public class NumberInfo {
@Override @Override
public String toString() { public String toString() {
return "NumberInfo{" + return "NumberInfo{" + "number=" + number + '}';
"number=" + number +
'}';
} }
} }

View File

@ -8,7 +8,8 @@ import org.springframework.batch.repeat.RepeatStatus;
public class NotifierTasklet implements Tasklet { public class NotifierTasklet implements Tasklet {
@Override @Override
public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception { public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
System.err.println("[" + chunkContext.getStepContext().getJobName() + "] contains interesting data!!"); System.err.println("[" + chunkContext.getStepContext()
.getJobName() + "] contains interesting data!!");
return RepeatStatus.FINISHED; return RepeatStatus.FINISHED;
} }
} }

View File

@ -7,20 +7,23 @@ import org.springframework.batch.core.annotation.BeforeStep;
import org.springframework.batch.core.listener.ItemListenerSupport; import org.springframework.batch.core.listener.ItemListenerSupport;
import org.springframework.batch.item.ItemProcessor; import org.springframework.batch.item.ItemProcessor;
public class NumberInfoClassifier extends ItemListenerSupport<NumberInfo, Integer> import static org.baeldung.conditionalflow.NumberInfoDecider.NOTIFY;
implements ItemProcessor<NumberInfo, Integer> { import static org.baeldung.conditionalflow.NumberInfoDecider.QUIET;
public class NumberInfoClassifier extends ItemListenerSupport<NumberInfo, Integer> implements ItemProcessor<NumberInfo, Integer> {
private StepExecution stepExecution; private StepExecution stepExecution;
@BeforeStep @BeforeStep
public void beforeStep(StepExecution stepExecution) { public void beforeStep(StepExecution stepExecution) {
this.stepExecution = stepExecution; this.stepExecution = stepExecution;
this.stepExecution.setExitStatus(new ExitStatus(QUIET));
} }
@Override @Override
public void afterProcess(NumberInfo item, Integer result) { public void afterProcess(NumberInfo item, Integer result) {
super.afterProcess(item, result); super.afterProcess(item, result);
if (item.isPositive()) { if (item.isPositive()) {
stepExecution.setExitStatus(new ExitStatus("NOTIFY")); stepExecution.setExitStatus(new ExitStatus(NOTIFY));
} }
} }

View File

@ -1,15 +1,17 @@
package org.baeldung.conditionalflow.step; package org.baeldung.conditionalflow.step;
import org.baeldung.conditionalflow.model.NumberInfo; import org.baeldung.conditionalflow.model.NumberInfo;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.StepExecution; import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.annotation.BeforeStep;
import org.springframework.batch.core.listener.ItemListenerSupport;
import org.springframework.batch.item.ItemProcessor; import org.springframework.batch.item.ItemProcessor;
public class NumberInfoClassifierWithDecider public class NumberInfoClassifierWithDecider extends ItemListenerSupport<NumberInfo, Integer> implements ItemProcessor<NumberInfo, Integer> {
implements ItemProcessor<NumberInfo, Integer> {
private StepExecution stepExecution;
@Override @Override
public Integer process(NumberInfo numberInfo) throws Exception { public Integer process(NumberInfo numberInfo) throws Exception {
return Integer.valueOf(numberInfo.getNumber()); return Integer.valueOf(numberInfo.getNumber());
} }
} }

View File

@ -7,14 +7,14 @@ public class NumberInfoGenerator implements ItemReader<NumberInfo> {
private int[] values; private int[] values;
private int counter; private int counter;
public NumberInfoGenerator(int[] values){ public NumberInfoGenerator(int[] values) {
this.values = values; this.values = values;
counter = 0; counter = 0;
} }
@Override @Override
public NumberInfo read() { public NumberInfo read() {
if(counter == values.length){ if (counter == values.length) {
return null; return null;
} else { } else {
return new NumberInfo(values[counter++]); return new NumberInfo(values[counter++]);

View File

@ -1,17 +0,0 @@
package org.baeldung.conditionalflow.step;
import org.baeldung.conditionalflow.model.NumberInfo;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.annotation.BeforeStep;
import org.springframework.batch.core.listener.ItemListenerSupport;
import org.springframework.batch.item.ItemProcessor;
public class NumberInfoProcessor implements ItemProcessor<NumberInfo, Integer> {
private StepExecution stepExecution;
@Override
public Integer process(NumberInfo numberInfo) throws Exception {
return Integer.valueOf(numberInfo.getNumber());
}
}

View File

@ -1,18 +1,15 @@
package org.baeldung.conditionalflow.step; package org.baeldung.conditionalflow.step;
import org.springframework.batch.item.ItemWriter;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.List; import java.util.List;
import org.springframework.batch.item.ItemWriter;
public class PrependingStdoutWriter<T> implements ItemWriter<T> { public class PrependingStdoutWriter<T> implements ItemWriter<T> {
private String prependText; private String prependText;
private OutputStream writeTo; private OutputStream writeTo;
private PrependingStdoutWriter() { public PrependingStdoutWriter(String prependText, OutputStream os) {
}
public PrependingStdoutWriter(String prependText, OutputStream os){
this.prependText = prependText; this.prependText = prependText;
this.writeTo = os; this.writeTo = os;
} }

View File

@ -0,0 +1,56 @@
package org.baeldung.conditionalflow;
import org.baeldung.conditionalflow.config.NumberInfoConfig;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.test.AssertFile;
import org.springframework.batch.test.JobLauncherTestUtils;
import org.springframework.batch.test.JobRepositoryTestUtils;
import org.springframework.batch.test.context.SpringBatchTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import java.util.Collection;
import java.util.Iterator;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@RunWith(SpringRunner.class)
@SpringBatchTest
@EnableAutoConfiguration
@ContextConfiguration(classes = { NumberInfoConfig.class })
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class })
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public class DeciderJobIntegrationTest {
@Autowired
private JobLauncherTestUtils jobLauncherTestUtils;
@Test
public void whenNumberGeneratorDecider_thenNotifyStepRuns() throws Exception {
JobExecution jobExecution = jobLauncherTestUtils.launchJob();
Collection<StepExecution> actualStepExecutions = jobExecution.getStepExecutions();
ExitStatus actualJobExitStatus = jobExecution.getExitStatus();
assertEquals(actualJobExitStatus.getExitCode().toString(), "COMPLETED");
assertEquals(actualStepExecutions.size(), 2);
boolean notifyStepDidRun = false;
Iterator<StepExecution> iterator = actualStepExecutions.iterator();
while(iterator.hasNext() && !notifyStepDidRun){
if(iterator.next().getStepName().equals("Notify step")){
notifyStepDidRun = true;
}
}
assertTrue(notifyStepDidRun);
}
}

View File

@ -1,44 +1,71 @@
package org.baeldung.conditionalflow.model; package org.baeldung.conditionalflow.model;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.junit.jupiter.api.Assertions.*;
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
class NumberInfoUnitTest { class NumberInfoUnitTest {
@Test @Test
void isPositive() { void whenPositive_isPositive() {
assertTrue(NumberInfo.from(1).isPositive()); assertTrue(NumberInfo.from(1)
assertTrue(NumberInfo.from(11).isPositive()); .isPositive());
assertFalse(NumberInfo.from(0).isPositive()); assertTrue(NumberInfo.from(11)
assertFalse(NumberInfo.from(-1).isPositive()); .isPositive());
assertFalse(NumberInfo.from(-10).isPositive()); assertFalse(NumberInfo.from(0)
.isPositive());
} }
@Test @Test
void isEven() { void whenNegative_isPositive_isFalse() {
assertTrue(NumberInfo.from(0).isEven()); assertFalse(NumberInfo.from(-1)
assertTrue(NumberInfo.from(-2).isEven()); .isPositive());
assertTrue(NumberInfo.from(2).isEven()); assertFalse(NumberInfo.from(-10)
assertTrue(NumberInfo.from(-22).isEven()); .isPositive());
assertTrue(NumberInfo.from(22).isEven());
assertFalse(NumberInfo.from(1).isEven());
assertFalse(NumberInfo.from(-1).isEven());
assertFalse(NumberInfo.from(13).isEven());
assertFalse(NumberInfo.from(-13).isEven());
assertFalse(NumberInfo.from(31).isEven());
assertFalse(NumberInfo.from(-51).isEven());
} }
@Test @Test
void getNumber() { void whenEven_isEven() {
for(int i = -100 ; i < 100 ; i++){ assertTrue(NumberInfo.from(0)
assertEquals(i, NumberInfo.from(i).getNumber()); .isEven());
assertTrue(NumberInfo.from(-2)
.isEven());
assertTrue(NumberInfo.from(2)
.isEven());
assertTrue(NumberInfo.from(-22)
.isEven());
assertTrue(NumberInfo.from(22)
.isEven());
}
@Test
void whenOdd_isEven_isFalse() {
assertFalse(NumberInfo.from(1)
.isEven());
assertFalse(NumberInfo.from(-1)
.isEven());
assertFalse(NumberInfo.from(13)
.isEven());
assertFalse(NumberInfo.from(-13)
.isEven());
assertFalse(NumberInfo.from(31)
.isEven());
assertFalse(NumberInfo.from(-51)
.isEven());
}
@Test
void testStatic_fromMethod_equals_getNumber() {
for (int i = -100; i < 100; i++) {
assertEquals(i, NumberInfo.from(i)
.getNumber());
} }
} }
} }

View File

@ -1,15 +1,14 @@
package org.baeldung.conditionalflow.step; package org.baeldung.conditionalflow.step;
import org.baeldung.conditionalflow.model.NumberInfo; import static org.junit.jupiter.api.Assertions.assertEquals;
import org.baeldung.conditionalflow.step.NumberInfoClassifier;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*; import org.baeldung.conditionalflow.model.NumberInfo;
import org.junit.jupiter.api.Test;
class NumberInfoClassifierUnitTest { class NumberInfoClassifierUnitTest {
@Test @Test
void process() throws Exception { void process_convertsToInteger() throws Exception {
NumberInfoClassifier nic = new NumberInfoClassifier(); NumberInfoClassifier nic = new NumberInfoClassifier();
assertEquals(Integer.valueOf(4), nic.process(NumberInfo.from(4))); assertEquals(Integer.valueOf(4), nic.process(NumberInfo.from(4)));
assertEquals(Integer.valueOf(-4), nic.process(NumberInfo.from(-4))); assertEquals(Integer.valueOf(-4), nic.process(NumberInfo.from(-4)));

View File

@ -1,14 +1,14 @@
package org.baeldung.conditionalflow.step; package org.baeldung.conditionalflow.step;
import org.baeldung.conditionalflow.model.NumberInfo;
import org.junit.jupiter.api.Test;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import org.baeldung.conditionalflow.model.NumberInfo;
import org.junit.jupiter.api.Test;
public class NumberInfoGeneratorUnitTest { public class NumberInfoGeneratorUnitTest {
@Test @Test
public void testGenerateNumbers() { public void testGenerateNumbers_correctOrderAndValue() {
int[] numbers = new int[]{1, -2, 4, -10}; int[] numbers = new int[]{1, -2, 4, -10};
NumberInfoGenerator numberGenerator = new NumberInfoGenerator(numbers); NumberInfoGenerator numberGenerator = new NumberInfoGenerator(numbers);
assertEquals(new NumberInfo(numbers[0]), numberGenerator.read()); assertEquals(new NumberInfo(numbers[0]), numberGenerator.read());

View File

@ -1 +1 @@
<?xml version="1.0" encoding="UTF-8"?><transactionRecord><transactionRecord><age>10</age><amount>10000.0</amount><postCode>430222</postCode><transactionDate>2015-10-31 00:00:00</transactionDate><userId>1234</userId><username>sammy</username></transactionRecord><transactionRecord><age>10</age><amount>12321.0</amount><postCode>430222</postCode><transactionDate>2015-12-03 00:00:00</transactionDate><userId>9999</userId><username>john</username></transactionRecord></transactionRecord> <?xml version="1.0" encoding="UTF-8"?><transactionRecord></transactionRecord>