1 /* Woodstox XML processor
2  *
3  * Copyright (c) 2004- Tatu Saloranta, tatu.saloranta@iki.fi
4  *
5  * Licensed under the License specified in the file LICENSE which is
6  * included with the source code.
7  * You may not use this file except in compliance with the License.
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */

15
16 package com.ctc.wstx.stax;
17
18 import java.io.IOException;
19 import java.io.OutputStream;
20 import java.io.OutputStreamWriter;
21 import java.io.Writer;
22
23 import javax.xml.stream.XMLEventWriter;
24 import javax.xml.stream.XMLOutputFactory;
25 import javax.xml.stream.XMLStreamException;
26 import javax.xml.stream.XMLStreamWriter;
27 import javax.xml.transform.Result;
28 import javax.xml.transform.dom.DOMResult;
29 import javax.xml.transform.sax.SAXResult;
30 import javax.xml.transform.stream.StreamResult;
31
32 import org.codehaus.stax2.XMLOutputFactory2;
33 import org.codehaus.stax2.XMLStreamWriter2;
34 import org.codehaus.stax2.io.Stax2Result;
35 import org.codehaus.stax2.ri.Stax2EventWriterImpl;
36 import org.codehaus.stax2.ri.Stax2WriterAdapter;
37
38 import com.ctc.wstx.api.WriterConfig;
39 import com.ctc.wstx.api.WstxOutputProperties;
40 import com.ctc.wstx.cfg.OutputConfigFlags;
41 import com.ctc.wstx.dom.WstxDOMWrappingWriter;
42 import com.ctc.wstx.exc.WstxIOException;
43 import com.ctc.wstx.io.CharsetNames;
44 import com.ctc.wstx.io.UTF8Writer;
45 import com.ctc.wstx.sw.AsciiXmlWriter;
46 import com.ctc.wstx.sw.BufferingXmlWriter;
47 import com.ctc.wstx.sw.ISOLatin1XmlWriter;
48 import com.ctc.wstx.sw.NonNsStreamWriter;
49 import com.ctc.wstx.sw.RepairingNsStreamWriter;
50 import com.ctc.wstx.sw.SimpleNsStreamWriter;
51 import com.ctc.wstx.sw.XmlWriter;
52 import com.ctc.wstx.util.URLUtil;
53
54 /**
55  * Implementation of {@link XMLOutputFactory} for Wstx.
56  *<p>
57  * TODO:
58  *<ul>
59  * <li>Implement outputter that creates SAX events (DOM-backed
60  *   writer exists as of Woodstox 3.2)
61  *  </li>
62  *</ul>
63  */

