View Javadoc

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 }