Remove DataAccessResourceFailureException in xml related classes

Resolves #4132
This commit is contained in:
Marvin Deng
2022-07-07 14:13:41 +08:00
committed by Fadhel Mahmoud Ben Hassine
parent 0d7a16054c
commit 1e24e38db7
4 changed files with 156 additions and 158 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2006-2020 the original author or authors.
* Copyright 2006-2022 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.
@@ -52,7 +52,6 @@ import org.springframework.batch.item.xml.stax.UnopenedElementClosingEventWriter
import org.springframework.batch.support.transaction.TransactionAwareBufferedWriter;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.oxm.Marshaller;
import org.springframework.oxm.XmlMappingException;
import org.springframework.util.Assert;
@@ -64,12 +63,12 @@ import org.springframework.util.xml.StaxUtils;
/**
* An implementation of {@link ItemWriter} which uses StAX and
* {@link Marshaller} for serializing object to XML.
*
*
* This item writer also provides restart, statistics and transaction features
* by implementing corresponding interfaces.
*
*
* The implementation is <b>not</b> thread-safe.
*
*
* @author Peter Zozom
* @author Robert Kasanicky
* @author Michael Minella
@@ -98,7 +97,7 @@ ResourceAwareItemWriterItemStream<T>, InitializingBean {
// unclosed header callback elements property name
private static final String UNCLOSED_HEADER_CALLBACK_ELEMENTS_NAME = "unclosedHeaderCallbackElements";
// restart data property name
private static final String WRITE_STATISTICS_NAME = "record.count";
@@ -159,11 +158,11 @@ ResourceAwareItemWriterItemStream<T>, InitializingBean {
private boolean forceSync;
private boolean shouldDeleteIfEmpty = false;
private boolean restarted = false;
private boolean initialized = false;
// List holding the QName of elements that were opened in the header callback, but not closed
private List<QName> unclosedHeaderCallbackElements = Collections.emptyList();
@@ -173,7 +172,7 @@ ResourceAwareItemWriterItemStream<T>, InitializingBean {
/**
* Set output file.
*
*
* @param resource the output file
*/
@Override
@@ -183,7 +182,7 @@ ResourceAwareItemWriterItemStream<T>, InitializingBean {
/**
* Set Object to XML marshaller.
*
*
* @param marshaller the Object to XML marshaller
*/
public void setMarshaller(Marshaller marshaller) {
@@ -212,7 +211,7 @@ ResourceAwareItemWriterItemStream<T>, InitializingBean {
/**
* Flag to indicate that writes should be deferred to the end of a
* transaction if present. Defaults to true.
*
*
* @param transactional the flag to set
*/
public void setTransactional(boolean transactional) {
@@ -225,7 +224,7 @@ ResourceAwareItemWriterItemStream<T>, InitializingBean {
* be lost if the OS crashes in between a write and a cache flush. Setting
* to true may result in slower performance for usage patterns involving
* many frequent writes.
*
*
* @param forceSync the flag value to set
*/
public void setForceSync(boolean forceSync) {
@@ -235,7 +234,7 @@ ResourceAwareItemWriterItemStream<T>, InitializingBean {
/**
* Flag to indicate that the target file should be deleted if no items have
* been written (other than header and footer) on close. Defaults to false.
*
*
* @param shouldDeleteIfEmpty the flag value to set
*/
public void setShouldDeleteIfEmpty(boolean shouldDeleteIfEmpty) {
@@ -244,7 +243,7 @@ ResourceAwareItemWriterItemStream<T>, InitializingBean {
/**
* Get used encoding.
*
*
* @return the encoding used
*/
public String getEncoding() {
@@ -253,7 +252,7 @@ ResourceAwareItemWriterItemStream<T>, InitializingBean {
/**
* Set encoding to be used for output file.
*
*
* @param encoding the encoding to be used
*/
public void setEncoding(String encoding) {
@@ -271,7 +270,7 @@ ResourceAwareItemWriterItemStream<T>, InitializingBean {
/**
* Set XML version to be used for output XML.
*
*
* @param version the XML version to be used
*/
public void setVersion(String version) {
@@ -303,7 +302,7 @@ ResourceAwareItemWriterItemStream<T>, InitializingBean {
/**
* Get the tag name of the root element.
*
*
* @return the root element tag name
*/
public String getRootTagName() {
@@ -314,16 +313,16 @@ ResourceAwareItemWriterItemStream<T>, InitializingBean {
* Set the tag name of the root element. If not set, default name is used
* ("root"). Namespace URI and prefix can also be set optionally using the
* notation:
*
*
* <pre>
* {uri}prefix:root
* </pre>
*
*
* The prefix is optional (defaults to empty), but if it is specified then
* the uri must be provided. In addition you might want to declare other
* namespaces using the {@link #setRootElementAttributes(Map) root
* attributes}.
*
*
* @param rootTagName the tag name to be used for the root element
*/
public void setRootTagName(String rootTagName) {
@@ -332,7 +331,7 @@ ResourceAwareItemWriterItemStream<T>, InitializingBean {
/**
* Get the namespace prefix of the root element. Empty by default.
*
*
* @return the rootTagNamespacePrefix
*/
public String getRootTagNamespacePrefix() {
@@ -341,7 +340,7 @@ ResourceAwareItemWriterItemStream<T>, InitializingBean {
/**
* Get the namespace of the root element.
*
*
* @return the rootTagNamespace
*/
public String getRootTagNamespace() {
@@ -350,7 +349,7 @@ ResourceAwareItemWriterItemStream<T>, InitializingBean {
/**
* Get attributes of the root element.
*
*
* @return attributes of the root element
*/
public Map<String, String> getRootElementAttributes() {
@@ -360,7 +359,7 @@ ResourceAwareItemWriterItemStream<T>, InitializingBean {
/**
* Set the root element attributes to be written. If any of the key names
* begin with "xmlns:" then they are treated as namespace declarations.
*
*
* @param rootElementAttributes attributes of the root element
*/
public void setRootElementAttributes(Map<String, String> rootElementAttributes) {
@@ -370,7 +369,7 @@ ResourceAwareItemWriterItemStream<T>, InitializingBean {
/**
* Set "overwrite" flag for the output file. Flag is ignored when output
* file processing is restarted.
*
*
* @param overwriteOutput If set to true, output file will be overwritten
* (this flag is ignored when processing is restart).
*/
@@ -403,7 +402,7 @@ ResourceAwareItemWriterItemStream<T>, InitializingBean {
* Open the output source
*
* @param executionContext the batch context.
*
*
* @see org.springframework.batch.item.ItemStream#open(ExecutionContext)
*/
@SuppressWarnings("unchecked")
@@ -414,7 +413,7 @@ ResourceAwareItemWriterItemStream<T>, InitializingBean {
Assert.notNull(resource, "The resource must be set");
long startAtPosition = 0;
// if restart data is provided, restart from provided offset
// otherwise start from beginning
if (executionContext.containsKey(getExecutionContextKey(RESTART_DATA_NAME))) {
@@ -424,7 +423,7 @@ ResourceAwareItemWriterItemStream<T>, InitializingBean {
unclosedHeaderCallbackElements = (List<QName>) executionContext
.get(getExecutionContextKey(UNCLOSED_HEADER_CALLBACK_ELEMENTS_NAME));
}
restarted = true;
if (shouldDeleteIfEmpty && currentRecordCount == 0) {
// previous execution deleted the output file because no items were written
@@ -476,7 +475,7 @@ ResourceAwareItemWriterItemStream<T>, InitializingBean {
setPosition(position);
}
catch (IOException ioe) {
throw new DataAccessResourceFailureException("Unable to write to file resource: [" + resource + "]", ioe);
throw new ItemStreamException("Unable to write to file resource: [" + resource + "]", ioe);
}
XMLOutputFactory outputFactory = createXmlOutputFactory();
@@ -524,14 +523,14 @@ ResourceAwareItemWriterItemStream<T>, InitializingBean {
}
}
catch (XMLStreamException xse) {
throw new DataAccessResourceFailureException("Unable to write to file resource: [" + resource + "]", xse);
throw new ItemStreamException("Unable to write to file resource: [" + resource + "]", xse);
}
catch (UnsupportedEncodingException e) {
throw new DataAccessResourceFailureException("Unable to write to file resource: [" + resource
throw new ItemStreamException("Unable to write to file resource: [" + resource
+ "] with encoding=[" + encoding + "]", e);
}
}
catch (IOException e) {
throw new DataAccessResourceFailureException("Unable to write to file resource: [" + resource + "]", e);
throw new ItemStreamException("Unable to write to file resource: [" + resource + "]", e);
}
}
@@ -588,7 +587,7 @@ ResourceAwareItemWriterItemStream<T>, InitializingBean {
* <li>rootTagNamespacePrefix for rootTagName</li>
* <li>any other xmlns namespace prefix declarations in the root element attributes</li>
* </ul>
*
*
* @param writer XML event writer
*
* @throws XMLStreamException thrown if error occurs while setting the
@@ -627,7 +626,7 @@ ResourceAwareItemWriterItemStream<T>, InitializingBean {
* </ul>
* If this is not sufficient for you, simply override this method. Encoding,
* version and root tag name can be retrieved with corresponding getters.
*
*
* @param writer XML event writer
*
* @throws XMLStreamException thrown if error occurs.
@@ -685,7 +684,7 @@ ResourceAwareItemWriterItemStream<T>, InitializingBean {
/**
* Writes the EndDocument tag manually.
*
*
* @param writer XML event writer
*
* @throws XMLStreamException thrown if error occurs.
@@ -700,13 +699,13 @@ ResourceAwareItemWriterItemStream<T>, InitializingBean {
bufferedWriter.write("</" + nsPrefix + getRootTagName() + ">");
}
catch (IOException ioe) {
throw new DataAccessResourceFailureException("Unable to close file resource: [" + resource + "]", ioe);
throw new XMLStreamException("Unable to close file resource: [" + resource + "]", ioe);
}
}
/**
* Flush and close the output source.
*
*
* @see org.springframework.batch.item.ItemStream#close()
*/
@Override
@@ -727,7 +726,7 @@ ResourceAwareItemWriterItemStream<T>, InitializingBean {
if (restarted && !unclosedHeaderCallbackElements.isEmpty()) {
footerCallbackWriter = new UnopenedElementClosingEventWriter(
delegateEventWriter, bufferedWriter, unclosedHeaderCallbackElements);
}
}
footerCallback.write(footerCallbackWriter);
}
delegateEventWriter.flush();
@@ -784,7 +783,7 @@ ResourceAwareItemWriterItemStream<T>, InitializingBean {
/**
* Write the value objects and flush them to the file.
*
*
* @param items the value object
*
* @throws IOException thrown if general error occurs.
@@ -809,18 +808,18 @@ ResourceAwareItemWriterItemStream<T>, InitializingBean {
eventWriter.flush();
if (forceSync) {
channel.force(false);
}
}
}
catch (XMLStreamException | IOException e) {
throw new WriteFailedException("Failed to flush the events", e);
}
}
}
/**
* Get the restart data.
*
* @param executionContext the batch context.
*
*
* @see org.springframework.batch.item.ItemStream#update(ExecutionContext)
*/
@Override
@@ -833,14 +832,14 @@ ResourceAwareItemWriterItemStream<T>, InitializingBean {
if (!unclosedHeaderCallbackElements.isEmpty()) {
executionContext.put(getExecutionContextKey(UNCLOSED_HEADER_CALLBACK_ELEMENTS_NAME),
unclosedHeaderCallbackElements);
}
}
}
}
/*
* Get the actual position in file channel. This method flushes any buffered
* data before position is read.
*
*
* @return byte offset in file channel
*/
private long getPosition() {
@@ -855,7 +854,7 @@ ResourceAwareItemWriterItemStream<T>, InitializingBean {
}
}
catch (Exception e) {
throw new DataAccessResourceFailureException("Unable to write to file resource: [" + resource + "]", e);
throw new ItemStreamException("Unable to write to file resource: [" + resource + "]", e);
}
return position;
@@ -863,7 +862,7 @@ ResourceAwareItemWriterItemStream<T>, InitializingBean {
/**
* Set the file channel position.
*
*
* @param newPosition new file channel position
*/
private void setPosition(long newPosition) {
@@ -873,7 +872,7 @@ ResourceAwareItemWriterItemStream<T>, InitializingBean {
channel.position(newPosition);
}
catch (IOException e) {
throw new DataAccessResourceFailureException("Unable to write to file resource: [" + resource + "]", e);
throw new ItemStreamException("Unable to write to file resource: [" + resource + "]", e);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2006-2007 the original author or authors.
* Copyright 2006-2022 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.
@@ -28,7 +28,7 @@ import javax.xml.stream.events.StartDocument;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.batch.item.ItemStreamException;
/**
* Default implementation of {@link FragmentEventReader}
@@ -71,7 +71,7 @@ public class DefaultFragmentEventReader extends AbstractEventReaderWrapper imple
startDocumentEvent = (StartDocument) wrappedEventReader.peek();
}
catch (XMLStreamException e) {
throw new DataAccessResourceFailureException("Error reading start document from event reader", e);
throw new ItemStreamException("Error reading start document from event reader", e);
}
endDocumentEvent = XMLEventFactory.newInstance().createEndDocument();
@@ -91,7 +91,7 @@ public class DefaultFragmentEventReader extends AbstractEventReaderWrapper imple
}
}
catch (XMLStreamException e) {
throw new DataAccessResourceFailureException("Error reading XML stream", e);
throw new ItemStreamException("Error reading XML stream", e);
}
return false;
}
@@ -102,7 +102,7 @@ public class DefaultFragmentEventReader extends AbstractEventReaderWrapper imple
return nextEvent();
}
catch (XMLStreamException e) {
throw new DataAccessResourceFailureException("Error reading XML stream", e);
throw new ItemStreamException("Error reading XML stream", e);
}
}
@@ -186,7 +186,7 @@ public class DefaultFragmentEventReader extends AbstractEventReaderWrapper imple
}
}
catch (XMLStreamException e) {
throw new DataAccessResourceFailureException("Error reading XML stream", e);
throw new ItemStreamException("Error reading XML stream", e);
}
}
fakeDocumentEnd = false;

View File

@@ -1,82 +1,81 @@
/*
* Copyright 2014 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
*
* https://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.xml.stax;
import java.io.IOException;
import java.io.Writer;
import java.util.LinkedList;
import java.util.List;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.XMLEvent;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.util.StringUtils;
/**
* Delegating XMLEventWriter, which writes EndElement events that match a given collection of QNames directly
* to the underlying java.io.Writer instead of to the delegate XMLEventWriter.
*
* @author Jimmy Praet
* @since 3.0
*/
public class UnopenedElementClosingEventWriter extends AbstractEventWriterWrapper {
private LinkedList<QName> unopenedElements;
private Writer ioWriter;
public UnopenedElementClosingEventWriter(XMLEventWriter wrappedEventWriter, Writer ioWriter, List<QName> unopenedElements) {
super(wrappedEventWriter);
this.unopenedElements = new LinkedList<>(unopenedElements);
this.ioWriter = ioWriter;
}
/* (non-Javadoc)
* @see org.springframework.batch.item.xml.stax.AbstractEventWriterWrapper#add(javax.xml.stream.events.XMLEvent)
*/
@Override
public void add(XMLEvent event) throws XMLStreamException {
if (isUnopenedElementCloseEvent(event)) {
QName element = unopenedElements.removeLast();
String nsPrefix = !StringUtils.hasText(element.getPrefix()) ? "" : element.getPrefix() + ":";
try {
super.flush();
ioWriter.write("</" + nsPrefix + element.getLocalPart() + ">");
ioWriter.flush();
}
catch (IOException ioe) {
throw new DataAccessResourceFailureException("Unable to close tag: " + element, ioe);
}
} else {
super.add(event);
}
}
private boolean isUnopenedElementCloseEvent(XMLEvent event) {
if (unopenedElements.isEmpty()) {
return false;
} else if (!event.isEndElement()) {
return false;
} else {
return unopenedElements.getLast().equals(event.asEndElement().getName());
}
}
}
/*
* Copyright 2014-2022 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
*
* https://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.xml.stax;
import java.io.IOException;
import java.io.Writer;
import java.util.LinkedList;
import java.util.List;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.XMLEvent;
import org.springframework.util.StringUtils;
/**
* Delegating XMLEventWriter, which writes EndElement events that match a given collection of QNames directly
* to the underlying java.io.Writer instead of to the delegate XMLEventWriter.
*
* @author Jimmy Praet
* @since 3.0
*/
public class UnopenedElementClosingEventWriter extends AbstractEventWriterWrapper {
private LinkedList<QName> unopenedElements;
private Writer ioWriter;
public UnopenedElementClosingEventWriter(XMLEventWriter wrappedEventWriter, Writer ioWriter, List<QName> unopenedElements) {
super(wrappedEventWriter);
this.unopenedElements = new LinkedList<>(unopenedElements);
this.ioWriter = ioWriter;
}
/* (non-Javadoc)
* @see org.springframework.batch.item.xml.stax.AbstractEventWriterWrapper#add(javax.xml.stream.events.XMLEvent)
*/
@Override
public void add(XMLEvent event) throws XMLStreamException {
if (isUnopenedElementCloseEvent(event)) {
QName element = unopenedElements.removeLast();
String nsPrefix = !StringUtils.hasText(element.getPrefix()) ? "" : element.getPrefix() + ":";
try {
super.flush();
ioWriter.write("</" + nsPrefix + element.getLocalPart() + ">");
ioWriter.flush();
}
catch (IOException ioe) {
throw new XMLStreamException("Unable to close tag: " + element, ioe);
}
} else {
super.add(event);
}
}
private boolean isUnopenedElementCloseEvent(XMLEvent event) {
if (unopenedElements.isEmpty()) {
return false;
} else if (!event.isEndElement()) {
return false;
} else {
return unopenedElements.getLast().equals(event.asEndElement().getName());
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014 the original author or authors.
* Copyright 2014-2022 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.
@@ -27,6 +27,7 @@ import java.util.List;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventFactory;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.EndElement;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
@@ -34,11 +35,10 @@ import javax.xml.stream.events.XMLEvent;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.dao.DataAccessResourceFailureException;
/**
* Tests for {@link UnopenedElementClosingEventWriter}
*
*
* @author Jimmy Praet
*/
public class UnopenedElementClosingEventWriterTests {
@@ -46,17 +46,17 @@ public class UnopenedElementClosingEventWriterTests {
private UnopenedElementClosingEventWriter writer;
private XMLEventWriter wrappedWriter;
private Writer ioWriter;
private XMLEventFactory eventFactory = XMLEventFactory.newInstance();
private List<QName> unopenedElements = new LinkedList<>();
private QName unopenedA = new QName("http://test", "unopened-a", "t");
private QName unopenedB = new QName("", "unopened-b", "");
private QName other = new QName("http://test", "other", "t");
@Before
@@ -67,7 +67,7 @@ public class UnopenedElementClosingEventWriterTests {
unopenedElements.add(unopenedB);
writer = new UnopenedElementClosingEventWriter(wrappedWriter, ioWriter, unopenedElements);
}
@Test
public void testEndUnopenedElements() throws Exception {
EndElement endElementB = eventFactory.createEndElement(unopenedB, null);
@@ -83,17 +83,17 @@ public class UnopenedElementClosingEventWriterTests {
verify(ioWriter).write("</t:unopened-a>");
verify(ioWriter, Mockito.times(2)).flush();
}
@Test
public void testEndUnopenedElementRemovesFromList() throws Exception {
EndElement endElement = eventFactory.createEndElement(unopenedB, null);
writer.add(endElement);
verify(wrappedWriter, Mockito.never()).add(endElement);
verify(wrappedWriter).flush();
verify(ioWriter).write("</unopened-b>");
verify(ioWriter).flush();
StartElement startElement = eventFactory.createStartElement(unopenedB, null, null);
writer.add(startElement);
endElement = eventFactory.createEndElement(unopenedB, null);
@@ -101,28 +101,28 @@ public class UnopenedElementClosingEventWriterTests {
verify(wrappedWriter).add(startElement);
verify(wrappedWriter).add(endElement);
// only internal list should be modified
assertEquals(2, unopenedElements.size());
}
assertEquals(2, unopenedElements.size());
}
@Test
public void testOtherEndElement() throws Exception {
EndElement endElement = eventFactory.createEndElement(other, null);
writer.add(endElement);
verify(wrappedWriter).add(endElement);
}
}
@Test
public void testOtherEvent() throws Exception {
XMLEvent event = eventFactory.createCharacters("foo");
writer.add(event);
verify(wrappedWriter).add(event);
}
@Test (expected = DataAccessResourceFailureException.class)
}
@Test (expected = XMLStreamException.class)
public void testIOException() throws Exception {
EndElement endElementB = eventFactory.createEndElement(unopenedB, null);
Mockito.doThrow(new IOException("Simulated IOException")).when(ioWriter).write("</unopened-b>");