64 public class WstxOutputFactory
65     extends XMLOutputFactory2
66     implements OutputConfigFlags
67 {
68     /*
69     ///////////////////////////////////////////////////////////
70     // Actual storage of configuration settings
71     ///////////////////////////////////////////////////////////
72      */

73
74     protected final WriterConfig mConfig;
75
76     /*
77     ///////////////////////////////////////////////////////////
78     // Life-cycle
79     ///////////////////////////////////////////////////////////
80      */

81
82     public WstxOutputFactory() {
83         mConfig = WriterConfig.createFullDefaults();
84     }
85
86     /*
87     ///////////////////////////////////////////////////////////
88     // XMLOutputFactory API
89     ///////////////////////////////////////////////////////////
90      */

91
92     @Override
93     public XMLEventWriter createXMLEventWriter(OutputStream out)
94         throws XMLStreamException
95     {
96         return createXMLEventWriter(out, null);
97     }
98
99     @Override
100     public XMLEventWriter createXMLEventWriter(OutputStream out, String enc)
101          throws XMLStreamException
102     {
103        if (out == null) {
104            throw new IllegalArgumentException("Null OutputStream is not a valid argument");
105        }
106        return new Stax2EventWriterImpl(createSW(out, null, enc, false));
107     }
108
109     @Override
110     public XMLEventWriter createXMLEventWriter(javax.xml.transform.Result result)
111          throws XMLStreamException
112     {
113         return new Stax2EventWriterImpl(createSW(result));
114     }
115
116     @Override
117     public XMLEventWriter createXMLEventWriter(Writer w)
118         throws XMLStreamException
119     {
120         if (w == null) {
121             throw new IllegalArgumentException("Null Writer is not a valid argument");
122         }
123         return new Stax2EventWriterImpl(createSW(null, w, nullfalse));
124     }
125
126     @Override
127     public XMLStreamWriter createXMLStreamWriter(OutputStream out)
128         throws XMLStreamException
129     {
130         return createXMLStreamWriter(out, null);
131     }
132
133     @Override
134     public XMLStreamWriter createXMLStreamWriter(OutputStream out, String enc)
135         throws XMLStreamException
136     {
137         if (out == null) {
138             throw new IllegalArgumentException("Null OutputStream is not a valid argument");
139         }
140         return createSW(out, null, enc, false);
141     }
142
143     @Override
144     public XMLStreamWriter createXMLStreamWriter(javax.xml.transform.Result result)
145         throws XMLStreamException
146     {
147         return createSW(result);
148     }
149
150     @Override
151     public XMLStreamWriter createXMLStreamWriter(Writer w)
152         throws XMLStreamException
153     {
154         if (w == null) {
155             throw new IllegalArgumentException("Null Writer is not a valid argument");
156         }
157         return createSW(null, w, nullfalse);
158     }
159     
160     @Override
161     public Object getProperty(String name) {
162         return mConfig.getProperty(name);
163     }
164     
165     @Override
166     public boolean isPropertySupported(String name) {
167         return mConfig.isPropertySupported(name);
168     }
169     
170     @Override
171     public void setProperty(String name, Object value)
172     {
173         mConfig.setProperty(name, value);
174     }
175
176     /*
177     ///////////////////////////////////////////////////////////
178     // Stax2 extensions
179     ///////////////////////////////////////////////////////////
180      */

181
182     // // // Stax2 additional (encoding-aware) factory methods
183
184     @Override
185     public XMLEventWriter createXMLEventWriter(Writer w, String enc)
186         throws XMLStreamException
187     {
188         return new Stax2EventWriterImpl(createSW(null, w, enc, false));
189     }
190
191     @Override
192     public XMLEventWriter createXMLEventWriter(XMLStreamWriter sw)
193         throws XMLStreamException
194     {
195         XMLStreamWriter2 sw2 = Stax2WriterAdapter.wrapIfNecessary(sw);
196         return new Stax2EventWriterImpl(sw2);
197     }
198
199     @Override
200     public XMLStreamWriter2 createXMLStreamWriter(Writer w, String enc)
201         throws XMLStreamException
202     {
203         return createSW(null, w, enc, false);
204     }
205
206     // // // Stax2 "Profile" mutators
207
208     @Override
209     public void configureForXmlConformance() {
210         mConfig.configureForXmlConformance();
211     }
212
213     @Override
214     public void configureForRobustness() {
215         mConfig.configureForRobustness();
216     }
217
218     @Override
219     public void configureForSpeed() {
220         mConfig.configureForSpeed();
221     }
222
223     /*
224     ///////////////////////////////////////////////////////////
225     // Woodstox-specific configuration access
226     ///////////////////////////////////////////////////////////
227      */

228
229     public WriterConfig getConfig() {
230         return mConfig;
231     }
232
233     /*
234     ///////////////////////////////////////////////////////////
235     // Internal methods:
236     ///////////////////////////////////////////////////////////
237      */

238
239     /**
240      * Bottleneck factory method used internally; needs to take care of passing
241      * proper settings to stream writer.
242      *
243      * @param requireAutoClose Whether this result will always require
244      *   auto-close be enabled (true); or only if application has
245      *   requested it (false)
246      */

247     @SuppressWarnings("resource")
248     private XMLStreamWriter2 createSW(OutputStream out, Writer w, String enc,
249                                       boolean requireAutoClose)
250         throws XMLStreamException
251     {
252         /* Need to ensure that the configuration object is not shared
253          * any more; otherwise later changes via factory could be
254          * visible half-way through output...
255          */

256         WriterConfig cfg = mConfig.createNonShared();
257         XmlWriter xw;
258
259         boolean autoCloseOutput = requireAutoClose || mConfig.willAutoCloseOutput();
260
261         if (w == null) {
262             if (enc == null) {
263                 enc = WstxOutputProperties.DEFAULT_OUTPUT_ENCODING;
264             } else {
265                 /* Canonical ones are interned, so we may have
266                  * normalized encoding already...
267                  */

268                 if (enc != CharsetNames.CS_UTF8
269                     && enc != CharsetNames.CS_ISO_LATIN1
270                     && enc != CharsetNames.CS_US_ASCII) {
271                     enc = CharsetNames.normalize(enc);
272                 }
273             }
274
275             try {
276                 if (enc == CharsetNames.CS_UTF8) {
277                     w = new UTF8Writer(cfg, out, autoCloseOutput);
278                     xw = new BufferingXmlWriter(w, cfg, enc, autoCloseOutput, out, 16);
279                 } else if (enc == CharsetNames.CS_ISO_LATIN1) {
280                     xw = new ISOLatin1XmlWriter(out, cfg, autoCloseOutput);
281                 } else if (enc == CharsetNames.CS_US_ASCII) {
282                     xw = new AsciiXmlWriter(out, cfg, autoCloseOutput);
283                 } else {
284                     w = new OutputStreamWriter(out, enc);
285                     xw = new BufferingXmlWriter(w, cfg, enc, autoCloseOutput, out, -1);
286                 }
287             } catch (IOException ex) {
288                 throw new XMLStreamException(ex);
289             }
290         } else {
291             // we may still be able to figure out the encoding:
292             if (enc == null) {
293                 enc = CharsetNames.findEncodingFor(w);
294             }
295             try {
296                 xw = new BufferingXmlWriter(w, cfg, enc, autoCloseOutput, null, -1);
297             } catch (IOException ex) {
298                 throw new XMLStreamException(ex);
299             }
300         }
301
302         return createSW(enc, cfg, xw);
303     }
304
305     /**
306      * Called by {@link #createSW(OutputStream, Writer, String, boolean)} after all of the nessesary configuration
307      * logic is complete.
308      */

309     protected XMLStreamWriter2 createSW(String enc, WriterConfig cfg, XmlWriter xw) {
310         if (cfg.willSupportNamespaces()) {
311             if (cfg.automaticNamespacesEnabled()) {
312                 return new RepairingNsStreamWriter(xw, enc, cfg);
313             }
314             return new SimpleNsStreamWriter(xw, enc, cfg);
315         }
316         return new NonNsStreamWriter(xw, enc, cfg);
317     }
318
319     @SuppressWarnings("resource")
320     private XMLStreamWriter2 createSW(Result res)
321         throws XMLStreamException
322     {
323         OutputStream out = null;
324         Writer w = null;
325         String encoding = null;
326         boolean requireAutoClose;
327         String sysId = null;
328
329         if (res instanceof Stax2Result) {
330             Stax2Result sr = (Stax2Result) res;
331             try {
332                 out = sr.constructOutputStream();
333                 if (out == null) {
334                     w = sr.constructWriter();
335                 }
336             } catch (IOException ioe) {
337                 throw new WstxIOException(ioe);
338             }
339             // yes, it's required since caller has no access to stream/writer:
340             requireAutoClose = true;
341         } else if (res instanceof StreamResult) {
342             StreamResult sr = (StreamResult) res;
343             out = sr.getOutputStream();
344             sysId = sr.getSystemId();
345             if (out == null) {
346                 w = sr.getWriter();
347             }
348             /* Caller owns it, only auto-close if requested to do so:
349              * (except that for system-id-only, it'll still be required,
350              * see code below)
351              */

352             requireAutoClose = false;
353         } else if (res instanceof SAXResult) {
354             SAXResult sr = (SAXResult) res;
355             sysId = sr.getSystemId();
356             if (sysId == null || sysId.length() == 0) {
357                 throw new XMLStreamException("Can not create a stream writer for a SAXResult that does not have System Id (support for using SAX input source not implemented)");
358             }
359             requireAutoClose = true;
360         } else if (res instanceof DOMResult) {
361             return WstxDOMWrappingWriter.createFrom(mConfig.createNonShared(), (DOMResult) res);
362         } else {
363             throw new IllegalArgumentException("Can not instantiate a writer for XML result type "+res.getClass()+" (unrecognized type)");
364         }
365
366         if (out != null) {
367             return createSW(out, null, encoding, requireAutoClose);
368         }
369         if (w != null) {
370             return createSW(null, w, encoding, requireAutoClose);
371         }
372         if (sysId != null && sysId.length() > 0) {
373             /* 26-Dec-2008, TSa: If we must construct URL from system id,
374              *   it means caller will not have access to resulting
375              *   stream, thus we will force auto-closing.
376              */

377             requireAutoClose = true;
378             try {
379                 out = URLUtil.outputStreamFromURL(URLUtil.urlFromSystemId(sysId));
380             } catch (IOException ioe) {
381                 throw new WstxIOException(ioe);
382             }
383             return createSW(out, null, encoding, requireAutoClose);
384         }
385         throw new XMLStreamException("Can not create Stax writer for passed-in Result -- neither writer, output stream or system id was accessible");
386     }
387 }
388