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. */