EMMA Coverage Report (generated Tue May 06 07:28:24 PDT 2008)
[all classes][org.springframework.batch.item.xml]

COVERAGE SUMMARY FOR SOURCE FILE [StaxEventItemWriter.java]

nameclass, %method, %block, %line, %
StaxEventItemWriter.java100% (1/1)100% (25/25)75%  (365/485)88%  (107/121)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class StaxEventItemWriter100% (1/1)100% (25/25)75%  (365/485)88%  (107/121)
close (ExecutionContext): void 100% (1/1)29%  (14/48)60%  (6/10)
getPosition (): long 100% (1/1)37%  (10/27)67%  (4/6)
setPosition (long): void 100% (1/1)56%  (23/41)71%  (5/7)
open (long): void 100% (1/1)60%  (51/85)78%  (14/18)
endDocument (XMLEventWriter): void 100% (1/1)64%  (30/47)78%  (7/9)
StaxEventItemWriter (): void 100% (1/1)100% (49/49)100% (14/14)
afterPropertiesSet (): void 100% (1/1)100% (7/7)100% (3/3)
clear (): void 100% (1/1)100% (8/8)100% (3/3)
flush (): void 100% (1/1)100% (29/29)100% (8/8)
getEncoding (): String 100% (1/1)100% (3/3)100% (1/1)
getRootElementAttributes (): Map 100% (1/1)100% (3/3)100% (1/1)
getRootTagName (): String 100% (1/1)100% (3/3)100% (1/1)
getVersion (): String 100% (1/1)100% (3/3)100% (1/1)
open (ExecutionContext): void 100% (1/1)100% (21/21)100% (6/6)
setEncoding (String): void 100% (1/1)100% (4/4)100% (2/2)
setOverwriteOutput (boolean): void 100% (1/1)100% (4/4)100% (2/2)
setResource (Resource): void 100% (1/1)100% (4/4)100% (2/2)
setRootElementAttributes (Map): void 100% (1/1)100% (4/4)100% (2/2)
setRootTagName (String): void 100% (1/1)100% (4/4)100% (2/2)
setSaveState (boolean): void 100% (1/1)100% (4/4)100% (2/2)
setSerializer (EventWriterSerializer): void 100% (1/1)100% (4/4)100% (2/2)
setVersion (String): void 100% (1/1)100% (4/4)100% (2/2)
startDocument (XMLEventWriter): void 100% (1/1)100% (46/46)100% (9/9)
update (ExecutionContext): void 100% (1/1)100% (21/21)100% (5/5)
write (Object): void 100% (1/1)100% (12/12)100% (3/3)

