1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20 package org.apache.axiom.om.impl;
21
22 import java.io.ByteArrayOutputStream;
23 import java.io.IOException;
24 import java.io.OutputStream;
25 import java.io.UnsupportedEncodingException;
26 import java.util.LinkedList;
27
28 import javax.activation.DataHandler;
29 import javax.xml.namespace.NamespaceContext;
30 import javax.xml.stream.FactoryConfigurationError;
31 import javax.xml.stream.XMLStreamException;
32 import javax.xml.stream.XMLStreamWriter;
33
34 import org.apache.axiom.attachments.impl.BufferUtils;
35 import org.apache.axiom.om.OMException;
36 import org.apache.axiom.om.OMNode;
37 import org.apache.axiom.om.OMOutputFormat;
38 import org.apache.axiom.om.OMText;
39 import org.apache.axiom.om.util.CommonUtils;
40 import org.apache.axiom.om.util.StAXUtils;
41 import org.apache.axiom.soap.SOAP11Constants;
42 import org.apache.axiom.soap.SOAP12Constants;
43 import org.apache.commons.logging.Log;
44 import org.apache.commons.logging.LogFactory;
45
46
47 /**
48 * MTOMXMLStreamWriter is an XML + Attachments stream writer.
49 *
50 * For the moment this assumes that transport takes the decision of whether to optimize or not by
51 * looking at whether the MTOM optimize is enabled & also looking at the OM tree whether it has any
52 * optimizable content.
53 */
54 public class MTOMXMLStreamWriter implements XMLStreamWriter {
55 private static Log log = LogFactory.getLog(MTOMXMLStreamWriter.class);
56 private static boolean isDebugEnabled = log.isDebugEnabled();
57 private static boolean isTraceEnabled = log.isTraceEnabled();
58 private final static int UNSUPPORTED = -1;
59 private final static int EXCEED_LIMIT = 1;
60 private XMLStreamWriter xmlWriter;
61 private OutputStream outStream;
62 private LinkedList binaryNodeList = new LinkedList();
63 private ByteArrayOutputStream bufferedXML; // XML for the SOAPPart
64 private OMOutputFormat format = new OMOutputFormat();
65
66 // State variables
67 private boolean isEndDocument = false; // has endElement been called
68 private boolean isComplete = false; // have the attachments been written
69 private int depth = 0; // current eleement depth
70
71 public MTOMXMLStreamWriter(XMLStreamWriter xmlWriter) {
72 this.xmlWriter = xmlWriter;
73 if (isTraceEnabled) {
74 log.trace("Call Stack =" + CommonUtils.callStackToString());
75 }
76 }
77
78 /**
79 * Creates a new MTOMXMLStreamWriter with specified encoding.
80 *
81 * @param outStream
82 * @param format
83 * @throws XMLStreamException
84 * @throws FactoryConfigurationError
85 * @see OMOutputFormat#DEFAULT_CHAR_SET_ENCODING
86 */
87 public MTOMXMLStreamWriter(OutputStream outStream, OMOutputFormat format)
88 throws XMLStreamException, FactoryConfigurationError {
89 if (isDebugEnabled) {
90 log.debug("OutputStream =" + outStream.getClass());
91 log.debug("OMFormat = " + format.toString());
92 }
93 if (isTraceEnabled) {
94 log.trace("Call Stack =" + CommonUtils.callStackToString());
95 }
96 this.format = format;
97 this.outStream = outStream;
98
99 if (format.getCharSetEncoding() == null) //Default encoding is UTF-8
100 format.setCharSetEncoding(OMOutputFormat.DEFAULT_CHAR_SET_ENCODING);
101
102 if (format.isOptimized()) {
103 // REVIEW If the buffered XML gets too big, should it be written out to a file
104 bufferedXML = new ByteArrayOutputStream();
105 xmlWriter = StAXUtils.createXMLStreamWriter(bufferedXML,format.getCharSetEncoding());
106 } else {
107 xmlWriter = StAXUtils.createXMLStreamWriter(outStream,
108 format.getCharSetEncoding());
109 }
110 }
111
112 public void writeStartElement(String string) throws XMLStreamException {
113 xmlWriter.writeStartElement(string);
114 depth++;
115 }
116
117 public void writeStartElement(String string, String string1) throws XMLStreamException {
118 xmlWriter.writeStartElement(string, string1);
119 depth++;
120 }
121
122 public void writeStartElement(String string, String string1, String string2)
123 throws XMLStreamException {
124 xmlWriter.writeStartElement(string, string1, string2);
125 depth++;
126 }
127
128 public void writeEmptyElement(String string, String string1) throws XMLStreamException {
129 xmlWriter.writeEmptyElement(string, string1);
130 }
131
132 public void writeEmptyElement(String string, String string1, String string2)
133 throws XMLStreamException {
134 xmlWriter.writeEmptyElement(string, string1, string2);
135 }
136
137 public void writeEmptyElement(String string) throws XMLStreamException {
138 xmlWriter.writeEmptyElement(string);
139 }
140
141 public void writeEndElement() throws XMLStreamException {
142 xmlWriter.writeEndElement();
143 depth--;
144 }
145
146 public void writeEndDocument() throws XMLStreamException {
147 if (isDebugEnabled) {
148 log.debug("writeEndDocument");
149 }
150 xmlWriter.writeEndDocument();
151 isEndDocument = true;
152 }
153
154 public void close() throws XMLStreamException {
155 if (isDebugEnabled) {
156 log.debug("close");
157 }
158 xmlWriter.close();
159 }
160
161 /**
162 * Flush is overridden to trigger the attachment serialization
163 */
164 public void flush() throws XMLStreamException {
165 if (isDebugEnabled) {
166 log.debug("Calling MTOMXMLStreamWriter.flush");
167 }
168 xmlWriter.flush();
169 String SOAPContentType;
170 // flush() triggers the optimized attachment writing.
171 // If the optimized attachments are specified, and the xml
172 // document is completed, then write out the attachments.
173 if (format.isOptimized() && !isComplete & (isEndDocument || depth == 0)) {
174 if (isDebugEnabled) {
175 log.debug("The XML writing is completed. Now the attachments are written");
176 }
177 isComplete = true;
178 if (format.isSOAP11()) {
179 SOAPContentType = SOAP11Constants.SOAP_11_CONTENT_TYPE;
180 } else {
181 SOAPContentType = SOAP12Constants.SOAP_12_CONTENT_TYPE;
182 }
183 try {
184 MIMEOutputUtils.complete(outStream,
185 bufferedXML.toByteArray(),
186 binaryNodeList,
187 format.getMimeBoundary(),
188 format.getRootContentId(),
189 format.getCharSetEncoding(),
190 SOAPContentType,
191 format);
192 bufferedXML.close();
193 bufferedXML = null;
194 } catch (UnsupportedEncodingException e) {
195 throw new OMException(e);
196 } catch (IOException e) {
197 throw new OMException(e);
198 }
199 }
200 }
201
202
203 public void writeAttribute(String string, String string1) throws XMLStreamException {
204 xmlWriter.writeAttribute(string, string1);
205 }
206
207 public void writeAttribute(String string, String string1, String string2, String string3)
208 throws XMLStreamException {
209 xmlWriter.writeAttribute(string, string1, string2, string3);
210 }
211
212 public void writeAttribute(String string, String string1, String string2)
213 throws XMLStreamException {
214 xmlWriter.writeAttribute(string, string1, string2);
215 }
216
217 public void writeNamespace(String string, String string1) throws XMLStreamException {
218 xmlWriter.writeNamespace(string, string1);
219 }
220
221 public void writeDefaultNamespace(String string) throws XMLStreamException {
222 xmlWriter.writeDefaultNamespace(string);
223 }
224
225 public void writeComment(String string) throws XMLStreamException {
226 xmlWriter.writeComment(string);
227 }
228
229 public void writeProcessingInstruction(String string) throws XMLStreamException {
230 xmlWriter.writeProcessingInstruction(string);
231 }
232
233 public void writeProcessingInstruction(String string, String string1)
234 throws XMLStreamException {
235 xmlWriter.writeProcessingInstruction(string, string1);
236 }
237
238 public void writeCData(String string) throws XMLStreamException {
239 xmlWriter.writeCData(string);
240 }
241
242 public void writeDTD(String string) throws XMLStreamException {
243 xmlWriter.writeDTD(string);
244 }
245
246 public void writeEntityRef(String string) throws XMLStreamException {
247 xmlWriter.writeEntityRef(string);
248 }
249
250 public void writeStartDocument() throws XMLStreamException {
251 xmlWriter.writeStartDocument();
252 }
253
254 public void writeStartDocument(String string) throws XMLStreamException {
255 xmlWriter.writeStartDocument(string);
256 }
257
258 public void writeStartDocument(String string, String string1) throws XMLStreamException {
259 xmlWriter.writeStartDocument(string, string1);
260 }
261
262 public void writeCharacters(String string) throws XMLStreamException {
263 xmlWriter.writeCharacters(string);
264 }
265
266 public void writeCharacters(char[] chars, int i, int i1) throws XMLStreamException {
267 xmlWriter.writeCharacters(chars, i, i1);
268 }
269
270 public String getPrefix(String string) throws XMLStreamException {
271 return xmlWriter.getPrefix(string);
272 }
273
274 public void setPrefix(String string, String string1) throws XMLStreamException {
275 xmlWriter.setPrefix(string, string1);
276 }
277
278 public void setDefaultNamespace(String string) throws XMLStreamException {
279 xmlWriter.setDefaultNamespace(string);
280 }
281
282 public void setNamespaceContext(NamespaceContext namespaceContext) throws XMLStreamException {
283 xmlWriter.setNamespaceContext(namespaceContext);
284 }
285
286 public NamespaceContext getNamespaceContext() {
287 return xmlWriter.getNamespaceContext();
288 }
289
290 public Object getProperty(String string) throws IllegalArgumentException {
291 return xmlWriter.getProperty(string);
292 }
293
294 public boolean isOptimized() {
295 return format.isOptimized();
296 }
297
298 public String getContentType() {
299 return format.getContentType();
300 }
301
302 public void writeOptimized(OMText node) {
303 if(isDebugEnabled){
304 log.debug("Start MTOMXMLStreamWriter.writeOptimized()");
305 }
306 binaryNodeList.add(node);
307 if(isDebugEnabled){
308 log.debug("Exit MTOMXMLStreamWriter.writeOptimized()");
309 }
310 }
311 /*
312 * This method check if size of dataHandler exceeds the optimization Threshold
313 * set on OMOutputFormat.
314 * return true is size exceeds the threshold limit.
315 * return false otherwise.
316 */
317 public boolean isOptimizedThreshold(OMText node){
318 if(isDebugEnabled){
319 log.debug("Start MTOMXMLStreamWriter.isOptimizedThreshold()");
320 }
321 DataHandler dh = (DataHandler)node.getDataHandler();
322 int optimized = UNSUPPORTED;
323 if(dh!=null){
324 if(isDebugEnabled){
325 log.debug("DataHandler fetched, starting optimized Threshold processing");
326 }
327 optimized= BufferUtils.doesDataHandlerExceedLimit(dh, format.getOptimizedThreshold());
328 }
329 if(optimized == UNSUPPORTED || optimized == EXCEED_LIMIT){
330 if(log.isDebugEnabled()){
331 log.debug("node should be added to binart NodeList for optimization");
332 }
333 return true;
334 }
335 return false;
336 }
337
338 public void setXmlStreamWriter(XMLStreamWriter xmlWriter) {
339 this.xmlWriter = xmlWriter;
340 }
341
342 public XMLStreamWriter getXmlStreamWriter() {
343 return xmlWriter;
344 }
345
346 public String getMimeBoundary() {
347 return format.getMimeBoundary();
348 }
349
350 public String getRootContentId() {
351 return format.getRootContentId();
352 }
353
354 public String getNextContentId() {
355 return format.getNextContentId();
356 }
357
358 /**
359 * Returns the character set encoding scheme. If the value of the charSetEncoding is not set
360 * then the default will be returned.
361 *
362 * @return Returns encoding.
363 */
364 public String getCharSetEncoding() {
365 return format.getCharSetEncoding();
366 }
367
368 public void setCharSetEncoding(String charSetEncoding) {
369 format.setCharSetEncoding(charSetEncoding);
370 }
371
372 public String getXmlVersion() {
373 return format.getXmlVersion();
374 }
375
376 public void setXmlVersion(String xmlVersion) {
377 format.setXmlVersion(xmlVersion);
378 }
379
380 public void setSoap11(boolean b) {
381 format.setSOAP11(b);
382 }
383
384 public boolean isIgnoreXMLDeclaration() {
385 return format.isIgnoreXMLDeclaration();
386 }
387
388 public void setIgnoreXMLDeclaration(boolean ignoreXMLDeclaration) {
389 format.setIgnoreXMLDeclaration(ignoreXMLDeclaration);
390 }
391
392 public void setDoOptimize(boolean b) {
393 format.setDoOptimize(b);
394 }
395
396 /**
397 * Get the output format used by this writer.
398 * <p>
399 * The caller should use the returned instance in a read-only way, i.e.
400 * he should not modify the settings of the output format. Any attempt
401 * to do so will lead to unpredictable results.
402 *
403 * @return the output format used by this writer
404 */
405 public OMOutputFormat getOutputFormat() {
406 return format;
407 }
408
409 public void setOutputFormat(OMOutputFormat format) {
410 this.format = format;
411 }
412
413 /**
414 * If this XMLStreamWriter is connected to an OutputStream
415 * then the OutputStream is returned. This allows a node
416 * (perhaps an OMSourcedElement) to write its content
417 * directly to the OutputStream.
418 * @return OutputStream or null
419 */
420 public OutputStream getOutputStream() throws XMLStreamException {
421 OutputStream os = null;
422 if (bufferedXML != null) {
423 os = bufferedXML;
424 } else {
425 os = outStream;
426 }
427
428 if (isDebugEnabled) {
429 if (os == null) {
430 log.debug("Direct access to the output stream is not available.");
431 } else if (bufferedXML != null) {
432 log.debug("Returning access to the buffered xml stream: " + bufferedXML);
433 } else {
434 log.debug("Returning access to the original output stream: " + os);
435 }
436 }
437
438 if (os != null) {
439 // Flush the state of the writer..Many times the
440 // write defers the writing of tag characters (>)
441 // until the next write. Flush out this character
442 this.writeCharacters("");
443 this.flush();
444 }
445 return os;
446 }
447
448 /**
449 * Writes the relevant output.
450 *
451 * @param writer
452 * @throws XMLStreamException
453 */
454 private void writeOutput(OMText textNode) throws XMLStreamException {
455 int type = textNode.getType();
456 if (type == OMNode.TEXT_NODE || type == OMNode.SPACE_NODE) {
457 writeCharacters(textNode.getText());
458 } else if (type == OMNode.CDATA_SECTION_NODE) {
459 writeCData(textNode.getText());
460 } else if (type == OMNode.ENTITY_REFERENCE_NODE) {
461 writeEntityRef(textNode.getText());
462 }
463 }
464 }