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  	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 }