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 9da16b274..6be9f926d 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2020 the original author or authors.
+ * Copyright 2006-2023 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.
@@ -49,13 +49,13 @@ import org.springframework.util.xml.StaxUtils;
/**
* Item reader 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.
- *
+ *
* The implementation is not thread-safe.
- *
+ *
* @author Robert Kasanicky
* @author Mahmoud Ben Hassine
*/
@@ -140,16 +140,16 @@ ResourceAwareItemReaderItemStream, InitializingBean {
/**
* Set encoding to be used for the input file. Defaults to {@link #DEFAULT_ENCODING}.
*
- * @param encoding the encoding to be used
+ * @param encoding the encoding to be used. Can be {@code null}, in which case, the
+ * XML event reader will attempt to auto-detect the encoding from the input file.
*/
- public void setEncoding(String encoding) {
- Assert.notNull(encoding, "The encoding must not be null");
+ public void setEncoding(@Nullable String encoding) {
this.encoding = encoding;
}
/**
* Ensure that all required dependencies for the ItemReader to run are provided after all properties have been set.
- *
+ *
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
* @throws IllegalArgumentException if the Resource, FragmentDeserializer or FragmentRootElementName is null, or if
* the root element is empty.
@@ -161,19 +161,19 @@ ResourceAwareItemReaderItemStream, InitializingBean {
Assert.notEmpty(fragmentRootElementNames, "The FragmentRootElementNames must not be empty");
for (QName fragmentRootElementName : fragmentRootElementNames) {
Assert.hasText(fragmentRootElementName.getLocalPart(), "The FragmentRootElementNames must not contain empty elements");
- }
+ }
}
/**
* 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.
*
* @param reader the {@link XMLEventReader} to be used to find next fragment.
- *
+ *
* @return true if next fragment was found, false otherwise.
- *
+ *
* @throws NonTransientResourceException if the cursor could not be moved. This will be treated as fatal and
* subsequent calls to read will return null.
*/
@@ -237,7 +237,8 @@ ResourceAwareItemReaderItemStream, InitializingBean {
}
inputStream = resource.getInputStream();
- eventReader = xmlInputFactory.createXMLEventReader(inputStream, this.encoding);
+ eventReader = this.encoding != null ? xmlInputFactory.createXMLEventReader(inputStream, this.encoding)
+ : xmlInputFactory.createXMLEventReader(inputStream);
fragmentReader = new DefaultFragmentEventReader(eventReader);
noInput = false;
@@ -332,19 +333,19 @@ ResourceAwareItemReaderItemStream, InitializingBean {
}
}
}
-
+
protected boolean isFragmentRootElementName(QName name) {
for (QName fragmentRootElementName : fragmentRootElementNames) {
if (fragmentRootElementName.getLocalPart().equals(name.getLocalPart())) {
if (!StringUtils.hasText(fragmentRootElementName.getNamespaceURI())
- || fragmentRootElementName.getNamespaceURI().equals(name.getNamespaceURI())) {
+ || fragmentRootElementName.getNamespaceURI().equals(name.getNamespaceURI())) {
return true;
}
}
}
return false;
- }
-
+ }
+
private QName parseFragmentRootElementName(String fragmentRootElementName) {
String name = fragmentRootElementName;
String nameSpace = null;
@@ -354,5 +355,5 @@ ResourceAwareItemReaderItemStream, InitializingBean {
}
return new QName(nameSpace, name, "");
}
-
+
}
diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/builder/StaxEventItemReaderBuilder.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/builder/StaxEventItemReaderBuilder.java
index 5f9c7bb36..acf9084a3 100644
--- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/builder/StaxEventItemReaderBuilder.java
+++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/builder/StaxEventItemReaderBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2020 the original author or authors.
+ * Copyright 2017-2023 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.
@@ -26,6 +26,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.batch.item.xml.StaxEventItemReader;
import org.springframework.core.io.Resource;
+import org.springframework.lang.Nullable;
import org.springframework.oxm.Unmarshaller;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -203,13 +204,14 @@ public class StaxEventItemReaderBuilder {
}
/**
- * Encoding for the input file. Defaults to {@link StaxEventItemReader#DEFAULT_ENCODING}.
- *
+ * Encoding for the input file. Defaults to
+ * {@link StaxEventItemReader#DEFAULT_ENCODING}. Can be {@code null}, in which case
+ * the XML event reader will attempt to auto-detect the encoding from the input file.
* @param encoding String encoding algorithm
* @return the current instance of the builder
* @see StaxEventItemReader#setEncoding(String)
*/
- public StaxEventItemReaderBuilder encoding(String encoding) {
+ public StaxEventItemReaderBuilder encoding(@Nullable String encoding) {
this.encoding = encoding;
return this;
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 0737324e3..0f643dab9 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2008-2020 the original author or authors.
+ * Copyright 2008-2023 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.
@@ -36,8 +36,10 @@ import org.springframework.util.xml.StaxUtils;
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.StartDocument;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
import javax.xml.transform.Source;
@@ -57,10 +59,13 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
/**
* Tests for {@link StaxEventItemReader}.
- *
+ *
* @author Robert Kasanicky
* @author Michael Minella
* @author Mahmoud Ben Hassine
@@ -94,7 +99,7 @@ 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;
@@ -170,6 +175,37 @@ public class StaxEventItemReaderTests {
source.close();
}
+ @Test
+ public void testNullEncoding() throws Exception {
+ // given
+ XMLEventReader eventReader = mock(XMLEventReader.class);
+ when(eventReader.peek()).thenReturn(mock(StartDocument.class));
+
+ Resource resource = mock(Resource.class);
+ InputStream inputStream = mock(InputStream.class);
+ when(resource.getInputStream()).thenReturn(inputStream);
+ when(resource.isReadable()).thenReturn(true);
+ when(resource.exists()).thenReturn(true);
+ XMLInputFactory xmlInputFactory = mock(XMLInputFactory.class);
+ when(xmlInputFactory.createXMLEventReader(inputStream)).thenReturn(eventReader);
+
+ StaxEventItemReader