001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one
003: * or more contributor license agreements. See the NOTICE file
004: * distributed with this work for additional information
005: * regarding copyright ownership. The ASF licenses this file
006: * to you under the Apache License, Version 2.0 (the
007: * "License"); you may not use this file except in compliance
008: * with the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing,
013: * software distributed under the License is distributed on an
014: * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: */
019:
020: package org.apache.synapse.util;
021:
022: import java.io.IOException;
023: import java.io.Reader;
024: import java.util.Collections;
025:
026: import javax.xml.XMLConstants;
027: import javax.xml.namespace.NamespaceContext;
028: import javax.xml.namespace.QName;
029: import javax.xml.stream.Location;
030: import javax.xml.stream.XMLStreamException;
031: import javax.xml.stream.XMLStreamReader;
032:
033: import org.apache.axiom.om.impl.EmptyOMLocation;
034: import org.apache.axiom.om.impl.llom.util.NamespaceContextImpl;
035: import org.apache.commons.io.IOUtils;
036:
037: /**
038: * {@link javax.xml.stream.XMLStreamException XMLInputStreamReader} implementation that
039: * represents a text node wrapped inside an element. The text data is provided by a
040: * {@link java.io.Reader Reader}.
041: * <p>
042: * It will produce the following sequence of XML events:
043: * <ul>
044: * <li>START_DOCUMENT</li>
045: * <li>START_ELEMENT</li>
046: * <li>(CHARACTER)*</li>
047: * <li>END_ELEMENT</li>
048: * <li>END_DOCMENT</li>
049: * </ul>
050: * The class is implemented as a simple state machine, where the state is identified
051: * by the current event type and with the following transitions triggered by
052: * {@link #next()}:
053: * <ul>
054: * <li>-1 → START_DOCUMENT</li>
055: * <li>START_DOCUMENT → START_ELEMENT</li>
056: * <li>START_ELEMENT → END_ELEMENT (if character stream is empty)</li>
057: * <li>START_ELEMENT → CHARACTERS (if character stream is not empty)</li>
058: * <li>CHARACTERS → CHARACTERS (if data available in stream)</li>
059: * <li>CHARACTERS → END_ELEMENT (if end of stream reached)</li>
060: * <li>END_ELEMENT → END_DOCUMENT</li>
061: * </ul>
062: * Additionally, {@link #getElementText()} triggers the following transition:
063: * <ul>
064: * <li>START_ELEMENT → END_ELEMENT</li>
065: * </ul>
066: * Note that since multiple consecutive CHARACTERS events may be returned, this
067: * "parser" is not coalescing.
068: *
069: */
070: public class WrappedTextNodeStreamReader implements XMLStreamReader {
071: /**
072: * Location object returned by {@link #getLocation()}.
073: * It always returns -1 for the location and null for the publicId and systemId.
074: */
075: private final static Location EMPTY_LOCATION = new EmptyOMLocation();
076:
077: /**
078: * The qualified name of the wrapper element.
079: */
080: private final QName wrapperElementName;
081:
082: /**
083: * The Reader object that represents the text data.
084: */
085: private final Reader reader;
086:
087: /**
088: * The maximum number of characters to return for each CHARACTER event.
089: */
090: private final int chunkSize;
091:
092: /**
093: * The type of the current XML event.
094: */
095: private int eventType = -1;
096:
097: /**
098: * The character data for the current event. This is only set if the current
099: * event is a CHARACTER event. The size of the array is determined by
100: * {@link #chunkSize}
101: */
102: private char[] charData;
103:
104: /**
105: * The length of the character data in {@link #charData}.
106: */
107: private int charDataLength;
108:
109: /**
110: * The namespace context applicable in the scope of the wrapper element.
111: * Beside the default mappings for xml and xmlns, it only contains the
112: * mapping for the namespace of the wrapper element.
113: * This attribute is initialized lazily by {@link #getNamespaceContext()}.
114: */
115: private NamespaceContext namespaceContext;
116:
117: /**
118: * Create a new instance.
119: *
120: * @param wrapperElementName the qualified name of the wrapper element
121: * @param reader the Reader object holding the character data to be wrapped
122: * @param chunkSize the maximum number of characters that are returned for each CHARACTER event
123: */
124: public WrappedTextNodeStreamReader(QName wrapperElementName,
125: Reader reader, int chunkSize) {
126: this .wrapperElementName = wrapperElementName;
127: this .reader = reader;
128: this .chunkSize = chunkSize;
129: }
130:
131: /**
132: * Create a new instance with chunk size 4096.
133: *
134: * @param wrapperElementName the qualified name of the wrapper element
135: * @param reader the Reader object holding the character data to be wrapped
136: */
137: public WrappedTextNodeStreamReader(QName wrapperElementName,
138: Reader reader) {
139: this (wrapperElementName, reader, 4096);
140: }
141:
142: public Object getProperty(String name)
143: throws IllegalArgumentException {
144: // We don't define any properties
145: return null;
146: }
147:
148: //
149: // Methods to manipulate the parser state
150: //
151:
152: public boolean hasNext() throws XMLStreamException {
153: return eventType != END_DOCUMENT;
154: }
155:
156: public int next() throws XMLStreamException {
157: // Determine next event type based on current event type. If current event type
158: // is START_ELEMENT or CHARACTERS, pull new data from the reader.
159: switch (eventType) {
160: case -1:
161: eventType = START_DOCUMENT;
162: break;
163: case START_DOCUMENT:
164: eventType = START_ELEMENT;
165: break;
166: case START_ELEMENT:
167: charData = new char[chunkSize];
168: // No break here!
169: case CHARACTERS:
170: try {
171: charDataLength = reader.read(charData);
172: } catch (IOException ex) {
173: throw new XMLStreamException(ex);
174: }
175: if (charDataLength == -1) {
176: charData = null;
177: eventType = END_ELEMENT;
178: } else {
179: eventType = CHARACTERS;
180: }
181: break;
182: case END_ELEMENT:
183: eventType = END_DOCUMENT;
184: break;
185: default:
186: throw new IllegalStateException();
187: }
188: return eventType;
189: }
190:
191: public int nextTag() throws XMLStreamException {
192: // We don't have white space, comments or processing instructions
193: throw new XMLStreamException("Current event is not white space");
194: }
195:
196: public int getEventType() {
197: return eventType;
198: }
199:
200: public boolean isStartElement() {
201: return eventType == START_ELEMENT;
202: }
203:
204: public boolean isEndElement() {
205: return eventType == END_ELEMENT;
206: }
207:
208: public boolean isCharacters() {
209: return eventType == CHARACTERS;
210: }
211:
212: public boolean isWhiteSpace() {
213: return false;
214: }
215:
216: public boolean hasText() {
217: return eventType == CHARACTERS;
218: }
219:
220: public boolean hasName() {
221: return eventType == START_ELEMENT || eventType == END_ELEMENT;
222: }
223:
224: public void require(int type, String namespaceURI, String localName)
225: throws XMLStreamException {
226: if (type != eventType
227: || (namespaceURI != null && !namespaceURI
228: .equals(getNamespaceURI()))
229: || (localName != null && !namespaceURI
230: .equals(getLocalName()))) {
231: throw new XMLStreamException("Unexpected event type");
232: }
233: }
234:
235: public Location getLocation() {
236: // We do not support location information
237: return EMPTY_LOCATION;
238: }
239:
240: public void close() throws XMLStreamException {
241: // Javadoc says that this method should not close the underlying input source,
242: // but we need to close the reader somewhere.
243: try {
244: reader.close();
245: } catch (IOException ex) {
246: throw new XMLStreamException(ex);
247: }
248: }
249:
250: //
251: // Methods related to the xml declaration.
252: //
253:
254: public String getEncoding() {
255: // Encoding is not known (not relevant?)
256: return null;
257: }
258:
259: public String getCharacterEncodingScheme() {
260: // Encoding is not known (not relevant?)
261: return null;
262: }
263:
264: public String getVersion() {
265: // Version is not relevant
266: return null;
267: }
268:
269: public boolean standaloneSet() {
270: return false;
271: }
272:
273: public boolean isStandalone() {
274: return true;
275: }
276:
277: //
278: // Methods related to the namespace context
279: //
280:
281: public NamespaceContext getNamespaceContext() {
282: if (namespaceContext == null) {
283: namespaceContext = new NamespaceContextImpl(Collections
284: .singletonMap(wrapperElementName.getPrefix(),
285: wrapperElementName.getNamespaceURI()));
286: }
287: return namespaceContext;
288: }
289:
290: public String getNamespaceURI(String prefix) {
291: String namespaceURI = getNamespaceContext().getNamespaceURI(
292: prefix);
293: // NamespaceContext#getNamespaceURI and XMLStreamReader#getNamespaceURI have slightly
294: // different semantics for unbound prefixes.
295: return namespaceURI.equals(XMLConstants.NULL_NS_URI) ? null
296: : prefix;
297: }
298:
299: //
300: // Methods related to elements
301: //
302:
303: private void checkStartElement() {
304: if (eventType != START_ELEMENT) {
305: throw new IllegalStateException();
306: }
307: }
308:
309: public String getAttributeValue(String namespaceURI,
310: String localName) {
311: checkStartElement();
312: return null;
313: }
314:
315: public int getAttributeCount() {
316: checkStartElement();
317: return 0;
318: }
319:
320: public QName getAttributeName(int index) {
321: checkStartElement();
322: throw new ArrayIndexOutOfBoundsException();
323: }
324:
325: public String getAttributeLocalName(int index) {
326: checkStartElement();
327: throw new ArrayIndexOutOfBoundsException();
328: }
329:
330: public String getAttributePrefix(int index) {
331: checkStartElement();
332: throw new ArrayIndexOutOfBoundsException();
333: }
334:
335: public String getAttributeNamespace(int index) {
336: checkStartElement();
337: throw new ArrayIndexOutOfBoundsException();
338: }
339:
340: public String getAttributeType(int index) {
341: checkStartElement();
342: throw new ArrayIndexOutOfBoundsException();
343: }
344:
345: public String getAttributeValue(int index) {
346: checkStartElement();
347: throw new ArrayIndexOutOfBoundsException();
348: }
349:
350: public boolean isAttributeSpecified(int index) {
351: checkStartElement();
352: throw new ArrayIndexOutOfBoundsException();
353: }
354:
355: private void checkElement() {
356: if (eventType != START_ELEMENT && eventType != END_ELEMENT) {
357: throw new IllegalStateException();
358: }
359: }
360:
361: public QName getName() {
362: return null;
363: }
364:
365: public String getLocalName() {
366: checkElement();
367: return wrapperElementName.getLocalPart();
368: }
369:
370: public String getPrefix() {
371: return wrapperElementName.getPrefix();
372: }
373:
374: public String getNamespaceURI() {
375: checkElement();
376: return wrapperElementName.getNamespaceURI();
377: }
378:
379: public int getNamespaceCount() {
380: checkElement();
381: // There is one namespace declared on the wrapper element
382: return 1;
383: }
384:
385: public String getNamespacePrefix(int index) {
386: checkElement();
387: if (index == 0) {
388: return wrapperElementName.getPrefix();
389: } else {
390: throw new IndexOutOfBoundsException();
391: }
392: }
393:
394: public String getNamespaceURI(int index) {
395: checkElement();
396: if (index == 0) {
397: return wrapperElementName.getNamespaceURI();
398: } else {
399: throw new IndexOutOfBoundsException();
400: }
401: }
402:
403: public String getElementText() throws XMLStreamException {
404: if (eventType == START_ELEMENT) {
405: // Actually the purpose of this class is to avoid storing
406: // the character data entirely in memory, but if the caller
407: // wants a String, we don't have the choice...
408: try {
409: String result = IOUtils.toString(reader);
410: eventType = END_ELEMENT;
411: return result;
412: } catch (IOException ex) {
413: throw new XMLStreamException(ex);
414: }
415: } else {
416: throw new XMLStreamException(
417: "Current event is not a START_ELEMENT");
418: }
419: }
420:
421: private void checkCharacters() {
422: if (eventType != CHARACTERS) {
423: throw new IllegalStateException();
424: }
425: }
426:
427: public String getText() {
428: checkCharacters();
429: return new String(charData, 0, charDataLength);
430: }
431:
432: public char[] getTextCharacters() {
433: checkCharacters();
434: return charData;
435: }
436:
437: public int getTextStart() {
438: checkCharacters();
439: return 0;
440: }
441:
442: public int getTextLength() {
443: checkCharacters();
444: return charDataLength;
445: }
446:
447: public int getTextCharacters(int sourceStart, char[] target,
448: int targetStart, int length) throws XMLStreamException {
449: checkCharacters();
450: int c = Math.min(charDataLength - sourceStart, length);
451: System.arraycopy(charData, sourceStart, target, targetStart, c);
452: return c;
453: }
454:
455: //
456: // Methods related to processing instructions
457: //
458:
459: public String getPIData() {
460: throw new IllegalStateException();
461: }
462:
463: public String getPITarget() {
464: throw new IllegalStateException();
465: }
466: }
|