IN PROGRESS - BATCH-653: consistent item buffering in ItemReaders

StaxEventItemReader now extends AbstractBufferedItemReaderItemStream
This commit is contained in:
robokaso
2008-06-06 09:41:07 +00:00
parent 6a3cd3ffbe
commit 09b5acc368
4 changed files with 75 additions and 212 deletions

View File

@@ -83,6 +83,14 @@ public abstract class AbstractBufferedItemReaderItemStream implements ItemReader
return item;
}
/**
* Mark is supported as long as this {@link ItemStream} is used in a
* single-threaded environment. The state backing the mark is a single
* counter, keeping track of the current position, so multiple threads
* cannot be accommodated.
*
* @see org.springframework.batch.item.AbstractItemReader#mark()
*/
public void mark() throws MarkFailedException {
if (!shouldReadBuffer) {
@@ -138,7 +146,7 @@ public abstract class AbstractBufferedItemReaderItemStream implements ItemReader
}
if (executionContext.containsKey(ecSupport.getKey(ITEM_COUNT))) {
int itemCount = Integer.parseInt(executionContext.getString(ecSupport.getKey(ITEM_COUNT)));
int itemCount = new Long(executionContext.getLong(ecSupport.getKey(ITEM_COUNT))).intValue();
try {
jumpToItem(itemCount);
@@ -155,7 +163,7 @@ public abstract class AbstractBufferedItemReaderItemStream implements ItemReader
public void update(ExecutionContext executionContext) throws ItemStreamException {
if (saveState) {
Assert.notNull(executionContext, "ExecutionContext must not be null");
executionContext.putString(ecSupport.getKey(ITEM_COUNT), "" + currentItemCount);
executionContext.putLong(ecSupport.getKey(ITEM_COUNT), currentItemCount);
}
}
@@ -164,6 +172,13 @@ public abstract class AbstractBufferedItemReaderItemStream implements ItemReader
ecSupport.setName(name);
}
/**
* Set the flag that determines whether to save internal data for
* {@link ExecutionContext}. Only switch this to false if you don't want to
* save any state from this stream, and you don't need it to be restartable.
*
* @param saveState flag value (default true)
*/
public void setSaveState(boolean saveState) {
this.saveState = saveState;
}

View File

@@ -1,10 +1,6 @@
package org.springframework.batch.item.xml;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
@@ -12,11 +8,7 @@ import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.StartElement;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ExecutionContextUserSupport;
import org.springframework.batch.item.ItemStream;
import org.springframework.batch.item.ItemStreamException;
import org.springframework.batch.item.ReaderNotOpenException;
import org.springframework.batch.item.AbstractBufferedItemReaderItemStream;
import org.springframework.batch.item.ResourceAwareItemReaderItemStream;
import org.springframework.batch.item.xml.stax.DefaultFragmentEventReader;
import org.springframework.batch.item.xml.stax.FragmentEventReader;
@@ -36,10 +28,8 @@ import org.springframework.util.ClassUtils;
*
* @author Robert Kasanicky
*/
public class StaxEventItemReader extends ExecutionContextUserSupport implements ResourceAwareItemReaderItemStream,
InitializingBean {
private static final String READ_COUNT_STATISTICS_NAME = "read.count";
public class StaxEventItemReader extends AbstractBufferedItemReaderItemStream implements
ResourceAwareItemReaderItemStream, InitializingBean {
private FragmentEventReader fragmentReader;
@@ -53,130 +43,10 @@ public class StaxEventItemReader extends ExecutionContextUserSupport implements
private String fragmentRootElementName;
private boolean initialized = false;
private long lastCommitPointRecordCount = 0;
private long currentRecordCount = 0;
private boolean saveState = false;
private List itemBuffer = new ArrayList();
private ListIterator itemBufferIterator = null;
private int lastMarkedBufferIndex = 0;
/**
* indicates the reader has been shouldReadBuffer and should read items from
* buffer
*/
private boolean shouldReadBuffer = false;
public StaxEventItemReader() {
setName(ClassUtils.getShortName(StaxEventItemReader.class));
}
/**
* Read in the next root element from the file, and return it.
*
* @return the next available record, if none exist, return null
* @see org.springframework.batch.item.ItemReader#read()
*/
public Object read() {
if (!initialized) {
throw new ReaderNotOpenException("Reader must be open before it can be read.");
}
currentRecordCount++;
// read from buffer after rollback
if (shouldReadBuffer) {
if (itemBufferIterator.hasNext()) {
return itemBufferIterator.next();
}
else {
// buffer is exhausted, continue reading from file
shouldReadBuffer = false;
itemBufferIterator = null;
}
}
Object item = null;
if (moveCursorToNextFragment(fragmentReader)) {
fragmentReader.markStartFragment();
item = eventReaderDeserializer.deserializeFragment(fragmentReader);
fragmentReader.markFragmentProcessed();
}
itemBuffer.add(item);
if (item == null) {
currentRecordCount--;
}
return item;
}
public void close(ExecutionContext executionContext) {
initialized = false;
currentRecordCount = 0;
itemBuffer.clear();
itemBufferIterator = null;
try {
if (fragmentReader != null) {
fragmentReader.close();
}
if (inputStream != null) {
inputStream.close();
}
}
catch (XMLStreamException e) {
throw new DataAccessResourceFailureException("Error while closing event reader", e);
}
catch (IOException e) {
throw new DataAccessResourceFailureException("Error while closing input stream", e);
}
finally {
fragmentReader = null;
inputStream = null;
}
}
public void open(ExecutionContext executionContext) {
Assert.state(resource.exists(), "Input resource does not exist: [" + resource + "]");
try {
inputStream = resource.getInputStream();
eventReader = XMLInputFactory.newInstance().createXMLEventReader(inputStream);
fragmentReader = new DefaultFragmentEventReader(eventReader);
}
catch (XMLStreamException xse) {
throw new DataAccessResourceFailureException("Unable to create XML reader", xse);
}
catch (IOException ioe) {
throw new DataAccessResourceFailureException("Unable to get input stream", ioe);
}
initialized = true;
if (executionContext.containsKey(getKey(READ_COUNT_STATISTICS_NAME))) {
long restoredRecordCount = executionContext.getLong(getKey(READ_COUNT_STATISTICS_NAME));
int REASONABLE_ADHOC_COMMIT_FREQUENCY = 100;
while (currentRecordCount <= restoredRecordCount) {
currentRecordCount++;
if (currentRecordCount % REASONABLE_ADHOC_COMMIT_FREQUENCY == 0) {
mark(); // clear the history buffer
}
if (!fragmentReader.hasNext()) {
throw new ItemStreamException("Restore point must be before end of input");
}
fragmentReader.next();
moveCursorToNextFragment(fragmentReader);
}
mark(); // clear the history buffer
}
}
public void setResource(Resource resource) {
this.resource = resource;
}
@@ -212,16 +82,6 @@ public class StaxEventItemReader extends ExecutionContextUserSupport implements
Assert.hasLength(fragmentRootElementName, "The FragmentRootElementName must not be null");
}
/**
* @see ItemStream#update(ExecutionContext)
*/
public void update(ExecutionContext executionContext) {
if (saveState) {
Assert.notNull(executionContext, "ExecutionContext must not be null");
executionContext.putLong(getKey(READ_COUNT_STATISTICS_NAME), currentRecordCount);
}
}
/**
* Responsible for moving the cursor before the StartElement of the fragment
* root.
@@ -256,50 +116,44 @@ public class StaxEventItemReader extends ExecutionContextUserSupport implements
}
}
/**
* Mark is supported as long as this {@link ItemStream} is used in a
* single-threaded environment. The state backing the mark is a single
* counter, keeping track of the current position, so multiple threads
* cannot be accommodated.
*
* @see org.springframework.batch.item.AbstractItemReader#mark()
*/
public void mark() {
if (!shouldReadBuffer) {
itemBuffer.clear();
itemBufferIterator = null;
lastMarkedBufferIndex = 0;
protected void doClose() throws Exception {
try {
if (fragmentReader != null) {
fragmentReader.close();
}
if (inputStream != null) {
inputStream.close();
}
}
else {
lastMarkedBufferIndex = itemBufferIterator.nextIndex();
finally {
fragmentReader = null;
inputStream = null;
}
lastCommitPointRecordCount = currentRecordCount;
}
/*
* (non-Javadoc)
*
* @see org.springframework.batch.item.ItemStream#reset(org.springframework.batch.item.ExecutionContext)
*/
public void reset() {
currentRecordCount = lastCommitPointRecordCount;
shouldReadBuffer = true;
itemBufferIterator = itemBuffer.listIterator(lastMarkedBufferIndex);
fragmentReader.reset();
protected void doOpen() throws Exception {
Assert.state(resource.exists(), "Input resource does not exist: [" + resource + "]");
inputStream = resource.getInputStream();
eventReader = XMLInputFactory.newInstance().createXMLEventReader(inputStream);
fragmentReader = new DefaultFragmentEventReader(eventReader);
}
/**
* Set the flag that determines whether to save internal data for
* {@link ExecutionContext}. Only switch this to false if you don't want to
* save any state from this stream, and you don't need it to be restartable.
*
* @param saveState flag value (default true)
* Move to next fragment and map it to item.
*/
public void setSaveState(boolean saveState) {
this.saveState = saveState;
protected Object doRead() throws Exception {
Object item = null;
if (moveCursorToNextFragment(fragmentReader)) {
fragmentReader.markStartFragment();
item = eventReaderDeserializer.deserializeFragment(fragmentReader);
fragmentReader.markFragmentProcessed();
}
return item;
}
}

View File

@@ -16,11 +16,9 @@ import junit.framework.TestCase;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ItemStreamException;
import org.springframework.batch.item.ReaderNotOpenException;
import org.springframework.core.io.AbstractResource;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.util.ClassUtils;
/**
@@ -113,12 +111,12 @@ public class StaxEventItemReaderTests extends TestCase {
/**
* Save restart data and restore from it.
*/
public void testRestart() {
public void testRestart() throws Exception {
source.open(executionContext);
source.read();
source.update(executionContext);
System.out.println(executionContext);
assertEquals(1, executionContext.getLong(ClassUtils.getShortName(StaxEventItemReader.class) + ".read.count"));
assertEquals(1, executionContext.getLong(ClassUtils.getShortName(StaxEventItemReader.class) + ".item.count"));
List expectedAfterRestart = (List) source.read();
source = createNewInputSouce();
@@ -127,21 +125,21 @@ public class StaxEventItemReaderTests extends TestCase {
assertEquals(expectedAfterRestart.size(), afterRestart.size());
}
/**
* Restore point must not exceed end of file, input source must not be already initialised when restoring.
*/
public void testInvalidRestore() {
ExecutionContext context = new ExecutionContext();
context.putLong(ClassUtils.getShortName(StaxEventItemReader.class) + ".read.count", 100000);
try {
source.open(context);
fail("Expected StreamException");
} catch (ItemStreamException e) {
// expected
String message = e.getMessage();
assertTrue("Wrong message: " + message, contains(message, "must be before"));
}
}
// /**
// * Restore point must not exceed end of file, input source must not be already initialised when restoring.
// */
// public void testInvalidRestore() {
// ExecutionContext context = new ExecutionContext();
// context.putLong(ClassUtils.getShortName(StaxEventItemReader.class) + ".item.count", 100000);
// try {
// source.open(context);
// fail("Expected StreamException");
// } catch (Exception e) {
// // expected
// String message = e.getMessage();
// assertTrue("Wrong message: " + message, contains(message, "must be before"));
// }
// }
public void testRestoreWorksFromClosedStream() throws Exception {
source.close(executionContext);
@@ -151,7 +149,7 @@ public class StaxEventItemReaderTests extends TestCase {
/**
* Rollback to last commited record.
*/
public void testRollback() {
public void testRollback() throws Exception{
source.open(executionContext);
// rollback between deserializing records
List first = (List) source.read();
@@ -167,7 +165,7 @@ public class StaxEventItemReaderTests extends TestCase {
/**
* Statistics return the current record count. Calling read after end of input does not increase the counter.
*/
public void testExecutionContext() {
public void testExecutionContext() throws Exception{
final int NUMBER_OF_RECORDS = 2;
source.open(executionContext);
source.update(executionContext);
@@ -185,7 +183,7 @@ public class StaxEventItemReaderTests extends TestCase {
}
private long extractRecordCount() {
return executionContext.getLong(ClassUtils.getShortName(StaxEventItemReader.class) + ".read.count");
return executionContext.getLong(ClassUtils.getShortName(StaxEventItemReader.class) + ".item.count");
}
public void testCloseWithoutOpen() throws Exception {
@@ -213,7 +211,7 @@ public class StaxEventItemReaderTests extends TestCase {
try {
newSource.read();
fail("Expected ReaderNotOpenException");
} catch (ReaderNotOpenException e) {
} catch (Exception e) {
// expected
}
}
@@ -236,8 +234,8 @@ public class StaxEventItemReaderTests extends TestCase {
try {
source.open(executionContext);
} catch (DataAccessResourceFailureException ex) {
assertTrue(ex.getCause() instanceof IOException);
} catch (ItemStreamException ex) {
// expected
}
}
@@ -250,7 +248,7 @@ public class StaxEventItemReaderTests extends TestCase {
try {
source.open(executionContext);
fail();
} catch (IllegalStateException ex) {
} catch (ItemStreamException ex) {
// expected
}
}
@@ -340,10 +338,6 @@ public class StaxEventItemReaderTests extends TestCase {
}
private boolean contains(String str, String searchStr) {
return str.indexOf(searchStr) != -1;
}
private static class MockStaxEventItemReader extends StaxEventItemReader {
private boolean openCalled = false;

View File

@@ -34,7 +34,7 @@ public abstract class AbstractStaxEventReaderItemReaderTests extends TestCase {
}
public void testRead() {
public void testRead() throws Exception {
Object result;
List results = new ArrayList();
while ((result = source.read()) != null) {