package biz.c24.io.spring.batch.reader;
import java.io.IOException;
import java.io.Reader;
import biz.c24.io.api.data.ComplexDataObject;
import biz.c24.io.api.data.Element;
import biz.c24.io.api.presentation.Source;
import biz.c24.io.api.presentation.XMLSource;
import biz.c24.io.spring.batch.reader.source.SplittingReader;
/**
* Parser
* A wrapper class to manage parsing from iO sources.
* The Parser hierarchy exists primarily for 2 reasons:
*
* 1. To hide the decision on what to synchronise from the parsing logic; we simply select the correct type
* of parser based on configuration.
*
* 2. A single parser (hence iO source) might be shared between multiple threads. With a pure iO source, if we fail to
* parse an entity from the underlying reader, it will generate an exception but we have no way to tell other threads
* using the same iO source not to try and parse from the iO source again (which they will, and of course they will
* also get an exception). Wrapping it in a parser allows us to store a status flag (finished) that, when such an event
* occurs, we can set to prevent further parsing attempts.
*
* This base class does not synchronize on any of its methods.
*
* @author andrew
*
*/
class Parser {
/**
* Our data source
*/
private SplittingReader splitter;
/**
* The underlying iO source to read from
*/
private Source ioSource;
/**
* A flag to control when we've finished reading from this parser
* Set when we encounter a parsing exception; we can't process any further
*/
private volatile boolean finished = false;
/*
* The type of CDO that we're trying to parse from the underlying reader
*/
private Element element;
/**
* Construct a parser from the supplied iO source to read the specified type of Element
* @param ioSource
* @param element
*/
public Parser(SplittingReader splitter, Source ioSource, Element element) {
this.splitter = splitter;
this.ioSource = ioSource;
this.element = element;
}
/**
* Sets the reader that we'll consume data from
* @param reader
*/
public void setReader(Reader reader) {
ioSource.setReader(reader);
}
public Reader getReader() {
return ioSource.getReader();
}
public SplittingReader getSplitter() {
return splitter;
}
/**
* Attempts to read a ComplexDataObject from the Reader
* @return A parsed ComplexDataObject
* @throws IOException
*/
public ComplexDataObject read() throws IOException {
ComplexDataObject obj = null;
if(!finished) {
try {
obj = ioSource.readObject(element);
} catch(IOException ioEx) {
// If we're using the XML source, the underlying SAXParser can helpfully close the stream
// when it finished parsing the previous element, presumably because it assumes the document
// is well-formed (ie only one per file)
if(ioSource instanceof XMLSource) {
// Find the root cause
Throwable ex = ioEx;
while(ex.getCause() != null) {
ex = ex.getCause();
}
// Unfortunately we can only detect it via a string match. This is fragile.
if(ex instanceof IOException && "Stream closed".compareToIgnoreCase(ex.getMessage()) == 0) {
// Sigh. That looks like that's what's happened.
obj = null;
} else {
// Rethrow
throw ioEx;
}
} else {
// Rethrow
throw ioEx;
}
} finally {
if(obj == null) {
finished = true;
}
}
}
return obj;
}
}