Remove DataAccessResourceFailureException in xml related classes
Resolves #4132
This commit is contained in:
committed by
Fadhel Mahmoud Ben Hassine
parent
0d7a16054c
commit
1e24e38db7
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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>");
|
||||
|
||||
Reference in New Issue
Block a user