1package org.springframework.batch.item.xml;
2 
3import java.io.File;
4import java.io.FileOutputStream;
5import java.io.IOException;
6import java.nio.ByteBuffer;
7import java.nio.channels.FileChannel;
8import java.util.ArrayList;
9import java.util.Iterator;
10import java.util.List;
11import java.util.Map;
12 
13import javax.xml.stream.XMLEventFactory;
14import javax.xml.stream.XMLEventWriter;
15import javax.xml.stream.XMLOutputFactory;
16import javax.xml.stream.XMLStreamException;
17 
18import org.springframework.batch.item.ClearFailedException;
19import org.springframework.batch.item.ExecutionContext;
20import org.springframework.batch.item.ExecutionContextUserSupport;
21import org.springframework.batch.item.FlushFailedException;
22import org.springframework.batch.item.ItemStream;
23import org.springframework.batch.item.ItemWriter;
24import org.springframework.batch.item.util.FileUtils;
25import org.springframework.batch.item.xml.stax.NoStartEndDocumentStreamWriter;
26import org.springframework.beans.factory.InitializingBean;
27import org.springframework.core.io.Resource;
28import org.springframework.dao.DataAccessResourceFailureException;
29import org.springframework.util.Assert;
30import org.springframework.util.ClassUtils;
31import org.springframework.util.CollectionUtils;
32 
33/**
34 * An implementation of {@link ItemWriter} which uses StAX and {@link EventWriterSerializer} for serializing object to
35 * XML.
36 * 
37 * This item writer also provides restart, statistics and transaction features by implementing corresponding interfaces.
38 * 
39 * Output is buffered until {@link #flush()} is called - only then the actual writing to file takes place.
40 * 
41 * @author Peter Zozom
42 * @author Robert Kasanicky
43 * 
44 */
45public class StaxEventItemWriter extends ExecutionContextUserSupport implements ItemWriter, ItemStream,
46        InitializingBean {
47 
48        // default encoding
49        private static final String DEFAULT_ENCODING = "UTF-8";
50 
51        // default encoding
52        private static final String DEFAULT_XML_VERSION = "1.0";
53 
54        // default root tag name
55        private static final String DEFAULT_ROOT_TAG_NAME = "root";
56 
57        // restart data property name
58        private static final String RESTART_DATA_NAME = "position";
59 
60        // restart data property name
61        private static final String WRITE_STATISTICS_NAME = "record.count";
62 
63        // file system resource
64        private Resource resource;
65 
66        // xml serializer
67        private EventWriterSerializer serializer;
68 
69        // encoding to be used while reading from the resource
70        private String encoding = DEFAULT_ENCODING;
71 
72        // XML version
73        private String version = DEFAULT_XML_VERSION;
74 
75        // name of the root tag
76        private String rootTagName = DEFAULT_ROOT_TAG_NAME;
77 
78        // root element attributes
79        private Map rootElementAttributes = null;
80 
81        // signalizes that marshalling was restarted
82        private boolean restarted = false;
83 
84        // TRUE means, that output file will be overwritten if exists - default is
85        // TRUE
86        private boolean overwriteOutput = true;
87 
88        // file channel
89        private FileChannel channel;
90 
91        // wrapper for XML event writer that swallows StartDocument and EndDocument
92        // events
93        private XMLEventWriter eventWriter;
94 
95        // XML event writer
96        private XMLEventWriter delegateEventWriter;
97 
98        // byte offset in file channel at last commit point
99        private long lastCommitPointPosition = 0;
100 
101        // processed record count at last commit point
102        private long lastCommitPointRecordCount = 0;
103 
104        // current count of processed records
105        private long currentRecordCount = 0;
106 
107        private boolean saveState = false;
108 
109        // holds the list of items for writing before they are actually written on
110        // #flush()
111        private List buffer = new ArrayList();
112 
113        public StaxEventItemWriter() {
114                setName(ClassUtils.getShortName(StaxEventItemWriter.class));
115        }
116 
117        /**
118         * Set output file.
119         * 
120         * @param resource the output file
121         */
122        public void setResource(Resource resource) {
123                this.resource = resource;
124        }
125 
126        /**
127         * Set Object to XML serializer.
128         * 
129         * @param serializer the Object to XML serializer
130         */
131        public void setSerializer(EventWriterSerializer serializer) {
132                this.serializer = serializer;
133        }
134 
135        /**
136         * Get used encoding.
137         * 
138         * @return the encoding used
139         */
140        public String getEncoding() {
141                return encoding;
142        }
143 
144        /**
145         * Set encoding to be used for output file.
146         * 
147         * @param encoding the encoding to be used
148         */
149        public void setEncoding(String encoding) {
150                this.encoding = encoding;
151        }
152 
153        /**
154         * Get XML version.
155         * 
156         * @return the XML version used
157         */
158        public String getVersion() {
159                return version;
160        }
161 
162        /**
163         * Set XML version to be used for output XML.
164         * 
165         * @param version the XML version to be used
166         */
167        public void setVersion(String version) {
168                this.version = version;
169        }
170 
171        /**
172         * Get the tag name of the root element.
173         * 
174         * @return the root element tag name
175         */
176        public String getRootTagName() {
177                return rootTagName;
178        }
179 
180        /**
181         * Set the tag name of the root element. If not set, default name is used ("root").
182         * 
183         * @param rootTagName the tag name to be used for the root element
184         */
185        public void setRootTagName(String rootTagName) {
186                this.rootTagName = rootTagName;
187        }
188 
189        /**
190         * Get attributes of the root element.
191         * 
192         * @return attributes of the root element
193         */
194        public Map getRootElementAttributes() {
195                return rootElementAttributes;
196        }
197 
198        /**
199         * Set the root element attributes to be written.
200         * 
201         * @param rootElementAttributes attributes of the root element
202         */
203        public void setRootElementAttributes(Map rootElementAttributes) {
204                this.rootElementAttributes = rootElementAttributes;
205        }
206 
207        /**
208         * Set "overwrite" flag for the output file. Flag is ignored when output file processing is restarted.
209         * 
210         * @param overwriteOutput
211         */
212        public void setOverwriteOutput(boolean overwriteOutput) {
213                this.overwriteOutput = overwriteOutput;
214        }
215 
216        /**
217         * @throws Exception
218         * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
219         */
220        public void afterPropertiesSet() throws Exception {
221                Assert.notNull(resource);
222                Assert.notNull(serializer);
223        }
224 
225        /**
226         * Open the output source
227         * 
228         * @see org.springframework.batch.item.ItemStream#open(ExecutionContext)
229         */
230        public void open(ExecutionContext executionContext) {
231                long startAtPosition = 0;
232 
233                // if restart data is provided, restart from provided offset
234                // otherwise start from beginning
235                if (executionContext.containsKey(getKey(RESTART_DATA_NAME))) {
236                        startAtPosition = executionContext.getLong(getKey(RESTART_DATA_NAME));
237                        restarted = true;
238                }
239 
240                open(startAtPosition);
241        }
242 
243        /**
244         * Helper method for opening output source at given file position
245         */
246        private void open(long position) {
247 
248                File file;
249                FileOutputStream os = null;
250 
251                try {
252                        file = resource.getFile();
253                        FileUtils.setUpOutputFile(file, restarted, overwriteOutput);
254                        os = new FileOutputStream(file, true);
255                        channel = os.getChannel();
256                        setPosition(position);
257                } catch (IOException ioe) {
258                        throw new DataAccessResourceFailureException("Unable to write to file resource: [" + resource + "]", ioe);
259                }
260 
261                XMLOutputFactory outputFactory = XMLOutputFactory.newInstance();
262 
263                try {
264                        delegateEventWriter = outputFactory.createXMLEventWriter(os, encoding);
265                        eventWriter = new NoStartEndDocumentStreamWriter(delegateEventWriter);
266                        if (!restarted) {
267                                startDocument(delegateEventWriter);
268                        }
269                } catch (XMLStreamException xse) {
270                        throw new DataAccessResourceFailureException("Unable to write to file resource: [" + resource + "]", xse);
271                }
272 
273        }
274 
275        /**
276         * Writes simple XML header containing:
277         * <ul>
278         * <li>xml declaration - defines encoding and XML version</li>
279         * <li>opening tag of the root element and its attributes</li>
280         * </ul>
281         * If this is not sufficient for you, simply override this method. Encoding, version and root tag name can be
282         * retrieved with corresponding getters.
283         * 
284         * @param writer XML event writer
285         * @throws XMLStreamException
286         */
287        private void startDocument(XMLEventWriter writer) throws XMLStreamException {
288 
289                XMLEventFactory factory = XMLEventFactory.newInstance();
290 
291                // write start document
292                writer.add(factory.createStartDocument(getEncoding(), getVersion()));
293 
294                // write root tag
295                writer.add(factory.createStartElement("", "", getRootTagName()));
296 
297                // write root tag attributes
298                if (!CollectionUtils.isEmpty(getRootElementAttributes())) {
299 
300                        for (Iterator i = getRootElementAttributes().entrySet().iterator(); i.hasNext();) {
301                                Map.Entry entry = (Map.Entry) i.next();
302                                writer.add(factory.createAttribute((String) entry.getKey(), (String) entry.getValue()));
303                        }
304 
305                }
306 
307        }
308 
309        /**
310         * Finishes the XML document. It closes any start tag and writes corresponding end tags.
311         * 
312         * @param writer XML event writer
313         * @throws XMLStreamException
314         */
315        private void endDocument(XMLEventWriter writer) throws XMLStreamException {
316 
317                // writer.writeEndDocument(); <- this doesn't work after restart
318                // we need to write end tag of the root element manually
319                
320                //harmless event to close the root tag if there were no items
321                XMLEventFactory factory = XMLEventFactory.newInstance();
322                writer.add(factory.createCharacters(""));
323                
324                writer.flush();
325                
326                ByteBuffer bbuf = ByteBuffer.wrap(("</" + getRootTagName() + ">").getBytes());
327                try {
328                        channel.write(bbuf);
329                } catch (IOException ioe) {
330                        throw new DataAccessResourceFailureException("Unable to close file resource: [" + resource + "]", ioe);
331                }
332        }
333 
334        /**
335         * Flush and close the output source.
336         * 
337         * @see org.springframework.batch.item.ItemStream#close(ExecutionContext)
338         */
339        public void close(ExecutionContext executionContext) {
340                flush();
341                try {
342                        endDocument(delegateEventWriter);
343                        eventWriter.close();
344                        channel.close();
345                } catch (XMLStreamException xse) {
346                        throw new DataAccessResourceFailureException("Unable to close file resource: [" + resource + "]", xse);
347                } catch (IOException ioe) {
348                        throw new DataAccessResourceFailureException("Unable to close file resource: [" + resource + "]", ioe);
349                }
350        }
351 
352        /**
353         * Write the value object to internal buffer.
354         * 
355         * @param item the value object
356         * @see #flush()
357         */
358        public void write(Object item) {
359 
360                currentRecordCount++;
361                buffer.add(item);
362        }
363 
364        /**
365         * Get the restart data.
366         * 
367         * @see org.springframework.batch.item.ItemStream#update(ExecutionContext)
368         */
369        public void update(ExecutionContext executionContext) {
370 
371                if (saveState) {
372                        Assert.notNull(executionContext, "ExecutionContext must not be null");
373                        executionContext.putLong(getKey(RESTART_DATA_NAME), getPosition());
374                        executionContext.putLong(getKey(WRITE_STATISTICS_NAME), currentRecordCount);
375                }
376        }
377 
378        /*
379         * Get the actual position in file channel. This method flushes any buffered data before position is read.
380         * 
381         * @return byte offset in file channel
382         */
383        private long getPosition() {
384 
385                long position;
386 
387                try {
388                        eventWriter.flush();
389                        position = channel.position();
390                } catch (Exception e) {
391                        throw new DataAccessResourceFailureException("Unable to write to file resource: [" + resource + "]", e);
392                }
393 
394                return position;
395        }
396 
397        /**
398         * Set the file channel position.
399         * 
400         * @param newPosition new file channel position
401         */
402        private void setPosition(long newPosition) {
403 
404                try {
405                        Assert.state(channel.size() >= lastCommitPointPosition,
406                                "Current file size is smaller than size at last commit");
407                        channel.truncate(newPosition);
408                        channel.position(newPosition);
409                } catch (IOException e) {
410                        throw new DataAccessResourceFailureException("Unable to write to file resource: [" + resource + "]", e);
411                }
412 
413        }
414 
415        /**
416         * Writes buffered items to XML stream and marks restore point.
417         */
418        public void flush() throws FlushFailedException {
419 
420                for (Iterator iterator = buffer.listIterator(); iterator.hasNext();) {
421                        Object item = iterator.next();
422                        serializer.serializeObject(eventWriter, item);
423                }
424                buffer.clear();
425 
426                lastCommitPointPosition = getPosition();
427                lastCommitPointRecordCount = currentRecordCount;
428        }
429 
430        /**
431         * Clear the output buffer
432         */
433        public void clear() throws ClearFailedException {
434                currentRecordCount = lastCommitPointRecordCount;
435                buffer.clear();
436        }
437 
438        public void setSaveState(boolean saveState) {
439                this.saveState = saveState;
440        }
441 
442}

[all classes][org.springframework.batch.item.xml]
EMMA 2.0.5312 (C) Vladimir Roubtsov