1 /*
2  * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package com.sun.xml.internal.bind.v2.runtime.output;
27 
28 import java.io.IOException;
29 import java.io.Writer;
30 import java.lang.reflect.Constructor;
31 
32 import javax.xml.stream.XMLStreamException;
33 import javax.xml.stream.XMLStreamWriter;
34 
35 import com.sun.xml.internal.bind.marshaller.CharacterEscapeHandler;
36 import com.sun.xml.internal.bind.marshaller.NoEscapeHandler;
37 import com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl;
38 import com.sun.xml.internal.bind.v2.runtime.XMLSerializer;
39 
40 import org.xml.sax.SAXException;
41 
42 /**
43  * {@link XmlOutput} that writes to StAX {@link XMLStreamWriter}.
44  * <p>
45  * TODO:
46  * Finding the optimized FI implementations is a bit hacky and not very
47  * extensible. Can we use the service provider mechanism in general for
48  * concrete implementations of XmlOutputAbstractImpl.
49  *
50  * @author Kohsuke Kawaguchi
51  */
52 public class XMLStreamWriterOutput extends XmlOutputAbstractImpl {
53 
54     /**
55      * Creates a new {@link XmlOutput} from a {@link XMLStreamWriter}.
56      * This method recognizes an FI StAX writer.
57      */
create(XMLStreamWriter out, JAXBContextImpl context, CharacterEscapeHandler escapeHandler)58     public static XmlOutput create(XMLStreamWriter out, JAXBContextImpl context, CharacterEscapeHandler escapeHandler) {
59         // try optimized path
60         final Class writerClass = out.getClass();
61         if (writerClass==FI_STAX_WRITER_CLASS) {
62             try {
63                 return FI_OUTPUT_CTOR.newInstance(out, context);
64             } catch (Exception e) {
65             }
66         }
67         if (STAXEX_WRITER_CLASS!=null && STAXEX_WRITER_CLASS.isAssignableFrom(writerClass)) {
68             try {
69                 return STAXEX_OUTPUT_CTOR.newInstance(out);
70             } catch (Exception e) {
71             }
72         }
73 
74         CharacterEscapeHandler xmlStreamEscapeHandler = escapeHandler != null ?
75                 escapeHandler : NoEscapeHandler.theInstance;
76 
77         // otherwise the normal writer.
78         return new XMLStreamWriterOutput(out, xmlStreamEscapeHandler);
79     }
80 
81 
82     private final XMLStreamWriter out;
83 
84     private final CharacterEscapeHandler escapeHandler;
85 
86     private final XmlStreamOutWriterAdapter writerWrapper;
87 
88     protected final char[] buf = new char[256];
89 
XMLStreamWriterOutput(XMLStreamWriter out, CharacterEscapeHandler escapeHandler)90     protected XMLStreamWriterOutput(XMLStreamWriter out, CharacterEscapeHandler escapeHandler) {
91         this.out = out;
92         this.escapeHandler = escapeHandler;
93         this.writerWrapper = new XmlStreamOutWriterAdapter(out);
94     }
95 
96     // not called if we are generating fragments
97     @Override
startDocument(XMLSerializer serializer, boolean fragment, int[] nsUriIndex2prefixIndex, NamespaceContextImpl nsContext)98     public void startDocument(XMLSerializer serializer, boolean fragment, int[] nsUriIndex2prefixIndex, NamespaceContextImpl nsContext) throws IOException, SAXException, XMLStreamException {
99         super.startDocument(serializer, fragment,nsUriIndex2prefixIndex,nsContext);
100         if(!fragment)
101             out.writeStartDocument();
102     }
103 
104     @Override
endDocument(boolean fragment)105     public void endDocument(boolean fragment) throws IOException, SAXException, XMLStreamException {
106         if(!fragment) {
107             out.writeEndDocument();
108             out.flush();
109         }
110         super.endDocument(fragment);
111     }
112 
beginStartTag(int prefix, String localName)113     public void beginStartTag(int prefix, String localName) throws IOException, XMLStreamException {
114         out.writeStartElement(
115             nsContext.getPrefix(prefix),
116             localName,
117             nsContext.getNamespaceURI(prefix));
118 
119         NamespaceContextImpl.Element nse = nsContext.getCurrent();
120         if(nse.count()>0) {
121             for( int i=nse.count()-1; i>=0; i-- ) {
122                 String uri = nse.getNsUri(i);
123                 if(uri.length()==0 && nse.getBase()==1)
124                     continue;   // no point in definint xmlns='' on the root
125                 out.writeNamespace(nse.getPrefix(i),uri);
126             }
127         }
128     }
129 
attribute(int prefix, String localName, String value)130     public void attribute(int prefix, String localName, String value) throws IOException, XMLStreamException {
131         if(prefix==-1)
132             out.writeAttribute(localName,value);
133         else
134             out.writeAttribute(
135                     nsContext.getPrefix(prefix),
136                     nsContext.getNamespaceURI(prefix),
137                     localName, value);
138     }
139 
endStartTag()140     public void endStartTag() throws IOException, SAXException {
141         // noop
142     }
143 
endTag(int prefix, String localName)144     public void endTag(int prefix, String localName) throws IOException, SAXException, XMLStreamException {
145         out.writeEndElement();
146     }
147 
text(String value, boolean needsSeparatingWhitespace)148     public void text(String value, boolean needsSeparatingWhitespace) throws IOException, SAXException, XMLStreamException {
149         if(needsSeparatingWhitespace)
150             out.writeCharacters(" ");
151         escapeHandler.escape(value.toCharArray(), 0, value.length(), false, writerWrapper);
152     }
153 
text(Pcdata value, boolean needsSeparatingWhitespace)154     public void text(Pcdata value, boolean needsSeparatingWhitespace) throws IOException, SAXException, XMLStreamException {
155         if(needsSeparatingWhitespace)
156             out.writeCharacters(" ");
157 
158         int len = value.length();
159         if(len <buf.length) {
160             value.writeTo(buf,0);
161             out.writeCharacters(buf,0,len);
162         } else {
163             out.writeCharacters(value.toString());
164         }
165     }
166 
167     /**
168      * Reference to FI's XMLStreamWriter class, if FI can be loaded.
169      */
170     private static final Class FI_STAX_WRITER_CLASS = initFIStAXWriterClass();
171     private static final Constructor<? extends XmlOutput> FI_OUTPUT_CTOR = initFastInfosetOutputClass();
172 
initFIStAXWriterClass()173     private static Class initFIStAXWriterClass() {
174         try {
175             Class<?> llfisw = Class.forName("com.sun.xml.internal.org.jvnet.fastinfoset.stax.LowLevelFastInfosetStreamWriter");
176             Class<?> sds = Class.forName("com.sun.xml.internal.fastinfoset.stax.StAXDocumentSerializer");
177             // Check if StAXDocumentSerializer implements LowLevelFastInfosetStreamWriter
178             if (llfisw.isAssignableFrom(sds))
179                 return sds;
180             else
181                 return null;
182         } catch (Throwable e) {
183             return null;
184         }
185     }
186 
initFastInfosetOutputClass()187     private static Constructor<? extends XmlOutput> initFastInfosetOutputClass() {
188         try {
189             if (FI_STAX_WRITER_CLASS == null)
190                 return null;
191             Class c = Class.forName("com.sun.xml.internal.bind.v2.runtime.output.FastInfosetStreamWriterOutput");
192             return c.getConstructor(FI_STAX_WRITER_CLASS, JAXBContextImpl.class);
193         } catch (Throwable e) {
194             return null;
195         }
196     }
197 
198     //
199     // StAX-ex
200     //
201     private static final Class STAXEX_WRITER_CLASS = initStAXExWriterClass();
202     private static final Constructor<? extends XmlOutput> STAXEX_OUTPUT_CTOR = initStAXExOutputClass();
203 
initStAXExWriterClass()204     private static Class initStAXExWriterClass() {
205         try {
206             return Class.forName("com.sun.xml.internal.org.jvnet.staxex.XMLStreamWriterEx");
207         } catch (Throwable e) {
208             return null;
209         }
210     }
211 
initStAXExOutputClass()212     private static Constructor<? extends XmlOutput> initStAXExOutputClass() {
213         try {
214             Class c = Class.forName("com.sun.xml.internal.bind.v2.runtime.output.StAXExStreamWriterOutput");
215             return c.getConstructor(STAXEX_WRITER_CLASS);
216         } catch (Throwable e) {
217             return null;
218         }
219     }
220 
221     private static final class XmlStreamOutWriterAdapter extends Writer {
222 
223         private final XMLStreamWriter writer;
224 
XmlStreamOutWriterAdapter(XMLStreamWriter writer)225         private XmlStreamOutWriterAdapter(XMLStreamWriter writer) {
226             this.writer = writer;
227         }
228 
229         @Override
write(char[] cbuf, int off, int len)230         public void write(char[] cbuf, int off, int len) throws IOException {
231             try {
232                 writer.writeCharacters(cbuf, off, len);
233             } catch (XMLStreamException e) {
234                 throw new IOException("Error writing XML stream", e);
235             }
236         }
237 
writeEntityRef(String entityReference)238         public void writeEntityRef(String entityReference) throws XMLStreamException {
239             writer.writeEntityRef(entityReference);
240         }
241 
242         @Override
flush()243         public void flush() throws IOException {
244             try {
245                 writer.flush();
246             } catch (XMLStreamException e) {
247                 throw new IOException("Error flushing XML stream", e);
248             }
249         }
250 
251         @Override
close()252         public void close() throws IOException {
253             try {
254                 writer.close();
255             } catch (XMLStreamException e) {
256                 throw new IOException("Error closing XML stream", e);
257             }
258         }
259     }
260 }
261