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