diff --git a/docs/src/site/docbook/reference/readersAndWriters.xml b/docs/src/site/docbook/reference/readersAndWriters.xml index d15854e06..44c61c650 100644 --- a/docs/src/site/docbook/reference/readersAndWriters.xml +++ b/docs/src/site/docbook/reference/readersAndWriters.xml @@ -50,7 +50,7 @@ ItemReader is a basic interface for generic input operations: - public interface ItemReader { + +]]> The read method defines the most essential contract of the ItemReader, calling it returns one @@ -97,7 +97,7 @@ As with ItemReader, ItemWriter is a fairly generic interface: - public interface ItemWriter { + +]]> As with read on ItemReader, write provides @@ -135,7 +135,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 @@ -203,11 +203,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, @@ -240,9 +240,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 @@ -336,11 +336,11 @@ LineTokenizer to translate a line of data from a resource into an object of the desired type: - public interface FieldSetMapper { + + }]]> The pattern used is the same as RowMapper used by JdbcTemplate. @@ -356,11 +356,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 @@ -420,14 +420,14 @@ In code, the above flow looks like the following: - String line = readLine(); + + return null;]]> Exception handling has been removed for clarity. @@ -435,16 +435,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: - + + } ]]> The file can then be read by correctly constructing a FlatFileItemReader and calling read: - + +]]> Each call to read will return a new Player object from each line in the file. When the end of the file is @@ -517,9 +517,9 @@ fields in the flat file are injected into the LineTokenizer: - + + ]]> a FieldSetMapper can this use this information as follows: @@ -559,14 +559,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 @@ -586,11 +586,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: @@ -620,37 +620,37 @@ 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, allowing the same approachs above to be used such as the BeanWrapperFieldSetMapper, in a way that is - ignorant of how the actual line was parsed. + ignorant of how the actual line was parsed. It should be noted that supporting the above ranges requires a 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> + + + + + + + + + - +]]>
@@ -662,7 +662,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 @@ -721,11 +721,11 @@ writing to a file. In Spring Batch this is the LineAggregator: - public interface LineAggregator { + + }]]> The LineAggregator is the opposite of a LineTokenizer. @@ -747,11 +747,11 @@ string, there needs to be an interface that describes how to convert from an object into a FieldSet: - public interface FieldSetCreator { + + }]]> As with LineTokenizer and LineAggregator, @@ -792,22 +792,22 @@ FlatFileItemWriter expresses this in code: - public void write(Object data) 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="fieldSetCreator"> - <bean class="org.springframework.batch.io.file.mapping.PassThroughFieldSetMapper"/> - </property> - </bean> + + + + + + ]]>
@@ -911,28 +911,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: @@ -954,22 +954,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 @@ -980,16 +980,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). @@ -1003,7 +1003,7 @@ injection provided by the spring configuration would look something like the following: - + +]]>
@@ -1049,13 +1049,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 @@ -1075,7 +1075,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. @@ -1120,11 +1120,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 @@ -1134,10 +1134,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 @@ -1154,10 +1154,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 @@ -1168,11 +1168,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 @@ -1188,20 +1188,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 @@ -1209,7 +1209,7 @@ should be noted that, as with any ItemReader, adding extra input (in this case a file) could cause potential issues when restarting. It is recommended that batch jobs work with their own individual directories - until completed successfully. + until completed successfully.
@@ -1288,18 +1288,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 @@ -1326,12 +1326,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 @@ -1342,7 +1342,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 @@ -1473,7 +1473,7 @@ configuration using the same 'customer credit' example as the JDBC reader: - + +]]> This configured ItemReader will return CustomerCredit objects in the exact same manner @@ -1502,6 +1502,55 @@
+
+ Paging ItemReaders + + An alternative to using a database cursor is executing multiple + queries where each query is bringing back a portion of the results. We + refer to this portion as a page. Each query that is executed must + specify the starting row number and the number of rows that we want + returned for the page. + +
+ JpaPagingItemReader + + Currently the only available implementation of a paging + ItemReader is the + JpaPagingItemReader. JPA doesn't have a concept + similar to the Hibernate StatelessSession so we + have to use other features provided by the JPA specification. SInce + JPA supports paging, this is a natural choice when it comes to using + JPA for batch processing. After each page is read the entities will + become detached and the persistence context will be cleared in order + to allow the entities to be garbage collected once the page is + processed. + + The JpaPagingItemReader allows you to + declare a JPQL statement and pass in a + EntityManagerFactory. It will then pass back + one item per call to read in the same basic + fashion as any other ItemReader. The paging + happenes behind the scenes when additional entities are needed. Below + is an example configuration using the same 'customer credit' example + as the JDBC reader above: + + + + + + +]]> + + This configured ItemReader will return + CustomerCredit objects in the exact same manner + as described by the JdbcCursorItemReader above, + assuming the Customer object has the correct JPA annotations or ORM + mapping file. The 'pageSize' property determines the number of + entities read from the database for each query execution. +
+
+
Driving Query Based ItemReaders @@ -1563,12 +1612,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 @@ -1583,12 +1632,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 @@ -1651,36 +1700,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(); @@ -1688,19 +1737,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 @@ -1711,7 +1760,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. @@ -1738,13 +1787,13 @@ An ExecutionContextRowMapper provides this: - public interface ExecutionContextRowMapper extends RowMapper { + +]]> The ExecutionContextRowMapper interface extends the standard RowMapper interface to @@ -1819,7 +1868,7 @@ won't be written out again. However, in this scenario, there's no way for it to know which item caused the issue, the whole buffer was being written out when the failure happened. The only way to solve this issue - is to flush after each item: + is to flush after each item: @@ -1842,18 +1891,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: + + + + + + + + + + +]]>
@@ -1878,12 +1945,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 @@ -1894,13 +1961,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" /> - + +]]>
@@ -1915,7 +1982,7 @@ that contains another ItemReader. For example: - public class CompositeItemWriter implements ItemWriter { + +}]]> The class above contains another ItemWriter that it delgates to after having provided some business logic. It should @@ -1954,10 +2021,10 @@ just want to modify the item. For this scenario, Spring Batch provides the ItemTransformer interface: - public interface ItemTransformer { + + }]]> An ItemTransformer is very simple, given one object, transorm it and return another. The object provided may or may not @@ -1973,7 +2040,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 @@ -2013,31 +2080,29 @@ 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 -
- Note that the ItemTransformerItemWriter - and the CompositeItemWriter are examples of a - delegation pattern, which is common in Spring Batch. The delegates - themselves might implement callback interfaces like - ItemStream or - StepListener. If they do, and they are being - used in conjunction with Spring Batch Core as part of a - Step in a Job, then they - almost certainly need to be registered manually with the - Step. Registration is automatic when using the - factory beans (*StepFactoryBean) , but only for - the ItemReader and - ItemWriter injected directly. The delegates are - not known to the Step, so they need to be - injected as listeners or streams (or both if appropriate). -
+ Note that the ItemTransformerItemWriter and + the CompositeItemWriter are examples of a + delegation pattern, which is common in Spring Batch. The delegates + themselves might implement callback interfaces like + ItemStream or + StepListener. If they do, and they are being used + in conjunction with Spring Batch Core as part of a + Step in a Job, then they + almost certainly need to be registered manually with the + Step. Registration is automatic when using the + factory beans (*StepFactoryBean) , but only for + the ItemReader and + ItemWriter injected directly. The delegates are + not known to the Step, so they need to be + injected as listeners or streams (or both if appropriate).
@@ -2051,7 +2116,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: - CompositeItemTransformer compositeTransformer = new CompositeItemTransformer(); + + compositeTransformer.setItemTransformers(itemTransformers);]]> The compositeTransformer could be said to accept a Foo and return a Foobar. @@ -2132,11 +2197,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 @@ -2179,21 +2244,19 @@
The Delegate Pattern and Registering with the Step -
- Note that the ValidatingItemReader is - another example of a delegation pattern, and the delegates themselves - might implement callback interfaces like - ItemStream or - StepListener. If they do, and they are being - used in conjunction with Spring Batch Core as part of a step in a job, - then they almost certainly need to be registered manually with the - Step. Registration is automatic when using the - factory beans (*StepFactoryBean) , but only for - the ItemReader and - ItemWriter injected directly - the delegates - are not known to the step, so they need to be injected as listeners or - streams (or both if appropriate). -
+ Note that the ValidatingItemReader is + another example of a delegation pattern, and the delegates themselves + might implement callback interfaces like + ItemStream or + StepListener. If they do, and they are being used + in conjunction with Spring Batch Core as part of a step in a job, then + they almost certainly need to be registered manually with the + Step. Registration is automatic when using the + factory beans (*StepFactoryBean) , but only for + the ItemReader and + ItemWriter injected directly - the delegates are + not known to the step, so they need to be injected as listeners or + streams (or both if appropriate).
@@ -2267,7 +2330,7 @@ basic contract of ItemReader, read: - public class CustomItemReader implements ItemReader{ + + }]]> 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 items = new ArrayList(); + + assertNull(itemReader.read());]]>
Making the <classname>ItemReader</classname> @@ -2319,7 +2382,7 @@ empty, but we'll need to add code to them in order to support the rollback scenario:</para> - <programlisting> public class CustomItemReader implements ItemReader{ + <programlisting><![CDATA[ public class CustomItemReader implements ItemReader{ List items; int currentIndex = 0; @@ -2332,7 +2395,7 @@ public Object read() throws Exception, UnexpectedInputException, NoWorkFoundException, ParseException { - if (currentIndex < items.size()) { + if (currentIndex < items.size()) { return items.get(currentIndex++); } return null; @@ -2345,7 +2408,7 @@ public void reset() throws ResetFailedException { currentIndex = lastMarkedIndex; }; - }</programlisting> + }]]></programlisting> <para>The <classname>CustomItemReader</classname> has now been modified to keep track of where it is currently, and where it was when @@ -2355,12 +2418,12 @@ <classname>ItemReader</classname> to the state it was in when <methodname>mark</methodname> was last called:</para> - <programlisting> //Assume same setup as last example, a list with "1", "2", and "3" + <programlisting><![CDATA[ //Assume same setup as last example, a list with "1", "2", and "3" itemReader.mark(); assertEquals("1", itemReader.read()); assertEquals("2", itemReader.read()); itemReader.reset(); - assertEquals("1", itemReader.read());</programlisting> + assertEquals("1", itemReader.read());]]></programlisting> <para>In most real world scenarios, there will likely be some kind of underlying resource that will require tracking. In the case of a file, @@ -2402,7 +2465,7 @@ implemented with the <classname>ItemStream</classname> interface:</para> - <programlisting> public class CustomItemReader implements ItemReader, ItemStream{ + <programlisting><![CDATA[ public class CustomItemReader implements ItemReader, ItemStream{ List items; int currentIndex = 0; @@ -2416,7 +2479,7 @@ public Object read() throws Exception, UnexpectedInputException, NoWorkFoundException, ParseException { - if (currentIndex < items.size()) { + if (currentIndex < items.size()) { return items.get(currentIndex++); } return null; @@ -2445,7 +2508,7 @@ }; public void close(ExecutionContext executionContext) throws ItemStreamException {} - }</programlisting> + }]]></programlisting> <para>On each call to <classname>ItemStream</classname> <methodname>update</methodname> method, the current index of the @@ -2457,7 +2520,7 @@ 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); @@ -2469,7 +2532,7 @@ itemReader = new CustomItemReader(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 @@ -2500,7 +2563,7 @@ 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 implements ItemWriter{ + <programlisting><![CDATA[ public class CustomItemWriter implements ItemWriter{ List output = new ArrayList(); @@ -2511,7 +2574,7 @@ public void clear() throws ClearFailedException { } public void flush() throws FlushFailedException { } - }</programlisting> + }]]></programlisting> <section> <title>Making the <classname>ItemReader</classname> @@ -2531,7 +2594,7 @@ <methodname>flush</methodname> and <methodname>clear</methodname> should be implemented to allow for a buffering solution:</para> - <programlisting> public class CustomItemWriter implements ItemWriter{ + <programlisting><![CDATA[ public class CustomItemWriter implements ItemWriter{ List output = new ArrayList(); List buffer = new ArrayList(); @@ -2550,7 +2613,7 @@ it.remove(); } } - }</programlisting> + }]]></programlisting> <para>The <classname>ItemWriter</classname> buffers all output, only writing to the actual output (in this case by added to a list) when