001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.servicemix.soap.util.stax;
018
019 import java.io.InputStream;
020 import java.io.OutputStream;
021
022 import javax.xml.XMLConstants;
023 import javax.xml.namespace.QName;
024 import javax.xml.stream.XMLInputFactory;
025 import javax.xml.stream.XMLOutputFactory;
026 import javax.xml.stream.XMLStreamConstants;
027 import javax.xml.stream.XMLStreamException;
028 import javax.xml.stream.XMLStreamReader;
029 import javax.xml.stream.XMLStreamWriter;
030 import javax.xml.transform.Source;
031 import javax.xml.transform.dom.DOMSource;
032
033 import org.apache.servicemix.soap.api.Fault;
034 import org.apache.servicemix.soap.util.DomUtil;
035 import org.w3c.dom.Element;
036 import org.w3c.dom.Node;
037
038 /**
039 * Stax related utilities.
040 *
041 * Some of this code has been borrowed from the XFire project
042 *
043 * @author <a href="mailto:gnodet [at] gmail.com">Guillaume Nodet</a>
044 * @author Dan Diephouse
045 */
046 public class StaxUtil {
047
048 private static XMLOutputFactory xof;
049 private static XMLInputFactory xif;
050
051 public static XMLStreamReader createReader(InputStream is) {
052 return createReader(is, null);
053 }
054
055 public static XMLStreamReader createReader(InputStream is, String encoding) {
056 try {
057 return getXMLInputFactory().createXMLStreamReader(is, encoding);
058 } catch (XMLStreamException e) {
059 throw new Fault(e);
060 }
061 }
062
063 public static XMLStreamReader createReader(Source source) {
064 if (source instanceof StaxSource) {
065 return ((StaxSource) source).getStreamReader();
066 }
067 if (source instanceof DOMSource) {
068 Node node = ((DOMSource) source).getNode();
069 if (node instanceof Element) {
070 return new DOMStreamReader((Element) node);
071 } else {
072 return new DOMStreamReader(DomUtil.getFirstChildElement(node));
073 }
074 } else {
075 try {
076 return getXMLInputFactory().createXMLStreamReader(source);
077 } catch (XMLStreamException e) {
078 throw new Fault(e);
079 }
080 }
081 }
082
083 public static Source createSource(XMLStreamReader reader) {
084 if (reader instanceof DOMStreamReader) {
085 return new DOMSource(((DOMStreamReader) reader).skipElement());
086 } else {
087 return new StaxSource(new FragmentStreamReader(reader));
088 }
089 }
090
091 public static Element createElement(XMLStreamReader reader) {
092 if (reader instanceof DOMStreamReader) {
093 return ((DOMStreamReader) reader).skipElement();
094 } else {
095 Source src = new StaxSource(new FragmentStreamReader(reader));
096 return DomUtil.parse(src).getDocumentElement();
097 }
098 }
099
100 public static XMLInputFactory getXMLInputFactory() {
101 if (xif == null) {
102 xif = XMLInputFactory.newInstance();
103 }
104 return xif;
105 }
106
107 public static XMLStreamWriter createWriter(OutputStream os) {
108 return createWriter(os, null);
109 }
110
111 public static XMLStreamWriter createWriter(OutputStream os, String encoding) {
112 try {
113 return getXMLOutputFactory().createXMLStreamWriter(os, encoding);
114 } catch (XMLStreamException e) {
115 throw new Fault(e);
116 }
117 }
118
119 public static XMLOutputFactory getXMLOutputFactory() {
120 if (xof == null) {
121 xof = XMLOutputFactory.newInstance();
122 }
123 return xof;
124 }
125
126 /**
127 * Reads a QName from the element text. Reader must be positioned at the
128 * start tag.
129 */
130 public static QName readQName(XMLStreamReader reader) throws XMLStreamException {
131 String value = reader.getElementText();
132 if (value == null) {
133 return null;
134 }
135
136 int index = value.indexOf(":");
137
138 if (index == -1) {
139 return new QName(value);
140 }
141
142 String prefix = value.substring(0, index);
143 String localName = value.substring(index + 1);
144 String ns = reader.getNamespaceURI(prefix);
145
146 if (ns == null || localName == null) {
147 throw new RuntimeException("Invalid QName in mapping: " + value);
148 }
149
150 return new QName(ns, localName, prefix);
151 }
152
153 /**
154 * Copies the reader to the writer. The start and end document
155 * methods must be handled on the writer manually.
156 *
157 * TODO: if the namespace on the reader has been declared previously
158 * to where we are in the stream, this probably won't work.
159 *
160 * @param reader
161 * @param writer
162 * @throws XMLStreamException
163 */
164 public static void copy( XMLStreamReader reader, XMLStreamWriter writer )
165 throws XMLStreamException
166 {
167 int read = 0; // number of elements read in
168 int event = reader.getEventType();
169
170 while ( reader.hasNext() )
171 {
172 switch( event )
173 {
174 case XMLStreamConstants.START_ELEMENT:
175 read++;
176 writeStartElement( reader, writer );
177 break;
178 case XMLStreamConstants.END_ELEMENT:
179 writer.writeEndElement();
180 read--;
181 if ( read <= 0 )
182 return;
183 break;
184 case XMLStreamConstants.CHARACTERS:
185 writer.writeCharacters( reader.getText() );
186 break;
187 case XMLStreamConstants.START_DOCUMENT:
188 case XMLStreamConstants.END_DOCUMENT:
189 case XMLStreamConstants.ATTRIBUTE:
190 case XMLStreamConstants.NAMESPACE:
191 break;
192 default:
193 break;
194 }
195 event = reader.next();
196 }
197 }
198
199 private static void writeStartElement(XMLStreamReader reader, XMLStreamWriter writer)
200 throws XMLStreamException
201 {
202 String local = reader.getLocalName();
203 String uri = reader.getNamespaceURI();
204 String prefix = reader.getPrefix();
205 if (prefix == null)
206 {
207 prefix = "";
208 }
209 if (uri == null)
210 {
211 uri = "";
212 }
213
214 String boundPrefix = writer.getPrefix(uri);
215 boolean writeElementNS = false;
216 if ( boundPrefix == null || !prefix.equals(boundPrefix) )
217 {
218 writeElementNS = true;
219 }
220
221 // Write out the element name
222 if (uri != null)
223 {
224 if (prefix.length() == 0)
225 {
226
227 writer.writeStartElement(local);
228 writer.setDefaultNamespace(uri);
229
230 }
231 else
232 {
233 writer.writeStartElement(prefix, local, uri);
234 writer.setPrefix(prefix, uri);
235 }
236 }
237 else
238 {
239 writer.writeStartElement( local );
240 }
241
242 // Write out the namespaces
243 for ( int i = 0; i < reader.getNamespaceCount(); i++ )
244 {
245 String nsURI = reader.getNamespaceURI(i);
246 String nsPrefix = reader.getNamespacePrefix(i);
247 if (nsPrefix == null) nsPrefix = "";
248
249 if ( nsPrefix.length() == 0 )
250 {
251 writer.writeDefaultNamespace(nsURI);
252 }
253 else
254 {
255 writer.writeNamespace(nsPrefix, nsURI);
256 }
257
258 if (nsURI.equals(uri) && nsPrefix.equals(prefix))
259 {
260 writeElementNS = false;
261 }
262 }
263
264 // Check if the namespace still needs to be written.
265 // We need this check because namespace writing works
266 // different on Woodstox and the RI.
267 if (writeElementNS)
268 {
269 if ( prefix == null || prefix.length() == 0 )
270 {
271 writer.writeDefaultNamespace(uri);
272 }
273 else
274 {
275 writer.writeNamespace(prefix, uri);
276 }
277 }
278
279 // Write out attributes
280 for ( int i = 0; i < reader.getAttributeCount(); i++ )
281 {
282 String ns = reader.getAttributeNamespace(i);
283 String nsPrefix = reader.getAttributePrefix(i);
284 if ( ns == null || ns.length() == 0 )
285 {
286 writer.writeAttribute(
287 reader.getAttributeLocalName(i),
288 reader.getAttributeValue(i));
289 }
290 else if (nsPrefix == null || nsPrefix.length() == 0)
291 {
292 writer.writeAttribute(
293 reader.getAttributeNamespace(i),
294 reader.getAttributeLocalName(i),
295 reader.getAttributeValue(i));
296 }
297 else
298 {
299 writer.writeAttribute(reader.getAttributePrefix(i),
300 reader.getAttributeNamespace(i),
301 reader.getAttributeLocalName(i),
302 reader.getAttributeValue(i));
303 }
304
305
306 }
307 }
308
309 /**
310 * Write a start element with the specified parameters
311 * @param writer
312 * @param uri
313 * @param local
314 * @param prefix
315 * @throws XMLStreamException
316 */
317 public static void writeStartElement( XMLStreamWriter writer, String uri, String local, String prefix )
318 throws XMLStreamException
319 {
320 if (prefix == null)
321 {
322 prefix = "";
323 }
324 if (uri == null)
325 {
326 uri = "";
327 }
328
329 String boundPrefix = writer.getPrefix(uri);
330 boolean writeElementNS = false;
331 if ( boundPrefix == null || !prefix.equals(boundPrefix) )
332 {
333 writeElementNS = true;
334 }
335
336 // Write out the element name
337 if (uri != null)
338 {
339 if (prefix.length() == 0)
340 {
341
342 writer.writeStartElement(local);
343 writer.setDefaultNamespace(uri);
344
345 }
346 else
347 {
348 writer.writeStartElement(prefix, local, uri);
349 writer.setPrefix(prefix, uri);
350 }
351 }
352 else
353 {
354 writer.writeStartElement( local );
355 }
356
357 // Check if the namespace still needs to be written.
358 // We need this check because namespace writing works
359 // different on Woodstox and the RI.
360 if (writeElementNS)
361 {
362 if ( prefix.length() == 0 )
363 {
364 writer.writeDefaultNamespace(uri);
365 }
366 else
367 {
368 writer.writeNamespace(prefix, uri);
369 }
370 }
371 }
372
373 /**
374 * Write a start element with the given QName.
375 * However, if a namespace has already been bound to a prefix,
376 * use the existing one, else default to the prefix
377 * in the QName (if specified). Else, a prefix is generated.
378 *
379 * @param writer
380 * @param name
381 * @throws XMLStreamException
382 */
383 public static void writeStartElement(XMLStreamWriter writer, QName name) throws XMLStreamException {
384 String prefix = choosePrefix(writer, name, false);
385 writeStartElement(writer, name.getNamespaceURI(), name.getLocalPart(), prefix);
386 }
387
388 /**
389 *
390 * @param out
391 * @param name
392 * @throws XMLStreamException
393 */
394 public static void writeTextQName(XMLStreamWriter out, QName name) throws XMLStreamException {
395 String prefix = choosePrefix(out, name, true);
396 if (XMLConstants.DEFAULT_NS_PREFIX.equals(prefix)) {
397 out.writeCharacters(name.getLocalPart());
398 } else {
399 out.writeCharacters(prefix + ":" + name.getLocalPart());
400 }
401 }
402
403 protected static String choosePrefix(XMLStreamWriter out, QName name, boolean declare) throws XMLStreamException {
404 String uri = name.getNamespaceURI();
405 // If no namespace
406 if (uri == null || XMLConstants.NULL_NS_URI.equals(uri)) {
407 if (!XMLConstants.NULL_NS_URI.equals(out.getNamespaceContext().getNamespaceURI(XMLConstants.DEFAULT_NS_PREFIX))) {
408 out.setPrefix(XMLConstants.DEFAULT_NS_PREFIX, XMLConstants.NULL_NS_URI);
409 }
410 return XMLConstants.DEFAULT_NS_PREFIX;
411 // Need to write a prefix
412 } else {
413 String defPrefix = name.getPrefix();
414 // A prefix is specified
415 if (defPrefix != null && !XMLConstants.DEFAULT_NS_PREFIX.equals(defPrefix)) {
416 // if the uri is bound to the specified prefix, good, else
417 if (!uri.equals(out.getNamespaceContext().getNamespaceURI(defPrefix))) {
418 // if there is a prefix bound to the uri, use it
419 if (out.getNamespaceContext().getPrefix(uri) != null) {
420 defPrefix = out.getNamespaceContext().getPrefix(uri);
421 // get prefix from the writer
422 } else if (out.getPrefix(uri) != null) {
423 defPrefix = out.getPrefix(uri);
424 // we need to bind the prefix
425 } else if (declare) {
426 out.setPrefix(defPrefix, uri);
427 out.writeNamespace(defPrefix, uri);
428 }
429 }
430 // No prefix specified
431 } else {
432 // if there is a prefix bound to the uri, use it
433 if (out.getNamespaceContext().getPrefix(uri) != null) {
434 defPrefix = out.getNamespaceContext().getPrefix(uri);
435 // get prefix from the writer
436 } else if (out.getPrefix(uri) != null) {
437 defPrefix = out.getPrefix(uri);
438 // we need to generate a prefix
439 } else {
440 defPrefix = getUniquePrefix(out);
441 if (declare) {
442 out.setPrefix(defPrefix, uri);
443 out.writeNamespace(defPrefix, uri);
444 }
445 }
446 }
447 return defPrefix;
448 }
449 }
450
451 protected static String getUniquePrefix(XMLStreamWriter writer) {
452 int n = 1;
453 while (true) {
454 String nsPrefix = "ns" + n;
455 if (writer.getNamespaceContext().getNamespaceURI(nsPrefix) == null) {
456 return nsPrefix;
457 }
458 n++;
459 }
460 }
461
462 }