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 | public void markStartFragment() { |
81 | startFragmentFollows = true; |
82 | fragmentRootName = null; |
83 | } |
84 | |
85 | public boolean hasNext() { |
86 | try { |
87 | if (peek() != null) { |
88 | return true; |
89 | } |
90 | } |
91 | catch (XMLStreamException e) { |
92 | throw new DataAccessResourceFailureException("Error reading XML stream", e); |
93 | } |
94 | return false; |
95 | } |
96 | |
97 | public Object next() { |
98 | try { |
99 | return nextEvent(); |
100 | } |
101 | catch (XMLStreamException e) { |
102 | throw new DataAccessResourceFailureException("Error reading XML stream", e); |
103 | } |
104 | } |
105 | |
106 | public XMLEvent nextEvent() throws XMLStreamException { |
107 | if (fakeDocumentEnd) { |
108 | throw new NoSuchElementException(); |
109 | } |
110 | XMLEvent event = wrappedEventReader.peek(); |
111 | XMLEvent proxyEvent = alterEvent(event, false); |
112 | checkFragmentEnd(proxyEvent); |
113 | if (event == proxyEvent) { |
114 | wrappedEventReader.nextEvent(); |
115 | } |
116 | |
117 | return proxyEvent; |
118 | } |
119 | |
120 | /** |
121 | * Sets the endFragmentFollows flag to true if next event is the last event |
122 | * of the fragment. |
123 | * @param event peek() from wrapped event reader |
124 | */ |
125 | private void checkFragmentEnd(XMLEvent event) { |
126 | if (event.isStartElement() && ((StartElement) event).getName().equals(fragmentRootName)) { |
127 | matchCounter++; |
128 | } |
129 | else if (event.isEndElement() && ((EndElement) event).getName().equals(fragmentRootName)) { |
130 | matchCounter--; |
131 | if (matchCounter == 0) { |
132 | endFragmentFollows = true; |
133 | } |
134 | } |
135 | } |
136 | |
137 | /** |
138 | * @param event peek() from wrapped event reader |
139 | * @param peek if true do not change the internal state |
140 | * @return StartDocument event if peek() points to beginning of fragment |
141 | * EndDocument event if cursor is right behind the end of fragment original |
142 | * event otherwise |
143 | */ |
144 | private XMLEvent alterEvent(XMLEvent event, boolean peek) { |
145 | if (startFragmentFollows) { |
146 | fragmentRootName = ((StartElement) event).getName(); |
147 | if (!peek) { |
148 | startFragmentFollows = false; |
149 | insideFragment = true; |
150 | } |
151 | return startDocumentEvent; |
152 | } |
153 | else if (endFragmentFollows) { |
154 | if (!peek) { |
155 | endFragmentFollows = false; |
156 | insideFragment = false; |
157 | fakeDocumentEnd = true; |
158 | } |
159 | return endDocumentEvent; |
160 | } |
161 | return event; |
162 | } |
163 | |
164 | public XMLEvent peek() throws XMLStreamException { |
165 | if (fakeDocumentEnd) { |
166 | return null; |
167 | } |
168 | return alterEvent(wrappedEventReader.peek(), true); |
169 | } |
170 | |
171 | /** |
172 | * Finishes reading the fragment in case the fragment was processed without |
173 | * being read until the end. |
174 | */ |
175 | public void markFragmentProcessed() { |
176 | if (insideFragment|| startFragmentFollows) { |
177 | try { |
178 | while (!(nextEvent() instanceof EndDocument)) { |
179 | // just read all events until EndDocument |
180 | } |
181 | } |
182 | catch (XMLStreamException e) { |
183 | throw new DataAccessResourceFailureException("Error reading XML stream", e); |
184 | } |
185 | } |
186 | fakeDocumentEnd = false; |
187 | } |
188 | |
189 | public void reset() { |
190 | insideFragment = false; |
191 | startFragmentFollows = false; |
192 | endFragmentFollows = false; |
193 | fakeDocumentEnd = false; |
194 | fragmentRootName = null; |
195 | matchCounter = 0; |
196 | } |
197 | |
198 | } |