diff --git a/infrastructure/src/main/java/org/springframework/batch/item/processor/CompositeItemTransformer.java b/infrastructure/src/main/java/org/springframework/batch/item/processor/CompositeItemTransformer.java new file mode 100644 index 000000000..6f5945345 --- /dev/null +++ b/infrastructure/src/main/java/org/springframework/batch/item/processor/CompositeItemTransformer.java @@ -0,0 +1,42 @@ +package org.springframework.batch.item.processor; + +import java.util.Iterator; +import java.util.List; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; + +/** + * Composite {@link ItemTransformer} that passes the item through a sequence + * of injected ItemTransformers (return value of previous transformation + * is the entry value of the next). + * + * @author Robert Kasanicky + */ +public class CompositeItemTransformer implements ItemTransformer, InitializingBean { + + private List itemTransformers; + + public Object transform(Object item) { + Object result = item; + for (Iterator iterator = itemTransformers.listIterator(); iterator.hasNext();) { + result = ((ItemTransformer)iterator.next()).transform(result); + } + return result; + } + + public void afterPropertiesSet() throws Exception { + Assert.notEmpty(itemTransformers); + for (Iterator iterator = itemTransformers.iterator(); iterator.hasNext();) { + Assert.isInstanceOf(ItemTransformer.class, iterator.next()); + } + } + + /** + * @param itemTransformers will be chained to produce a composite transformation. + */ + public void setItemTransformers(List itemTransformers) { + this.itemTransformers = itemTransformers; + } + +} diff --git a/infrastructure/src/main/java/org/springframework/batch/item/processor/ItemTransformer.java b/infrastructure/src/main/java/org/springframework/batch/item/processor/ItemTransformer.java new file mode 100644 index 000000000..ab48a54fb --- /dev/null +++ b/infrastructure/src/main/java/org/springframework/batch/item/processor/ItemTransformer.java @@ -0,0 +1,11 @@ +package org.springframework.batch.item.processor; + +/** + * Interface for item transformations during processing phase. + * + * @author Robert Kasanicky + */ +public interface ItemTransformer { + + Object transform(Object item); +} diff --git a/infrastructure/src/main/java/org/springframework/batch/item/processor/OutputSourceItemProcessor.java b/infrastructure/src/main/java/org/springframework/batch/item/processor/OutputSourceItemProcessor.java index 23b0e4054..4e0c1ca07 100644 --- a/infrastructure/src/main/java/org/springframework/batch/item/processor/OutputSourceItemProcessor.java +++ b/infrastructure/src/main/java/org/springframework/batch/item/processor/OutputSourceItemProcessor.java @@ -9,6 +9,7 @@ import org.springframework.batch.restart.GenericRestartData; import org.springframework.batch.restart.RestartData; import org.springframework.batch.restart.Restartable; import org.springframework.batch.statistics.StatisticsProvider; +import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; /** @@ -16,23 +17,33 @@ import org.springframework.util.Assert; * {@link StatisticsProvider} where the {@link OutputSource} does. * * @author Dave Syer + * @author Robert Kasanicky */ public class OutputSourceItemProcessor implements ItemProcessor, Restartable, Skippable, - StatisticsProvider { + StatisticsProvider, InitializingBean { private OutputSource source; - /* (non-Javadoc) + /** + * Calls {@link #doProcess(Object)} and then writes the result to the output source. + * * @see org.springframework.batch.item.ItemProcessor#process(java.lang.Object) */ - public void process(Object data) throws Exception { - source.write(data); + final public void process(Object item) throws Exception { + Object result = doProcess(item); + source.write(result); + } + + /** + * By default returns the argument. This method is an extension point + * meant to be overridden by subclasses that implement processing logic. + */ + protected Object doProcess(Object item) { + return item; } /** * Setter for output source. - * - * @param source */ public void setOutputSource(OutputSource source) { this.source = source; @@ -85,4 +96,9 @@ public class OutputSourceItemProcessor implements ItemProcessor, Restartable, Sk } } + + public void afterPropertiesSet() throws Exception { + Assert.notNull(source); + } + } diff --git a/infrastructure/src/main/java/org/springframework/batch/item/processor/TransformerOutputSourceItemProcessor.java b/infrastructure/src/main/java/org/springframework/batch/item/processor/TransformerOutputSourceItemProcessor.java new file mode 100644 index 000000000..c0007336c --- /dev/null +++ b/infrastructure/src/main/java/org/springframework/batch/item/processor/TransformerOutputSourceItemProcessor.java @@ -0,0 +1,35 @@ +package org.springframework.batch.item.processor; + +import org.springframework.batch.io.OutputSource; +import org.springframework.util.Assert; + +/** + * Transforms the item using injected {@link ItemTransformer} + * before it is written to output by {@link OutputSource}. + * + * @author Robert Kasanicky + */ +public class TransformerOutputSourceItemProcessor extends OutputSourceItemProcessor { + + private ItemTransformer itemTransformer; + + /** + * Transform the item using the {@link #itemTransformer}. + */ + protected Object doProcess(Object item) { + return itemTransformer.transform(item); + } + + /** + * @param itemTransformer will transform the item before + * it is passed to {@link OutputSource}. + */ + public void setItemTransformer(ItemTransformer itemTransformer) { + this.itemTransformer = itemTransformer; + } + + public void afterPropertiesSet() throws Exception { + super.afterPropertiesSet(); + Assert.notNull(itemTransformer); + } +} diff --git a/infrastructure/src/test/java/org/springframework/batch/item/processor/CompositeItemTransformerTests.java b/infrastructure/src/test/java/org/springframework/batch/item/processor/CompositeItemTransformerTests.java new file mode 100644 index 000000000..1168a3d57 --- /dev/null +++ b/infrastructure/src/test/java/org/springframework/batch/item/processor/CompositeItemTransformerTests.java @@ -0,0 +1,95 @@ +package org.springframework.batch.item.processor; + +import java.util.ArrayList; + +import junit.framework.TestCase; + +import org.easymock.MockControl; + +/** + * Tests for {@link CompositeItemTransformer}. + * + * @author Robert Kasanicky + */ +public class CompositeItemTransformerTests extends TestCase { + + private CompositeItemTransformer composite = new CompositeItemTransformer(); + + private ItemTransformer transformer1; + private ItemTransformer transformer2; + + private MockControl tControl1 = MockControl.createControl(ItemTransformer.class); + private MockControl tControl2 = MockControl.createControl(ItemTransformer.class); + + protected void setUp() throws Exception { + transformer1 = (ItemTransformer) tControl1.getMock(); + transformer2 = (ItemTransformer) tControl2 .getMock(); + + composite.setItemTransformers(new ArrayList() {{ + add(transformer1); add(transformer2); + }}); + + composite.afterPropertiesSet(); + } + + /** + * Regular usage scenario - item is passed through the processing chain, + * return value of the of the last transformation is returned by the composite. + */ + public void testTransform() throws Exception { + Object item = new Object(); + Object itemAfterFirstTransfromation = new Object(); + Object itemAfterSecondTransformation = new Object(); + + transformer1.transform(item); + tControl1.setReturnValue(itemAfterFirstTransfromation); + + transformer2.transform(itemAfterFirstTransfromation); + tControl2.setReturnValue(itemAfterSecondTransformation); + + tControl1.replay(); + tControl2.replay(); + + assertSame(itemAfterSecondTransformation, composite.transform(item)); + + tControl1.verify(); + tControl2.verify(); + } + + /** + * The list of transformers must not be null or empty and + * can contain only instances of {@link ItemTransformer}. + */ + public void testAfterPropertiesSet() throws Exception { + + // value not set + composite.setItemTransformers(null); + try { + composite.afterPropertiesSet(); + fail(); + } + catch (IllegalArgumentException e) { + // expected + } + + // empty list + composite.setItemTransformers(new ArrayList()); + try { + composite.afterPropertiesSet(); + fail(); + } + catch (IllegalArgumentException e) { + // expected + } + + // invalid list member + composite.setItemTransformers(new ArrayList() {{ add(new Object()); }}); + try { + composite.afterPropertiesSet(); + fail(); + } + catch (IllegalArgumentException e) { + // expected + } + } +} diff --git a/infrastructure/src/test/java/org/springframework/batch/item/processor/OutputSourceItemProcessorTests.java b/infrastructure/src/test/java/org/springframework/batch/item/processor/OutputSourceItemProcessorTests.java index 879fff33f..295c2137d 100644 --- a/infrastructure/src/test/java/org/springframework/batch/item/processor/OutputSourceItemProcessorTests.java +++ b/infrastructure/src/test/java/org/springframework/batch/item/processor/OutputSourceItemProcessorTests.java @@ -47,6 +47,7 @@ public class OutputSourceItemProcessorTests extends TestCase { protected void setUp() throws Exception { source = new MockOutputSource("test"); processor.setOutputSource(source); + processor.afterPropertiesSet(); } public void testProcess() throws Exception { @@ -126,6 +127,20 @@ public class OutputSourceItemProcessorTests extends TestCase { assertEquals("after skip", list.get(0)); } + /** + * Output source property must be set. + */ + public void testAfterPropertiesSet() throws Exception { + processor.setOutputSource(null); + try { + processor.afterPropertiesSet(); + fail(); + } + catch (IllegalArgumentException e) { + // expected + } + } + private List list = new ArrayList(); /** diff --git a/infrastructure/src/test/java/org/springframework/batch/item/processor/TransformerOutputSourceItemProcessorTests.java b/infrastructure/src/test/java/org/springframework/batch/item/processor/TransformerOutputSourceItemProcessorTests.java new file mode 100644 index 000000000..c727a90c9 --- /dev/null +++ b/infrastructure/src/test/java/org/springframework/batch/item/processor/TransformerOutputSourceItemProcessorTests.java @@ -0,0 +1,71 @@ +package org.springframework.batch.item.processor; + +import junit.framework.TestCase; + +import org.easymock.MockControl; +import org.springframework.batch.io.OutputSource; + +/** + * Tests for {@link TransformerOutputSourceItemProcessor}. + * + * @author Robert Kasanicky + */ +public class TransformerOutputSourceItemProcessorTests extends TestCase { + + private TransformerOutputSourceItemProcessor processor = new TransformerOutputSourceItemProcessor(); + + private ItemTransformer transformer; + private OutputSource outputSource; + + private MockControl tControl = MockControl.createControl(ItemTransformer.class); + private MockControl outControl = MockControl.createControl(OutputSource.class); + + protected void setUp() throws Exception { + transformer = (ItemTransformer) tControl.getMock(); + outputSource = (OutputSource) outControl.getMock(); + + processor.setItemTransformer(transformer); + processor.setOutputSource(outputSource); + + processor.afterPropertiesSet(); + } + + /** + * Regular usage scenario - item is passed to transformer + * and the result of transformation is passed to output source. + */ + public void testProcess() throws Exception { + Object item = new Object(); + Object itemAfterTransformation = new Object(); + + transformer.transform(item); + tControl.setReturnValue(itemAfterTransformation); + + outputSource.write(itemAfterTransformation); + outControl.setVoidCallable(); + + tControl.replay(); + outControl.replay(); + + processor.process(item); + + tControl.verify(); + outControl.verify(); + } + + /** + * Item transformer must be set. + */ + public void testAfterPropertiesSet() throws Exception { + + // value not set + processor.setItemTransformer(null); + try { + processor.afterPropertiesSet(); + fail(); + } + catch (IllegalArgumentException e) { + // expected + } + } +}