BATCH-2049: Support multiple fragmentRootElementNames in

StaxEventItemReader
This commit is contained in:
jpraet
2013-08-14 22:37:03 +02:00
committed by Michael Minella
parent 7f1130c9a2
commit a21df75ce9
2 changed files with 177 additions and 27 deletions

View File

@@ -17,6 +17,8 @@
package org.springframework.batch.item.xml;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import javax.xml.namespace.QName;
@@ -39,6 +41,7 @@ import org.springframework.core.io.Resource;
import org.springframework.oxm.Unmarshaller;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* Item reader for reading XML input based on StAX.
@@ -66,14 +69,12 @@ ResourceAwareItemReaderItemStream<T>, InitializingBean {
private InputStream inputStream;
private String fragmentRootElementName;
private List<QName> fragmentRootElementNames;
private boolean noInput;
private boolean strict = true;
private String fragmentRootElementNameSpace;
public StaxEventItemReader() {
setName(ClassUtils.getShortName(StaxEventItemReader.class));
}
@@ -103,7 +104,17 @@ ResourceAwareItemReaderItemStream<T>, InitializingBean {
* @param fragmentRootElementName name of the root element of the fragment
*/
public void setFragmentRootElementName(String fragmentRootElementName) {
this.fragmentRootElementName = fragmentRootElementName;
setFragmentRootElementNames(new String[] {fragmentRootElementName});
}
/**
* @param fragmentRootElementNames list of the names of the root element of the fragment
*/
public void setFragmentRootElementNames(String[] fragmentRootElementNames) {
this.fragmentRootElementNames = new ArrayList<QName>();
for (String fragmentRootElementName : fragmentRootElementNames) {
this.fragmentRootElementNames.add(parseFragmentRootElementName(fragmentRootElementName));
}
}
/**
@@ -117,11 +128,11 @@ ResourceAwareItemReaderItemStream<T>, InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
Assert.notNull(unmarshaller, "The Unmarshaller must not be null.");
Assert.hasLength(fragmentRootElementName, "The FragmentRootElementName must not be null");
if (fragmentRootElementName.contains("{")) {
fragmentRootElementNameSpace = fragmentRootElementName.replaceAll("\\{(.*)\\}.*", "$1");
fragmentRootElementName = fragmentRootElementName.replaceAll("\\{.*\\}(.*)", "$1");
}
Assert.notNull(fragmentRootElementNames, "The FragmentRootElementNames must not be null");
Assert.notNull(fragmentRootElementNames, "The FragmentRootElementNames must not be empty");
for (QName fragmentRootElementName : fragmentRootElementNames) {
Assert.hasText(fragmentRootElementName.getLocalPart(), "The FragmentRootElementNames must contain empty elements");
}
}
/**
@@ -145,11 +156,8 @@ ResourceAwareItemReaderItemStream<T>, InitializingBean {
return false;
}
QName startElementName = ((StartElement) reader.peek()).getName();
if (startElementName.getLocalPart().equals(fragmentRootElementName)) {
if (fragmentRootElementNameSpace == null
|| startElementName.getNamespaceURI().equals(fragmentRootElementNameSpace)) {
return true;
}
if (isFragmentRootElementName(startElementName)) {
return true;
}
reader.nextEvent();
@@ -249,8 +257,8 @@ ResourceAwareItemReaderItemStream<T>, InitializingBean {
protected void jumpToItem(int itemIndex) throws Exception {
for (int i = 0; i < itemIndex; i++) {
try {
readToStartFragment();
readToEndFragment();
QName fragmentName = readToStartFragment();
readToEndFragment(fragmentName);
} catch (NoSuchElementException e) {
if (itemIndex == (i + 1)) {
// we can presume a NoSuchElementException on the last item means the EOF was reached on the last run
@@ -264,16 +272,16 @@ ResourceAwareItemReaderItemStream<T>, InitializingBean {
}
/*
* Read until the first StartElement tag that matches the provided fragmentRootElementName. Because there may be any
* Read until the first StartElement tag that matches any of the provided fragmentRootElementNames. Because there may be any
* number of tags in between where the reader is now and the fragment start, this is done in a loop until the
* element type and name match.
*/
private void readToStartFragment() throws XMLStreamException {
private QName readToStartFragment() throws XMLStreamException {
while (true) {
XMLEvent nextEvent = eventReader.nextEvent();
if (nextEvent.isStartElement()
&& ((StartElement) nextEvent).getName().getLocalPart().equals(fragmentRootElementName)) {
return;
&& isFragmentRootElementName(((StartElement) nextEvent).getName())) {
return ((StartElement) nextEvent).getName();
}
}
}
@@ -283,13 +291,36 @@ ResourceAwareItemReaderItemStream<T>, InitializingBean {
* number of tags in between where the reader is now and the fragment end tag, this is done in a loop until the
* element type and name match
*/
private void readToEndFragment() throws XMLStreamException {
private void readToEndFragment(QName fragmentRootElementName) throws XMLStreamException {
while (true) {
XMLEvent nextEvent = eventReader.nextEvent();
if (nextEvent.isEndElement()
&& ((EndElement) nextEvent).getName().getLocalPart().equals(fragmentRootElementName)) {
&& fragmentRootElementName.equals(((EndElement) nextEvent).getName())) {
return;
}
}
}
private boolean isFragmentRootElementName(QName name) {
for (QName fragmentRootElementName : fragmentRootElementNames) {
if (fragmentRootElementName.getLocalPart().equals(name.getLocalPart())) {
if (!StringUtils.hasText(fragmentRootElementName.getNamespaceURI())
|| fragmentRootElementName.getNamespaceURI().equals(name.getNamespaceURI())) {
return true;
}
}
}
return false;
}
private QName parseFragmentRootElementName(String fragmentRootElementName) {
String name = fragmentRootElementName;
String nameSpace = null;
if (fragmentRootElementName.contains("{")) {
nameSpace = fragmentRootElementName.replaceAll("\\{(.*)\\}.*", "$1");
name = fragmentRootElementName.replaceAll("\\{.*\\}(.*)", "$1");
}
return new QName(nameSpace, name, "");
}
}

View File

@@ -10,13 +10,16 @@ import static org.junit.Assert.fail;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.xml.namespace.QName;
import javax.xml.stream.FactoryConfigurationError;
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 javax.xml.transform.Source;
@@ -48,6 +51,12 @@ public class StaxEventItemReaderTests {
// test xml input
private String xml = "<root> <fragment> <misc1/> </fragment> <misc2/> <fragment> testString </fragment> </root>";
// test xml input
private String xmlMultiFragment = "<root> <fragmentA> <misc1/> </fragmentA> <misc2/> <fragmentB> testString </fragmentB> <fragmentA xmlns=\"urn:org.test.bar\"> testString </fragmentA></root>";
// test xml input
private String xmlMultiFragmentNested = "<root> <fragmentA> <misc1/> <fragmentB> nested</fragmentB> <fragmentB> nested </fragmentB></fragmentA> <misc2/> <fragmentB> testString </fragmentB> <fragmentA xmlns=\"urn:org.test.bar\"> testString </fragmentA></root>";
// test xml input
private String emptyXml = "<root></root>";
@@ -63,6 +72,8 @@ public class StaxEventItemReaderTests {
private Unmarshaller unmarshaller = new MockFragmentUnmarshaller();
private static final String FRAGMENT_ROOT_ELEMENT = "fragment";
private static final String[] MULTI_FRAGMENT_ROOT_ELEMENTS = {"fragmentA", "fragmentB"};
private ExecutionContext executionContext;
@@ -194,7 +205,110 @@ public class StaxEventItemReaderTests {
source.close();
}
@Test
public void testMultiFragment() throws Exception {
source.setResource(new ByteArrayResource(xmlMultiFragment.getBytes()));
source.setFragmentRootElementNames(MULTI_FRAGMENT_ROOT_ELEMENTS);
source.afterPropertiesSet();
source.open(executionContext);
// see asserts in the mock unmarshaller
assertNotNull(source.read());
assertNotNull(source.read());
assertNotNull(source.read());
assertNull(source.read()); // there are only three fragments
source.close();
}
@Test
public void testMultiFragmentNameSpace() throws Exception {
source.setResource(new ByteArrayResource(xmlMultiFragment.getBytes()));
source.setFragmentRootElementNames(new String[] {"{urn:org.test.bar}fragmentA", "fragmentB"});
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 (one has wrong namespace)
source.close();
}
@Test
public void testMultiFragmentRestart() throws Exception {
source.setResource(new ByteArrayResource(xmlMultiFragment.getBytes()));
source.setFragmentRootElementNames(MULTI_FRAGMENT_ROOT_ELEMENTS);
source.afterPropertiesSet();
source.open(executionContext);
// see asserts in the mock unmarshaller
assertNotNull(source.read());
assertNotNull(source.read());
source.update(executionContext);
assertEquals(2, executionContext.getInt(ClassUtils.getShortName(StaxEventItemReader.class) + ".read.count"));
source.close();
source = createNewInputSouce();
source.setResource(new ByteArrayResource(xmlMultiFragment.getBytes()));
source.setFragmentRootElementNames(MULTI_FRAGMENT_ROOT_ELEMENTS);
source.afterPropertiesSet();
source.open(executionContext);
assertNotNull(source.read());
assertNull(source.read()); // there are only three fragments
source.close();
}
@Test
public void testMultiFragmentNested() throws Exception {
source.setResource(new ByteArrayResource(xmlMultiFragmentNested.getBytes()));
source.setFragmentRootElementNames(MULTI_FRAGMENT_ROOT_ELEMENTS);
source.afterPropertiesSet();
source.open(executionContext);
// see asserts in the mock unmarshaller
assertNotNull(source.read());
assertNotNull(source.read());
assertNotNull(source.read());
assertNull(source.read()); // there are only three fragments
source.close();
}
@Test
public void testMultiFragmentNestedRestart() throws Exception {
source.setResource(new ByteArrayResource(xmlMultiFragmentNested.getBytes()));
source.setFragmentRootElementNames(MULTI_FRAGMENT_ROOT_ELEMENTS);
source.afterPropertiesSet();
source.open(executionContext);
// see asserts in the mock unmarshaller
assertNotNull(source.read());
assertNotNull(source.read());
source.update(executionContext);
assertEquals(2, executionContext.getInt(ClassUtils.getShortName(StaxEventItemReader.class) + ".read.count"));
source.close();
source = createNewInputSouce();
source.setResource(new ByteArrayResource(xmlMultiFragment.getBytes()));
source.setFragmentRootElementNames(MULTI_FRAGMENT_ROOT_ELEMENTS);
source.afterPropertiesSet();
source.open(executionContext);
assertNotNull(source.read());
assertNull(source.read()); // there are only three fragments
source.close();
}
/**
* Cursor is moved before beginning of next fragment.
*/
@@ -504,13 +618,13 @@ public class StaxEventItemReaderTests {
/**
* Skips the XML fragment contents.
*/
private List<XMLEvent> readRecordsInsideFragment(XMLEventReader eventReader) throws XMLStreamException {
private List<XMLEvent> readRecordsInsideFragment(XMLEventReader eventReader, QName fragmentName) throws XMLStreamException {
XMLEvent eventInsideFragment;
List<XMLEvent> events = new ArrayList<XMLEvent>();
do {
eventInsideFragment = eventReader.peek();
if (eventInsideFragment instanceof EndElement
&& ((EndElement) eventInsideFragment).getName().getLocalPart().equals(FRAGMENT_ROOT_ELEMENT)) {
&& fragmentName.equals(((EndElement) eventInsideFragment).getName())) {
break;
}
events.add(eventReader.nextEvent());
@@ -545,15 +659,16 @@ public class StaxEventItemReaderTests {
// second should be StartElement of the fragment
XMLEvent event2 = eventReader.nextEvent();
assertTrue(event2.isStartElement());
assertTrue(EventHelper.startElementName(event2).equals(FRAGMENT_ROOT_ELEMENT));
assertTrue(isFragmentRootElement(EventHelper.startElementName(event2)));
QName fragmentName = ((StartElement) event2).getName();
// jump before the end of fragment
fragmentContent = readRecordsInsideFragment(eventReader);
fragmentContent = readRecordsInsideFragment(eventReader, fragmentName);
// end of fragment
XMLEvent event3 = eventReader.nextEvent();
assertTrue(event3.isEndElement());
assertTrue(EventHelper.endElementName(event3).equals(FRAGMENT_ROOT_ELEMENT));
assertTrue(isFragmentRootElement(EventHelper.endElementName(event3)));
// EndDocument should follow the end of fragment
XMLEvent event4 = eventReader.nextEvent();
@@ -565,6 +680,10 @@ public class StaxEventItemReaderTests {
}
return fragmentContent;
}
private boolean isFragmentRootElement(String name) {
return FRAGMENT_ROOT_ELEMENT.equals(name) || Arrays.asList(MULTI_FRAGMENT_ROOT_ELEMENTS).contains(name);
}
}