diff --git a/docs/src/site/docbook/reference/readersAndWriters.xml b/docs/src/site/docbook/reference/readersAndWriters.xml index f47fdb5fd..aebae3723 100644 --- a/docs/src/site/docbook/reference/readersAndWriters.xml +++ b/docs/src/site/docbook/reference/readersAndWriters.xml @@ -50,12 +50,12 @@ ItemReader is a basic interface for generic input operations: - public interface ItemReader<T> { + { T read() throws Exception, UnexpectedInputException, ParseException; } - +]]> The read method defines the most essential contract of the ItemReader, calling it returns one @@ -92,12 +92,12 @@ As with ItemReader, ItemWriter is a fairly generic interface: - public interface ItemWriter<T> { + { - void write(List<? extends T> items) throws Exception; + void write(List items) throws Exception; } - +]]> As with read on ItemReader, write provides @@ -121,7 +121,7 @@ readers and writers need to be opened, closed, and require a mechanism for persisting state: - public interface ItemStream { + +]]> Before describing each method, its worth briefly mentioning the ExecutionContext. Clients of an @@ -189,11 +189,11 @@ fields so that the fields may be accessed either by index or name as patterned after ResultSet: - String[] tokens = new String[]{"foo", "1", "true"}; + + boolean booleanValue = fs.readBoolean(2);]]> There are many more options on the FieldSet interface, such as Date, long, @@ -226,9 +226,9 @@ Framework, Chapter 4.Resources. Therefore, this guide will not go into the details of creating Resource objects. However, a simple example of a - file system resource can be found below: + file system resource can be found below: + ]]> In complex batch environments the directory structures are often managed by the EAI infrastructure where drop zones for external @@ -316,10 +316,10 @@ As with RowMapper, which takes a low level construct such as ResultSet and returns an Object, flat file procesing requires the same construct to convert a String - line into an Object:public interface LineMapper<T> { + line into an Object: { T mapLine(String line, int lineNumber) throws Exception; -} +}]]>
@@ -334,11 +334,11 @@ LineTokenizer to translate a line of data from a resource into an object of the desired type: - public interface FieldSetMapper<T> { + { T mapFieldSet(FieldSet fieldSet); - } + }]]> The pattern used is the same as RowMapper used by JdbcTemplate. @@ -354,11 +354,11 @@ FieldSet is necessary. In Spring Batch, this is called a LineTokenizer: - public interface LineTokenizer { + + }]]> The contract of a LineTokenizer is such that, given a line of input (in theory the @@ -418,14 +418,14 @@ In code, the above flow looks like the following: - String line = readLine(); + + return null;]]> Exception handling has been removed for clarity. @@ -433,16 +433,16 @@ The following example will be used to illustrate this using an actual domain scenario. This particular batch job reads in football - players from the following file: ID,lastName,firstName,position,birthYear,debutYear + players from the following file: + "AdamCh00,Adams,Charlie,wr,1979,2003" ]]> The contents of this file will be mapped to the following Player - domain object: + domain object: + ]]> In order to map a FieldSet into a Player object, a FieldSetMapper that returns players needs to be defined: - - protected static class PlayerFieldSetMapper implements FieldSetMapper<Player> { + { public Object mapLine(FieldSet fieldSet) { Player player = new Player(); @@ -482,14 +482,14 @@ return player; } - } + } ]]> The file can then be read by correctly constructing a FlatFileItemReader and calling read: - - FlatFileItemReader<Player> itemReader = new FlatFileItemReader<Player>(); + itemReader = new FlatFileItemReader(); itemReader.setResource(new FileSystemResource("resources/players.csv")); //DelimitedLineTokenizer defaults to comma as it's delimiter itemReader.setLineTokenizer(new DelimitedLineTokenizer()); @@ -497,7 +497,7 @@ itemReader.open(new ExecutionContext()); Player player = itemReader.read(); - +]]> Each call to read will return a new Player object from each line in the file. When the end of the file is @@ -515,9 +515,9 @@ fields in the flat file are injected into the LineTokenizer: - + + ]]> a FieldSetMapper can this use this information as follows: @@ -557,14 +557,14 @@ FieldSetMapper configuration looks like the following: - <bean id="fieldSetMapper" - class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper"> - <property name="prototypeBeanName" value="player" /> - </bean> + + + - <bean id="player" + + scope="prototype" />]]> For each entry in the FieldSet, the mapper will look for a corresponding setter on a new instance of the @@ -584,11 +584,11 @@ organizations that use flat files use fixed length formats. An example fixed length file is below: - UK21341EAH4121131.11customer1 + + UK21341EAH4521535.11customer5]]> While this looks like one large field, it actually represent 4 distinct fields: @@ -618,14 +618,14 @@ FixedLengthLineTokenizer, each of these lengths must be provided in the form of ranges: - - <bean id="fixedLengthLineTokenizer" - class="org.springframework.batch.io.file.transform.FixedLengthTokenizer"> - <property name="names" value="ISIN, Quantity, Price, Customer" /> - <property name="columns" value="1-12, 13-15, 16-20, 21-29" /> - </bean> + + + + - +]]> This LineTokenizer will return the same FieldSet as if a dlimiter had been used, @@ -637,18 +637,18 @@ specialized property editor be configured anywhere in the ApplicationContext: - - <bean id="customEditorConfigurer" class="org.springframework.beans.factory.config.CustomEditorConfigurer"> - <property name="customEditors"> - <map> - <entry key="org.springframework.batch.item.file.transform.Range[]"> - <bean class="org.springframework.batch.item.file.transform.RangeArrayPropertyEditor" /> - </entry> - </map> - </property> - </bean> + + + + + + + + + - +]]>
@@ -660,7 +660,7 @@ might have records spanning multiple lines with multiple formats. The following excerpt from a file illustrates this: - HEA;0013100345;2007-02-15 + + FOT;2;2;267.34]]> Everything between the line starting with 'HEA' and the line starting with 'FOT' is considered one record. The PrefixMatchingCompositeLineTokenizer makes this easier by matching the prefix in a line with a particular tokenizer: - <bean id="orderFileDescriptor" - class="org.springframework.batch.io.file.transform.PrefixMatchingCompositeLineTokenizer"> - <property name="tokenizers"> - <map> - <entry key="HEA" value-ref="headerRecordDescriptor" /> - <entry key="FOT" value-ref="footerRecordDescriptor" /> - <entry key="BCU" value-ref="businessCustomerLineDescriptor" /> - <entry key="NCU" value-ref="customerLineDescriptor" /> - <entry key="BAD" value-ref="billingAddressLineDescriptor" /> - <entry key="SAD" value-ref="shippingAddressLineDescriptor" /> - <entry key="BIN" value-ref="billingLineDescriptor" /> - <entry key="SIN" value-ref="shippingLineDescriptor" /> - <entry key="LIT" value-ref="itemLineDescriptor" /> - <entry key="" value-ref="defaultLineDescriptor" /> - </map> - </property> - </bean> + + + + + + + + + + + + + + + + ]]> This ensures that the line will be parsed correctly, which is especially important for fixed length input. Any users of the @@ -733,7 +733,7 @@ IncorrectTokenCountException is thrown, which contains the number of tokens encountered, and the number expected: - + +]]> Because the tokenizer was configured with 4 columns, but only 3 tokens were found in the file, an IncorrectTokenCountException was @@ -758,9 +758,9 @@ requirements when parsing because unlike a delimited format, each column must strictly adhere to the width defined for it. If the total line length doesn't add up to the widest value of this column, - an exception is thrown: + an exception is thrown: - + +]]> The configured ranges for the tokenizer above are: 1-5, 6-10, and 11-15, thus the total length of the line expected is 15. @@ -785,14 +785,14 @@ For this reason, validation of line length can be turned off via the 'strict' property: - + +]]> The above example is almost identical to the one before it, except the tokenizer.setStrict(false) was called. This setting tells @@ -821,11 +821,11 @@ writing to a file. In Spring Batch this is the LineAggregator: - public interface LineAggregator<T> { + { public String aggregate(T item); - } + }]]> The LineAggregator is the opposite of a LineTokenizer. @@ -862,21 +862,21 @@ FlatFileItemWriter expresses this in code: - public void write(T item) throws Exception { + + }]]> A simple configuration with the smallest ammount of setters would look like the following: - <bean id="itemWriter" - class="org.springframework.batch.io.file.FlatFileItemWriter"> - <property name="resource" - value="file:target/test-outputs/20070122.testStream.multilineStep.txt" /> - <property name="lineAggregator"> - <bean class="org.springframework.batch.item.file.transform.PassThroughLineAggregator"/> - </property> - </bean> + + + + + + ]]>
@@ -980,28 +980,28 @@ stream. First, lets examine a set of XML records that the StaxEventItemReader can process. - -<?xml version="1.0" encoding="UTF-8"?> -<records> - <trade xmlns="http://springframework.org/batch/sample/io/oxm/domain"> - <isin>XYZ0001</isin> - <quantity>5</quantity> - <price>11.39</price> - <customer>Customer1</customer> - </trade> - <trade xmlns="http://springframework.org/batch/sample/io/oxm/domain"> - <isin>XYZ0002</isin> - <quantity>2</quantity> - <price>72.99</price> - <customer>Customer2c</customer> - </trade> - <trade xmlns="http://springframework.org/batch/sample/io/oxm/domain"> - <isin>XYZ0003</isin> - <quantity>9</quantity> - <price>99.99</price> - <customer>Customer3</customer> - </trade> -</records> + + + + XYZ0001 + 5 + 11.39 + Customer1 + + + XYZ0002 + 2 + 72.99 + Customer2c + + + XYZ0003 + 9 + 99.99 + Customer3 + +]]> To be able to process the XML records the following is needed: @@ -1023,22 +1023,22 @@ - <property name="itemReader"> - <bean class="org.springframework.batch.io.xml.StaxEventItemReader"> - <property name="fragmentRootElementName" value="trade" /> - <property name="resource" value="data/staxJob/input/20070918.testStream.xmlFileStep.xml" /> - <property name="fragmentDeserializer"> - <bean class="org.springframework.batch.io.xml.oxm.UnmarshallingEventReaderDeserializer"> - <constructor-arg> - <bean class="org.springframework.oxm.xstream.XStreamMarshaller"> - <property name="aliases" ref="aliases" /> - </bean> - </constructor-arg> - </bean> - </property> - </bean> -</property> - + + + + + + + + + + + + + + + + ]]> Notice that in this example we have chosen to use an XStreamMarshaller that requires an alias passed @@ -1049,16 +1049,16 @@ the map. In the configuration file we can use a spring configuration utility to describe the required alias as follows: - - <util:map id="aliases"> - <entry key="trade" - value="org.springframework.batch.sample.domain.Trade" /> - <entry key="isin" value="java.lang.String" /> - <entry key="quantity" value="long" /> - <entry key="price" value="java.math.BigDecimal" /> - <entry key="customer" value="java.lang.String" /> - </util:map> - + + + + + + + + ]]> On input the reader reads the XML resource until it recognizes a new fragment is about to start (by matching the tag name by default). @@ -1072,7 +1072,7 @@ injection provided by the spring configuration would look something like the following: - + +]]>
@@ -1118,13 +1118,13 @@ MarshallingEventWriterSerializer. The Spring configuration for this setup looks as follows: - <bean class="org.springframework.batch.item.xml.StaxEventItemWriter" id="tradeStaxWriter"> - <property name="resource"value="file:target/test-outputs/20070918.testStream.xmlFileStep.output.xml" /> - <property name="serializer" ref="tradeMarshallingSerializer" /> - <property name="rootTagName" value="trades" /> - <property name="overwriteOutput" value="true" /> -</bean> - + + + + + + +]]> The configuration sets up the three required properties and optionally sets the overwriteOutput=true, mentioned earlier in the @@ -1144,7 +1144,7 @@ all of the points discussed, demonstrating the programmatic setup of the required properties. - StaxEventItemWriter staxItemWriter = new StaxEventItemWriter() + + staxItemWriter.flush()]]> For a complete example configuration of XML input and output and a corresponding Job see the sample xmlStaxJob. @@ -1189,11 +1189,11 @@ java.io.File. Both XML and Flat File resources can be configured using standard Spring constructs: - <bean id="flatFileItemReader" - class="org.springframework.batch.item.file.FlatFileItemReader"> - <property name="resource" - value="file://outputs/20070122.testStream.CustomerReportStep.TEMP.txt" /> - </bean> + + + ]]> The above Resource will load the file from the file system, at the location specificied. Note that absolute locations @@ -1203,10 +1203,10 @@ determined at runtime as a parameter to the job. This could be solved using '-D' parameters, i.e. a system property: - <bean id="flatFileItemReader" - class="org.springframework.batch.item.file.FlatFileItemReader"> - <property name="resource" value="${input.file.name}" /> -</bean> + + +]]> All that would be required for this solution to work would be a system argument (-Dinput.file.name="file://file.txt"). (Note that although @@ -1223,10 +1223,10 @@ either job name, step name, or any values from the JobParameters, by surrounding them with %: - <bean id="inputFile" - class="org.springframework.batch.core.resource.StepExecutionResourceProxy" /> - <property name="filePattern" value="//%JOB_NAME%/%STEP_NAME%/%file.name%" /> - </bean> + + + ]]> Assuming a job name of 'fooJob', and a step name of 'fooStep', and the key-value pair of 'file.name="fileName.txt"' is in the @@ -1237,11 +1237,11 @@ StepExecution, it must be registered as a StepListener: - <bean id="fooStep" parent="abstractStep" + + p:itemWriter-ref="itemWriter"> + + ]]> The StepListener interface will be discussed in more detail in Chapter 4. For now, it is sufficient to know that the @@ -1257,20 +1257,20 @@ for both XML and FlatFile processing. Consider the following files in a directory: - file-1.txt file-2.txt ignored.txt + file-1.txt and file-2.txt are formatted the same and for business reasons should be processed together. The MuliResourceItemReader can be used to read in both files by using wildcards: - - <bean id="multiResourceReader" class="org.springframework.batch.item.SortedMultiResourceItemReader"> - <property name="resources" value="classpath:data/multiResourceJob/input/file-*.txt" /> - <property name="delegate" ref="flatFileItemReader" /> - </bean> + + + + - +]]> The referenced delegate is a simple FlatFileItemReader. The above configuration will @@ -1357,18 +1357,18 @@ DataSource. The following database schema will be used as an example: - CREATE TABLE CUSTOMER ( + +);]]> Many people prefer to use a domain object for each row, so we'll use an implementation of the RowMapper interface to map a CustomerCredit object: - public class CustomerCreditRowMapper implements RowMapper { + +}]]> Because JdbcTemplate is so familiar to users of Spring, and the JdbcCursorItemReader @@ -1395,12 +1395,12 @@ CUSTOMER database. The first example will be using JdbcTemplate: - + +]]> After running this code snippet the customerCredits list will contain 1,000 CustomerCredit objects. In the @@ -1411,7 +1411,7 @@ constrast this with the approach of the JdbcCursorItemReader: - + +]]> After running this code snippet the counter will equal 1,000. If the code above had put the returned customerCredit into a list, the @@ -1542,7 +1542,7 @@ configuration using the same 'customer credit' example as the JDBC reader: - + +]]> This configured ItemReader will return CustomerCredit objects in the exact same manner @@ -1600,12 +1600,12 @@ The SimpleDelegatingPagingQueryProvider requires that you specify a select clause and a from clause. You can also provide an optional where clause. These clauses will be used to - build an SQL statement combined with the required sortKey. + build an SQL statement combined with the required sortKey. After the reader has been opened, it will pass back one item per call to read in the same basic fashion as any other ItemReader. The paging happens behind the - scenes when additional rows are needed. + scenes when additional rows are needed. Below is an example configuration using a similar 'customer credit' example as the cursor based ItemReaders above: @@ -1626,7 +1626,7 @@ - + ]]> @@ -1635,7 +1635,7 @@ CustomerCredit objects using the ParameterizedRowMapper that must be specified. The 'pageSize' property determines the number of entities read from - the database for each query execution. + the database for each query execution. The 'parameterValues' property can be used to specify a Map of parameter values for the query. If you use named parameters in the @@ -1683,6 +1683,56 @@ mapping file. The 'pageSize' property determines the number of entities read from the database for each query execution.
+ +
+ IbatisPagingItemReader + + If you use IBATIS for your data access ten you can use the + IbatisPagingItemReader which, as the name + indicates, is an implementation of a paging + ItemReader. IBATIS doesn't have direct support + for reading rows in pages but by providing a couple of standard + variables you can add paging support to your IBATIS queries. + + Here is an example of a configuration for a + IbatisPagingItemReader reading CustomerCredits + as in the examples above: + + + + + + +]]> + + The IbatisPagingItemReader configuration + above references an IBATIS query called "getPagedCustomerCredits". + Here is an example of what that query should look like for + MySQL. + + + select id, name, credit from customer order by id asc LIMIT #_skiprows#, #_pagesize# + +]]> + + The _skiprows and + _pagesize variables are provided by the + IbatisPagingItemReader and there is also a + _page variable that can be used if necessary. + The syntax for the paging queries varies with the database used. Here + is an example for Oracle (unfortunatey we need to use CDATA for some + operators since this belongs in an XML document): + + <select id="getPagedCustomerCredits" resultMap="customerCreditResult"> + select * from ( + select * from ( + select t.id, t.name, t.credit, ROWNUM ROWNUM_ from customer t order by id + ) where ROWNUM_ <![CDATA[ > ]]> ( #_page# * #_pagesize# ) + ) where ROWNUM <![CDATA[ <= ]]> #_pagesize# + </select> + +
@@ -1746,12 +1796,12 @@ real complication is how those keys are obtained. The KeyCollector interface abstracts this: - public interface KeyCollector { + + }]]> The primary method in this interface is the retrieveKeys method. It is expected that this @@ -1766,12 +1816,12 @@ retrieveKeys method can then use this value to retrieve a subset of the original keys: - ExecutionContext executionContext = new ExecutionContext(); + + //keys should now contains 500 through 1,000]]> This generalization illustrates the KeyCollector contract. If we assume that @@ -1834,36 +1884,36 @@ The following code helps illustrate how to setup and use a SingleColumnJdbcKeyCollector: - SingleColumnJdbcKeyCollector keyCollector = new SingleColumnJdbcKeyCollector(getJdbcTemplate(), + ? order by ID"); ExecutionContext executionContext = new ExecutionContext(); List keys = keyStrategy.retrieveKeys(new ExecutionContext()); - for (int i = 0; i < keys.size(); i++) { + for (int i = 0; i < keys.size(); i++) { System.out.println(keys.get(i)); - } + }]]> If this code were run in the proper environment with the correct database tables setup, then it would output the following: - 1 + +5]]> Now, let's modify the code slightly to show what would happen if the code were started again after a restart, having failed after processing key 3 successfully: - SingleColumnJdbcKeyCollector keyCollector = new SingleColumnJdbcKeyCollector(getJdbcTemplate(), + ? order by ID"); ExecutionContext executionContext = new ExecutionContext(); @@ -1871,19 +1921,19 @@ List keys = keyStrategy.retrieveKeys(executionContext); - for (int i = 0; i < keys.size(); i++) { + for (int i = 0; i < keys.size(); i++) { System.out.println(keys.get(i)); - } + }]]> Running this code snippet would result in the following: - 4 -5 + The key difference between the two examples is the following line: - keyStrategy.updateContext(new Long(3), executionContext); + This tells the key collector to update the provided ExecutionContext with the key of three. This @@ -1894,7 +1944,7 @@ ExecutionContext that was updated to contain 3, the argument of 3 will be passed to the restartSql: - keyCollector.setRestartSql("SELECT ID from T_FOOS where ID > ? order by ID"); + ? order by ID");]]> This will cause only keys 4 and 5 to be returned, since they are the only ones with an ID greater than 3. @@ -1921,13 +1971,13 @@ An ExecutionContextRowMapper provides this: - public interface ExecutionContextRowMapper extends RowMapper { + +]]> The ExecutionContextRowMapper interface extends the standard RowMapper interface to @@ -2025,36 +2075,36 @@ items to be skipped reliably. The following example illustrates how to configure the HibernateAwareItemWriter: - <bean id="hibernateItemWriter" - class="org.springframework.batch.item.database.HibernateAwareItemWriter"> - <property name="sessionFactory" ref="sessionFactory" /> - <property name="delegate" ref="customerCreditWriter" /> - </bean> + + + + - <bean id="customerCreditWriter" - class="org.springframework.batch.sample.dao.HibernateCreditDao"> - <property name="sessionFactory" ref="sessionFactory" /> - </bean> + + + - +]]> If you are using JPA then the JpaAwareItemWriter provides comparable functionality. The following example illustrates how to configure the JpaAwareItemWriter: - <bean id="hibernateItemWriter" - class="org.springframework.batch.item.database.JpaAwareItemWriter"> - <property name="entityManagerFactory" ref="entityManagerFactory" /> - <property name="delegate" ref="customerCreditWriter" /> - </bean> + + + + - <bean id="customerCreditWriter" - class="org.springframework.batch.sample.dao.JpaCreditDao"> - <property name="entityManagerFactory" ref="entityManagerFactory" /> - </bean> + + + - +]]>
@@ -2079,12 +2129,12 @@ standard Spring method invoking delegator pattern and are fairly simple to set up. Below is an example of the reader: - <bean id="itemReader" class="org.springframework.batch.item.adapter.ItemReaderAdapter"> - <property name="targetObject" ref="fooService" /> - <property name="targetMethod" value="generateFoo" /> - </bean> + + + + - <bean id="fooService" class="org.springframework.batch.item.sample.FooService" /> + ]]> One important point to note is that the contract of the targetMethod must be the same as the contract for read: when @@ -2095,13 +2145,13 @@ ItemWriter. The ItemWriter implementation is equally as simple: - <bean id="itemWriter" class="org.springframework.batch.item.adapter.ItemWriterAdapter"> - <property name="targetObject" ref="fooService" /> - <property name="targetMethod" value="processFoo" /> - </bean> + + + + - <bean id="fooService" class="org.springframework.batch.item.sample.FooService" /> - + +]]>
@@ -2116,11 +2166,11 @@ that contains another ItemReader. For example: - public class CompositeItemWriter<T> implements ItemWriter<T> { + implements ItemWriter { - ItemWriter<T> itemWriter; + ItemWriter itemWriter; - public CompositeItemWriter(ItemWriter<T> itemWriter) { + public CompositeItemWriter(ItemWriter itemWriter) { this.itemWriter = itemWriter; } @@ -2138,7 +2188,7 @@ public void flush() throws FlushFailedException { itemWriter.flush(); } -} +}]]> The class above contains another ItemWriter that it delgates to after having provided some business logic. It should @@ -2155,10 +2205,10 @@ just want to modify the item. For this scenario, Spring Batch provides the ItemTransformer interface: - public interface ItemProcessor<I, O> { + { O process(I item) throws Exception; -} +}]]> An ItemTransformer is very simple, given one object, transorm it and return another. The object provided may or may not @@ -2174,7 +2224,7 @@ ItemTransformer can be written that performs the conversion: - public class Foo {} + + }]]> In the very simple example above, there is a class Foo, a class Bar, and a @@ -2214,10 +2264,10 @@ Bar returned. The resulting Bar will then be written: - ItemTransformerItemWriter itemTransformerItemWriter = new ItemTransformerItemWriter(); + + itemTransformerItemWriter.write(new Foo());]]>
The Delegate Pattern and Registering with the Step @@ -2250,7 +2300,7 @@ Transformed to Bar, which will be transformed to Foobar and written out: - public class Foo {} + + }]]> A FooTransformer and BarTransformer can be 'chained' together to give the resultant Foobar: - CompositeItemProcessor compositeTransformer = new CompositeItemProcessor(); + + compositeTransformer.setItemTransformers(itemTransformers);]]> The compositeTransformer could be said to accept a Foo and return a Foobar. @@ -2331,11 +2381,11 @@ rather provides a very simple interface that can be implemented by any number of frameworks: - public interface Validator { + + }]]> The contract is that the validate method will throw an exception if the object is invalid, and return normally if @@ -2464,11 +2514,11 @@ basic contract of ItemReader, read: - public class CustomItemReader<T> implements ItemReader<T>{ + implements ItemReader{ - List<T> items; + List items; - public CustomItemReader(List<T> items) { + public CustomItemReader(List items) { this.items = items; } @@ -2480,23 +2530,23 @@ } return null; } - } + }]]> This very simple class takes a list of items, and returns one at a time, removing it from the list. When the list empty, it returns null, thus satisfying the most basic requirements of an ItemReader, as illustrated below: - List<String> items = new ArrayList<String>(); + items = new ArrayList(); items.add("1"); items.add("2"); items.add("3"); - ItemReader itemReader = new CustomItemReader<String>(items); + ItemReader itemReader = new CustomItemReader(items); assertEquals("1", itemReader.read()); assertEquals("2", itemReader.read()); assertEquals("3", itemReader.read()); - assertNull(itemReader.read()); + assertNull(itemReader.read());]]>
Making the <classname>ItemReader</classname> @@ -2518,20 +2568,20 @@ implemented with the <classname>ItemStream</classname> interface:</para> - <programlisting> public class CustomItemReader<T> implements ItemReader<T>, ItemStream { + <programlisting><![CDATA[ public class CustomItemReader<T> implements ItemReader<T>, ItemStream { - List<T> items; + List<T> items; int currentIndex = 0; private static final String CURRENT_INDEX = "current.index"; - public CustomItemReader(List<T> items) { + public CustomItemReader(List<T> items) { this.items = items; } public T read() throws Exception, UnexpectedInputException, ParseException { - if (currentIndex < items.size()) { + if (currentIndex < items.size()) { return items.get(currentIndex++); } @@ -2552,7 +2602,7 @@ public void update(ExecutionContext executionContext) throws ItemStreamException { executionContext.putLong(CURRENT_INDEX, new Long(currentIndex).longValue()); }; - }</programlisting> + }]]></programlisting> <para>On each call to <classname>ItemStream</classname> <methodname>update</methodname> method, the current index of the @@ -2564,19 +2614,19 @@ current index is moved to that location. This is a fairly trivial example, but it still meets the general contract:</para> - <programlisting> ExecutionContext executionContext = new ExecutionContext(); + <programlisting><![CDATA[ ExecutionContext executionContext = new ExecutionContext(); ((ItemStream)itemReader).open(executionContext); assertEquals("1", itemReader.read()); ((ItemStream)itemReader).update(executionContext); - List<String> items = new ArrayList<String>(); + List<String> items = new ArrayList<String>(); items.add("1"); items.add("2"); items.add("3"); - itemReader = new CustomItemReader<String>(items); + itemReader = new CustomItemReader<String>(items); ((ItemStream)itemReader).open(executionContext); - assertEquals("2", itemReader.read());</programlisting> + assertEquals("2", itemReader.read());]]></programlisting> <para>Most ItemReaders have much more sophisticated restart logic. The <classname>DrivingQueryItemReader</classname>, for example, only loads @@ -2607,18 +2657,18 @@ example. As with the <classname>ItemReader</classname> example, a List will be used in order to keep the example as simple as possible:</para> - <programlisting> public class CustomItemWriter<T> implements ItemWriter<T> { + <programlisting><![CDATA[ public class CustomItemWriter<T> implements ItemWriter<T> { - List<T> output = TransactionAwareProxyFactory.createTransactionalList(); + List<T> output = TransactionAwareProxyFactory.createTransactionalList(); - public void write(List<? extends T> items) throws Exception { + public void write(List<? extends T> items) throws Exception { output.addAll(items); } - public List<T> getOutput() { + public List<T> getOutput() { return output; } - }</programlisting> + }]]></programlisting> <section> <title>Making the <classname>ItemWriter</classname>