001/*
002 * Copyright 2006-2007 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.batch.item.xml.stax;
018
019import java.util.NoSuchElementException;
020
021import javax.xml.namespace.QName;
022import javax.xml.stream.XMLEventFactory;
023import javax.xml.stream.XMLEventReader;
024import javax.xml.stream.XMLStreamException;
025import javax.xml.stream.events.EndDocument;
026import javax.xml.stream.events.EndElement;
027import javax.xml.stream.events.StartDocument;
028import javax.xml.stream.events.StartElement;
029import javax.xml.stream.events.XMLEvent;
030
031import org.springframework.dao.DataAccessResourceFailureException;
032
033/**
034 * Default implementation of {@link FragmentEventReader}
035 * 
036 * @author Robert Kasanicky
037 */
038public class DefaultFragmentEventReader extends AbstractEventReaderWrapper implements FragmentEventReader {
039
040        // true when the next event is the StartElement of next fragment
041        private boolean startFragmentFollows = false;
042
043        // true when the next event is the EndElement of current fragment
044        private boolean endFragmentFollows = false;
045
046        // true while cursor is inside fragment
047        private boolean insideFragment = false;
048
049        // true when reader should behave like the cursor was at the end of document
050        private boolean fakeDocumentEnd = false;
051
052        private StartDocument startDocumentEvent = null;
053
054        private EndDocument endDocumentEvent = null;
055
056        // fragment root name is remembered so that the matching closing element can
057        // be identified
058        private QName fragmentRootName = null;
059
060        // counts the occurrences of current fragmentRootName (increased for
061        // StartElement, decreased for EndElement)
062        private int matchCounter = 0;
063
064        /**
065         * Caches the StartDocument event for later use.
066         * @param wrappedEventReader the original wrapped event reader
067         */
068        public DefaultFragmentEventReader(XMLEventReader wrappedEventReader) {
069                super(wrappedEventReader);
070                try {
071                        startDocumentEvent = (StartDocument) wrappedEventReader.peek();
072                }
073                catch (XMLStreamException e) {
074                        throw new DataAccessResourceFailureException("Error reading start document from event reader", e);
075                }
076
077                endDocumentEvent = XMLEventFactory.newInstance().createEndDocument();
078        }
079
080    @Override
081        public void markStartFragment() {
082                startFragmentFollows = true;
083                fragmentRootName = null;
084        }
085
086    @Override
087        public boolean hasNext() {
088                try {
089                        if (peek() != null) {
090                                return true;
091                        }
092                }
093                catch (XMLStreamException e) {
094                        throw new DataAccessResourceFailureException("Error reading XML stream", e);
095                }
096                return false;
097        }
098
099    @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}