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    }