/*
* Copyright 2008-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
*
* http://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;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import javax.xml.stream.XMLEventFactory;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLStreamException;
import javax.xml.transform.Result;
import org.apache.commons.io.FileUtils;
import org.junit.Before;
import org.junit.Test;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
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.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* Tests for {@link StaxEventItemWriter}.
*/
public class TransactionalStaxEventItemWriterTests {
// object under test
private StaxEventItemWriter<Object> writer;
private PlatformTransactionManager transactionManager = new ResourcelessTransactionManager();
// output file
private Resource resource;
private ExecutionContext executionContext;
// test item for writing to output
private Object item = new Object() {
@Override
public String toString() {
return ClassUtils.getShortName(StaxEventItemWriter.class) + "-testString";
}
};
private List<? extends Object> items = Collections.singletonList(item);
private static final String TEST_STRING = "<!--" + ClassUtils.getShortName(StaxEventItemWriter.class)
+ "-testString-->";
@Before
public void setUp() throws Exception {
resource = new FileSystemResource(File.createTempFile("StaxEventWriterOutputSourceTests", ".xml"));
writer = createItemWriter();
executionContext = new ExecutionContext();
}
/**
* Item is written to the output file only after flush.
*/
@Test
public void testWriteAndFlush() throws Exception {
writer.open(executionContext);
new TransactionTemplate(transactionManager).execute(new TransactionCallback<Void>() {
@Override
public Void doInTransaction(TransactionStatus status) {
try {
writer.write(items);
}
catch ( Exception e) {
throw new RuntimeException(e);
}
return null;
}
});
writer.close();
String content = outputFileContent();
assertTrue("Wrong content: " + content, content.contains(TEST_STRING));
}
/**
* Item is written to the output file only after flush.
*/
@Test
public void testWriteWithHeaderAfterRollback() throws Exception {
writer.setHeaderCallback(new StaxWriterCallback(){
@Override
public void write(XMLEventWriter writer) throws IOException {
XMLEventFactory factory = XMLEventFactory.newInstance();
try {
writer.add(factory.createStartElement("", "", "header"));
writer.add(factory.createEndElement("", "", "header"));
}
catch (XMLStreamException e) {
throw new RuntimeException(e);
}
}
});
writer.open(executionContext);
try {
new TransactionTemplate(transactionManager).execute(new TransactionCallback<Void>() {
@Override
public Void doInTransaction(TransactionStatus status) {
try {
writer.write(items);
}
catch (Exception e) {
throw new RuntimeException(e);
}
throw new RuntimeException("Planned");
}
});
fail("Expected RuntimeException");
}
catch (RuntimeException e) {
// expected
}
writer.close();
writer.open(executionContext);
new TransactionTemplate(transactionManager).execute(new TransactionCallback<Void>() {
@Override
public Void doInTransaction(TransactionStatus status) {
try {
writer.write(items);
}
catch (Exception e) {
throw new RuntimeException(e);
}
return null;
}
});
writer.close();
String content = outputFileContent();
assertEquals("Wrong content: " + content, 1, StringUtils.countOccurrencesOf(content, ("<header/>")));
assertEquals("Wrong content: " + content, 1, StringUtils.countOccurrencesOf(content, TEST_STRING));
}
/**
* Item is written to the output file only after flush.
*/
@Test
public void testWriteWithHeaderAfterFlushAndRollback() throws Exception {
writer.setHeaderCallback(new StaxWriterCallback(){
@Override
public void write(XMLEventWriter writer) throws IOException {
XMLEventFactory factory = XMLEventFactory.newInstance();
try {
writer.add(factory.createStartElement("", "", "header"));
writer.add(factory.createEndElement("", "", "header"));
}
catch (XMLStreamException e) {
throw new RuntimeException(e);
}
}
});
writer.open(executionContext);
new TransactionTemplate(transactionManager).execute(new TransactionCallback<Void>() {
@Override
public Void doInTransaction(TransactionStatus status) {
try {
writer.write(items);
}
catch (Exception e) {
throw new RuntimeException(e);
}
return null;
}
});
writer.update(executionContext);
writer.close();
writer.open(executionContext);
try {
new TransactionTemplate(transactionManager).execute(new TransactionCallback<Void>() {
@Override
public Void doInTransaction(TransactionStatus status) {
try {
writer.write(items);
}
catch (Exception e) {
throw new RuntimeException(e);
}
throw new RuntimeException("Planned");
}
});
fail("Expected RuntimeException");
}
catch (RuntimeException e) {
// expected
}
writer.close();
String content = outputFileContent();
assertEquals("Wrong content: " + content, 1, StringUtils.countOccurrencesOf(content, ("<header/>")));
assertEquals("Wrong content: " + content, 1, StringUtils.countOccurrencesOf(content, TEST_STRING));
}
/**
* @return output file content as String
*/
private String outputFileContent() throws IOException {
return FileUtils.readFileToString(resource.getFile(), (String)null);
}
/**
* Writes object's toString representation as XML comment.
*/
private static class SimpleMarshaller implements Marshaller {
@Override
public void marshal(Object graph, Result result) throws XmlMappingException, IOException {
try {
StaxUtils.getXmlEventWriter(result).add(XMLEventFactory.newInstance().createComment(graph.toString()));
}
catch ( Exception e) {
throw new RuntimeException("Exception while writing to output file", e);
}
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
/**
* @return new instance of fully configured writer
*/
private StaxEventItemWriter<Object> createItemWriter() throws Exception {
StaxEventItemWriter<Object> source = new StaxEventItemWriter<Object>();
source.setResource(resource);
Marshaller marshaller = new SimpleMarshaller();
source.setMarshaller(marshaller);
source.setEncoding("UTF-8");
source.setRootTagName("root");
source.setVersion("1.0");
source.setOverwriteOutput(true);
source.setSaveState(true);
source.afterPropertiesSet();
return source;
}
}