diff --git a/infrastructure/src/main/java/org/springframework/batch/io/oxm/MarshallingObjectToXmlSerializer.java b/infrastructure/src/main/java/org/springframework/batch/io/oxm/MarshallingObjectToXmlSerializer.java
new file mode 100644
index 000000000..c42c8fd36
--- /dev/null
+++ b/infrastructure/src/main/java/org/springframework/batch/io/oxm/MarshallingObjectToXmlSerializer.java
@@ -0,0 +1,44 @@
+package org.springframework.batch.io.oxm;
+
+import java.io.IOException;
+
+import javax.xml.stream.XMLEventWriter;
+import javax.xml.transform.Result;
+
+import org.springframework.batch.io.stax.ObjectToXmlSerializer;
+import org.springframework.dao.DataAccessResourceFailureException;
+import org.springframework.oxm.Marshaller;
+import org.springframework.util.Assert;
+import org.springframework.xml.transform.StaxResult;
+
+/**
+ * Object to xml serializer that wraps a Spring-OXM
+ * Marshaller object.
+ *
+ * @author Lucas Ward
+ *
+ */
+public class MarshallingObjectToXmlSerializer implements ObjectToXmlSerializer{
+
+ private Marshaller marshaller;
+
+ private Result result;
+
+ public MarshallingObjectToXmlSerializer(Marshaller marshaller){
+ this.marshaller = marshaller;
+ }
+
+ public void setEventWriter(XMLEventWriter writer) {
+ result = new StaxResult(writer);
+ }
+
+ public void serializeObject(Object output) {
+
+ try {
+ marshaller.marshal(output, result);
+ } catch (IOException xse) {
+ throw new DataAccessResourceFailureException(
+ "Unable to write to file resource: [" + result.getSystemId() + "]", xse);
+ }
+ }
+}
diff --git a/infrastructure/src/main/java/org/springframework/batch/io/oxm/UnmarshallingFragmentDeserializer.java b/infrastructure/src/main/java/org/springframework/batch/io/oxm/UnmarshallingFragmentDeserializer.java
new file mode 100644
index 000000000..9079f2559
--- /dev/null
+++ b/infrastructure/src/main/java/org/springframework/batch/io/oxm/UnmarshallingFragmentDeserializer.java
@@ -0,0 +1,41 @@
+package org.springframework.batch.io.oxm;
+
+import java.io.IOException;
+
+import javax.xml.stream.XMLEventReader;
+
+import org.springframework.batch.io.stax.FragmentDeserializer;
+import org.springframework.dao.DataAccessResourceFailureException;
+import org.springframework.oxm.Unmarshaller;
+import org.springframework.oxm.UnmarshallingFailureException;
+import org.springframework.oxm.XmlMappingException;
+import org.springframework.xml.transform.StaxSource;
+
+/**
+ * Delegates deserializing to Spring-WS {@link Unmarshaller}.
+ *
+ * @author Robert Kasanicky
+ * @authoer Lucas Ward
+ */
+public class UnmarshallingFragmentDeserializer implements FragmentDeserializer {
+
+ private Unmarshaller unmarshaller;
+
+ public UnmarshallingFragmentDeserializer(Unmarshaller unmarshaller){
+ this.unmarshaller = unmarshaller;
+ }
+
+ public Object deserializeFragment(XMLEventReader eventReader) {
+ Object item = null;
+ try {
+ item = unmarshaller.unmarshal(new StaxSource(eventReader));
+ }
+ catch (XmlMappingException e) {
+ throw new UnmarshallingFailureException("Mapping failure during unmarshalling", e);
+ }
+ catch (IOException e) {
+ throw new DataAccessResourceFailureException("IO error during unmarshalling", e);
+ }
+ return item;
+ }
+}
diff --git a/infrastructure/src/main/java/org/springframework/batch/io/stax/AbstractEventReaderWrapper.java b/infrastructure/src/main/java/org/springframework/batch/io/stax/AbstractEventReaderWrapper.java
new file mode 100644
index 000000000..782790c21
--- /dev/null
+++ b/infrastructure/src/main/java/org/springframework/batch/io/stax/AbstractEventReaderWrapper.java
@@ -0,0 +1,58 @@
+package org.springframework.batch.io.stax;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.XMLEvent;
+
+/**
+ * Delegates all functionality to the wrapped reader allowing
+ * subclasses to override only the methods they want to change.
+ *
+ * @author Robert Kasanicky
+ */
+abstract class AbstractEventReaderWrapper implements XMLEventReader {
+
+ protected XMLEventReader wrappedEventReader;
+
+ public AbstractEventReaderWrapper(XMLEventReader wrappedEventReader) {
+ this.wrappedEventReader = wrappedEventReader;
+ }
+
+ public void close() throws XMLStreamException {
+ wrappedEventReader.close();
+
+ }
+
+ public String getElementText() throws XMLStreamException {
+ return wrappedEventReader.getElementText();
+ }
+
+ public Object getProperty(String name) throws IllegalArgumentException {
+ return wrappedEventReader.getProperty(name);
+ }
+
+ public boolean hasNext() {
+ return wrappedEventReader.hasNext();
+ }
+
+ public XMLEvent nextEvent() throws XMLStreamException {
+ return wrappedEventReader.nextEvent();
+ }
+
+ public XMLEvent nextTag() throws XMLStreamException {
+ return wrappedEventReader.nextTag();
+ }
+
+ public XMLEvent peek() throws XMLStreamException {
+ return wrappedEventReader.peek();
+ }
+
+ public Object next() {
+ return wrappedEventReader.next();
+ }
+
+ public void remove() {
+ wrappedEventReader.remove();
+ }
+
+}
diff --git a/infrastructure/src/main/java/org/springframework/batch/io/stax/DefaultFragmentEventReader.java b/infrastructure/src/main/java/org/springframework/batch/io/stax/DefaultFragmentEventReader.java
new file mode 100644
index 000000000..43829e51c
--- /dev/null
+++ b/infrastructure/src/main/java/org/springframework/batch/io/stax/DefaultFragmentEventReader.java
@@ -0,0 +1,180 @@
+package org.springframework.batch.io.stax;
+
+import java.util.NoSuchElementException;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLEventFactory;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.EndDocument;
+import javax.xml.stream.events.EndElement;
+import javax.xml.stream.events.StartDocument;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+
+import org.springframework.dao.DataAccessResourceFailureException;
+
+/**
+ * Default implementation of {@link FragmentEventReader}
+ *
+ * @author Robert Kasanicky
+ */
+class DefaultFragmentEventReader extends AbstractEventReaderWrapper implements FragmentEventReader {
+
+ // true when the next event is the StartElement of next fragment
+ private boolean startFragmentFollows = false;
+
+ // true when the next event is the EndElement of current fragment
+ private boolean endFragmentFollows = false;
+
+ // true while cursor is inside fragment
+ private boolean insideFragment = false;
+
+ // true when reader should behave like the cursor was at the end of document
+ private boolean fakeDocumentEnd = false;
+
+ private StartDocument startDocumentEvent = null;
+
+ private EndDocument endDocumentEvent = null;
+
+ // fragment root name is remembered so that the matching closing element can
+ // be identified
+ private QName fragmentRootName = null;
+
+ // counts the occurrences of current fragmentRootName (increased for
+ // StartElement, decreased for EndElement)
+ private int matchCounter = 0;
+
+ /**
+ * Caches the StartDocument event for later use.
+ * @param wrappedEventReader the original wrapped event reader
+ */
+ public DefaultFragmentEventReader(XMLEventReader wrappedEventReader) {
+ super(wrappedEventReader);
+ try {
+ startDocumentEvent = (StartDocument) wrappedEventReader.peek();
+ }
+ catch (XMLStreamException e) {
+ throw new DataAccessResourceFailureException("Error reading start document from event reader", e);
+ }
+
+ endDocumentEvent = XMLEventFactory.newInstance().createEndDocument();
+ }
+
+ public void markStartFragment() {
+ startFragmentFollows = true;
+ fragmentRootName = null;
+ }
+
+ public boolean hasNext() {
+ try {
+ if (peek() != null) {
+ return true;
+ }
+ }
+ catch (XMLStreamException e) {
+ throw new DataAccessResourceFailureException("Error reading XML stream", e);
+ }
+ return false;
+ }
+
+ public Object next() {
+ try {
+ return nextEvent();
+ }
+ catch (XMLStreamException e) {
+ throw new DataAccessResourceFailureException("Error reading XML stream", e);
+ }
+ }
+
+ public XMLEvent nextEvent() throws XMLStreamException {
+ if (fakeDocumentEnd) {
+ throw new NoSuchElementException();
+ }
+ XMLEvent event = wrappedEventReader.peek();
+ XMLEvent proxyEvent = alterEvent(event, false);
+ checkFragmentEnd(proxyEvent);
+ if (event == proxyEvent) {
+ wrappedEventReader.nextEvent();
+ }
+
+ return proxyEvent;
+ }
+
+ /**
+ * Sets the endFragmentFollows flag to true if next event is the last event
+ * of the fragment.
+ * @param event peek() from wrapped event reader
+ */
+ private void checkFragmentEnd(XMLEvent event) {
+ if (event.isStartElement() && ((StartElement) event).getName().equals(fragmentRootName)) {
+ matchCounter++;
+ }
+ else if (event.isEndElement() && ((EndElement) event).getName().equals(fragmentRootName)) {
+ matchCounter--;
+ if (matchCounter == 0) {
+ endFragmentFollows = true;
+ }
+ }
+ }
+
+ /**
+ * @param event peek() from wrapped event reader
+ * @param peek if true do not change the internal state
+ * @return StartDocument event if peek() points to beginning of fragment
+ * EndDocument event if cursor is right behind the end of fragment original
+ * event otherwise
+ */
+ private XMLEvent alterEvent(XMLEvent event, boolean peek) {
+ if (startFragmentFollows) {
+ fragmentRootName = ((StartElement) event).getName();
+ if (!peek) {
+ startFragmentFollows = false;
+ insideFragment = true;
+ }
+ return startDocumentEvent;
+ }
+ else if (endFragmentFollows) {
+ if (!peek) {
+ endFragmentFollows = false;
+ insideFragment = false;
+ fakeDocumentEnd = true;
+ }
+ return endDocumentEvent;
+ }
+ return event;
+ }
+
+ public XMLEvent peek() throws XMLStreamException {
+ if (fakeDocumentEnd) {
+ return null;
+ }
+ return alterEvent(wrappedEventReader.peek(), true);
+ }
+
+ /**
+ * Finishes reading the fragment in case the fragment was processed without
+ * being read until the end.
+ */
+ public void markFragmentProcessed() {
+ if (insideFragment) {
+ try {
+ while (!(nextEvent() instanceof EndDocument)) {
+ // just read all events until EndDocument
+ }
+ }
+ catch (XMLStreamException e) {
+ throw new DataAccessResourceFailureException("Error reading XML stream", e);
+ }
+ }
+ fakeDocumentEnd = false;
+ }
+
+ public void reset() {
+ insideFragment = false;
+ startFragmentFollows = false;
+ endFragmentFollows = false;
+ fakeDocumentEnd = false;
+ }
+
+}
diff --git a/infrastructure/src/main/java/org/springframework/batch/io/stax/DefaultTransactionalEventReader.java b/infrastructure/src/main/java/org/springframework/batch/io/stax/DefaultTransactionalEventReader.java
new file mode 100644
index 000000000..80fb169f1
--- /dev/null
+++ b/infrastructure/src/main/java/org/springframework/batch/io/stax/DefaultTransactionalEventReader.java
@@ -0,0 +1,220 @@
+package org.springframework.batch.io.stax;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.Characters;
+import javax.xml.stream.events.XMLEvent;
+
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.util.Assert;
+
+/**
+ * Class used to wrap XMLEventReader. Events from wrapped reader are stored in
+ * {@link EventSequence} to support transactions.
+ *
+ * @author tomas.slanina
+ */
+class DefaultTransactionalEventReader implements TransactionalEventReader, InitializingBean {
+
+ private EventSequence recorder = new EventSequence();
+
+ private XMLEventReader parent;
+
+
+ /**
+ * Creates instance of this class and wraps XMLEventReader.
+ *
+ * @param parent event reader to be wrapped.
+ */
+ public DefaultTransactionalEventReader(XMLEventReader parent) {
+ setParent(parent);
+ }
+
+ public void afterPropertiesSet() throws Exception {
+ Assert.notNull(parent);
+ }
+
+ /**
+ * Callback on transaction rollback.
+ */
+ public void onRollback() {
+ recorder.reset();
+ }
+
+ /**
+ * Callback on transacion commit.
+ *
+ */
+ public void onCommit() {
+ recorder.clear();
+ }
+
+ /**
+ * @return the parent
+ */
+ public XMLEventReader getParent() {
+ return parent;
+ }
+
+ /**
+ * @param parent the parent to set
+ */
+ public void setParent(XMLEventReader parent) {
+ this.parent = parent;
+ }
+
+ /**
+ * @param recorder the recorder to set
+ */
+ public void setRecorder(EventSequence recorder) {
+ this.recorder = recorder;
+ }
+
+ /**
+ * Returns the xml event recorder
+ * @return the xml event recorder
+ */
+ public EventSequence getRecorder() {
+ return recorder;
+ }
+
+ /**
+ * Frees any resources associated with this Reader. This method does not
+ * close the underlying input source.
+ *
+ * @throws XMLStreamException if there are errors freeing associated
+ * resources
+ */
+ public void close() throws XMLStreamException {
+ parent.close();
+
+ }
+
+ /**
+ * Reads the content of a text-only element. Precondition: the current event
+ * is START_ELEMENT. Postcondition: The current event is the corresponding
+ * END_ELEMENT.
+ *
+ * @throws XMLStreamException if the current event is not a START_ELEMENT or
+ * if a non text element is encountered
+ */
+ public String getElementText() throws XMLStreamException {
+ StringBuffer buf = new StringBuffer();
+ XMLEvent e = nextEvent();
+ if (!e.isStartElement()) {
+ throw new XMLStreamException(
+ "Precondition for readText is nextEvent().getEventType() == START_ELEMENT (got " + e.getEventType()
+ + ")");
+ }
+
+ while (hasNext()) {
+ e = peek();
+ if (e.isStartElement()) {
+ throw new XMLStreamException("Unexpected Element start");
+ }
+ if (e.isCharacters()) {
+ buf.append(((Characters) e).getData());
+ }
+ if (e.isEndElement()) {
+ return buf.toString();
+ }
+ nextEvent();
+ }
+
+ throw new XMLStreamException("Unexpected end of Document");
+ }
+
+ /**
+ * Get the value of a feature/property from the underlying implementation
+ *
+ * @param name The name of the property
+ * @return The value of the property
+ * @throws IllegalArgumentException if the property is not supported
+ */
+ public Object getProperty(String name) throws IllegalArgumentException {
+ return parent.getProperty(name);
+ }
+
+ /**
+ * Check if there are more events. Returns true if there are more events and
+ * false otherwise.
+ *
+ * @return true if the event reader has more events, false otherwise
+ */
+ public boolean hasNext() {
+ return recorder.hasNext() || parent.hasNext();
+ }
+
+ /**
+ * Get the next XMLEvent
+ *
+ * @see XMLEvent
+ * @throws XMLStreamException if there is an error with the underlying XML.
+ * @throws NoSuchElementException iteration has no more elements.
+ */
+ public XMLEvent nextEvent() throws XMLStreamException {
+ if (!recorder.hasNext()) {
+ recorder.addEvent(parent.nextEvent());
+ }
+ return recorder.nextEvent();
+ }
+
+ /**
+ * Skips any insignificant space events until a START_ELEMENT or END_ELEMENT
+ * is reached. If anything other than space characters are encountered, an
+ * exception is thrown. This method should be used when processing
+ * element-only content because the parser is not able to recognize
+ * ignorable whitespace if the DTD is missing or not interpreted.
+ *
+ * @throws XMLStreamException if anything other than space characters are
+ * encountered
+ */
+ public XMLEvent nextTag() throws XMLStreamException {
+ while (hasNext()) {
+ XMLEvent e = nextEvent();
+ if (e.isCharacters() && !((Characters) e).isWhiteSpace()) {
+ throw new XMLStreamException("Unexpected text");
+ }
+ if (e.isStartElement() || e.isEndElement()) {
+ return e;
+ }
+ }
+ throw new XMLStreamException("Unexpected end of Document");
+ }
+
+ /**
+ * Check the next XMLEvent without reading it from the stream. Returns null
+ * if the stream is at EOF or has no more XMLEvents. A call to peek() will
+ * be equal to the next return of next().
+ *
+ * @see XMLEvent
+ * @throws XMLStreamException
+ */
+ public XMLEvent peek() throws XMLStreamException {
+ return (recorder.hasNext()) ? recorder.peek() : parent.peek();
+ }
+
+ /**
+ * Returns the next element in the iteration. Calling this method repeatedly
+ * until the {@link #hasNext()} method returns false will return each
+ * element in the underlying collection exactly once.
+ *
+ * @return the next element in the iteration.
+ * @exception NoSuchElementException iteration has no more elements.
+ */
+ public Object next() {
+ try {
+ return nextEvent();
+ }
+ catch (XMLStreamException e) {
+ return null;
+ }
+ }
+
+ /**
+ * In this implementation throws UnsupportedOperationException.
+ */
+ public void remove() {
+ throw new java.lang.UnsupportedOperationException();
+ }
+}
diff --git a/infrastructure/src/main/java/org/springframework/batch/io/stax/EventSequence.java b/infrastructure/src/main/java/org/springframework/batch/io/stax/EventSequence.java
new file mode 100644
index 000000000..05a84b0ec
--- /dev/null
+++ b/infrastructure/src/main/java/org/springframework/batch/io/stax/EventSequence.java
@@ -0,0 +1,92 @@
+package org.springframework.batch.io.stax;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.xml.stream.events.XMLEvent;
+
+/**
+ * Holds a list of XML events, typically corresponding to a single record.
+ *
+ * @author tomas.slanina
+ */
+class EventSequence {
+
+ private static final int BEFORE_BEGINNING = -1;
+
+ private List events;
+
+ private int currentIndex;
+
+ /**
+ * Creates instance of this class.
+ *
+ */
+ public EventSequence() {
+ init();
+ }
+
+ /**
+ * Adds event to the list of stored events.
+ *
+ * @param event
+ */
+ public void addEvent(XMLEvent event) {
+ events.add(event);
+ }
+
+ /**
+ * Gets next XMLEvent from cache and moves cursor to next event.
+ * If cache contains no more events, null is returned.
+ *
+ * @return
+ */
+ public XMLEvent nextEvent() {
+ return (hasNext()) ? (XMLEvent)events.get(++currentIndex) :null;
+ }
+
+ /**
+ * Gets next XMLEvent from cache but cursor remains on the same position.
+ * If cache contains no more events, null is returned.
+ *
+ * @return
+ */
+ public XMLEvent peek() {
+ return (hasNext()) ? (XMLEvent)events.get(currentIndex+1) :null;
+ }
+
+ /**
+ * Removes events from the internal cache.
+ *
+ */
+ public void clear() {
+ init();
+ }
+
+ /**
+ * Resets cursor to the cache start.
+ *
+ */
+ public void reset() {
+ currentIndex = BEFORE_BEGINNING;
+ }
+
+ /**
+ * Check if there are more events. Returns true if there are more events and
+ * false otherwise.
+ *
+ * @return true if the event reader has more events, false otherwise
+ */
+ public boolean hasNext() {
+ return currentIndex + 1 < events.size();
+ }
+
+ private void init() {
+ events = (events != null) ? new ArrayList(events.size())
+ : new ArrayList(1000);
+
+ reset();
+ }
+
+
+}
diff --git a/infrastructure/src/main/java/org/springframework/batch/io/stax/FragmentDeserializer.java b/infrastructure/src/main/java/org/springframework/batch/io/stax/FragmentDeserializer.java
new file mode 100644
index 000000000..7db2506c9
--- /dev/null
+++ b/infrastructure/src/main/java/org/springframework/batch/io/stax/FragmentDeserializer.java
@@ -0,0 +1,14 @@
+package org.springframework.batch.io.stax;
+
+import javax.xml.stream.XMLEventReader;
+
+/**
+ * Deserializes XML fragment to domain object.
+ * XML fragment is a standalone XML document corresponding to a single record.
+ *
+ * @author Robert Kasanicky
+ */
+public interface FragmentDeserializer {
+
+ Object deserializeFragment(XMLEventReader eventReader);
+}
diff --git a/infrastructure/src/main/java/org/springframework/batch/io/stax/FragmentEventReader.java b/infrastructure/src/main/java/org/springframework/batch/io/stax/FragmentEventReader.java
new file mode 100644
index 000000000..d9e4c09ee
--- /dev/null
+++ b/infrastructure/src/main/java/org/springframework/batch/io/stax/FragmentEventReader.java
@@ -0,0 +1,33 @@
+package org.springframework.batch.io.stax;
+
+import javax.xml.stream.XMLEventReader;
+
+
+/**
+ * Interface for event readers which support treating XML fragments as standalone XML documents
+ * by wrapping the fragments with StartDocument and EndDocument events.
+ *
+ * @author Robert Kasanicky
+ */
+interface FragmentEventReader extends XMLEventReader {
+
+ /**
+ * Tells the event reader its cursor position is exactly before the fragment.
+ */
+ void markStartFragment();
+
+ /**
+ * Tells the event reader the current fragment has been processed.
+ * If the cursor is still inside the fragment it should be moved
+ * after the end of the fragment.
+ */
+ void markFragmentProcessed();
+
+ /**
+ * Reset the state of the fragment reader - make it forget
+ * it assumptions about current position of cursor
+ * (e.g. in case of rollback of the wrapped reader).
+ */
+ void reset();
+
+}
diff --git a/infrastructure/src/main/java/org/springframework/batch/io/stax/NoStartEndDocumentStreamWriter.java b/infrastructure/src/main/java/org/springframework/batch/io/stax/NoStartEndDocumentStreamWriter.java
new file mode 100644
index 000000000..a522c2291
--- /dev/null
+++ b/infrastructure/src/main/java/org/springframework/batch/io/stax/NoStartEndDocumentStreamWriter.java
@@ -0,0 +1,62 @@
+package org.springframework.batch.io.stax;
+
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLEventWriter;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.XMLEvent;
+
+/**
+ * Delegating XMLEventWriter, which ignores start and end document events,
+ * but passes through everything else.
+ *
+ * @author peter.zozom
+ */
+class NoStartEndDocumentStreamWriter implements XMLEventWriter {
+
+ private XMLEventWriter delegate;
+
+ public NoStartEndDocumentStreamWriter(XMLEventWriter delegate) {
+ this.delegate = delegate;
+ }
+
+ public void add(XMLEvent event) throws XMLStreamException {
+ if ((!event.isStartDocument()) && (!event.isEndDocument())) {
+ delegate.add(event);
+ }
+ }
+
+ public void add(XMLEventReader reader) throws XMLStreamException {
+ delegate.add(reader);
+ }
+
+ public void close() throws XMLStreamException {
+ delegate.close();
+ }
+
+ public void flush() throws XMLStreamException {
+ delegate.flush();
+ }
+
+ public NamespaceContext getNamespaceContext() {
+ return delegate.getNamespaceContext();
+ }
+
+ public String getPrefix(String uri) throws XMLStreamException {
+ return delegate.getPrefix(uri);
+ }
+
+ public void setDefaultNamespace(String uri) throws XMLStreamException {
+ delegate.setDefaultNamespace(uri);
+ }
+
+ public void setNamespaceContext(NamespaceContext context)
+ throws XMLStreamException {
+ delegate.setNamespaceContext(context);
+ }
+
+ public void setPrefix(String prefix, String uri) throws XMLStreamException {
+ delegate.setPrefix(prefix, uri);
+ }
+
+}
diff --git a/infrastructure/src/main/java/org/springframework/batch/io/stax/ObjectToXmlSerializer.java b/infrastructure/src/main/java/org/springframework/batch/io/stax/ObjectToXmlSerializer.java
new file mode 100644
index 000000000..5ac416078
--- /dev/null
+++ b/infrastructure/src/main/java/org/springframework/batch/io/stax/ObjectToXmlSerializer.java
@@ -0,0 +1,28 @@
+package org.springframework.batch.io.stax;
+
+import javax.xml.stream.XMLEventWriter;
+
+/**
+ * Interface wrapping the serialization of an object
+ * to xml. Primarily useful for abstracting how an object
+ * is serialized to an XMLEvent from a specific marshaller.
+ *
+ * @author Lucas Ward
+ *
+ */
+public interface ObjectToXmlSerializer {
+
+ /**
+ * Set event writer objects should be serialized to.
+ *
+ * @param writer
+ */
+ void setEventWriter(XMLEventWriter writer);
+
+ /**
+ * Serialize an Object.
+ *
+ * @param output
+ */
+ void serializeObject(Object output);
+}
diff --git a/infrastructure/src/main/java/org/springframework/batch/io/stax/StaxEventReaderInputSource.java b/infrastructure/src/main/java/org/springframework/batch/io/stax/StaxEventReaderInputSource.java
new file mode 100644
index 000000000..9aa8f7af7
--- /dev/null
+++ b/infrastructure/src/main/java/org/springframework/batch/io/stax/StaxEventReaderInputSource.java
@@ -0,0 +1,269 @@
+package org.springframework.batch.io.stax;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.StartElement;
+
+import org.springframework.batch.io.InputSource;
+import org.springframework.batch.io.Skippable;
+import org.springframework.batch.repeat.synch.BatchTransactionSynchronizationManager;
+import org.springframework.batch.restart.GenericRestartData;
+import org.springframework.batch.restart.RestartData;
+import org.springframework.batch.restart.Restartable;
+import org.springframework.batch.statistics.StatisticsProvider;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.core.io.Resource;
+import org.springframework.dao.DataAccessResourceFailureException;
+import org.springframework.transaction.support.TransactionSynchronization;
+import org.springframework.transaction.support.TransactionSynchronizationAdapter;
+import org.springframework.util.Assert;
+
+/**
+ * Input source for reading XML input based on StAX.
+ *
+ * It extracts fragments from the input XML document which correspond to records
+ * for processing. The fragments are wrapped with StartDocument and EndDocument
+ * events so that the fragments can be further processed like standalone XML
+ * documents.
+ *
+ * @author Robert Kasanicky
+ */
+public class StaxEventReaderInputSource implements InputSource, Skippable, Restartable, StatisticsProvider, InitializingBean, DisposableBean {
+
+ public static final String READ_COUNT_STATISTICS_NAME = "StaxEventReaderInputSource.readCount";
+
+ private static final String RESTART_DATA_NAME = "StaxEventReaderInputSource.recordcount";
+
+ private FragmentEventReader fragmentReader;
+
+ private TransactionalEventReader txReader;
+
+ private FragmentDeserializer fragmentDeserializer;
+
+ private Resource resource;
+
+ private InputStream inputStream;
+
+ private String fragmentRootElementName;
+
+ private boolean initialized = false;
+
+ private TransactionSynchronization synchronization = new StaxEventReaderInputSourceTransactionSychronization();
+
+ private long lastCommitPointRecordCount = 0;
+
+ private long currentRecordCount = 0;
+
+ private List skipRecords = new ArrayList();
+
+ public Object read() {
+ if (!initialized) {
+ open();
+ }
+ Object item = null;
+
+ do {
+ currentRecordCount++;
+ if (moveCursorToNextFragment(fragmentReader)) {
+ fragmentReader.markStartFragment();
+ item = fragmentDeserializer.deserializeFragment(fragmentReader);
+ fragmentReader.markFragmentProcessed();
+ }
+ } while (skipRecords.contains(new Long(currentRecordCount)));
+
+ if (item == null) {
+ currentRecordCount--;
+ }
+ return item;
+ }
+
+ // TODO make sure exception stack is not lost in any case.
+ public void close() {
+ try {
+ initialized = false;
+ fragmentReader.close();
+ }
+ catch (XMLStreamException e) {
+ throw new DataAccessResourceFailureException("Error while closing event reader", e);
+ }
+ finally {
+ try {
+ inputStream.close();
+ }
+ catch (IOException e) {
+ throw new DataAccessResourceFailureException("Error while closing input stream", e);
+ }
+ }
+ }
+
+ public void open() {
+ registerSynchronization();
+ try {
+ inputStream = resource.getInputStream();
+ txReader = new DefaultTransactionalEventReader(XMLInputFactory
+ .newInstance().createXMLEventReader(inputStream));
+ fragmentReader = new DefaultFragmentEventReader(txReader);
+ }
+ 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;
+ }
+
+ public void setResource(Resource resource) {
+ this.resource = resource;
+ }
+
+ /**
+ * @param fragmentDeserializer maps xml fragments corresponding to records to
+ * objects
+ */
+ public void setFragmentDeserializer(FragmentDeserializer fragmentDeserializer) {
+ this.fragmentDeserializer = fragmentDeserializer;
+ }
+
+ /**
+ * @param fragmentRootElementName name of the root element of the fragment
+ * TODO String can be ambiguous due to namespaces, use QName?
+ */
+ public void setFragmentRootElementName(String fragmentRootElementName) {
+ this.fragmentRootElementName = fragmentRootElementName;
+ }
+
+ public void skip() {
+ skipRecords.add(new Long(currentRecordCount));
+ }
+
+ /**
+ * @return Properties wrapper for the count of records read so far.
+ */
+ public Properties getStatistics() {
+ Properties statistics = new Properties();
+ statistics.setProperty(READ_COUNT_STATISTICS_NAME, String.valueOf(currentRecordCount));
+ return statistics;
+ }
+
+ public void afterPropertiesSet() throws Exception {
+ Assert.notNull(resource);
+ Assert.state(resource.exists(), "Input resource does not exist: [" + resource + "]");
+ Assert.notNull(fragmentDeserializer);
+ }
+
+ /**
+ * @return wrapped count of records read so far.
+ */
+ public RestartData getRestartData() {
+ Properties restartData = new Properties();
+
+ restartData.setProperty(RESTART_DATA_NAME, String.valueOf(currentRecordCount));
+
+ return new GenericRestartData(restartData);
+ }
+
+ /**
+ * Rereads (skips) the number of records extracted from restart data.
+ */
+ public void restoreFrom(RestartData data) {
+ if (data == null || data.getProperties() == null ||
+ data.getProperties().getProperty(RESTART_DATA_NAME) == null) {
+ return;
+ }
+
+ if (!initialized) {
+ open();
+ }
+
+ long restoredRecordCount = Long.parseLong(data.getProperties().getProperty(RESTART_DATA_NAME));
+ int REASONABLE_ADHOC_COMMIT_FREQUENCY = 10000;
+ while (currentRecordCount <= restoredRecordCount) {
+ currentRecordCount++;
+ if (currentRecordCount % REASONABLE_ADHOC_COMMIT_FREQUENCY == 0) {
+ txReader.onCommit(); // reset the history buffer
+ }
+ fragmentReader.next();
+ moveCursorToNextFragment(fragmentReader);
+ }
+ txReader.onCommit(); // reset the history buffer
+ }
+
+ /**
+ * Responsible for moving the cursor before the StartElement of the fragment root.
+ *
+ * This implementation simply looks for the next corresponding element, it does not care
+ * about element nesting. You will need to override this method to correctly handle
+ * composite fragments.
+ *
+ * @return true if next fragment was found, false otherwise.
+ */
+ protected boolean moveCursorToNextFragment(XMLEventReader reader) {
+ try {
+ while (true) {
+ while (reader.peek() != null && !reader.peek().isStartElement()) {
+ reader.nextEvent();
+ }
+ if (reader.peek() == null) {
+ return false;
+ }
+ QName startElementName = ((StartElement) reader.peek()).getName();
+ if (startElementName.getLocalPart().equals(fragmentRootElementName)) {
+ return true;
+ } else {
+ reader.nextEvent();
+ }
+ }
+ }
+ catch (XMLStreamException e) {
+ throw new DataAccessResourceFailureException("Error while reading from event reader", e);
+ }
+ }
+
+ // package visibility method for simulating transaction events
+ TransactionSynchronization getSynchronization() {
+ return synchronization;
+ }
+
+ /**
+ * Encapsulates transaction events for the StaxEventReaderInputSource.
+ */
+ private class StaxEventReaderInputSourceTransactionSychronization extends TransactionSynchronizationAdapter {
+
+ /**
+ * @param status
+ * @see org.springframework.transaction.support.TransactionSynchronizationAdapter#afterCompletion(int)
+ */
+ public void afterCompletion(int status) {
+ if (status == TransactionSynchronization.STATUS_COMMITTED) {
+ lastCommitPointRecordCount = currentRecordCount;
+ txReader.onCommit();
+ skipRecords = new ArrayList();
+ }
+ else if (status == TransactionSynchronization.STATUS_ROLLED_BACK) {
+ currentRecordCount = lastCommitPointRecordCount;
+ txReader.onRollback();
+ fragmentReader.reset();
+ }
+ }
+
+ }
+
+ public void destroy() throws Exception {
+ close();
+ }
+
+ private void registerSynchronization() {
+ BatchTransactionSynchronizationManager.registerSynchronization(synchronization);
+ }
+
+}
diff --git a/infrastructure/src/main/java/org/springframework/batch/io/stax/StaxEventWriterOutputSource.java b/infrastructure/src/main/java/org/springframework/batch/io/stax/StaxEventWriterOutputSource.java
new file mode 100644
index 000000000..79deb14ed
--- /dev/null
+++ b/infrastructure/src/main/java/org/springframework/batch/io/stax/StaxEventWriterOutputSource.java
@@ -0,0 +1,512 @@
+package org.springframework.batch.io.stax;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.xml.stream.XMLEventFactory;
+import javax.xml.stream.XMLEventWriter;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+
+import org.springframework.batch.io.OutputSource;
+import org.springframework.batch.io.support.FileUtils;
+import org.springframework.batch.repeat.synch.BatchTransactionSynchronizationManager;
+import org.springframework.batch.restart.GenericRestartData;
+import org.springframework.batch.restart.RestartData;
+import org.springframework.batch.restart.Restartable;
+import org.springframework.batch.statistics.StatisticsProvider;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.core.io.Resource;
+import org.springframework.dao.DataAccessResourceFailureException;
+import org.springframework.transaction.support.TransactionSynchronization;
+import org.springframework.transaction.support.TransactionSynchronizationAdapter;
+import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
+
+/**
+ * StaxEventWriterOutputSource is implementation of {@link OutputSource} which uses
+ * StAX and {@link ObjectToXmlSerializer} for serializing object to XML.
+ *
+ * This output source also provides restart, statistics and transaction
+ * features by implementing corresponding interfaces.
+ *
+ * @author Peter Zozom
+ *
+ */
+public class StaxEventWriterOutputSource implements OutputSource, Restartable,
+ StatisticsProvider, InitializingBean, DisposableBean {
+
+ // default encoding
+ private static final String DEFAULT_ENCODING = "UTF-8";
+
+ // default encoding
+ private static final String DEFAULT_XML_VERSION = "1.0";
+
+ // default root tag name
+ private static final String DEFAULT_ROOT_TAG_NAME = "root";
+
+ // restart data property name
+ private static final String RESTART_DATA_NAME = "staxstreamoutputsource.position";
+
+ // read statistics property name
+ public static final String WRITE_STATISTICS_NAME = "staxstreamoutputsource.processedrecordcount";
+
+ // file system resource
+ private Resource resource;
+
+ // xml serializer
+ private ObjectToXmlSerializer serializer;
+
+ // encoding to be used while reading from the resource
+ private String encoding = DEFAULT_ENCODING;
+
+ // XML version
+ private String version = DEFAULT_XML_VERSION;
+
+ // name of the root tag
+ private String rootTagName = DEFAULT_ROOT_TAG_NAME;
+
+ // root element attributes
+ private Map rootElementAttributes = null;
+
+ // signalizes that output source has been initialized
+ private boolean initialized = false;
+
+ // signalizes that marshalling was restarted
+ private boolean restarted = false;
+
+ // TRUE means, that output file will be overwritten if exists - default is TRUE
+ private boolean overwriteOutput = true;
+
+ // file channel
+ private FileChannel channel;
+
+ // wrapper for XML event writer that swallows StartDocument and EndDocument events
+ private XMLEventWriter eventWriter;
+
+ // XML event writer
+ private XMLEventWriter delegateEventWriter;
+
+ // transaction synchronization object
+ private TransactionSynchronization synchronization = new StaxEventWriterOutputSourceTransactionSychronization();
+
+ // byte offset in file channel at last commit point
+ private long lastCommitPointPosition = 0;
+
+ // processed record count at last commit point
+ private long lastCommitPointRecordCount = 0;
+
+ // current count of processed records
+ private long currentRecordCount = 0;
+
+
+ /**
+ * Set output file.
+ *
+ * @param resource the output file
+ */
+ public void setResource(Resource resource) {
+ this.resource = resource;
+ }
+
+ /**
+ * Set Object to XML serializer.
+ *
+ * @param serializer the Object to XML serializer
+ */
+ public void setSerializer(ObjectToXmlSerializer serializer) {
+ this.serializer = serializer;
+ }
+
+ /**
+ * Get used encoding.
+ *
+ * @return the encoding used
+ */
+ public String getEncoding() {
+ return encoding;
+ }
+
+ /**
+ * Set encoding to be used for output file.
+ *
+ * @param encoding the encoding to be used
+ */
+ public void setEncoding(String encoding) {
+ this.encoding = encoding;
+ }
+
+ /**
+ * Get XML version.
+ *
+ * @return the XML version used
+ */
+ public String getVersion() {
+ return version;
+ }
+
+ /**
+ * Set XML version to be used for output XML.
+ *
+ * @param version the XML version to be used
+ */
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ /**
+ * Get the tag name of the root element.
+ *
+ * @return the root element tag name
+ */
+ public String getRootTagName() {
+ return rootTagName;
+ }
+
+ /**
+ * Set the tag name of the root element. If not set, default name is used ("root").
+ *
+ * @param rootTagName the tag name to be used for the root element
+ */
+ public void setRootTagName(String rootTagName) {
+ this.rootTagName = rootTagName;
+ }
+
+ /**
+ * Get attributes of the root element.
+ *
+ * @return attributes of the root element
+ */
+ public Map getRootElementAttributes() {
+ return rootElementAttributes;
+ }
+
+ /**
+ * Set the root element attributes to be written.
+ *
+ * @param rootElementAttributes attributes of the root element
+ */
+ public void setRootElementAttributes(Map rootElementAttributes) {
+ this.rootElementAttributes = rootElementAttributes;
+ }
+
+ /**
+ * Set "overwrite" flag for the output file. Flag is ignored when output file processing is restarted.
+ *
+ * @param shouldDeleteIfExists
+ */
+ public void setOverwriteOutput(boolean overwriteOutput) {
+ this.overwriteOutput = overwriteOutput;
+ }
+
+ protected FileChannel getChannel() {
+ return channel;
+ }
+
+ /**
+ * @throws Exception
+ * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
+ */
+ public void afterPropertiesSet() throws Exception {
+ Assert.notNull(resource);
+ Assert.notNull(serializer);
+ }
+
+ /**
+ * @throws Exception
+ * @see org.springframework.beans.factory.DisposableBean#destroy()
+ */
+ public void destroy() throws Exception {
+ close();
+ }
+
+ /**
+ * Register the input source for transaction synchronization.
+ */
+ private void registerSynchronization() {
+ BatchTransactionSynchronizationManager.registerSynchronization(synchronization);
+ }
+
+ /**
+ * Open the output source
+ *
+ * @see org.springframework.batch.item.ResourceLifecycle#open()
+ */
+ public void open() {
+ open(0);
+ }
+
+ /*
+ * Helper method for opening output source at given file position
+ */
+ private void open(long position) {
+
+ registerSynchronization();
+
+ File file;
+ try {
+ file = resource.getFile();
+ FileUtils.setUpOutputFile(file, restarted, overwriteOutput);
+ } catch (IOException ioe) {
+ throw new DataAccessResourceFailureException(
+ "Unable to write to file resource: [" + resource + "]", ioe);
+ }
+
+ FileOutputStream os = null;
+
+ try {
+ os = new FileOutputStream(file, true);
+ channel = os.getChannel();
+ setPosition(position);
+ } catch (IOException ioe) {
+ throw new DataAccessResourceFailureException(
+ "Unable to write to file resource: [" + resource + "]", ioe);
+ }
+
+ XMLOutputFactory outputFactory = XMLOutputFactory.newInstance();
+
+ try {
+ delegateEventWriter = outputFactory.createXMLEventWriter(os, encoding);
+ eventWriter = new NoStartEndDocumentStreamWriter(delegateEventWriter);
+ serializer.setEventWriter(eventWriter);
+ if (!restarted) {
+ startDocument(delegateEventWriter);
+ }
+ } catch (XMLStreamException xse) {
+ throw new DataAccessResourceFailureException(
+ "Unable to write to file resource: [" + resource + "]", xse);
+ }
+
+ initialized = true;
+ }
+
+ /**
+ * Writes simple XML header containing:
+ *
When starting output file processing, method creates/overwrites new file.
+ * When restaring output file processing, method checks whether file is writable.
+ *
+ * @param file file to be set up
+ * @param restarted TRUE signalizes that we are restarting output file processing
+ * @param overwriteOutputFile If set to TRUE, output file will be overwritten
+ * (this flag is ignored when processing is restart)
+ *
+ * @throws IllegalArgumentException when file is NULL
+ * @throws IllegalStateException when staring output file processing, file exists and
+ * flag "shouldDeleteExisting" is set to FALSE
+ * @throws DataAccessResourceFailureException when unable to create file or file is not writable
+ */
+ public static void setUpOutputFile(File file, boolean restarted,
+ boolean overwriteOutputFile) {
+
+ Assert.notNull(file);
+
+ try {
+ if (!restarted) {
+ if (file.exists()) {
+ Assert.state(overwriteOutputFile, "File already exists: ["
+ + file.getAbsolutePath() + "]");
+ file.delete();
+ }
+
+ if (file.getParent() != null ) {
+ new File(file.getParent()).mkdirs();
+ }
+ file.createNewFile();
+ }
+ } catch (IOException ioe) {
+ throw new DataAccessResourceFailureException(
+ "Unable to create file: [" + file.getAbsolutePath() + "]",
+ ioe);
+ }
+
+ if (!file.canWrite()) {
+ throw new DataAccessResourceFailureException(
+ "File is not writable: [" + file.getAbsolutePath() + "]");
+ }
+ }
+}
diff --git a/infrastructure/src/test/java/org/springframework/batch/io/oxm/MarshallingObjectToXmlSerializerTests.java b/infrastructure/src/test/java/org/springframework/batch/io/oxm/MarshallingObjectToXmlSerializerTests.java
new file mode 100644
index 000000000..450c73ac1
--- /dev/null
+++ b/infrastructure/src/test/java/org/springframework/batch/io/oxm/MarshallingObjectToXmlSerializerTests.java
@@ -0,0 +1,128 @@
+/*
+ * 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.io.oxm;
+
+import java.io.IOException;
+
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLEventWriter;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.XMLEvent;
+import javax.xml.transform.Result;
+
+import org.springframework.dao.DataAccessResourceFailureException;
+import org.springframework.oxm.Marshaller;
+import org.springframework.oxm.XmlMappingException;
+
+import junit.framework.TestCase;
+
+/**
+ *
+ *
+ * @author Lucas Ward
+ *
+ */
+public class MarshallingObjectToXmlSerializerTests extends TestCase {
+
+ MarshallingObjectToXmlSerializer xmlSerializer;
+
+ MockMarshaller mockMarshaller = new MockMarshaller();
+
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ xmlSerializer = new MarshallingObjectToXmlSerializer(mockMarshaller);
+ xmlSerializer.setEventWriter(new StubXmlEventWriter());
+ }
+
+ public void testSuccessfulWrite(){
+
+ Object objectToOutput = new Object();
+ xmlSerializer.serializeObject(objectToOutput);
+ assertEquals(objectToOutput, mockMarshaller.getMarshalledObject());
+ }
+
+ public void testUnsucessfulWrite(){
+
+ mockMarshaller.setThrowException(true);
+ try{
+ xmlSerializer.serializeObject(new Object());
+ }catch(DataAccessResourceFailureException ex){
+ //expected
+ }
+ }
+
+ private class MockMarshaller implements Marshaller{
+
+ private Object marshalledObject;
+ private boolean throwException = false;
+
+ public void marshal(Object arg0, Result arg1)
+ throws XmlMappingException, IOException {
+ if(throwException){
+ throw new IOException();
+ }
+ marshalledObject = arg0;
+ }
+
+ public boolean supports(Class arg0) {
+ return false;
+ }
+
+ public Object getMarshalledObject() {
+ return marshalledObject;
+ }
+
+ public void setThrowException(boolean throwException) {
+ this.throwException = throwException;
+ }
+ }
+
+ private class StubXmlEventWriter implements XMLEventWriter{
+
+ public void add(XMLEvent arg0) throws XMLStreamException { }
+
+ public void add(XMLEventReader arg0) throws XMLStreamException { }
+
+ public void close() throws XMLStreamException {
+ }
+
+ public void flush() throws XMLStreamException {
+ }
+
+ public NamespaceContext getNamespaceContext() {
+ return null;
+ }
+
+ public String getPrefix(String arg0) throws XMLStreamException {
+ return null;
+ }
+
+ public void setDefaultNamespace(String arg0) throws XMLStreamException {
+ }
+
+ public void setNamespaceContext(NamespaceContext arg0)
+ throws XMLStreamException {
+ }
+
+ public void setPrefix(String arg0, String arg1)
+ throws XMLStreamException {
+ }
+
+ }
+}
+
diff --git a/infrastructure/src/test/java/org/springframework/batch/io/stax/DefaultFragmentEventReaderTests.java b/infrastructure/src/test/java/org/springframework/batch/io/stax/DefaultFragmentEventReaderTests.java
new file mode 100644
index 000000000..5438fb98a
--- /dev/null
+++ b/infrastructure/src/test/java/org/springframework/batch/io/stax/DefaultFragmentEventReaderTests.java
@@ -0,0 +1,119 @@
+package org.springframework.batch.io.stax;
+
+import java.util.NoSuchElementException;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.EndElement;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+
+import junit.framework.TestCase;
+
+import org.springframework.core.io.ByteArrayResource;
+import org.springframework.core.io.Resource;
+
+/**
+ * Tests for {@link DefaultFragmentEventReader}.
+ *
+ * @author Robert Kasanicky
+ */
+public class DefaultFragmentEventReaderTests extends TestCase {
+
+ // object under test
+ private FragmentEventReader fragmentReader;
+
+ // wrapped event fragmentReader
+ private XMLEventReader eventReader;
+
+ // test xml input
+ private String xml = "