From ee0236d657346f1b118c0e0c737921fc7ad96e84 Mon Sep 17 00:00:00 2001 From: dsyer Date: Thu, 21 Feb 2008 09:15:51 +0000 Subject: [PATCH] OPEN - issue BATCH-371: FlatFileItemWriter no longer uses LineAggregator http://jira.springframework.org/browse/BATCH-371 Extend ItemTransformer implementations that deal with field sets and arrays --- spring-batch-infrastructure/.springBeans | 3 +- .../file/transform/ConversionException.java | 31 ++ .../LineAggregatorItemTransformer.java | 21 +- .../RecursiveCollectionItemTransformer.java | 55 +- ...cursiveCollectionItemTransformerTests.java | 127 +++++ .../.settings/org.eclipse.jdt.core.prefs | 4 + spring-batch-samples/.springBeans | 499 +++++++++--------- 7 files changed, 467 insertions(+), 273 deletions(-) create mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/io/file/transform/ConversionException.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/io/file/transform/RecursiveCollectionItemTransformerTests.java create mode 100644 spring-batch-samples/.settings/org.eclipse.jdt.core.prefs diff --git a/spring-batch-infrastructure/.springBeans b/spring-batch-infrastructure/.springBeans index 588810304..5c11fe605 100644 --- a/spring-batch-infrastructure/.springBeans +++ b/spring-batch-infrastructure/.springBeans @@ -1,10 +1,11 @@ 1 - + + src/test/resources/org/springframework/batch/io/sql/data-source-context.xml src/test/resources/org/springframework/batch/retry/aop/retry-transaction-test.xml diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/io/file/transform/ConversionException.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/io/file/transform/ConversionException.java new file mode 100644 index 000000000..046f3fde6 --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/io/file/transform/ConversionException.java @@ -0,0 +1,31 @@ +/* + * 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.io.file.transform; + +/** + * @author Dave Syer + * + */ +public class ConversionException extends RuntimeException { + + /** + * @param msg + */ + public ConversionException(String msg) { + super(msg); + } + +} diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/io/file/transform/LineAggregatorItemTransformer.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/io/file/transform/LineAggregatorItemTransformer.java index 8886f64ce..45e270299 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/io/file/transform/LineAggregatorItemTransformer.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/io/file/transform/LineAggregatorItemTransformer.java @@ -49,10 +49,23 @@ public class LineAggregatorItemTransformer implements ItemTransformer { } /** - * @param item - * @return + * Extension point for subclasses. The default implementation just attempts + * to cast the item to String[] and creates a {@link DefaultFieldSet} from + * it. + * + * @param item an object (in this implementation of type String[]). + * @return a {@link FieldSet} representing the item + * + * @throws ConversionException if the field set cannot be created */ - protected FieldSet createFieldSet(Object item) { - return new DefaultFieldSet((String[]) item); + protected FieldSet createFieldSet(Object item) throws ConversionException { + try { + return new DefaultFieldSet((String[]) item); + } + catch (ClassCastException e) { + throw new ConversionException( + "Item must be of type String[] for conversion to FieldSet. " + + "Consider overriding this method to specify a less generic algorithm."); + } } } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/io/file/transform/RecursiveCollectionItemTransformer.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/io/file/transform/RecursiveCollectionItemTransformer.java index 7accd44ac..0980b06d0 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/io/file/transform/RecursiveCollectionItemTransformer.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/io/file/transform/RecursiveCollectionItemTransformer.java @@ -6,10 +6,12 @@ import java.util.Iterator; import org.springframework.batch.item.writer.ItemTransformer; /** - * An implementation of {@link ItemTransformer} that just calls toString() on - * its argument, unless it it an array or collection, in which case it loops - * though, calling itself on each member in turn, concatenating the result with - * line separators. + * An implementation of {@link ItemTransformer} that treats its argument + * specially if it is an array or collection. In this case it loops though, + * calling itself on each member in turn, until it encounters a non collection. + * At this point, if the item is a String, that is used, or else it is passed to + * the delegate {@link ItemTransformer}. The transformed single item Strings + * are all concatenated with line separators. * * @author Dave Syer * @@ -18,18 +20,42 @@ public class RecursiveCollectionItemTransformer implements ItemTransformer { private static final String LINE_SEPARATOR = System.getProperty("line.separator"); - /* (non-Javadoc) + private ItemTransformer delegate = new ItemTransformer() { + public Object transform(Object item) throws Exception { + return item; + } + }; + + /** + * Public setter for the {@link ItemTransformer} to use on single items, + * that are not Strings. This can be used to strategise the conversion of + * collection and array elements to a String, e.g. via a subclass of + * {@link LineAggregatorItemTransformer}.
+ * + * N.B. if the delegate returns an array or collection, it will not be + * treated the same way as the original item passed in for transformation. + * Rather, in this case, it will simply be converted immediately to a String + * by calling its toString(). + * + * @param delegate the delegate to set. Defaults to a pass through. + */ + public void setDelegate(ItemTransformer delegate) { + this.delegate = delegate; + } + + /* + * (non-Javadoc) * @see org.springframework.batch.item.writer.ItemTransformer#transform(java.lang.Object) */ - public Object transform(Object input) { + public Object transform(Object input) throws Exception { TransformHolder holder = new TransformHolder(); transformRecursively(input, holder); String result = holder.builder.toString(); return result.substring(0, result.lastIndexOf(LINE_SEPARATOR)); } - public String stringify(Object input) { - return "" + input; + public String stringify(Object item) throws Exception { + return "" + delegate.transform(item); } /** @@ -38,10 +64,9 @@ public class RecursiveCollectionItemTransformer implements ItemTransformer { * @param converted * @throws Exception */ - private void transformRecursively(Object data, TransformHolder converted) { + private void transformRecursively(Object data, TransformHolder converted) throws Exception { if (data instanceof Collection) { - converted.value = false; for (Iterator iterator = ((Collection) data).iterator(); iterator.hasNext();) { Object value = (Object) iterator.next(); // (recursive) @@ -50,7 +75,6 @@ public class RecursiveCollectionItemTransformer implements ItemTransformer { return; } if (data.getClass().isArray()) { - converted.value = false; Object[] array = (Object[]) data; for (int i = 0; i < array.length; i++) { Object value = array[i]; @@ -63,21 +87,14 @@ public class RecursiveCollectionItemTransformer implements ItemTransformer { // This is where the output stream is actually written to converted.builder.append(data + LINE_SEPARATOR); } - else if (!converted.value) { + else { // (recursive) - converted.value = true; transformRecursively(stringify(data), converted); return; } - else { - // Should not happen... - throw new IllegalStateException( - "Infinite loop detected - converter did not convert to String or collection/array of objects convertible to String."); - } } private static class TransformHolder { - boolean value; StringBuilder builder = new StringBuilder(); diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/io/file/transform/RecursiveCollectionItemTransformerTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/io/file/transform/RecursiveCollectionItemTransformerTests.java new file mode 100644 index 000000000..fd3cc5835 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/io/file/transform/RecursiveCollectionItemTransformerTests.java @@ -0,0 +1,127 @@ +/* + * 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.io.file.transform; + +import java.util.Arrays; +import java.util.Collections; + +import org.springframework.batch.item.writer.ItemTransformer; +import org.springframework.util.StringUtils; + +import junit.framework.TestCase; + +/** + * @author Dave Syer + * + */ +public class RecursiveCollectionItemTransformerTests extends TestCase { + + private static final String LINE_SEPARATOR = System.getProperty("line.separator"); + + private RecursiveCollectionItemTransformer transformer = new RecursiveCollectionItemTransformer(); + + /** + * Test method for + * {@link org.springframework.batch.io.file.transform.RecursiveCollectionItemTransformer#setDelegate(org.springframework.batch.item.writer.ItemTransformer)}. + * @throws Exception + */ + public void testSetDelegate() throws Exception { + transformer.setDelegate(new ItemTransformer() { + public Object transform(Object item) throws Exception { + return "bar"; + } + }); + assertEquals("bar", transformer.transform(new Object())); + } + + /** + * Test method for + * {@link org.springframework.batch.io.file.transform.RecursiveCollectionItemTransformer#setDelegate(org.springframework.batch.item.writer.ItemTransformer)}. + * @throws Exception + */ + public void testSetDelegateAndPassInString() throws Exception { + transformer.setDelegate(new ItemTransformer() { + public Object transform(Object item) throws Exception { + return "bar"; + } + }); + assertEquals("foo", transformer.transform("foo")); + } + + /** + * Test method for + * {@link org.springframework.batch.io.file.transform.RecursiveCollectionItemTransformer#setDelegate(org.springframework.batch.item.writer.ItemTransformer)}. + * @throws Exception + */ + public void testSetDelegateReturnsList() throws Exception { + transformer.setDelegate(new ItemTransformer() { + public Object transform(Object item) throws Exception { + return Collections.singletonList("bar"); + } + }); + // The result of the delegate is a list, which will simply be + // converted to a string by concatenating with "": + assertEquals("[bar]", transformer.transform(new Object())); + } + + /** + * Test method for + * {@link org.springframework.batch.io.file.transform.RecursiveCollectionItemTransformer#transform(java.lang.Object)}. + * @throws Exception + */ + public void testTransformString() throws Exception { + assertEquals("foo", transformer.transform("foo")); + } + + /** + * Test method for + * {@link org.springframework.batch.io.file.transform.RecursiveCollectionItemTransformer#transform(java.lang.Object)}. + * @throws Exception + */ + public void testTransformArray() throws Exception { + String result = (String) transformer.transform(StringUtils.commaDelimitedListToStringArray("foo,bar")); + String[] array = StringUtils.delimitedListToStringArray(result, LINE_SEPARATOR); + assertEquals("foo", array[0]); + assertEquals("bar", array[1]); + } + + /** + * Test method for + * {@link org.springframework.batch.io.file.transform.RecursiveCollectionItemTransformer#transform(java.lang.Object)}. + * @throws Exception + */ + public void testTransformList() throws Exception { + String result = (String) transformer.transform(Arrays.asList(StringUtils.commaDelimitedListToStringArray("foo,bar"))); + String[] array = StringUtils.delimitedListToStringArray(result, LINE_SEPARATOR); + assertEquals("foo", array[0]); + assertEquals("bar", array[1]); + } + + /** + * Test method for + * {@link org.springframework.batch.io.file.transform.RecursiveCollectionItemTransformer#transform(java.lang.Object)}. + * @throws Exception + */ + public void testTransformArrayOfArrays() throws Exception { + String[][] input = new String[][] { StringUtils.commaDelimitedListToStringArray("foo,bar"), + StringUtils.commaDelimitedListToStringArray("spam,bucket") }; + String result = (String) transformer.transform(input); + String[] array = StringUtils.delimitedListToStringArray(result, LINE_SEPARATOR); + assertEquals(4,array.length); + assertEquals("foo", array[0]); + assertEquals("spam", array[2]); + } +} diff --git a/spring-batch-samples/.settings/org.eclipse.jdt.core.prefs b/spring-batch-samples/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..c05bcf41e --- /dev/null +++ b/spring-batch-samples/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,4 @@ +#Wed Feb 13 08:39:25 GMT 2008 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.4 +org.eclipse.jdt.core.compiler.source=1.4 diff --git a/spring-batch-samples/.springBeans b/spring-batch-samples/.springBeans index 9d0265c41..2b728a193 100644 --- a/spring-batch-samples/.springBeans +++ b/spring-batch-samples/.springBeans @@ -1,249 +1,250 @@ - - - 1 - - - - - - src/main/resources/jobs/fixedLengthImportJob.xml - src/main/resources/jobs/multilineJob.xml - src/main/resources/jobs/multilineOrderInputDescriptors.xml - src/main/resources/jobs/multilineOrderIo.xml - src/main/resources/jobs/multilineOrderJob.xml - src/main/resources/jobs/multilineOrderOutputDescriptors.xml - src/main/resources/jobs/tradeJob.xml - src/main/resources/jobs/tradeJobIo.xml - src/main/resources/jobs/restartSample.xml - src/main/resources/data-source-context.xml - src/main/resources/simple-container-definition.xml - src/main/resources/jobs/beanWrapperMapperSampleJob.xml - src/main/resources/jobs/adhocLoopJob.xml - src/main/resources/jobs/infiniteLoopJob.xml - src/main/resources/data-source-context-init.xml - src/main/resources/jobs/xmlStaxJob.xml - src/main/resources/jobs/hibernateJob.xml - src/main/resources/jobs/ibatisJob.xml - src/main/resources/jobs/compositeProcessorSampleJob.xml - src/main/resources/jobs/footballJob.xml - src/main/resources/beanRefContext.xml - src/main/resources/jobs/delegatingJob.xml - src/main/resources/jobs/parallelJob.xml - src/main/resources/jobs/rollbackJob.xml - src/main/resources/jobs/retrySample.xml - - - - - true - false - - src/main/resources/jobs/adhocLoopJob.xml - src/main/resources/data-source-context.xml - src/main/resources/data-source-context-init.xml - src/main/resources/simple-container-definition.xml - - - - - true - false - - src/main/resources/jobs/beanWrapperMapperSampleJob.xml - src/main/resources/data-source-context.xml - src/main/resources/data-source-context-init.xml - src/main/resources/simple-container-definition.xml - - - - - true - false - - src/main/resources/jobs/fixedLengthImportJob.xml - src/main/resources/data-source-context.xml - src/main/resources/data-source-context-init.xml - src/main/resources/simple-container-definition.xml - - - - - true - false - - src/main/resources/jobs/infiniteLoopJob.xml - src/main/resources/data-source-context.xml - src/main/resources/data-source-context-init.xml - src/main/resources/simple-container-definition.xml - - - - - true - false - - src/main/resources/data-source-context.xml - src/main/resources/data-source-context-init.xml - src/main/resources/jobs/multilineJob.xml - src/main/resources/simple-container-definition.xml - - - - - true - false - - src/main/resources/data-source-context.xml - src/main/resources/data-source-context-init.xml - src/main/resources/jobs/restartSample.xml - src/main/resources/simple-container-definition.xml - - - - - true - false - - src/main/resources/data-source-context.xml - src/main/resources/data-source-context-init.xml - src/main/resources/jobs/tradeJob.xml - src/main/resources/jobs/tradeJobIo.xml - src/main/resources/simple-container-definition.xml - - - - - true - false - - src/main/resources/data-source-context.xml - src/main/resources/data-source-context-init.xml - src/main/resources/jobs/multilineOrderInputDescriptors.xml - src/main/resources/jobs/multilineOrderIo.xml - src/main/resources/jobs/multilineOrderJob.xml - src/main/resources/jobs/multilineOrderOutputDescriptors.xml - src/main/resources/simple-container-definition.xml - - - - - true - false - - src/main/resources/data-source-context.xml - src/main/resources/data-source-context-init.xml - src/main/resources/simple-container-definition.xml - src/main/resources/jobs/xmlStaxJob.xml - - - - - true - false - - src/main/resources/data-source-context.xml - src/main/resources/simple-container-definition.xml - src/main/resources/jobs/footballJob.xml - - - - - true - false - - src/main/resources/data-source-context.xml - src/main/resources/simple-container-definition.xml - - - - - true - false - - src/main/resources/data-source-context.xml - src/main/resources/jobs/hibernateJob.xml - src/main/resources/simple-container-definition.xml - - - - - true - false - - src/main/resources/data-source-context.xml - src/main/resources/jobs/ibatisJob.xml - src/main/resources/simple-container-definition.xml - - - - - true - false - - src/main/resources/data-source-context.xml - src/main/resources/jobs/parallelJob.xml - src/main/resources/simple-container-definition.xml - - - - - true - false - - src/main/resources/simple-container-definition.xml - src/main/resources/data-source-context.xml - src/main/resources/data-source-context-init.xml - src/main/resources/jobs/compositeProcessorSampleJob.xml - - - - - true - false - - src/main/resources/data-source-context.xml - src/main/resources/jobs/delegatingJob.xml - src/main/resources/simple-container-definition.xml - - - - - true - false - - src/main/resources/data-source-context.xml - src/main/resources/simple-container-definition.xml - - - - - true - false - - src/main/resources/data-source-context.xml - - - - - true - false - - src/main/resources/data-source-context.xml - src/main/resources/jobs/rollbackJob.xml - src/main/resources/simple-container-definition.xml - src/main/resources/jobs/tradeJobIo.xml - - - - - true - false - - src/main/resources/jobs/retrySample.xml - src/main/resources/simple-container-definition.xml - src/main/resources/data-source-context.xml - src/main/resources/data-source-context-init.xml - - - - + + + 1 + + + + + + + src/main/resources/jobs/fixedLengthImportJob.xml + src/main/resources/jobs/multilineJob.xml + src/main/resources/jobs/multilineOrderInputDescriptors.xml + src/main/resources/jobs/multilineOrderIo.xml + src/main/resources/jobs/multilineOrderJob.xml + src/main/resources/jobs/multilineOrderOutputDescriptors.xml + src/main/resources/jobs/tradeJob.xml + src/main/resources/jobs/tradeJobIo.xml + src/main/resources/jobs/restartSample.xml + src/main/resources/data-source-context.xml + src/main/resources/simple-container-definition.xml + src/main/resources/jobs/beanWrapperMapperSampleJob.xml + src/main/resources/jobs/adhocLoopJob.xml + src/main/resources/jobs/infiniteLoopJob.xml + src/main/resources/data-source-context-init.xml + src/main/resources/jobs/xmlStaxJob.xml + src/main/resources/jobs/hibernateJob.xml + src/main/resources/jobs/ibatisJob.xml + src/main/resources/jobs/compositeProcessorSampleJob.xml + src/main/resources/jobs/footballJob.xml + src/main/resources/beanRefContext.xml + src/main/resources/jobs/delegatingJob.xml + src/main/resources/jobs/parallelJob.xml + src/main/resources/jobs/rollbackJob.xml + src/main/resources/jobs/retrySample.xml + + + + + true + false + + src/main/resources/jobs/adhocLoopJob.xml + src/main/resources/data-source-context.xml + src/main/resources/data-source-context-init.xml + src/main/resources/simple-container-definition.xml + + + + + true + false + + src/main/resources/jobs/beanWrapperMapperSampleJob.xml + src/main/resources/data-source-context.xml + src/main/resources/data-source-context-init.xml + src/main/resources/simple-container-definition.xml + + + + + true + false + + src/main/resources/jobs/fixedLengthImportJob.xml + src/main/resources/data-source-context.xml + src/main/resources/data-source-context-init.xml + src/main/resources/simple-container-definition.xml + + + + + true + false + + src/main/resources/jobs/infiniteLoopJob.xml + src/main/resources/data-source-context.xml + src/main/resources/data-source-context-init.xml + src/main/resources/simple-container-definition.xml + + + + + true + false + + src/main/resources/data-source-context.xml + src/main/resources/data-source-context-init.xml + src/main/resources/jobs/multilineJob.xml + src/main/resources/simple-container-definition.xml + + + + + true + false + + src/main/resources/data-source-context.xml + src/main/resources/data-source-context-init.xml + src/main/resources/jobs/restartSample.xml + src/main/resources/simple-container-definition.xml + + + + + true + false + + src/main/resources/data-source-context.xml + src/main/resources/data-source-context-init.xml + src/main/resources/jobs/tradeJob.xml + src/main/resources/jobs/tradeJobIo.xml + src/main/resources/simple-container-definition.xml + + + + + true + false + + src/main/resources/data-source-context.xml + src/main/resources/data-source-context-init.xml + src/main/resources/jobs/multilineOrderInputDescriptors.xml + src/main/resources/jobs/multilineOrderIo.xml + src/main/resources/jobs/multilineOrderJob.xml + src/main/resources/jobs/multilineOrderOutputDescriptors.xml + src/main/resources/simple-container-definition.xml + + + + + true + false + + src/main/resources/data-source-context.xml + src/main/resources/data-source-context-init.xml + src/main/resources/simple-container-definition.xml + src/main/resources/jobs/xmlStaxJob.xml + + + + + true + false + + src/main/resources/data-source-context.xml + src/main/resources/simple-container-definition.xml + src/main/resources/jobs/footballJob.xml + + + + + true + false + + src/main/resources/data-source-context.xml + src/main/resources/simple-container-definition.xml + + + + + true + false + + src/main/resources/data-source-context.xml + src/main/resources/jobs/hibernateJob.xml + src/main/resources/simple-container-definition.xml + + + + + true + false + + src/main/resources/data-source-context.xml + src/main/resources/jobs/ibatisJob.xml + src/main/resources/simple-container-definition.xml + + + + + true + false + + src/main/resources/data-source-context.xml + src/main/resources/jobs/parallelJob.xml + src/main/resources/simple-container-definition.xml + + + + + true + false + + src/main/resources/simple-container-definition.xml + src/main/resources/data-source-context.xml + src/main/resources/data-source-context-init.xml + src/main/resources/jobs/compositeProcessorSampleJob.xml + + + + + true + false + + src/main/resources/data-source-context.xml + src/main/resources/jobs/delegatingJob.xml + src/main/resources/simple-container-definition.xml + + + + + true + false + + src/main/resources/data-source-context.xml + src/main/resources/simple-container-definition.xml + + + + + true + false + + src/main/resources/data-source-context.xml + + + + + true + false + + src/main/resources/data-source-context.xml + src/main/resources/jobs/rollbackJob.xml + src/main/resources/simple-container-definition.xml + src/main/resources/jobs/tradeJobIo.xml + + + + + true + false + + src/main/resources/jobs/retrySample.xml + src/main/resources/simple-container-definition.xml + src/main/resources/data-source-context.xml + src/main/resources/data-source-context-init.xml + + + +