Merge pull request 133 from jpraet/BATCH-1975

* BATCH-1975:
  BATCH-1975: Minor cleanup
  BATCH-1975: StaxEventItemWriter namespace added to elements after restart
This commit is contained in:
Michael Minella
2013-03-04 16:19:29 -06:00
2 changed files with 162 additions and 30 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2006-2012 the original author or authors.
* Copyright 2006-2013 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.
@@ -70,7 +70,7 @@ import org.springframework.util.StringUtils;
*
*/
public class StaxEventItemWriter<T> extends AbstractItemStreamItemWriter<T> implements
ResourceAwareItemWriterItemStream<T>, InitializingBean {
ResourceAwareItemWriterItemStream<T>, InitializingBean {
private static final Log log = LogFactory.getLog(StaxEventItemWriter.class);
@@ -151,7 +151,7 @@ public class StaxEventItemWriter<T> extends AbstractItemStreamItemWriter<T> impl
*
* @param resource the output file
*/
@Override
@Override
public void setResource(Resource resource) {
this.resource = resource;
}
@@ -323,7 +323,7 @@ public class StaxEventItemWriter<T> extends AbstractItemStreamItemWriter<T> impl
* @throws Exception
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
@Override
@Override
public void afterPropertiesSet() throws Exception {
Assert.notNull(marshaller);
if (rootTagName.contains("{")) {
@@ -341,10 +341,10 @@ public class StaxEventItemWriter<T> extends AbstractItemStreamItemWriter<T> impl
*
* @see org.springframework.batch.item.ItemStream#open(ExecutionContext)
*/
@Override
@Override
public void open(ExecutionContext executionContext) {
super.open(executionContext);
super.open(executionContext);
Assert.notNull(resource, "The resource must be set");
long startAtPosition = 0;
@@ -375,6 +375,7 @@ public class StaxEventItemWriter<T> extends AbstractItemStreamItemWriter<T> impl
/**
* Helper method for opening output source at given file position
*/
@SuppressWarnings("resource")
private void open(long position, boolean restarted) {
File file;
@@ -415,12 +416,12 @@ public class StaxEventItemWriter<T> extends AbstractItemStreamItemWriter<T> impl
final FileChannel channel = fileChannel;
if (transactional) {
TransactionAwareBufferedWriter writer = new TransactionAwareBufferedWriter(channel, new Runnable() {
@Override
@Override
public void run() {
closeStream();
}
});
writer.setEncoding(encoding);
bufferedWriter = writer;
}
@@ -438,6 +439,7 @@ public class StaxEventItemWriter<T> extends AbstractItemStreamItemWriter<T> impl
}
delegateEventWriter = createXmlEventWriter(outputFactory, bufferedWriter);
eventWriter = new NoStartEndDocumentStreamWriter(delegateEventWriter);
initNamespaceContext(delegateEventWriter);
if (!restarted) {
startDocument(delegateEventWriter);
}
@@ -492,6 +494,39 @@ public class StaxEventItemWriter<T> extends AbstractItemStreamItemWriter<T> impl
return StaxUtils.getResult(eventWriter);
}
/**
* Inits the namespace context of the XMLEventWriter:
* <ul>
* <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
*/
protected void initNamespaceContext(XMLEventWriter writer) throws XMLStreamException {
if (StringUtils.hasText(getRootTagNamespace())) {
if(StringUtils.hasText(getRootTagNamespacePrefix())) {
writer.setPrefix(getRootTagNamespacePrefix(), getRootTagNamespace());
} else {
writer.setDefaultNamespace(getRootTagNamespace());
}
}
if (!CollectionUtils.isEmpty(getRootElementAttributes())) {
for (Map.Entry<String, String> entry : getRootElementAttributes().entrySet()) {
String key = entry.getKey();
if (key.startsWith("xmlns")) {
String prefix = "";
if (key.contains(":")) {
prefix = key.substring(key.indexOf(":") + 1);
}
log.debug("registering prefix: " +prefix + "=" + entry.getValue());
writer.setPrefix(prefix, entry.getValue());
}
}
}
}
/**
* Writes simple XML header containing:
* <ul>
@@ -575,10 +610,10 @@ public class StaxEventItemWriter<T> extends AbstractItemStreamItemWriter<T> impl
*
* @see org.springframework.batch.item.ItemStream#close()
*/
@Override
@Override
public void close() {
super.close();
super.close();
XMLEventFactory factory = createXmlEventFactory();
try {
delegateEventWriter.add(factory.createCharacters(""));
@@ -640,7 +675,7 @@ public class StaxEventItemWriter<T> extends AbstractItemStreamItemWriter<T> impl
* @throws IOException
* @throws XmlMappingException
*/
@Override
@Override
public void write(List<? extends T> items) throws XmlMappingException, Exception {
currentRecordCount += items.size();
@@ -665,9 +700,9 @@ public class StaxEventItemWriter<T> extends AbstractItemStreamItemWriter<T> impl
*
* @see org.springframework.batch.item.ItemStream#update(ExecutionContext)
*/
@Override
@Override
public void update(ExecutionContext executionContext) {
super.update(executionContext);
super.update(executionContext);
if (saveState) {
Assert.notNull(executionContext, "ExecutionContext must not be null");
executionContext.putLong(getExecutionContextKey(RESTART_DATA_NAME), getPosition());

View File

@@ -1,17 +1,18 @@
package org.springframework.batch.item.xml;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.stream.XMLEventFactory;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLStreamException;
@@ -27,6 +28,7 @@ import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.oxm.Marshaller;
import org.springframework.oxm.XmlMappingException;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
@@ -50,14 +52,18 @@ public class StaxEventItemWriterTests {
// test item for writing to output
private Object item = new Object() {
@Override
@Override
public String toString() {
return ClassUtils.getShortName(StaxEventItemWriter.class) + "-testString";
}
};
private JAXBItem jaxbItem = new JAXBItem();
private List<? extends Object> items = Collections.singletonList(item);
private List<? extends Object> jaxbItems = Collections.singletonList(jaxbItem);
private static final String TEST_STRING = "<" + ClassUtils.getShortName(StaxEventItemWriter.class)
+ "-testString/>";
@@ -69,6 +75,8 @@ public class StaxEventItemWriterTests {
private SimpleMarshaller marshaller;
private Jaxb2Marshaller jaxbMarshaller;
@Before
public void setUp() throws Exception {
File directory = new File("target/data");
@@ -76,6 +84,8 @@ public class StaxEventItemWriterTests {
resource = new FileSystemResource(File.createTempFile("StaxEventWriterOutputSourceTests", ".xml", directory));
writer = createItemWriter();
executionContext = new ExecutionContext();
jaxbMarshaller = new Jaxb2Marshaller();
jaxbMarshaller.setClassesToBeBound(JAXBItem.class);
}
/**
@@ -126,13 +136,14 @@ public class StaxEventItemWriterTests {
}
@Test
@SuppressWarnings({"unchecked", "rawtypes"})
public void testTransactionalRestart() throws Exception {
writer.open(executionContext);
PlatformTransactionManager transactionManager = new ResourcelessTransactionManager();
new TransactionTemplate(transactionManager).execute(new TransactionCallback() {
@Override
@Override
public Object doInTransaction(TransactionStatus status) {
try {
// write item
@@ -152,7 +163,7 @@ public class StaxEventItemWriterTests {
writer = createItemWriter();
writer.open(executionContext);
new TransactionTemplate(transactionManager).execute(new TransactionCallback() {
@Override
@Override
public Object doInTransaction(TransactionStatus status) {
try {
writer.write(items);
@@ -175,6 +186,7 @@ public class StaxEventItemWriterTests {
}
@Test
@SuppressWarnings({"unchecked", "rawtypes"})
public void testTransactionalRestartFailOnFirstWrite() throws Exception {
PlatformTransactionManager transactionManager = new ResourcelessTransactionManager();
@@ -182,7 +194,7 @@ public class StaxEventItemWriterTests {
writer.open(executionContext);
try {
new TransactionTemplate(transactionManager).execute(new TransactionCallback() {
@Override
@Override
public Object doInTransaction(TransactionStatus status) {
try {
writer.write(items);
@@ -198,14 +210,13 @@ public class StaxEventItemWriterTests {
// expected
}
writer.close();
System.err.println(getOutputFileContent());
String outputFile = getOutputFileContent();
assertEquals("<root></root>", outputFile);
// create new writer from saved restart data and continue writing
writer = createItemWriter();
new TransactionTemplate(transactionManager).execute(new TransactionCallback() {
@Override
@Override
public Object doInTransaction(TransactionStatus status) {
writer.open(executionContext);
try {
@@ -224,7 +235,6 @@ public class StaxEventItemWriterTests {
// check the output is concatenation of 'before restart' and 'after
// restart' writes.
outputFile = getOutputFileContent();
System.err.println(getOutputFileContent());
assertEquals(1, StringUtils.countOccurrencesOf(outputFile, TEST_STRING));
assertTrue(outputFile.contains("<root>" + TEST_STRING + "</root>"));
assertEquals("<root><StaxEventItemWriter-testString/></root>", outputFile);
@@ -238,7 +248,7 @@ public class StaxEventItemWriterTests {
writer.setHeaderCallback(new StaxWriterCallback() {
@Override
@Override
public void write(XMLEventWriter writer) throws IOException {
XMLEventFactory factory = XMLEventFactory.newInstance();
try {
@@ -284,7 +294,7 @@ public class StaxEventItemWriterTests {
public void testOpenAndClose() throws Exception {
writer.setHeaderCallback(new StaxWriterCallback() {
@Override
@Override
public void write(XMLEventWriter writer) throws IOException {
XMLEventFactory factory = XMLEventFactory.newInstance();
try {
@@ -300,7 +310,7 @@ public class StaxEventItemWriterTests {
});
writer.setFooterCallback(new StaxWriterCallback() {
@Override
@Override
public void write(XMLEventWriter writer) throws IOException {
XMLEventFactory factory = XMLEventFactory.newInstance();
try {
@@ -402,6 +412,89 @@ public class StaxEventItemWriterTests {
assertTrue("Wrong content: " + content, content.contains(("<ns:root")));
}
/**
* Namespace prefixes are properly initialized on restart.
*/
@Test
public void testRootTagWithNamespaceRestart() throws Exception {
writer.setMarshaller(jaxbMarshaller);
writer.setRootTagName("{http://www.springframework.org/test}root");
writer.afterPropertiesSet();
writer.open(executionContext);
writer.write(jaxbItems);
writer.update(executionContext);
writer.close();
writer = createItemWriter();
writer.setMarshaller(jaxbMarshaller);
writer.setRootTagName("{http://www.springframework.org/test}root");
writer.afterPropertiesSet();
writer.open(executionContext);
writer.write(jaxbItems);
writer.update(executionContext);
writer.close();
String content = getOutputFileContent();
assertEquals("Wrong content: " + content,
"<root xmlns=\"http://www.springframework.org/test\"><item/><item/></root>", content);
}
/**
* Namespace prefixes are properly initialized on restart.
*/
@Test
public void testRootTagWithNamespaceAndPrefixRestart() throws Exception {
writer.setMarshaller(jaxbMarshaller);
writer.setRootTagName("{http://www.springframework.org/test}ns:root");
writer.afterPropertiesSet();
writer.open(executionContext);
writer.write(jaxbItems);
writer.update(executionContext);
writer.close();
writer = createItemWriter();
writer.setMarshaller(jaxbMarshaller);
writer.setRootTagName("{http://www.springframework.org/test}ns:root");
writer.afterPropertiesSet();
writer.open(executionContext);
writer.write(jaxbItems);
writer.update(executionContext);
writer.close();
String content = getOutputFileContent();
assertEquals("Wrong content: " + content,
"<ns:root xmlns:ns=\"http://www.springframework.org/test\"><ns:item/><ns:item/></ns:root>", content);
}
/**
* Namespace prefixes are properly initialized on restart.
*/
@Test
public void testRootTagWithAdditionalNamespaceRestart() throws Exception {
writer.setMarshaller(jaxbMarshaller);
writer.setRootTagName("{urn:org.test.foo}foo:root");
writer.setRootElementAttributes(Collections.singletonMap("xmlns:ns", "http://www.springframework.org/test"));
writer.afterPropertiesSet();
writer.open(executionContext);
writer.write(jaxbItems);
writer.update(executionContext);
writer.close();
writer = createItemWriter();
writer.setMarshaller(jaxbMarshaller);
writer.setRootTagName("{urn:org.test.foo}foo:root");
writer.setRootElementAttributes(Collections.singletonMap("xmlns:ns", "http://www.springframework.org/test"));
writer.afterPropertiesSet();
writer.open(executionContext);
writer.write(jaxbItems);
writer.update(executionContext);
writer.close();
String content = getOutputFileContent();
assertEquals("Wrong content: " + content,
"<foo:root xmlns:foo=\"urn:org.test.foo\" xmlns:ns=\"http://www.springframework.org/test\"><ns:item/><ns:item/></foo:root>", content);
}
/**
* Writes object's toString representation as XML comment.
*/
@@ -419,9 +512,9 @@ public class StaxEventItemWriterTests {
this.namespacePrefix = namespacePrefix;
}
@Override
@Override
public void marshal(Object graph, Result result) throws XmlMappingException, IOException {
Assert.isInstanceOf( Result.class, result);
Assert.isInstanceOf( Result.class, result);
try {
StaxUtils.getXmlEventWriter( result ).add( XMLEventFactory.newInstance().createStartElement(namespacePrefix, namespace, graph.toString()));
StaxUtils.getXmlEventWriter( result ).add( XMLEventFactory.newInstance().createEndElement(namespacePrefix, namespace, graph.toString()));
@@ -431,7 +524,7 @@ public class StaxEventItemWriterTests {
}
}
@Override
@Override
@SuppressWarnings("rawtypes")
public boolean supports(Class clazz) {
return true;
@@ -468,4 +561,8 @@ public class StaxEventItemWriterTests {
return source;
}
@XmlRootElement(name="item", namespace="http://www.springframework.org/test")
private static class JAXBItem {
}
}