1 | package org.springframework.batch.item.xml.stax; |
2 | |
3 | import java.util.NoSuchElementException; |
4 | |
5 | import javax.xml.namespace.QName; |
6 | import javax.xml.stream.XMLEventFactory; |
7 | import javax.xml.stream.XMLEventReader; |
8 | import javax.xml.stream.XMLStreamException; |
9 | import javax.xml.stream.events.EndDocument; |
10 | import javax.xml.stream.events.EndElement; |
11 | import javax.xml.stream.events.StartDocument; |
12 | import javax.xml.stream.events.StartElement; |
13 | import javax.xml.stream.events.XMLEvent; |
14 | |
15 | import org.springframework.dao.DataAccessResourceFailureException; |
16 | |
17 | /** |
18 | * Default implementation of {@link FragmentEventReader} |
19 | * |
20 | * @author Robert Kasanicky |
21 | */ |
22 | public class DefaultFragmentEventReader extends AbstractEventReaderWrapper implements FragmentEventReader { |
23 | |
24 | // true when the next event is the StartElement of next fragment |
25 | private boolean startFragmentFollows = false; |
26 | |
27 | // true when the next event is the EndElement of current fragment |
28 | private boolean endFragmentFollows = false; |
29 | |
30 | // true while cursor is inside fragment |
31 | private boolean insideFragment = false; |
32 | |
33 | // true when reader should behave like the cursor was at the end of document |
34 | private boolean fakeDocumentEnd = false; |
35 | |
36 | private StartDocument startDocumentEvent = null; |
37 | |
38 | private EndDocument endDocumentEvent = null; |
39 | |
40 | // fragment root name is remembered so that the matching closing element can |
41 | // be identified |
42 | private QName fragmentRootName = null; |
43 | |
44 | // counts the occurrences of current fragmentRootName (increased for |
45 | // StartElement, decreased for EndElement) |
46 | private int matchCounter = 0; |
47 | |
48 | /** |
49 | * Caches the StartDocument event for later use. |
50 | * @param wrappedEventReader the original wrapped event reader |
51 | */ |
52 | public DefaultFragmentEventReader(XMLEventReader wrappedEventReader) { |
53 | super(wrappedEventReader); |
54 | try { |
55 | startDocumentEvent = (StartDocument) wrappedEventReader.peek(); |
56 | } |
57 | catch (XMLStreamException e) { |
58 | throw new DataAccessResourceFailureException("Error reading start document from event reader", e); |
59 | } |
60 | |
61 | endDocumentEvent = XMLEventFactory.newInstance().createEndDocument(); |
62 | } |
63 | |
64 | public void markStartFragment() { |
65 | startFragmentFollows = true; |
66 | fragmentRootName = null; |
67 | } |
68 | |
69 | public boolean hasNext() { |
70 | try { |
71 | if (peek() != null) { |
72 | return true; |
73 | } |
74 | } |
75 | catch (XMLStreamException e) { |
76 | throw new DataAccessResourceFailureException("Error reading XML stream", e); |
77 | } |
78 | return false; |
79 | } |
80 | |
81 | public Object next() { |
82 | try { |
83 | return nextEvent(); |
84 | } |
85 | catch (XMLStreamException e) { |
86 | throw new DataAccessResourceFailureException("Error reading XML stream", e); |
87 | } |
88 | } |
89 | |
90 | public XMLEvent nextEvent() throws XMLStreamException { |
91 | if (fakeDocumentEnd) { |
92 | throw new NoSuchElementException(); |
93 | } |
94 | XMLEvent event = wrappedEventReader.peek(); |
95 | XMLEvent proxyEvent = alterEvent(event, false); |
96 | checkFragmentEnd(proxyEvent); |
97 | if (event == proxyEvent) { |
98 | wrappedEventReader.nextEvent(); |
99 | } |
100 | |
101 | return proxyEvent; |
102 | } |
103 | |
104 | /** |
105 | * Sets the endFragmentFollows flag to true if next event is the last event |
106 | * of the fragment. |
107 | * @param event peek() from wrapped event reader |
108 | */ |
109 | private void checkFragmentEnd(XMLEvent event) { |
110 | if (event.isStartElement() && ((StartElement) event).getName().equals(fragmentRootName)) { |
111 | matchCounter++; |
112 | } |
113 | else if (event.isEndElement() && ((EndElement) event).getName().equals(fragmentRootName)) { |
114 | matchCounter--; |
115 | if (matchCounter == 0) { |
116 | endFragmentFollows = true; |
117 | } |
118 | } |
119 | } |
120 | |
121 | /** |
122 | * @param event peek() from wrapped event reader |
123 | * @param peek if true do not change the internal state |
124 | * @return StartDocument event if peek() points to beginning of fragment |
125 | * EndDocument event if cursor is right behind the end of fragment original |
126 | * event otherwise |
127 | */ |
128 | private XMLEvent alterEvent(XMLEvent event, boolean peek) { |
129 | if (startFragmentFollows) { |
130 | fragmentRootName = ((StartElement) event).getName(); |
131 | if (!peek) { |
132 | startFragmentFollows = false; |
133 | insideFragment = true; |
134 | } |
135 | return startDocumentEvent; |
136 | } |
137 | else if (endFragmentFollows) { |
138 | if (!peek) { |
139 | endFragmentFollows = false; |
140 | insideFragment = false; |
141 | fakeDocumentEnd = true; |
142 | } |
143 | return endDocumentEvent; |
144 | } |
145 | return event; |
146 | } |
147 | |
148 | public XMLEvent peek() throws XMLStreamException { |
149 | if (fakeDocumentEnd) { |
150 | return null; |
151 | } |
152 | return alterEvent(wrappedEventReader.peek(), true); |
153 | } |
154 | |
155 | /** |
156 | * Finishes reading the fragment in case the fragment was processed without |
157 | * being read until the end. |
158 | */ |
159 | public void markFragmentProcessed() { |
160 | if (insideFragment|| startFragmentFollows) { |
161 | try { |
162 | while (!(nextEvent() instanceof EndDocument)) { |
163 | // just read all events until EndDocument |
164 | } |
165 | } |
166 | catch (XMLStreamException e) { |
167 | throw new DataAccessResourceFailureException("Error reading XML stream", e); |
168 | } |
169 | } |
170 | fakeDocumentEnd = false; |
171 | } |
172 | |
173 | public void reset() { |
174 | insideFragment = false; |
175 | startFragmentFollows = false; |
176 | endFragmentFollows = false; |
177 | fakeDocumentEnd = false; |
178 | } |
179 | |
180 | } |