1 | package org.springframework.batch.item.xml; |
2 | |
3 | import java.io.InputStream; |
4 | |
5 | import javax.xml.namespace.QName; |
6 | import javax.xml.stream.XMLEventReader; |
7 | import javax.xml.stream.XMLInputFactory; |
8 | import javax.xml.stream.XMLStreamException; |
9 | import javax.xml.stream.events.StartElement; |
10 | |
11 | import org.apache.commons.logging.Log; |
12 | import org.apache.commons.logging.LogFactory; |
13 | import org.springframework.batch.item.file.ResourceAwareItemReaderItemStream; |
14 | import org.springframework.batch.item.support.AbstractBufferedItemReaderItemStream; |
15 | import org.springframework.batch.item.xml.stax.DefaultFragmentEventReader; |
16 | import org.springframework.batch.item.xml.stax.FragmentEventReader; |
17 | import org.springframework.beans.factory.InitializingBean; |
18 | import org.springframework.core.io.Resource; |
19 | import org.springframework.dao.DataAccessResourceFailureException; |
20 | import org.springframework.util.Assert; |
21 | import org.springframework.util.ClassUtils; |
22 | |
23 | /** |
24 | * Item reader for reading XML input based on StAX. |
25 | * |
26 | * It extracts fragments from the input XML document which correspond to records |
27 | * for processing. The fragments are wrapped with StartDocument and EndDocument |
28 | * events so that the fragments can be further processed like standalone XML |
29 | * documents. |
30 | * |
31 | * The implementation is *not* thread-safe. |
32 | * |
33 | * @author Robert Kasanicky |
34 | */ |
35 | public class StaxEventItemReader extends AbstractBufferedItemReaderItemStream implements |
36 | ResourceAwareItemReaderItemStream, InitializingBean { |
37 | |
38 | private static final Log logger = LogFactory.getLog(StaxEventItemReader.class); |
39 | |
40 | private FragmentEventReader fragmentReader; |
41 | |
42 | private XMLEventReader eventReader; |
43 | |
44 | private EventReaderDeserializer eventReaderDeserializer; |
45 | |
46 | private Resource resource; |
47 | |
48 | private InputStream inputStream; |
49 | |
50 | private String fragmentRootElementName; |
51 | |
52 | private boolean noInput; |
53 | |
54 | public StaxEventItemReader() { |
55 | setName(ClassUtils.getShortName(StaxEventItemReader.class)); |
56 | } |
57 | |
58 | public void setResource(Resource resource) { |
59 | this.resource = resource; |
60 | } |
61 | |
62 | /** |
63 | * @param eventReaderDeserializer maps xml fragments corresponding to |
64 | * records to objects |
65 | */ |
66 | public void setFragmentDeserializer(EventReaderDeserializer eventReaderDeserializer) { |
67 | this.eventReaderDeserializer = eventReaderDeserializer; |
68 | } |
69 | |
70 | /** |
71 | * @param fragmentRootElementName name of the root element of the fragment |
72 | * TODO String can be ambiguous due to namespaces, use QName? |
73 | */ |
74 | public void setFragmentRootElementName(String fragmentRootElementName) { |
75 | this.fragmentRootElementName = fragmentRootElementName; |
76 | } |
77 | |
78 | /** |
79 | * Ensure that all required dependencies for the ItemReader to run are |
80 | * provided after all properties have been set. |
81 | * |
82 | * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() |
83 | * @throws IllegalArgumentException if the Resource, FragmentDeserializer or |
84 | * FragmentRootElementName is null, or if the root element is empty. |
85 | * @throws IllegalStateException if the Resource does not exist. |
86 | */ |
87 | public void afterPropertiesSet() throws Exception { |
88 | Assert.notNull(eventReaderDeserializer, "The FragmentDeserializer must not be null."); |
89 | Assert.hasLength(fragmentRootElementName, "The FragmentRootElementName must not be null"); |
90 | } |
91 | |
92 | /** |
93 | * Responsible for moving the cursor before the StartElement of the fragment |
94 | * root. |
95 | * |
96 | * This implementation simply looks for the next corresponding element, it |
97 | * does not care about element nesting. You will need to override this |
98 | * method to correctly handle composite fragments. |
99 | * |
100 | * @return <code>true</code> if next fragment was found, |
101 | * <code>false</code> otherwise. |
102 | */ |
103 | protected boolean moveCursorToNextFragment(XMLEventReader reader) { |
104 | try { |
105 | while (true) { |
106 | while (reader.peek() != null && !reader.peek().isStartElement()) { |
107 | reader.nextEvent(); |
108 | } |
109 | if (reader.peek() == null) { |
110 | return false; |
111 | } |
112 | QName startElementName = ((StartElement) reader.peek()).getName(); |
113 | if (startElementName.getLocalPart().equals(fragmentRootElementName)) { |
114 | return true; |
115 | } |
116 | else { |
117 | reader.nextEvent(); |
118 | } |
119 | } |
120 | } |
121 | catch (XMLStreamException e) { |
122 | throw new DataAccessResourceFailureException("Error while reading from event reader", e); |
123 | } |
124 | } |
125 | |
126 | protected void doClose() throws Exception { |
127 | try { |
128 | if (fragmentReader != null) { |
129 | fragmentReader.close(); |
130 | } |
131 | if (inputStream != null) { |
132 | inputStream.close(); |
133 | } |
134 | } |
135 | finally { |
136 | fragmentReader = null; |
137 | inputStream = null; |
138 | } |
139 | |
140 | } |
141 | |
142 | protected void doOpen() throws Exception { |
143 | Assert.notNull(resource, "The Resource must not be null."); |
144 | |
145 | noInput = false; |
146 | if (!resource.exists()) { |
147 | noInput = true; |
148 | logger.warn("Input resource does not exist"); |
149 | return; |
150 | } |
151 | |
152 | inputStream = resource.getInputStream(); |
153 | eventReader = XMLInputFactory.newInstance().createXMLEventReader(inputStream); |
154 | fragmentReader = new DefaultFragmentEventReader(eventReader); |
155 | |
156 | } |
157 | |
158 | /** |
159 | * Move to next fragment and map it to item. |
160 | */ |
161 | protected Object doRead() throws Exception { |
162 | if (noInput) { |
163 | return null; |
164 | } |
165 | |
166 | Object item = null; |
167 | |
168 | if (moveCursorToNextFragment(fragmentReader)) { |
169 | fragmentReader.markStartFragment(); |
170 | item = eventReaderDeserializer.deserializeFragment(fragmentReader); |
171 | fragmentReader.markFragmentProcessed(); |
172 | } |
173 | |
174 | return item; |
175 | } |
176 | |
177 | } |