From 39d985d263adafff98855a3af3a768eeed0aca99 Mon Sep 17 00:00:00 2001 From: Glenn Renfro Date: Thu, 11 May 2017 09:53:23 -0400 Subject: [PATCH] Adds builder for MultiResourceItemReader resolves BATCH-2604 --- .../item/file/MultiResourceItemReader.java | 3 +- .../builder/FlatFileItemReaderBuilder.java | 2 +- .../MultiResourceItemReaderBuilder.java | 167 ++++++++++++++++++ .../MultiResourceItemReaderBuilderTests.java | 107 +++++++++++ 4 files changed, 277 insertions(+), 2 deletions(-) create mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/builder/MultiResourceItemReaderBuilder.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/builder/MultiResourceItemReaderBuilderTests.java diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/MultiResourceItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/MultiResourceItemReader.java index ae1f066b3..4abdf3956 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/MultiResourceItemReader.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/MultiResourceItemReader.java @@ -221,7 +221,8 @@ public class MultiResourceItemReader extends AbstractItemStreamItemReader * Set the boolean indicating whether or not state should be saved in the provided {@link ExecutionContext} during * the {@link ItemStream} call to update. * - * @param saveState + * @param saveState true to update ExecutionContext. False do not update + * ExecutionContext. */ public void setSaveState(boolean saveState) { this.saveState = saveState; diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/builder/FlatFileItemReaderBuilder.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/builder/FlatFileItemReaderBuilder.java index bcec4b962..a7d113afc 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/builder/FlatFileItemReaderBuilder.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/builder/FlatFileItemReaderBuilder.java @@ -385,7 +385,7 @@ public class FlatFileItemReaderBuilder { public FlatFileItemReader build() throws Exception { if(this.saveState) { Assert.state(StringUtils.hasText(this.name), - "A name is required when saveSate is set to true."); + "A name is required when saveState is set to true."); } if(this.resource == null) { diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/builder/MultiResourceItemReaderBuilder.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/builder/MultiResourceItemReaderBuilder.java new file mode 100644 index 000000000..40c629e0d --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/builder/MultiResourceItemReaderBuilder.java @@ -0,0 +1,167 @@ +/* + * Copyright 2017 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.item.file.builder; + +import java.util.Comparator; + +import org.springframework.batch.item.ExecutionContext; +import org.springframework.batch.item.ItemStream; +import org.springframework.batch.item.file.MultiResourceItemReader; +import org.springframework.batch.item.file.ResourceAwareItemReaderItemStream; +import org.springframework.core.io.Resource; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * A builder implementation for the {@link MultiResourceItemReader}. + * + * @author Glenn Renfro + * @since 4.0 + * @see MultiResourceItemReader + */ +public class MultiResourceItemReaderBuilder { + + private ResourceAwareItemReaderItemStream delegate; + + private Resource[] resources; + + private boolean saveState = true; + + private boolean strict = false; + + private String name; + + private Comparator comparator; + + /** + * The array of resources that the {@link MultiResourceItemReader} will use to + * retrieve items. + * + * @param resources the array of resources to use. + * @return this instance for method chaining. + * + * @see MultiResourceItemReader#setResources(Resource[]) + */ + public MultiResourceItemReaderBuilder resources(Resource[] resources) { + this.resources = resources; + + return this; + } + + /** + * Establishes the delegate to use for reading the resources provided. + * + * @param delegate reads items from single {@link Resource}. + * @return this instance for method chaining. + * + * @see MultiResourceItemReader#setDelegate(ResourceAwareItemReaderItemStream) + */ + public MultiResourceItemReaderBuilder delegate(ResourceAwareItemReaderItemStream delegate) { + this.delegate = delegate; + + return this; + } + + /** + * Set the boolean indicating whether or not state should be saved in the provided + * {@link ExecutionContext} during the {@link ItemStream} call to update. + * + * @param saveState true to update ExecutionContext. False do not update + * ExecutionContext. + * @return this instance for method chaining. + * @see MultiResourceItemReader#setSaveState(boolean) + * + */ + public MultiResourceItemReaderBuilder saveState(boolean saveState) { + this.saveState = saveState; + + return this; + } + + /** + * In strict mode the reader will throw an exception on + * {@link MultiResourceItemReader#open(org.springframework.batch.item.ExecutionContext)} + * if there are no resources to read. + * + * @param strict false by default. + * @return this instance for method chaining. + * @see MultiResourceItemReader#setStrict(boolean) + */ + public MultiResourceItemReaderBuilder setStrict(boolean strict) { + this.strict = strict; + + return this; + } + + /** + * The name of the component which will be used as a stem for keys in the + * {@link ExecutionContext}. Subclasses should provide a default value, e.g. the short + * form of the class name. + * + * @param name the name for the component. + * @return this instance for method chaining. + * @see MultiResourceItemReader#setName(String) + */ + public MultiResourceItemReaderBuilder name(String name) { + this.name = name; + + return this; + } + + /** + * Used to order the injected resources, by default compares + * {@link Resource#getFilename()} values. + * + * @param comparator the comparator to use for ordering resources. + * @return this instance for method chaining. + * @see MultiResourceItemReader#setComparator(Comparator) + */ + public MultiResourceItemReaderBuilder comparator(Comparator comparator) { + this.comparator = comparator; + + return this; + } + + /** + * Builds the {@link MultiResourceItemReader}. + * + * @return a {@link MultiResourceItemReader} + */ + public MultiResourceItemReader build() { + Assert.notNull(this.resources, "resources array is required."); + Assert.notNull(this.delegate, "delegate is required."); + if (this.saveState) { + Assert.state(StringUtils.hasText(this.name), "A name is required when saveState is set to true."); + } + + MultiResourceItemReader reader = new MultiResourceItemReader<>(); + reader.setResources(this.resources); + reader.setDelegate(this.delegate); + reader.setSaveState(this.saveState); + reader.setStrict(this.strict); + + if (comparator != null) { + reader.setComparator(this.comparator); + } + if (StringUtils.hasText(this.name)) { + reader.setName(this.name); + } + + return reader; + } + +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/builder/MultiResourceItemReaderBuilderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/builder/MultiResourceItemReaderBuilderTests.java new file mode 100644 index 000000000..edebadd51 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/builder/MultiResourceItemReaderBuilderTests.java @@ -0,0 +1,107 @@ +/* + * Copyright 2017 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.item.file.builder; + +import java.util.Comparator; + +import org.junit.Test; + +import org.springframework.batch.item.AbstractItemStreamItemReaderTests; +import org.springframework.batch.item.ExecutionContext; +import org.springframework.batch.item.ItemReader; +import org.springframework.batch.item.file.FlatFileItemReader; +import org.springframework.batch.item.file.LineMapper; +import org.springframework.batch.item.file.MultiResourceItemReader; +import org.springframework.batch.item.sample.Foo; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; + +/** + * @author Glenn Renfro + */ +public class MultiResourceItemReaderBuilderTests extends AbstractItemStreamItemReaderTests { + + @Override + protected ItemReader getItemReader() throws Exception { + + FlatFileItemReader fileReader = new FlatFileItemReader(); + + fileReader.setLineMapper(new LineMapper() { + + @Override + public Foo mapLine(String line, int lineNumber) throws Exception { + Foo foo = new Foo(); + foo.setValue(Integer.valueOf(line)); + return foo; + } + + }); + fileReader.setSaveState(true); + + Resource r1 = new ByteArrayResource("1\n2\n".getBytes()); + Resource r2 = new ByteArrayResource("".getBytes()); + Resource r3 = new ByteArrayResource("3\n".getBytes()); + Resource r4 = new ByteArrayResource("4\n5\n".getBytes()); + + Comparator comparator = new Comparator() { + @Override + public int compare(Resource arg0, Resource arg1) { + return 0; // preserve original ordering + } + + }; + return new MultiResourceItemReaderBuilder().delegate(fileReader) + .resources(new Resource[] { r1, r2, r3, r4 }).saveState(true).comparator(comparator).name("FOO") + .build(); + } + + @Test + public void testNullDelegate() { + try { + new MultiResourceItemReaderBuilder().resources(new Resource[]{}).build(); + fail("IllegalArgumentException should have been thrown"); + } + catch (IllegalArgumentException ise) { + assertEquals("IllegalArgumentException message did not match the expected result.", + "delegate is required.", ise.getMessage()); + } + } + + @Test + public void testNullResources() { + try { + new MultiResourceItemReaderBuilder().delegate(mock(FlatFileItemReader.class)).build(); + fail("IllegalArgumentException should have been thrown"); + } + catch (IllegalArgumentException ise) { + assertEquals("IllegalArgumentException message did not match the expected result.", + "resources array is required.", ise.getMessage()); + } + } + + @Override + protected void pointToEmptyInput(ItemReader tested) throws Exception { + MultiResourceItemReader multiReader = (MultiResourceItemReader) tested; + multiReader.close(); + multiReader.setResources(new Resource[] { new ByteArrayResource("".getBytes()) }); + multiReader.open(new ExecutionContext()); + } +}