1 package org.springframework.batch.item.xml;
2
3 import static org.easymock.EasyMock.createMock;
4 import static org.easymock.EasyMock.expect;
5 import static org.easymock.EasyMock.replay;
6 import static org.junit.Assert.assertEquals;
7 import static org.junit.Assert.assertFalse;
8 import static org.junit.Assert.assertTrue;
9 import static org.junit.Assert.fail;
10
11 import java.io.File;
12 import java.io.IOException;
13 import java.util.Collections;
14 import java.util.List;
15
16 import javax.xml.stream.XMLEventFactory;
17 import javax.xml.stream.XMLEventWriter;
18 import javax.xml.stream.XMLStreamException;
19 import javax.xml.transform.Result;
20
21 import org.apache.commons.io.FileUtils;
22 import org.junit.Before;
23 import org.junit.Test;
24 import org.springframework.batch.item.ExecutionContext;
25 import org.springframework.batch.item.UnexpectedInputException;
26 import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
27 import org.springframework.core.io.FileSystemResource;
28 import org.springframework.core.io.Resource;
29 import org.springframework.oxm.Marshaller;
30 import org.springframework.oxm.XmlMappingException;
31 import org.springframework.transaction.PlatformTransactionManager;
32 import org.springframework.transaction.TransactionStatus;
33 import org.springframework.transaction.support.TransactionCallback;
34 import org.springframework.transaction.support.TransactionTemplate;
35 import org.springframework.util.Assert;
36 import org.springframework.util.ClassUtils;
37 import org.springframework.util.StringUtils;
38
39
40
41
42 public class StaxEventItemWriterTests {
43
44
45 private StaxEventItemWriter<Object> writer;
46
47
48 private Resource resource;
49
50 private ExecutionContext executionContext;
51
52
53 private Object item = new Object() {
54 public String toString() {
55 return ClassUtils.getShortName(StaxEventItemWriter.class) + "-testString";
56 }
57 };
58
59 private List<? extends Object> items = Collections.singletonList(item);
60
61 private static final String TEST_STRING = "<" + ClassUtils.getShortName(StaxEventItemWriter.class)
62 + "-testString/>";
63
64 private static final String NS_TEST_STRING = "<ns:" + ClassUtils.getShortName(StaxEventItemWriter.class)
65 + "-testString/>";
66
67 private static final String FOO_TEST_STRING = "<foo:" + ClassUtils.getShortName(StaxEventItemWriter.class)
68 + "-testString/>";
69
70 private SimpleMarshaller marshaller;
71
72 @Before
73 public void setUp() throws Exception {
74 File directory = new File("target/data");
75 directory.mkdirs();
76 resource = new FileSystemResource(File.createTempFile("StaxEventWriterOutputSourceTests", ".xml", directory));
77 writer = createItemWriter();
78 executionContext = new ExecutionContext();
79 }
80
81
82
83
84 @Test
85 public void testWriteAndFlush() throws Exception {
86 writer.open(executionContext);
87 writer.write(items);
88 writer.close();
89 String content = getOutputFileContent();
90 assertTrue("Wrong content: " + content, content.contains(TEST_STRING));
91 }
92
93 @Test
94 public void testWriteAndForceFlush() throws Exception {
95 writer.setForceSync(true);
96 writer.open(executionContext);
97 writer.write(items);
98 writer.close();
99 String content = getOutputFileContent();
100 assertTrue("Wrong content: " + content, content.contains(TEST_STRING));
101 }
102
103
104
105
106 @Test
107 public void testRestart() throws Exception {
108 writer.open(executionContext);
109
110 writer.write(items);
111 writer.update(executionContext);
112 writer.close();
113
114
115 writer = createItemWriter();
116 writer.open(executionContext);
117 writer.write(items);
118 writer.write(items);
119 writer.close();
120
121
122
123 String outputFile = getOutputFileContent();
124 assertEquals(3, StringUtils.countOccurrencesOf(outputFile, TEST_STRING));
125 assertEquals("<root>" + TEST_STRING + TEST_STRING + TEST_STRING + "</root>", outputFile.replace(" ", ""));
126 }
127
128 @Test
129 public void testTransactionalRestart() throws Exception {
130 writer.open(executionContext);
131
132 PlatformTransactionManager transactionManager = new ResourcelessTransactionManager();
133
134 new TransactionTemplate(transactionManager).execute(new TransactionCallback() {
135 public Object doInTransaction(TransactionStatus status) {
136 try {
137
138 writer.write(items);
139 }
140 catch (Exception e) {
141 throw new UnexpectedInputException("Could not write data", e);
142 }
143
144 writer.update(executionContext);
145 return null;
146 }
147 });
148 writer.close();
149
150
151 writer = createItemWriter();
152 writer.open(executionContext);
153 new TransactionTemplate(transactionManager).execute(new TransactionCallback() {
154 public Object doInTransaction(TransactionStatus status) {
155 try {
156 writer.write(items);
157 }
158 catch (Exception e) {
159 throw new UnexpectedInputException("Could not write data", e);
160 }
161
162 writer.update(executionContext);
163 return null;
164 }
165 });
166 writer.close();
167
168
169
170 String outputFile = getOutputFileContent();
171 assertEquals(2, StringUtils.countOccurrencesOf(outputFile, TEST_STRING));
172 assertTrue(outputFile.contains("<root>" + TEST_STRING + TEST_STRING + "</root>"));
173 }
174
175 @Test
176 public void testTransactionalRestartFailOnFirstWrite() throws Exception {
177
178 PlatformTransactionManager transactionManager = new ResourcelessTransactionManager();
179
180 writer.open(executionContext);
181 try {
182 new TransactionTemplate(transactionManager).execute(new TransactionCallback() {
183 public Object doInTransaction(TransactionStatus status) {
184 try {
185 writer.write(items);
186 }
187 catch (Exception e) {
188 throw new IllegalStateException("Could not write data", e);
189 }
190 throw new UnexpectedInputException("Could not write data");
191 }
192 });
193 }
194 catch (UnexpectedInputException e) {
195
196 }
197 writer.close();
198 System.err.println(getOutputFileContent());
199 String outputFile = getOutputFileContent();
200 assertEquals("<root></root>", outputFile);
201
202
203 writer = createItemWriter();
204 new TransactionTemplate(transactionManager).execute(new TransactionCallback() {
205 public Object doInTransaction(TransactionStatus status) {
206 writer.open(executionContext);
207 try {
208 writer.write(items);
209 }
210 catch (Exception e) {
211 throw new UnexpectedInputException("Could not write data", e);
212 }
213
214 writer.update(executionContext);
215 return null;
216 }
217 });
218 writer.close();
219
220
221
222 outputFile = getOutputFileContent();
223 System.err.println(getOutputFileContent());
224 assertEquals(1, StringUtils.countOccurrencesOf(outputFile, TEST_STRING));
225 assertTrue(outputFile.contains("<root>" + TEST_STRING + "</root>"));
226 assertEquals("<root><StaxEventItemWriter-testString/></root>", outputFile);
227 }
228
229
230
231
232 @Test
233 public void testWriteWithHeader() throws Exception {
234
235 writer.setHeaderCallback(new StaxWriterCallback() {
236
237 public void write(XMLEventWriter writer) throws IOException {
238 XMLEventFactory factory = XMLEventFactory.newInstance();
239 try {
240 writer.add(factory.createStartElement("", "", "header"));
241 writer.add(factory.createEndElement("", "", "header"));
242 }
243 catch (XMLStreamException e) {
244 throw new RuntimeException(e);
245 }
246
247 }
248
249 });
250 writer.open(executionContext);
251 writer.write(items);
252 String content = getOutputFileContent();
253 assertTrue("Wrong content: " + content, content.contains(("<header/>")));
254 assertTrue("Wrong content: " + content, content.contains(TEST_STRING));
255 }
256
257
258
259
260 @Test
261 public void testStreamContext() throws Exception {
262 writer.open(executionContext);
263 final int NUMBER_OF_RECORDS = 10;
264 assertFalse(executionContext.containsKey(ClassUtils.getShortName(StaxEventItemWriter.class) + ".record.count"));
265 for (int i = 1; i <= NUMBER_OF_RECORDS; i++) {
266 writer.write(items);
267 writer.update(executionContext);
268 long writeStatistics = executionContext.getLong(ClassUtils.getShortName(StaxEventItemWriter.class)
269 + ".record.count");
270
271 assertEquals(i, writeStatistics);
272 }
273 }
274
275
276
277
278 @Test
279 public void testOpenAndClose() throws Exception {
280 writer.setHeaderCallback(new StaxWriterCallback() {
281
282 public void write(XMLEventWriter writer) throws IOException {
283 XMLEventFactory factory = XMLEventFactory.newInstance();
284 try {
285 writer.add(factory.createStartElement("", "", "header"));
286 writer.add(factory.createEndElement("", "", "header"));
287 }
288 catch (XMLStreamException e) {
289 throw new RuntimeException(e);
290 }
291
292 }
293
294 });
295 writer.setFooterCallback(new StaxWriterCallback() {
296
297 public void write(XMLEventWriter writer) throws IOException {
298 XMLEventFactory factory = XMLEventFactory.newInstance();
299 try {
300 writer.add(factory.createStartElement("", "", "footer"));
301 writer.add(factory.createEndElement("", "", "footer"));
302 }
303 catch (XMLStreamException e) {
304 throw new RuntimeException(e);
305 }
306
307 }
308
309 });
310 writer.setRootTagName("testroot");
311 writer.setRootElementAttributes(Collections.<String, String> singletonMap("attribute", "value"));
312 writer.open(executionContext);
313 writer.close();
314 String content = getOutputFileContent();
315
316 assertTrue(content.contains("<testroot attribute=\"value\">"));
317 assertTrue(content.contains("<header/>"));
318 assertTrue(content.contains("<footer/>"));
319 assertTrue(content.endsWith("</testroot>"));
320 }
321
322 @Test
323 public void testNonExistantResource() throws Exception {
324 Resource doesntExist = createMock(Resource.class);
325 expect(doesntExist.getFile()).andReturn(File.createTempFile("arbitrary", null));
326 expect(doesntExist.exists()).andReturn(false);
327 replay(doesntExist);
328
329 writer.setResource(doesntExist);
330
331 try {
332 writer.open(executionContext);
333 fail();
334 }
335 catch (IllegalStateException e) {
336 assertEquals("Output resource must exist", e.getMessage());
337 }
338 }
339
340
341
342
343 @Test
344 public void testWriteRootTagWithNamespace() throws Exception {
345 writer.setRootTagName("{http://www.springframework.org/test}root");
346 writer.afterPropertiesSet();
347 writer.open(executionContext);
348 writer.write(items);
349 writer.close();
350 String content = getOutputFileContent();
351 assertTrue("Wrong content: " + content, content
352 .contains(("<root xmlns=\"http://www.springframework.org/test\">")));
353 assertTrue("Wrong content: " + content, content.contains(TEST_STRING));
354 assertTrue("Wrong content: " + content, content.contains(("</root>")));
355 }
356
357
358
359
360 @Test
361 public void testWriteRootTagWithNamespaceAndPrefix() throws Exception {
362 writer.setRootTagName("{http://www.springframework.org/test}ns:root");
363 writer.afterPropertiesSet();
364 marshaller.setNamespace(writer.getRootTagNamespace());
365 marshaller.setNamespacePrefix(writer.getRootTagNamespacePrefix());
366 writer.open(executionContext);
367 writer.write(items);
368 writer.close();
369 String content = getOutputFileContent();
370 assertTrue("Wrong content: " + content, content
371 .contains(("<ns:root xmlns:ns=\"http://www.springframework.org/test\">")));
372 assertTrue("Wrong content: " + content, content.contains(NS_TEST_STRING));
373 assertTrue("Wrong content: " + content, content.contains(("</ns:root>")));
374 assertTrue("Wrong content: " + content, content.contains(("<ns:root")));
375 }
376
377
378
379
380 @Test
381 public void testWriteRootTagWithAdditionalNamespace() throws Exception {
382 writer.setRootTagName("{http://www.springframework.org/test}ns:root");
383 marshaller.setNamespace("urn:org.test.foo");
384 marshaller.setNamespacePrefix("foo");
385 writer.setRootElementAttributes(Collections.singletonMap("xmlns:foo", "urn:org.test.foo"));
386 writer.afterPropertiesSet();
387 writer.open(executionContext);
388 writer.write(items);
389 writer.close();
390 String content = getOutputFileContent();
391 assertTrue("Wrong content: " + content, content
392 .contains(("<ns:root xmlns:ns=\"http://www.springframework.org/test\" "
393 + "xmlns:foo=\"urn:org.test.foo\">")));
394 assertTrue("Wrong content: " + content, content.contains(FOO_TEST_STRING));
395 assertTrue("Wrong content: " + content, content.contains(("</ns:root>")));
396 assertTrue("Wrong content: " + content, content.contains(("<ns:root")));
397 }
398
399
400
401
402 private static class SimpleMarshaller implements Marshaller {
403
404 private String namespacePrefix = "";
405
406 private String namespace = "";
407
408 public void setNamespace(String namespace) {
409 this.namespace = namespace;
410 }
411
412 public void setNamespacePrefix(String namespacePrefix) {
413 this.namespacePrefix = namespacePrefix;
414 }
415
416 public void marshal(Object graph, Result result) throws XmlMappingException, IOException {
417 Assert.isInstanceOf( Result.class, result);
418 try {
419 StaxUtils.getXmlEventWriter( result ).add( XMLEventFactory.newInstance().createStartElement(namespacePrefix, namespace, graph.toString()));
420 StaxUtils.getXmlEventWriter( result ).add( XMLEventFactory.newInstance().createEndElement(namespacePrefix, namespace, graph.toString()));
421 }
422 catch ( Exception e) {
423 throw new RuntimeException("Exception while writing to output file", e);
424 }
425 }
426
427 @SuppressWarnings("rawtypes")
428 public boolean supports(Class clazz) {
429 return true;
430 }
431 }
432
433
434
435
436 private String getOutputFileContent() throws IOException {
437 String value = FileUtils.readFileToString(resource.getFile(), null);
438 value = value.replace("<?xml version='1.0' encoding='UTF-8'?>", "");
439 return value;
440 }
441
442
443
444
445 private StaxEventItemWriter<Object> createItemWriter() throws Exception {
446 StaxEventItemWriter<Object> source = new StaxEventItemWriter<Object>();
447 source.setResource(resource);
448
449 marshaller = new SimpleMarshaller();
450 source.setMarshaller(marshaller);
451
452 source.setEncoding("UTF-8");
453 source.setRootTagName("root");
454 source.setVersion("1.0");
455 source.setOverwriteOutput(true);
456 source.setSaveState(true);
457
458 source.afterPropertiesSet();
459
460 return source;
461 }
462
463 }