├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── toptal │ │ ├── BatchApplication.java │ │ ├── BirthdayFilterProcessor.java │ │ ├── Customer.java │ │ ├── CustomerItemReader.java │ │ ├── CustomerItemWriter.java │ │ ├── CustomerReportJobConfig.java │ │ └── TransactionValidatingProcessor.java └── resources │ └── application.properties └── test └── java └── com └── toptal ├── BatchApplicationTest.java ├── BatchTestConfiguration.java ├── BirthdayFilterProcessorTest.java └── CustomerReportJobConfigTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 2 | hs_err_pid* 3 | 4 | # idea 5 | .idea/ 6 | *.iml 7 | 8 | # eclipse 9 | .classpath 10 | .project 11 | .settings/ 12 | .springBeans 13 | 14 | # maven 15 | target/ 16 | database.xml 17 | output.txt 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Alexey Saenko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # spring-batch-article 2 | 3 | Project for the post [Spring Batch Tutorial: Batch Processing Made Easy with Spring](https://www.toptal.com/spring/spring-batch-tutorial) in the Toptal blog 4 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.toptal 7 | spring-batch-article 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | spring-batch-article 12 | Demo project for Spring Batch 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 1.5.2.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-batch 31 | 32 | 33 | org.projectlombok 34 | lombok 35 | 1.16.16 36 | 37 | 38 | 39 | com.h2database 40 | h2 41 | runtime 42 | 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-starter-test 47 | test 48 | 49 | 50 | org.springframework.batch 51 | spring-batch-test 52 | 53 | 54 | 55 | 56 | 57 | 58 | org.springframework.boot 59 | spring-boot-maven-plugin 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /src/main/java/com/toptal/BatchApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2017 Alexey Saenko 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package com.toptal; 25 | 26 | import java.beans.XMLEncoder; 27 | import java.io.FileNotFoundException; 28 | import java.io.FileOutputStream; 29 | import java.util.Calendar; 30 | import java.util.Collection; 31 | import java.util.GregorianCalendar; 32 | import java.util.LinkedList; 33 | import java.util.UUID; 34 | import lombok.extern.slf4j.Slf4j; 35 | import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; 36 | import org.springframework.boot.SpringApplication; 37 | import org.springframework.boot.autoconfigure.SpringBootApplication; 38 | import org.springframework.scheduling.annotation.EnableScheduling; 39 | 40 | /** 41 | * Entry point of the application. 42 | * 43 | * @author Alexey Saenko (alexey.saenko@gmail.com) 44 | */ 45 | @Slf4j 46 | @EnableScheduling 47 | @EnableBatchProcessing 48 | @SpringBootApplication 49 | public class BatchApplication { 50 | 51 | public static void main(String[] args) { 52 | prepareTestData(1000); 53 | SpringApplication.run(BatchApplication.class, args); 54 | } 55 | 56 | private static void prepareTestData(final int amount) { 57 | final int actualYear = new GregorianCalendar().get(Calendar.YEAR); 58 | final Collection customers = new LinkedList<>(); 59 | for (int i = 1; i <= amount; i++) { 60 | final Calendar birthday = new GregorianCalendar(); 61 | birthday.set(Calendar.YEAR, random(actualYear - 100, actualYear)); 62 | birthday.set(Calendar.DAY_OF_YEAR, random(1, birthday.getActualMaximum(Calendar.DAY_OF_YEAR))); 63 | final Customer customer = new Customer(); 64 | customer.setId(i); 65 | customer.setName(UUID.randomUUID().toString().replaceAll("[^a-z]", "")); 66 | customer.setBirthday(birthday); 67 | customer.setTransactions(random(0, 100)); 68 | customers.add(customer); 69 | } 70 | try (final XMLEncoder encoder = new XMLEncoder(new FileOutputStream(CustomerReportJobConfig.XML_FILE))) { 71 | encoder.writeObject(customers); 72 | } catch (final FileNotFoundException e) { 73 | log.error(e.getMessage(), e); 74 | System.exit(-1); 75 | } 76 | } 77 | 78 | private static int random(final int start, final int end) { 79 | return start + (int) Math.round(Math.random() * (end - start)); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/toptal/BirthdayFilterProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2017 Alexey Saenko 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package com.toptal; 25 | 26 | import java.util.Calendar; 27 | import java.util.GregorianCalendar; 28 | import lombok.extern.slf4j.Slf4j; 29 | import org.springframework.batch.item.ItemProcessor; 30 | 31 | /** 32 | * Processor filters customers by being born in the actual month. 33 | * 34 | * @author Alexey Saenko (alexey.saenko@gmail.com) 35 | */ 36 | @Slf4j 37 | public class BirthdayFilterProcessor implements ItemProcessor { 38 | @Override 39 | public Customer process(final Customer item) throws Exception { 40 | if (new GregorianCalendar().get(Calendar.MONTH) == item.getBirthday().get(Calendar.MONTH)) { 41 | log.info("Customer {} matched the birthday filter", item); 42 | return item; 43 | } 44 | return null; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/toptal/Customer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2017 Alexey Saenko 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package com.toptal; 25 | 26 | import java.io.Serializable; 27 | import java.util.Calendar; 28 | import lombok.Data; 29 | 30 | /** 31 | * Data class. 32 | * 33 | * @author Alexey Saenko (alexey.saenko@gmail.com) 34 | */ 35 | @Data 36 | public class Customer implements Serializable { 37 | 38 | private int id; 39 | private String name; 40 | private Calendar birthday; 41 | private int transactions; 42 | 43 | @Override 44 | public String toString() { 45 | return String.format( 46 | "#%s, %s born on %3$tb %3$te, %3$tY, finished %4$s transactions", 47 | id, 48 | name, 49 | birthday.getTime(), 50 | transactions 51 | ); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/toptal/CustomerItemReader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2017 Alexey Saenko 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package com.toptal; 25 | 26 | import java.beans.XMLDecoder; 27 | import java.io.FileInputStream; 28 | import java.io.FileNotFoundException; 29 | import java.util.List; 30 | import lombok.extern.slf4j.Slf4j; 31 | import org.springframework.batch.item.ItemReader; 32 | import org.springframework.batch.item.support.IteratorItemReader; 33 | 34 | /** 35 | * Customer item reader. 36 | * 37 | * @author Alexey Saenko (alexey.saenko@gmail.com) 38 | */ 39 | @Slf4j 40 | public class CustomerItemReader implements ItemReader { 41 | 42 | private final String filename; 43 | 44 | private ItemReader delegate; 45 | 46 | public CustomerItemReader(final String filename) { 47 | this.filename = filename; 48 | } 49 | 50 | @Override 51 | public Customer read() throws Exception { 52 | if (delegate == null) { 53 | log.info("Creating iterator item reader"); 54 | delegate = new IteratorItemReader<>(customers()); 55 | } 56 | log.info("Reading next customer"); 57 | return delegate.read(); 58 | } 59 | 60 | private List customers() throws FileNotFoundException { 61 | try (XMLDecoder decoder = new XMLDecoder(new FileInputStream(filename))) { 62 | return (List) decoder.readObject(); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/toptal/CustomerItemWriter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2017 Alexey Saenko 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package com.toptal; 25 | 26 | import java.io.Closeable; 27 | import java.io.FileNotFoundException; 28 | import java.io.FileOutputStream; 29 | import java.io.IOException; 30 | import java.io.OutputStream; 31 | import java.io.PrintWriter; 32 | import java.util.List; 33 | import javax.annotation.PreDestroy; 34 | import lombok.extern.slf4j.Slf4j; 35 | import org.springframework.batch.item.ItemWriter; 36 | 37 | /** 38 | * Customer item writer. 39 | * 40 | * @author Alexey Saenko (alexey.saenko@gmail.com) 41 | */ 42 | @Slf4j 43 | public class CustomerItemWriter implements ItemWriter, Closeable { 44 | private final PrintWriter writer; 45 | 46 | public CustomerItemWriter() { 47 | OutputStream out; 48 | try { 49 | out = new FileOutputStream("output.txt"); 50 | } catch (FileNotFoundException e) { 51 | out = System.out; 52 | } 53 | this.writer = new PrintWriter(out); 54 | } 55 | 56 | @Override 57 | public void write(final List items) throws Exception { 58 | for (Customer item : items) { 59 | writer.println(item.toString()); 60 | } 61 | } 62 | 63 | @PreDestroy 64 | @Override 65 | public void close() throws IOException { 66 | writer.close(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/toptal/CustomerReportJobConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2017 Alexey Saenko 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package com.toptal; 25 | 26 | import java.util.Arrays; 27 | import javax.annotation.PreDestroy; 28 | import lombok.extern.slf4j.Slf4j; 29 | import org.springframework.batch.core.Job; 30 | import org.springframework.batch.core.JobExecution; 31 | import org.springframework.batch.core.JobParametersBuilder; 32 | import org.springframework.batch.core.Step; 33 | import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; 34 | import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; 35 | import org.springframework.batch.core.configuration.annotation.StepScope; 36 | import org.springframework.batch.core.explore.JobExplorer; 37 | import org.springframework.batch.core.launch.JobLauncher; 38 | import org.springframework.batch.core.launch.NoSuchJobException; 39 | import org.springframework.batch.core.step.tasklet.Tasklet; 40 | import org.springframework.batch.item.ItemProcessor; 41 | import org.springframework.batch.item.ItemReader; 42 | import org.springframework.batch.item.ItemWriter; 43 | import org.springframework.batch.item.support.CompositeItemProcessor; 44 | import org.springframework.batch.repeat.RepeatStatus; 45 | import org.springframework.beans.factory.annotation.Autowired; 46 | import org.springframework.context.annotation.Bean; 47 | import org.springframework.context.annotation.Configuration; 48 | import org.springframework.scheduling.annotation.Scheduled; 49 | 50 | /** 51 | * Job configuration. 52 | * 53 | * @author Alexey Saenko (alexey.saenko@gmail.com) 54 | */ 55 | @Slf4j 56 | @Configuration 57 | public class CustomerReportJobConfig { 58 | 59 | public static final String TASKLET_STEP = "taskletStep"; 60 | 61 | public static final String XML_FILE = "database.xml"; 62 | 63 | private static final String JOB_NAME = "customerReportJob"; 64 | 65 | @Autowired 66 | private JobLauncher jobLauncher; 67 | 68 | @Autowired 69 | private JobBuilderFactory jobBuilders; 70 | 71 | @Autowired 72 | private StepBuilderFactory stepBuilders; 73 | 74 | @Autowired 75 | private JobExplorer jobs; 76 | 77 | @PreDestroy 78 | public void destroy() throws NoSuchJobException { 79 | jobs.getJobNames().forEach(name -> log.info("job name: {}", name)); 80 | jobs.getJobInstances(JOB_NAME, 0, jobs.getJobInstanceCount(JOB_NAME)).forEach( 81 | jobInstance -> { 82 | log.info("job instance id {}", jobInstance.getInstanceId()); 83 | } 84 | ); 85 | 86 | } 87 | 88 | @Scheduled(fixedRate = 5000) 89 | public void run() throws Exception { 90 | JobExecution execution = jobLauncher.run( 91 | customerReportJob(), 92 | new JobParametersBuilder().addLong("uniqueness", System.nanoTime()).toJobParameters() 93 | ); 94 | log.info("Exit status: {}", execution.getStatus()); 95 | } 96 | 97 | @Bean 98 | public Job customerReportJob() { 99 | return jobBuilders.get(JOB_NAME) 100 | .start(taskletStep()) 101 | .next(chunkStep()) 102 | .build(); 103 | } 104 | 105 | @Bean 106 | public Step taskletStep() { 107 | return stepBuilders.get(TASKLET_STEP) 108 | .tasklet(tasklet()) 109 | .build(); 110 | } 111 | 112 | @Bean 113 | public Step chunkStep() { 114 | return stepBuilders.get("chunkStep") 115 | .chunk(20) 116 | .reader(reader()) 117 | .processor(processor()) 118 | .writer(writer()) 119 | .build(); 120 | } 121 | 122 | @StepScope 123 | @Bean 124 | public ItemReader reader() { 125 | return new CustomerItemReader(XML_FILE); 126 | } 127 | 128 | @StepScope 129 | @Bean 130 | public ItemProcessor processor() { 131 | final CompositeItemProcessor processor = new CompositeItemProcessor<>(); 132 | processor.setDelegates(Arrays.asList(birthdayFilterProcessor(), transactionValidatingProcessor())); 133 | return processor; 134 | } 135 | 136 | @StepScope 137 | @Bean 138 | public BirthdayFilterProcessor birthdayFilterProcessor() { 139 | return new BirthdayFilterProcessor(); 140 | } 141 | 142 | @StepScope 143 | @Bean 144 | public TransactionValidatingProcessor transactionValidatingProcessor() { 145 | return new TransactionValidatingProcessor(5); 146 | } 147 | 148 | @StepScope 149 | @Bean 150 | public ItemWriter writer() { 151 | return new CustomerItemWriter(); 152 | } 153 | 154 | @Bean 155 | public Tasklet tasklet() { 156 | return (contribution, chunkContext) -> { 157 | log.info("Executing tasklet step"); 158 | return RepeatStatus.FINISHED; 159 | }; 160 | } 161 | 162 | } 163 | -------------------------------------------------------------------------------- /src/main/java/com/toptal/TransactionValidatingProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2017 Alexey Saenko 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package com.toptal; 25 | 26 | import lombok.extern.slf4j.Slf4j; 27 | import org.springframework.batch.item.validator.ValidatingItemProcessor; 28 | import org.springframework.batch.item.validator.ValidationException; 29 | 30 | /** 31 | * Processor filters customers by the amount of transactions. 32 | * 33 | * @author Alexey Saenko (alexey.saenko@gmail.com) 34 | */ 35 | @Slf4j 36 | public class TransactionValidatingProcessor extends ValidatingItemProcessor { 37 | public TransactionValidatingProcessor(final int limit) { 38 | super( 39 | item -> { 40 | if (item.getTransactions() >= limit) { 41 | throw new ValidationException("Customer has more than " + limit + " transactions"); 42 | } 43 | log.info("Customer {} matched the transaction filter", item); 44 | } 45 | ); 46 | setFilter(true); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.batch.job.enabled=false 2 | -------------------------------------------------------------------------------- /src/test/java/com/toptal/BatchApplicationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2017 Alexey Saenko 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package com.toptal; 25 | 26 | import org.junit.Test; 27 | import org.junit.runner.RunWith; 28 | import org.springframework.test.context.ContextConfiguration; 29 | import org.springframework.test.context.junit4.SpringRunner; 30 | 31 | /** 32 | * Tests for the application. 33 | * 34 | * @author Alexey Saenko (alexey.saenko@gmail.com) 35 | */ 36 | @RunWith(SpringRunner.class) 37 | @ContextConfiguration(classes = {BatchApplication.class, BatchTestConfiguration.class}) 38 | public class BatchApplicationTest { 39 | 40 | @Test 41 | public void contextLoads() { 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/com/toptal/BatchTestConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2017 Alexey Saenko 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package com.toptal; 25 | 26 | import org.springframework.batch.test.JobLauncherTestUtils; 27 | import org.springframework.context.annotation.Bean; 28 | import org.springframework.context.annotation.Configuration; 29 | 30 | /** 31 | * Test configuration. 32 | * 33 | * @author Alexey Saenko (alexey.saenko@gmail.com) 34 | */ 35 | @Configuration 36 | public class BatchTestConfiguration { 37 | @Bean 38 | public JobLauncherTestUtils jobLauncherTestUtils() { 39 | return new JobLauncherTestUtils(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/com/toptal/BirthdayFilterProcessorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2017 Alexey Saenko 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package com.toptal; 25 | 26 | import java.util.Calendar; 27 | import java.util.GregorianCalendar; 28 | import java.util.concurrent.Callable; 29 | import lombok.extern.slf4j.Slf4j; 30 | import org.junit.Assert; 31 | import org.junit.Test; 32 | import org.junit.runner.RunWith; 33 | import org.springframework.batch.core.StepExecution; 34 | import org.springframework.batch.test.MetaDataInstanceFactory; 35 | import org.springframework.batch.test.StepScopeTestExecutionListener; 36 | import org.springframework.batch.test.StepScopeTestUtils; 37 | import org.springframework.beans.factory.annotation.Autowired; 38 | import org.springframework.test.context.ContextConfiguration; 39 | import org.springframework.test.context.TestExecutionListeners; 40 | import org.springframework.test.context.junit4.SpringRunner; 41 | import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; 42 | 43 | /** 44 | * Tests for {@link BirthdayFilterProcessor}. 45 | * 46 | * @author Alexey Saenko (alexey.saenko@gmail.com) 47 | */ 48 | @Slf4j 49 | @RunWith(SpringRunner.class) 50 | @TestExecutionListeners({DependencyInjectionTestExecutionListener.class, StepScopeTestExecutionListener.class}) 51 | @ContextConfiguration(classes = {BatchApplication.class, BatchTestConfiguration.class}) 52 | public class BirthdayFilterProcessorTest { 53 | 54 | @Autowired 55 | private BirthdayFilterProcessor processor; 56 | 57 | public StepExecution getStepExecution() { 58 | log.info("getting step execution"); 59 | return MetaDataInstanceFactory.createStepExecution(); 60 | } 61 | 62 | @Test 63 | public void filter() throws Exception { 64 | final Customer customer = new Customer(); 65 | customer.setId(1); 66 | customer.setName("name"); 67 | customer.setBirthday(new GregorianCalendar()); 68 | Assert.assertNotNull(processor.process(customer)); 69 | } 70 | 71 | @Test 72 | public void filterId() throws Exception { 73 | final Customer customer = new Customer(); 74 | customer.setId(1); 75 | customer.setName("name"); 76 | customer.setBirthday(new GregorianCalendar()); 77 | final int id = StepScopeTestUtils.doInStepScope( 78 | getStepExecution(), 79 | () -> processor.process(customer).getId() 80 | ); 81 | Assert.assertEquals(1, id); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/test/java/com/toptal/CustomerReportJobConfigTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2017 Alexey Saenko 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package com.toptal; 25 | 26 | import org.junit.Assert; 27 | import org.junit.Test; 28 | import org.junit.runner.RunWith; 29 | import org.springframework.batch.core.BatchStatus; 30 | import org.springframework.batch.core.JobExecution; 31 | import org.springframework.batch.test.JobLauncherTestUtils; 32 | import org.springframework.beans.factory.annotation.Autowired; 33 | import org.springframework.test.context.ContextConfiguration; 34 | import org.springframework.test.context.junit4.SpringRunner; 35 | 36 | /** 37 | * Tests for {@link CustomerReportJobConfig}. 38 | * 39 | * @author Alexey Saenko (alexey.saenko@gmail.com) 40 | */ 41 | @RunWith(SpringRunner.class) 42 | @ContextConfiguration(classes = {BatchApplication.class, BatchTestConfiguration.class}) 43 | public class CustomerReportJobConfigTest { 44 | 45 | @Autowired 46 | private JobLauncherTestUtils testUtils; 47 | 48 | @Autowired 49 | private CustomerReportJobConfig config; 50 | 51 | @Test 52 | public void testEntireJob() throws Exception { 53 | final JobExecution result = testUtils.getJobLauncher().run(config.customerReportJob(), testUtils.getUniqueJobParameters()); 54 | Assert.assertNotNull(result); 55 | Assert.assertEquals(BatchStatus.COMPLETED, result.getStatus()); 56 | } 57 | 58 | @Test 59 | public void testSpecificStep() { 60 | Assert.assertEquals(BatchStatus.COMPLETED, testUtils.launchStep(CustomerReportJobConfig.TASKLET_STEP).getStatus()); 61 | } 62 | 63 | } 64 | --------------------------------------------------------------------------------