diff --git a/.gitignore b/.gitignore
index a34d07b9b..be8627a54 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,4 @@ bin
integration-repo
derby-home
derby.log
-
+com.springsource.sts.config.flow.prefs
diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/ItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/ItemReader.java
index 24ffdde88..4d04769bc 100644
--- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/ItemReader.java
+++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/ItemReader.java
@@ -21,8 +21,7 @@ package org.springframework.batch.item;
*
* Implementations are expected to be stateful and will be called multiple times
* for each batch, with each call to {@link #read()} returning a different value
- * and finally returning null when all input data is
- * exhausted.
+ * and finally returning null when all input data is exhausted.
*
* Implementations need *not* be thread safe and clients of a {@link ItemReader}
* need to be aware that this is the case.
@@ -44,8 +43,16 @@ public interface ItemReader {
* twice from successive calls (or otherwise), if the first call was in a
* transaction that rolled back.
*
- * @throws Exception if an underlying resource is unavailable.
+ * @throws ParseException if there is a problem parsing the current record
+ * (but the next one may still be valid)
+ * @throws NonTransientResourceException if there is a fatal exception in
+ * the underlying resource. After throwing this exception implementations
+ * should endeavour to return null from subsequent calls to read.
+ * @throws UnexpectedInputException if there is an uncategorised problem
+ * with the input data. Assume potentially transient, so subsequent calls to
+ * read might succeed.
+ * @throws Exception if an there is a non-specific error.
*/
- T read() throws Exception, UnexpectedInputException, ParseException;
+ T read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException;
}
diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/NonTransientResourceException.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/NonTransientResourceException.java
new file mode 100644
index 000000000..31f73dc88
--- /dev/null
+++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/NonTransientResourceException.java
@@ -0,0 +1,46 @@
+/*
+ * 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.item;
+
+/**
+ * Exception indicating that an error has been encountered doing I/O from a
+ * reader, and the exception should be considered fatal.
+ *
+ * @author Dave Syer
+ */
+public class NonTransientResourceException extends ItemReaderException {
+
+ /**
+ * Create a new {@link NonTransientResourceException} based on a message and
+ * another exception.
+ *
+ * @param message the message for this exception
+ * @param cause the other exception
+ */
+ public NonTransientResourceException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * Create a new {@link NonTransientResourceException} based on a message.
+ *
+ * @param message the message for this exception
+ */
+ public NonTransientResourceException(String message) {
+ super(message);
+ }
+
+}
diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/FlatFileItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/FlatFileItemReader.java
index 3496bb78b..b772a2aae 100644
--- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/FlatFileItemReader.java
+++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/FlatFileItemReader.java
@@ -223,7 +223,10 @@ public class FlatFileItemReader extends AbstractItemCountingItemStreamItemRea
line = applyRecordSeparatorPolicy(line);
}
catch (IOException e) {
- throw new FlatFileParseException("Unable to read from resource: [" + resource + "]", e, line, lineCount);
+ // Prevent IOException from recurring indefinitely
+ // if client keeps catching and re-calling
+ noInput = true;
+ throw new NonTransientFlatFileException("Unable to read from resource: [" + resource + "]", e, line, lineCount);
}
return line;
}
diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/NonTransientFlatFileException.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/NonTransientFlatFileException.java
new file mode 100644
index 000000000..7175f43d8
--- /dev/null
+++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/NonTransientFlatFileException.java
@@ -0,0 +1,55 @@
+/*
+ * 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.item.file;
+
+import org.springframework.batch.item.NonTransientResourceException;
+
+/**
+ * Exception thrown when errors are encountered with the underlying resource.
+ *
+ * @author Dave Syer
+ */
+public class NonTransientFlatFileException extends NonTransientResourceException {
+
+ private String input;
+
+ private int lineNumber;
+
+ public NonTransientFlatFileException(String message, String input) {
+ super(message);
+ this.input = input;
+ }
+
+ public NonTransientFlatFileException(String message, String input, int lineNumber) {
+ super(message);
+ this.input = input;
+ this.lineNumber = lineNumber;
+ }
+
+ public NonTransientFlatFileException(String message, Throwable cause, String input, int lineNumber) {
+ super(message, cause);
+ this.input = input;
+ this.lineNumber = lineNumber;
+ }
+
+ public String getInput() {
+ return input;
+ }
+
+ public int getLineNumber() {
+ return lineNumber;
+ }
+}
diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/StaxEventItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/StaxEventItemReader.java
index cbb6058e0..81c404f00 100644
--- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/StaxEventItemReader.java
+++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/StaxEventItemReader.java
@@ -136,8 +136,12 @@ public class StaxEventItemReader extends AbstractItemCountingItemStreamItemRe
*
* @return true if next fragment was found, false
* otherwise.
+ *
+ * @throws DataAccessResourceFailureException if the cursor could not be
+ * moved. This will be treated as fatal and subsequent calls to read will
+ * return null.
*/
- protected boolean moveCursorToNextFragment(XMLEventReader reader) {
+ protected boolean moveCursorToNextFragment(XMLEventReader reader) throws DataAccessResourceFailureException {
try {
while (true) {
while (reader.peek() != null && !reader.peek().isStartElement()) {
@@ -215,7 +219,16 @@ public class StaxEventItemReader extends AbstractItemCountingItemStreamItemRe
T item = null;
- if (moveCursorToNextFragment(fragmentReader)) {
+ boolean success = false;
+ try {
+ success = moveCursorToNextFragment(fragmentReader);
+ }
+ catch (DataAccessResourceFailureException e) {
+ // Prevent caller from retrying indefinitely since this is fatal
+ noInput = true;
+ throw e;
+ }
+ if (success) {
fragmentReader.markStartFragment();
@SuppressWarnings("unchecked")
diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/StaxEventItemReaderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/StaxEventItemReaderTests.java
index e5403b39f..39460ca81 100644
--- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/StaxEventItemReaderTests.java
+++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/StaxEventItemReaderTests.java
@@ -45,6 +45,8 @@ public class StaxEventItemReaderTests {
private String mixedXml = " testString ";
+ private String invalidXml = " testString ";
+
private Unmarshaller unmarshaller = new MockFragmentUnmarshaller();
private static final String FRAGMENT_ROOT_ELEMENT = "fragment";
@@ -131,6 +133,21 @@ public class StaxEventItemReaderTests {
source.close();
}
+ @Test
+ public void testFragmentInvalid() throws Exception {
+
+ source.setResource(new ByteArrayResource(invalidXml.getBytes()));
+ source.setFragmentRootElementName(FRAGMENT_ROOT_ELEMENT);
+ source.afterPropertiesSet();
+ source.open(executionContext);
+ // see asserts in the mock unmarshaller
+ assertNotNull(source.read());
+ assertNotNull(source.read());
+ assertNull(source.read()); // there are only two fragments
+
+ source.close();
+ }
+
/**
* Cursor is moved before beginning of next fragment.
*/