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.ws.message.stream;
27
28 import com.sun.istack.internal.NotNull;
29 import com.sun.istack.internal.Nullable;
30 import com.sun.istack.internal.XMLStreamReaderToContentHandler;
31 import com.sun.xml.internal.bind.api.Bridge;
32 import com.sun.xml.internal.stream.buffer.MutableXMLStreamBuffer;
33 import com.sun.xml.internal.stream.buffer.stax.StreamReaderBufferCreator;
34 import com.sun.xml.internal.ws.api.SOAPVersion;
35 import com.sun.xml.internal.ws.api.message.AttachmentSet;
36 import com.sun.xml.internal.ws.api.message.Header;
37 import com.sun.xml.internal.ws.api.message.HeaderList;
38 import com.sun.xml.internal.ws.api.message.Message;
39 import com.sun.xml.internal.ws.api.message.MessageHeaders;
40 import com.sun.xml.internal.ws.api.streaming.XMLStreamReaderFactory;
41 import com.sun.xml.internal.ws.encoding.TagInfoset;
42 import com.sun.xml.internal.ws.message.AbstractMessageImpl;
43 import com.sun.xml.internal.ws.message.AttachmentUnmarshallerImpl;
44 import com.sun.xml.internal.ws.spi.db.XMLBridge;
45 import com.sun.xml.internal.ws.streaming.XMLStreamReaderUtil;
46 import com.sun.xml.internal.ws.util.xml.DummyLocation;
47 import com.sun.xml.internal.ws.util.xml.StAXSource;
48 import com.sun.xml.internal.ws.util.xml.XMLStreamReaderToXMLStreamWriter;
49 import org.xml.sax.ContentHandler;
50 import org.xml.sax.ErrorHandler;
51 import org.xml.sax.SAXException;
52 import org.xml.sax.SAXParseException;
53 import org.xml.sax.helpers.NamespaceSupport;
54
55 import javax.xml.bind.JAXBException;
56 import javax.xml.bind.Unmarshaller;
57 import javax.xml.stream.*;
58 import static javax.xml.stream.XMLStreamConstants.START_DOCUMENT;
59 import static javax.xml.stream.XMLStreamConstants.START_ELEMENT;
60 import static javax.xml.stream.XMLStreamConstants.END_ELEMENT;
61 import javax.xml.transform.Source;
62 import javax.xml.ws.WebServiceException;
63 import java.util.ArrayList;
64 import java.util.Enumeration;
65 import java.util.List;
66
67 /**
68 * {@link Message} implementation backed by {@link XMLStreamReader}.
69 *
70 * TODO: we need another message class that keeps {@link XMLStreamReader} that points
71 * at the start of the envelope element.
72 */
73 public class StreamMessage extends AbstractMessageImpl {
74 /**
75 * The reader will be positioned at
76 * the first child of the SOAP body
77 */
78 private @NotNull XMLStreamReader reader;
79
80 // lazily created
81 private @Nullable MessageHeaders headers;
82
83 /**
84 * Because the StreamMessage leaves out the white spaces around payload
85 * when being instantiated the space characters between soap:Body opening and
86 * payload is stored in this field to be reused later (necessary for message security);
87 * Instantiated after StreamMessage creation
88 */
89 private String bodyPrologue = null;
90
91 /**
92 * instantiated after writing message to XMLStreamWriter
93 */
94 private String bodyEpilogue = null;
95
96 private final String payloadLocalName;
97
98 private final String payloadNamespaceURI;
99
100 /**
101 * infoset about the SOAP envelope, header, and body.
102 *
103 * <p>
104 * If the creater of this object didn't care about those,
105 * we use stock values.
106 */
107 private @NotNull TagInfoset envelopeTag;
108 private @NotNull TagInfoset headerTag;
109 private @NotNull TagInfoset bodyTag;
110
111 /**
112 * Used only for debugging. This records where the message was consumed.
113 */
114 private Throwable consumedAt;
115
116 /**
117 * Default s:Envelope, s:Header, and s:Body tag infoset definitions.
118 *
119 * We need 3 for SOAP 1.1, 3 for SOAP 1.2.
120 */
121 private static final TagInfoset[] DEFAULT_TAGS;
122
123 static {
124 DEFAULT_TAGS = new TagInfoset[6];
125 create(SOAPVersion.SOAP_11);
126 create(SOAPVersion.SOAP_12);
127 }
128
129 public StreamMessage(SOAPVersion v) {
130 super(v);
131 payloadLocalName = null;
132 payloadNamespaceURI = null;
133 }
134 /**
135 * Creates a {@link StreamMessage} from a {@link XMLStreamReader}
136 * that points at the start element of the payload, and headers.
137 *
138 * <p>
139 * This method creates a {@link Message} from a payload.
140 *
141 * @param headers
142 * if null, it means no headers. if non-null,
143 * it will be owned by this message.
144 * @param reader
145 * points at the start element/document of the payload (or the end element of the <s:Body>
146 * if there's no payload)
147 */
148 public StreamMessage(@Nullable MessageHeaders headers, @NotNull AttachmentSet attachmentSet, @NotNull XMLStreamReader reader, @NotNull SOAPVersion soapVersion) {
149 super(soapVersion);
150 this.headers = headers;
151 this.attachmentSet = attachmentSet;
152 this.reader = reader;
153
154 if(reader.getEventType()== START_DOCUMENT)
155 XMLStreamReaderUtil.nextElementContent(reader);
156
157 //if the reader is pointing to the end element </soapenv:Body> then its empty message
158 // or no payload
159 if(reader.getEventType() == XMLStreamConstants.END_ELEMENT){
160 String body = reader.getLocalName();
161 String nsUri = reader.getNamespaceURI();
162 assert body != null;
163 assert nsUri != null;
164 //if its not soapenv:Body then throw exception, we received malformed stream
165 if(body.equals("Body") && nsUri.equals(soapVersion.nsUri)){
166 this.payloadLocalName = null;
167 this.payloadNamespaceURI = null;
168 }else{ //TODO: i18n and also we should be throwing better message that this
169 throw new WebServiceException("Malformed stream: {"+nsUri+"}"+body);
170 }
171 }else{
172 this.payloadLocalName = reader.getLocalName();
173 this.payloadNamespaceURI = reader.getNamespaceURI();
174 }
175
176 // use the default infoset representation for headers
177 int base = soapVersion.ordinal()*3;
178 this.envelopeTag = DEFAULT_TAGS[base];
179 this.headerTag = DEFAULT_TAGS[base+1];
180 this.bodyTag = DEFAULT_TAGS[base+2];
181 }
182
183 /**
184 * Creates a {@link StreamMessage} from a {@link XMLStreamReader}
185 * and the complete infoset of the SOAP envelope.
186 *
187 * <p>
188 * See {@link #StreamMessage(MessageHeaders, AttachmentSet, XMLStreamReader, SOAPVersion)} for
189 * the description of the basic parameters.
190 *
191 * @param headerTag
192 * Null if the message didn't have a header tag.
193 *
194 */
195 public StreamMessage(@NotNull TagInfoset envelopeTag, @Nullable TagInfoset headerTag, @NotNull AttachmentSet attachmentSet, @Nullable MessageHeaders headers, @NotNull TagInfoset bodyTag, @NotNull XMLStreamReader reader, @NotNull SOAPVersion soapVersion) {
196 this(envelopeTag, headerTag, attachmentSet, headers, null, bodyTag, null, reader, soapVersion);
197 }
198
199 public StreamMessage(@NotNull TagInfoset envelopeTag, @Nullable TagInfoset headerTag, @NotNull AttachmentSet attachmentSet, @Nullable MessageHeaders headers, @Nullable String bodyPrologue, @NotNull TagInfoset bodyTag, @Nullable String bodyEpilogue, @NotNull XMLStreamReader reader, @NotNull SOAPVersion soapVersion) {
200 this(headers,attachmentSet,reader,soapVersion);
201 if(envelopeTag == null ) {
202 throw new IllegalArgumentException("EnvelopeTag TagInfoset cannot be null");
203 }
204 if(bodyTag == null ) {
205 throw new IllegalArgumentException("BodyTag TagInfoset cannot be null");
206 }
207 this.envelopeTag = envelopeTag;
208 this.headerTag = headerTag;
209 this.bodyTag = bodyTag;
210 this.bodyPrologue = bodyPrologue;
211 this.bodyEpilogue = bodyEpilogue;
212 }
213
214 public boolean hasHeaders() {
215 return headers!=null && headers.hasHeaders();
216 }
217
218 public MessageHeaders getHeaders() {
219 if (headers == null) {
220 headers = new HeaderList(getSOAPVersion());
221 }
222 return headers;
223 }
224
225 public String getPayloadLocalPart() {
226 return payloadLocalName;
227 }
228
229 public String getPayloadNamespaceURI() {
230 return payloadNamespaceURI;
231 }
232
233 public boolean hasPayload() {
234 return payloadLocalName!=null;
235 }
236
237 public Source readPayloadAsSource() {
238 if(hasPayload()) {
239 assert unconsumed();
240 return new StAXSource(reader, true, getInscopeNamespaces());
241 } else
242 return null;
243 }
244
245 /**
246 * There is no way to enumerate inscope namespaces for XMLStreamReader. That means
247 * namespaces declared in envelope, and body tags need to be computed using their
248 * {@link TagInfoset}s.
249 *
250 * @return array of the even length of the form { prefix0, uri0, prefix1, uri1, ... }
251 */
252 private String[] getInscopeNamespaces() {
253 NamespaceSupport nss = new NamespaceSupport();
312 return r;
313 }
314
315 @Override
316 public void consume() {
317 assert unconsumed();
318 XMLStreamReaderUtil.readRest(reader);
319 XMLStreamReaderUtil.close(reader);
320 XMLStreamReaderFactory.recycle(reader);
321 }
322
323 public XMLStreamReader readPayload() {
324 if(!hasPayload())
325 return null;
326 // TODO: What about access at and beyond </soap:Body>
327 assert unconsumed();
328 return this.reader;
329 }
330
331 public void writePayloadTo(XMLStreamWriter writer)throws XMLStreamException {
332 assert unconsumed();
333
334 if(payloadLocalName==null) {
335 return; // no body
336 }
337
338 if (bodyPrologue != null) {
339 writer.writeCharacters(bodyPrologue);
340 }
341
342 XMLStreamReaderToXMLStreamWriter conv = new XMLStreamReaderToXMLStreamWriter();
343
344 while(reader.getEventType() != XMLStreamConstants.END_DOCUMENT){
345 String name = reader.getLocalName();
346 String nsUri = reader.getNamespaceURI();
347
348 // After previous conv.bridge() call the cursor will be at END_ELEMENT.
349 // Check if its not soapenv:Body then move to next ELEMENT
350 if(reader.getEventType() == XMLStreamConstants.END_ELEMENT){
351
362 // body closed > exit
363 break;
364 }
365
366 } else {
367 // payload opening element: copy payload to writer
368 conv.bridge(reader,writer);
369 }
370 }
371
372 XMLStreamReaderUtil.readRest(reader);
373 XMLStreamReaderUtil.close(reader);
374 XMLStreamReaderFactory.recycle(reader);
375 }
376
377 private boolean isBodyElement(String name, String nsUri) {
378 return name.equals("Body") && nsUri.equals(soapVersion.nsUri);
379 }
380
381 public void writeTo(XMLStreamWriter sw) throws XMLStreamException{
382 writeEnvelope(sw);
383 }
384
385 /**
386 * This method should be called when the StreamMessage is created with a payload
387 * @param writer
388 */
389 private void writeEnvelope(XMLStreamWriter writer) throws XMLStreamException {
390 writer.writeStartDocument();
391 envelopeTag.writeStart(writer);
392
393 //write headers
394 MessageHeaders hl = getHeaders();
395 if (hl.hasHeaders() && headerTag == null) headerTag = new TagInfoset(envelopeTag.nsUri,"Header",envelopeTag.prefix,EMPTY_ATTS);
396 if (headerTag != null) {
397 headerTag.writeStart(writer);
398 if (hl.hasHeaders()){
399 for(Header h : hl.asList()){
400 h.writeTo(writer);
401 }
402 }
403 writer.writeEndElement();
404 }
405 bodyTag.writeStart(writer);
406 if(hasPayload())
407 writePayloadTo(writer);
408 writer.writeEndElement();
409 writer.writeEndElement();
410 writer.writeEndDocument();
411 }
412
413 public void writePayloadTo(ContentHandler contentHandler, ErrorHandler errorHandler, boolean fragment) throws SAXException {
414 assert unconsumed();
415
416 try {
417 if(payloadLocalName==null)
418 return; // no body
419
420 if (bodyPrologue != null) {
421 char[] chars = bodyPrologue.toCharArray();
422 contentHandler.characters(chars, 0, chars.length);
423 }
424
425 XMLStreamReaderToContentHandler conv = new XMLStreamReaderToContentHandler(reader,contentHandler,true,fragment,getInscopeNamespaces());
426
427 while(reader.getEventType() != XMLStreamConstants.END_DOCUMENT){
428 String name = reader.getLocalName();
429 String nsUri = reader.getNamespaceURI();
430
431 // After previous conv.bridge() call the cursor will be at END_ELEMENT.
432 // Check if its not soapenv:Body then move to next ELEMENT
433 if(reader.getEventType() == XMLStreamConstants.END_ELEMENT){
448 }
449
450 } else {
451 // payload opening element: copy payload to writer
452 conv.bridge();
453 }
454 }
455 XMLStreamReaderUtil.readRest(reader);
456 XMLStreamReaderUtil.close(reader);
457 XMLStreamReaderFactory.recycle(reader);
458 } catch (XMLStreamException e) {
459 Location loc = e.getLocation();
460 if(loc==null) loc = DummyLocation.INSTANCE;
461
462 SAXParseException x = new SAXParseException(
463 e.getMessage(),loc.getPublicId(),loc.getSystemId(),loc.getLineNumber(),loc.getColumnNumber(),e);
464 errorHandler.error(x);
465 }
466 }
467
468 // TODO: this method should be probably rewritten to respect spaces between eelements; is it used at all?
469 public Message copy() {
470 try {
471 assert unconsumed();
472 consumedAt = null; // but we don't want to mark it as consumed
473 MutableXMLStreamBuffer xsb = new MutableXMLStreamBuffer();
474 StreamReaderBufferCreator c = new StreamReaderBufferCreator(xsb);
475
476 // preserving inscope namespaces from envelope, and body. Other option
477 // would be to create a filtering XMLStreamReader from reader+envelopeTag+bodyTag
478 c.storeElement(envelopeTag.nsUri, envelopeTag.localName, envelopeTag.prefix, envelopeTag.ns);
479 c.storeElement(bodyTag.nsUri, bodyTag.localName, bodyTag.prefix, bodyTag.ns);
480
481 if (hasPayload()) {
482 // Loop all the way for multi payload case
483 while(reader.getEventType() != XMLStreamConstants.END_DOCUMENT){
484 String name = reader.getLocalName();
485 String nsUri = reader.getNamespaceURI();
486 if(isBodyElement(name, nsUri) || (reader.getEventType() == XMLStreamConstants.END_DOCUMENT))
487 break;
488 c.create(reader);
489
511
512 // advance to the start tag of the <Body> first child element
513 proceedToRootElement(reader);
514 proceedToRootElement(clone);
515
516 return new StreamMessage(envelopeTag, headerTag, attachmentSet, HeaderList.copy(headers), bodyPrologue, bodyTag, bodyEpilogue, clone, soapVersion);
517 } catch (XMLStreamException e) {
518 throw new WebServiceException("Failed to copy a message",e);
519 }
520 }
521
522 private void proceedToRootElement(XMLStreamReader xsr) throws XMLStreamException {
523 assert xsr.getEventType()==START_DOCUMENT;
524 xsr.nextTag();
525 xsr.nextTag();
526 xsr.nextTag();
527 assert xsr.getEventType()==START_ELEMENT || xsr.getEventType()==END_ELEMENT;
528 }
529
530 public void writeTo(ContentHandler contentHandler, ErrorHandler errorHandler ) throws SAXException {
531 contentHandler.setDocumentLocator(NULL_LOCATOR);
532 contentHandler.startDocument();
533 envelopeTag.writeStart(contentHandler);
534 if (hasHeaders() && headerTag == null) headerTag = new TagInfoset(envelopeTag.nsUri,"Header",envelopeTag.prefix,EMPTY_ATTS);
535 if (headerTag != null) {
536 headerTag.writeStart(contentHandler);
537 if (hasHeaders()) {
538 MessageHeaders headers = getHeaders();
539 for (Header h : headers.asList()) {
540 // shouldn't JDK be smart enough to use array-style indexing for this foreach!?
541 h.writeTo(contentHandler,errorHandler);
542 }
543 }
544 headerTag.writeEnd(contentHandler);
545 }
546 bodyTag.writeStart(contentHandler);
547 writePayloadTo(contentHandler,errorHandler, true);
548 bodyTag.writeEnd(contentHandler);
549 envelopeTag.writeEnd(contentHandler);
550 contentHandler.endDocument();
553 /**
554 * Used for an assertion. Returns true when the message is unconsumed,
555 * or otherwise throw an exception.
556 *
557 * <p>
558 * Calling this method also marks the stream as 'consumed'
559 */
560 private boolean unconsumed() {
561 if(payloadLocalName==null)
562 return true; // no payload. can be consumed multiple times.
563
564 if(reader.getEventType()!=XMLStreamReader.START_ELEMENT) {
565 AssertionError error = new AssertionError("StreamMessage has been already consumed. See the nested exception for where it's consumed");
566 error.initCause(consumedAt);
567 throw error;
568 }
569 consumedAt = new Exception().fillInStackTrace();
570 return true;
571 }
572
573 private static void create(SOAPVersion v) {
574 int base = v.ordinal()*3;
575 DEFAULT_TAGS[base ] = new TagInfoset(v.nsUri,"Envelope","S",EMPTY_ATTS,"S",v.nsUri);
576 DEFAULT_TAGS[base+1] = new TagInfoset(v.nsUri,"Header","S",EMPTY_ATTS);
577 DEFAULT_TAGS[base+2] = new TagInfoset(v.nsUri,"Body","S",EMPTY_ATTS);
578 }
579
580 public String getBodyPrologue() {
581 return bodyPrologue;
582 }
583
584 public String getBodyEpilogue() {
585 return bodyEpilogue;
586 }
587
588 public XMLStreamReader getReader() {
589 assert unconsumed();
590 return reader;
591 }
592 }
|
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.ws.message.stream;
27
28 import com.sun.istack.internal.NotNull;
29 import com.sun.istack.internal.Nullable;
30 import com.sun.istack.internal.XMLStreamReaderToContentHandler;
31 import com.sun.xml.internal.bind.api.Bridge;
32 import com.sun.xml.internal.stream.buffer.MutableXMLStreamBuffer;
33 import com.sun.xml.internal.stream.buffer.XMLStreamBuffer;
34 import com.sun.xml.internal.stream.buffer.XMLStreamBufferMark;
35 import com.sun.xml.internal.stream.buffer.stax.StreamReaderBufferCreator;
36 import com.sun.xml.internal.ws.api.SOAPVersion;
37 import com.sun.xml.internal.ws.api.message.AttachmentSet;
38 import com.sun.xml.internal.ws.api.message.Header;
39 import com.sun.xml.internal.ws.api.message.HeaderList;
40 import com.sun.xml.internal.ws.api.message.Message;
41 import com.sun.xml.internal.ws.api.message.MessageHeaders;
42 import com.sun.xml.internal.ws.api.message.StreamingSOAP;
43 import com.sun.xml.internal.ws.api.streaming.XMLStreamReaderFactory;
44 import com.sun.xml.internal.ws.encoding.TagInfoset;
45 import com.sun.xml.internal.ws.message.AbstractMessageImpl;
46 import com.sun.xml.internal.ws.message.AttachmentUnmarshallerImpl;
47 import com.sun.xml.internal.ws.protocol.soap.VersionMismatchException;
48 import com.sun.xml.internal.ws.spi.db.XMLBridge;
49 import com.sun.xml.internal.ws.streaming.XMLStreamReaderUtil;
50 import com.sun.xml.internal.ws.util.xml.DummyLocation;
51 import com.sun.xml.internal.ws.util.xml.StAXSource;
52 import com.sun.xml.internal.ws.util.xml.XMLReaderComposite;
53 import com.sun.xml.internal.ws.util.xml.XMLStreamReaderToXMLStreamWriter;
54 import com.sun.xml.internal.ws.util.xml.XMLReaderComposite.ElemInfo;
55
56 import org.xml.sax.ContentHandler;
57 import org.xml.sax.ErrorHandler;
58 import org.xml.sax.SAXException;
59 import org.xml.sax.SAXParseException;
60 import org.xml.sax.helpers.NamespaceSupport;
61
62 import javax.xml.bind.JAXBException;
63 import javax.xml.bind.Unmarshaller;
64 import javax.xml.stream.*;
65
66 import static javax.xml.stream.XMLStreamConstants.START_DOCUMENT;
67 import static javax.xml.stream.XMLStreamConstants.START_ELEMENT;
68 import static javax.xml.stream.XMLStreamConstants.END_ELEMENT;
69 import javax.xml.transform.Source;
70 import javax.xml.ws.WebServiceException;
71 import java.util.ArrayList;
72 import java.util.Enumeration;
73 import java.util.HashMap;
74 import java.util.List;
75 import java.util.Map;
76
77 /**
78 * {@link Message} implementation backed by {@link XMLStreamReader}.
79 *
80 * TODO: we need another message class that keeps {@link XMLStreamReader} that points
81 * at the start of the envelope element.
82 */
83 public class StreamMessage extends AbstractMessageImpl implements StreamingSOAP {
84 /**
85 * The reader will be positioned at
86 * the first child of the SOAP body
87 */
88 private @NotNull XMLStreamReader reader;
89
90 // lazily created
91 private @Nullable MessageHeaders headers;
92
93 /**
94 * Because the StreamMessage leaves out the white spaces around payload
95 * when being instantiated the space characters between soap:Body opening and
96 * payload is stored in this field to be reused later (necessary for message security);
97 * Instantiated after StreamMessage creation
98 */
99 private String bodyPrologue = null;
100
101 /**
102 * instantiated after writing message to XMLStreamWriter
103 */
104 private String bodyEpilogue = null;
105
106 private String payloadLocalName;
107
108 private String payloadNamespaceURI;
109
110 /**
111 * Used only for debugging. This records where the message was consumed.
112 */
113 private Throwable consumedAt;
114
115 private XMLStreamReader envelopeReader;
116
117 public StreamMessage(SOAPVersion v) {
118 super(v);
119 payloadLocalName = null;
120 payloadNamespaceURI = null;
121 }
122
123 public StreamMessage(SOAPVersion v, @NotNull XMLStreamReader envelope, @NotNull AttachmentSet attachments) {
124 super(v);
125 envelopeReader = envelope;
126 attachmentSet = attachments;
127 }
128
129 public XMLStreamReader readEnvelope() {
130 if (envelopeReader == null) {
131 List<XMLStreamReader> hReaders = new java.util.ArrayList<XMLStreamReader>();
132 ElemInfo envElem = new ElemInfo(envelopeTag, null);
133 ElemInfo hdrElem = (headerTag != null) ? new ElemInfo(headerTag, envElem) : null;
134 ElemInfo bdyElem = new ElemInfo(bodyTag, envElem);
135 for (Header h : getHeaders().asList()) {
136 try {
137 hReaders.add(h.readHeader());
138 } catch (XMLStreamException e) {
139 throw new RuntimeException(e);
140 }
141 }
142 XMLStreamReader soapHeader = (hdrElem != null) ? new XMLReaderComposite(hdrElem, hReaders.toArray(new XMLStreamReader[hReaders.size()])) : null;
143 XMLStreamReader[] payload = {readPayload()};
144 XMLStreamReader soapBody = new XMLReaderComposite(bdyElem, payload);
145 XMLStreamReader[] soapContent = (soapHeader != null) ? new XMLStreamReader[]{soapHeader, soapBody} : new XMLStreamReader[]{soapBody};
146 return new XMLReaderComposite(envElem, soapContent);
147 }
148 return envelopeReader;
149 }
150
151 /**
152 * Creates a {@link StreamMessage} from a {@link XMLStreamReader}
153 * that points at the start element of the payload, and headers.
154 *
155 * <p>
156 * This method creates a {@link Message} from a payload.
157 *
158 * @param headers
159 * if null, it means no headers. if non-null,
160 * it will be owned by this message.
161 * @param reader
162 * points at the start element/document of the payload (or the end element of the <s:Body>
163 * if there's no payload)
164 */
165 public StreamMessage(@Nullable MessageHeaders headers, @NotNull AttachmentSet attachmentSet, @NotNull XMLStreamReader reader, @NotNull SOAPVersion soapVersion) {
166 super(soapVersion);
167 init(headers, attachmentSet, reader, soapVersion);
168 }
169
170 private void init(@Nullable MessageHeaders headers, @NotNull AttachmentSet attachmentSet, @NotNull XMLStreamReader reader, @NotNull SOAPVersion soapVersion) {
171 this.headers = headers;
172 this.attachmentSet = attachmentSet;
173 this.reader = reader;
174
175 if(reader.getEventType()== START_DOCUMENT)
176 XMLStreamReaderUtil.nextElementContent(reader);
177
178 //if the reader is pointing to the end element </soapenv:Body> then its empty message
179 // or no payload
180 if(reader.getEventType() == XMLStreamConstants.END_ELEMENT){
181 String body = reader.getLocalName();
182 String nsUri = reader.getNamespaceURI();
183 assert body != null;
184 assert nsUri != null;
185 //if its not soapenv:Body then throw exception, we received malformed stream
186 if(body.equals("Body") && nsUri.equals(soapVersion.nsUri)){
187 this.payloadLocalName = null;
188 this.payloadNamespaceURI = null;
189 }else{ //TODO: i18n and also we should be throwing better message that this
190 throw new WebServiceException("Malformed stream: {"+nsUri+"}"+body);
191 }
192 }else{
193 this.payloadLocalName = reader.getLocalName();
194 this.payloadNamespaceURI = reader.getNamespaceURI();
195 }
196
197 // use the default infoset representation for headers
198 int base = soapVersion.ordinal()*3;
199 this.envelopeTag = DEFAULT_TAGS.get(base);
200 this.headerTag = DEFAULT_TAGS.get(base+1);
201 this.bodyTag = DEFAULT_TAGS.get(base+2);
202 }
203
204 /**
205 * Creates a {@link StreamMessage} from a {@link XMLStreamReader}
206 * and the complete infoset of the SOAP envelope.
207 *
208 * <p>
209 * See {@link #StreamMessage(MessageHeaders, AttachmentSet, XMLStreamReader, SOAPVersion)} for
210 * the description of the basic parameters.
211 *
212 * @param headerTag
213 * Null if the message didn't have a header tag.
214 *
215 */
216 public StreamMessage(@NotNull TagInfoset envelopeTag, @Nullable TagInfoset headerTag, @NotNull AttachmentSet attachmentSet, @Nullable MessageHeaders headers, @NotNull TagInfoset bodyTag, @NotNull XMLStreamReader reader, @NotNull SOAPVersion soapVersion) {
217 this(envelopeTag, headerTag, attachmentSet, headers, null, bodyTag, null, reader, soapVersion);
218 }
219
220 public StreamMessage(@NotNull TagInfoset envelopeTag, @Nullable TagInfoset headerTag, @NotNull AttachmentSet attachmentSet, @Nullable MessageHeaders headers, @Nullable String bodyPrologue, @NotNull TagInfoset bodyTag, @Nullable String bodyEpilogue, @NotNull XMLStreamReader reader, @NotNull SOAPVersion soapVersion) {
221 super(soapVersion);
222 init(envelopeTag, headerTag, attachmentSet, headers, bodyPrologue, bodyTag, bodyEpilogue, reader, soapVersion);
223 }
224
225 private void init(@NotNull TagInfoset envelopeTag, @Nullable TagInfoset headerTag, @NotNull AttachmentSet attachmentSet, @Nullable MessageHeaders headers, @Nullable String bodyPrologue, @NotNull TagInfoset bodyTag, @Nullable String bodyEpilogue, @NotNull XMLStreamReader reader, @NotNull SOAPVersion soapVersion) {
226 init(headers,attachmentSet,reader,soapVersion);
227 if(envelopeTag == null ) {
228 throw new IllegalArgumentException("EnvelopeTag TagInfoset cannot be null");
229 }
230 if(bodyTag == null ) {
231 throw new IllegalArgumentException("BodyTag TagInfoset cannot be null");
232 }
233 this.envelopeTag = envelopeTag;
234 this.headerTag = headerTag;
235 this.bodyTag = bodyTag;
236 this.bodyPrologue = bodyPrologue;
237 this.bodyEpilogue = bodyEpilogue;
238 }
239
240 public boolean hasHeaders() {
241 if ( envelopeReader != null ) readEnvelope(this);
242 return headers!=null && headers.hasHeaders();
243 }
244
245 public MessageHeaders getHeaders() {
246 if ( envelopeReader != null ) readEnvelope(this);
247 if (headers == null) {
248 headers = new HeaderList(getSOAPVersion());
249 }
250 return headers;
251 }
252
253 public String getPayloadLocalPart() {
254 if ( envelopeReader != null ) readEnvelope(this);
255 return payloadLocalName;
256 }
257
258 public String getPayloadNamespaceURI() {
259 if ( envelopeReader != null ) readEnvelope(this);
260 return payloadNamespaceURI;
261 }
262
263 public boolean hasPayload() {
264 if ( envelopeReader != null ) readEnvelope(this);
265 return payloadLocalName!=null;
266 }
267
268 public Source readPayloadAsSource() {
269 if(hasPayload()) {
270 assert unconsumed();
271 return new StAXSource(reader, true, getInscopeNamespaces());
272 } else
273 return null;
274 }
275
276 /**
277 * There is no way to enumerate inscope namespaces for XMLStreamReader. That means
278 * namespaces declared in envelope, and body tags need to be computed using their
279 * {@link TagInfoset}s.
280 *
281 * @return array of the even length of the form { prefix0, uri0, prefix1, uri1, ... }
282 */
283 private String[] getInscopeNamespaces() {
284 NamespaceSupport nss = new NamespaceSupport();
343 return r;
344 }
345
346 @Override
347 public void consume() {
348 assert unconsumed();
349 XMLStreamReaderUtil.readRest(reader);
350 XMLStreamReaderUtil.close(reader);
351 XMLStreamReaderFactory.recycle(reader);
352 }
353
354 public XMLStreamReader readPayload() {
355 if(!hasPayload())
356 return null;
357 // TODO: What about access at and beyond </soap:Body>
358 assert unconsumed();
359 return this.reader;
360 }
361
362 public void writePayloadTo(XMLStreamWriter writer)throws XMLStreamException {
363 if ( envelopeReader != null ) readEnvelope(this);
364 assert unconsumed();
365
366 if(payloadLocalName==null) {
367 return; // no body
368 }
369
370 if (bodyPrologue != null) {
371 writer.writeCharacters(bodyPrologue);
372 }
373
374 XMLStreamReaderToXMLStreamWriter conv = new XMLStreamReaderToXMLStreamWriter();
375
376 while(reader.getEventType() != XMLStreamConstants.END_DOCUMENT){
377 String name = reader.getLocalName();
378 String nsUri = reader.getNamespaceURI();
379
380 // After previous conv.bridge() call the cursor will be at END_ELEMENT.
381 // Check if its not soapenv:Body then move to next ELEMENT
382 if(reader.getEventType() == XMLStreamConstants.END_ELEMENT){
383
394 // body closed > exit
395 break;
396 }
397
398 } else {
399 // payload opening element: copy payload to writer
400 conv.bridge(reader,writer);
401 }
402 }
403
404 XMLStreamReaderUtil.readRest(reader);
405 XMLStreamReaderUtil.close(reader);
406 XMLStreamReaderFactory.recycle(reader);
407 }
408
409 private boolean isBodyElement(String name, String nsUri) {
410 return name.equals("Body") && nsUri.equals(soapVersion.nsUri);
411 }
412
413 public void writeTo(XMLStreamWriter sw) throws XMLStreamException{
414 if ( envelopeReader != null ) readEnvelope(this);
415 writeEnvelope(sw);
416 }
417
418 /**
419 * This method should be called when the StreamMessage is created with a payload
420 * @param writer
421 */
422 private void writeEnvelope(XMLStreamWriter writer) throws XMLStreamException {
423 if ( envelopeReader != null ) readEnvelope(this);
424 writer.writeStartDocument();
425 envelopeTag.writeStart(writer);
426
427 //write headers
428 MessageHeaders hl = getHeaders();
429 if (hl.hasHeaders() && headerTag == null) headerTag = new TagInfoset(envelopeTag.nsUri,"Header",envelopeTag.prefix,EMPTY_ATTS);
430 if (headerTag != null) {
431 headerTag.writeStart(writer);
432 if (hl.hasHeaders()){
433 for(Header h : hl.asList()){
434 h.writeTo(writer);
435 }
436 }
437 writer.writeEndElement();
438 }
439 bodyTag.writeStart(writer);
440 if(hasPayload())
441 writePayloadTo(writer);
442 writer.writeEndElement();
443 writer.writeEndElement();
444 writer.writeEndDocument();
445 }
446
447 public void writePayloadTo(ContentHandler contentHandler, ErrorHandler errorHandler, boolean fragment) throws SAXException {
448 if ( envelopeReader != null ) readEnvelope(this);
449 assert unconsumed();
450
451 try {
452 if(payloadLocalName==null)
453 return; // no body
454
455 if (bodyPrologue != null) {
456 char[] chars = bodyPrologue.toCharArray();
457 contentHandler.characters(chars, 0, chars.length);
458 }
459
460 XMLStreamReaderToContentHandler conv = new XMLStreamReaderToContentHandler(reader,contentHandler,true,fragment,getInscopeNamespaces());
461
462 while(reader.getEventType() != XMLStreamConstants.END_DOCUMENT){
463 String name = reader.getLocalName();
464 String nsUri = reader.getNamespaceURI();
465
466 // After previous conv.bridge() call the cursor will be at END_ELEMENT.
467 // Check if its not soapenv:Body then move to next ELEMENT
468 if(reader.getEventType() == XMLStreamConstants.END_ELEMENT){
483 }
484
485 } else {
486 // payload opening element: copy payload to writer
487 conv.bridge();
488 }
489 }
490 XMLStreamReaderUtil.readRest(reader);
491 XMLStreamReaderUtil.close(reader);
492 XMLStreamReaderFactory.recycle(reader);
493 } catch (XMLStreamException e) {
494 Location loc = e.getLocation();
495 if(loc==null) loc = DummyLocation.INSTANCE;
496
497 SAXParseException x = new SAXParseException(
498 e.getMessage(),loc.getPublicId(),loc.getSystemId(),loc.getLineNumber(),loc.getColumnNumber(),e);
499 errorHandler.error(x);
500 }
501 }
502
503 // TODO: this method should be probably rewritten to respect spaces between elements; is it used at all?
504 @Override
505 public Message copy() {
506 if ( envelopeReader != null ) readEnvelope(this);
507 try {
508 assert unconsumed();
509 consumedAt = null; // but we don't want to mark it as consumed
510 MutableXMLStreamBuffer xsb = new MutableXMLStreamBuffer();
511 StreamReaderBufferCreator c = new StreamReaderBufferCreator(xsb);
512
513 // preserving inscope namespaces from envelope, and body. Other option
514 // would be to create a filtering XMLStreamReader from reader+envelopeTag+bodyTag
515 c.storeElement(envelopeTag.nsUri, envelopeTag.localName, envelopeTag.prefix, envelopeTag.ns);
516 c.storeElement(bodyTag.nsUri, bodyTag.localName, bodyTag.prefix, bodyTag.ns);
517
518 if (hasPayload()) {
519 // Loop all the way for multi payload case
520 while(reader.getEventType() != XMLStreamConstants.END_DOCUMENT){
521 String name = reader.getLocalName();
522 String nsUri = reader.getNamespaceURI();
523 if(isBodyElement(name, nsUri) || (reader.getEventType() == XMLStreamConstants.END_DOCUMENT))
524 break;
525 c.create(reader);
526
548
549 // advance to the start tag of the <Body> first child element
550 proceedToRootElement(reader);
551 proceedToRootElement(clone);
552
553 return new StreamMessage(envelopeTag, headerTag, attachmentSet, HeaderList.copy(headers), bodyPrologue, bodyTag, bodyEpilogue, clone, soapVersion);
554 } catch (XMLStreamException e) {
555 throw new WebServiceException("Failed to copy a message",e);
556 }
557 }
558
559 private void proceedToRootElement(XMLStreamReader xsr) throws XMLStreamException {
560 assert xsr.getEventType()==START_DOCUMENT;
561 xsr.nextTag();
562 xsr.nextTag();
563 xsr.nextTag();
564 assert xsr.getEventType()==START_ELEMENT || xsr.getEventType()==END_ELEMENT;
565 }
566
567 public void writeTo(ContentHandler contentHandler, ErrorHandler errorHandler ) throws SAXException {
568 if ( envelopeReader != null ) readEnvelope(this);
569 contentHandler.setDocumentLocator(NULL_LOCATOR);
570 contentHandler.startDocument();
571 envelopeTag.writeStart(contentHandler);
572 if (hasHeaders() && headerTag == null) headerTag = new TagInfoset(envelopeTag.nsUri,"Header",envelopeTag.prefix,EMPTY_ATTS);
573 if (headerTag != null) {
574 headerTag.writeStart(contentHandler);
575 if (hasHeaders()) {
576 MessageHeaders headers = getHeaders();
577 for (Header h : headers.asList()) {
578 // shouldn't JDK be smart enough to use array-style indexing for this foreach!?
579 h.writeTo(contentHandler,errorHandler);
580 }
581 }
582 headerTag.writeEnd(contentHandler);
583 }
584 bodyTag.writeStart(contentHandler);
585 writePayloadTo(contentHandler,errorHandler, true);
586 bodyTag.writeEnd(contentHandler);
587 envelopeTag.writeEnd(contentHandler);
588 contentHandler.endDocument();
591 /**
592 * Used for an assertion. Returns true when the message is unconsumed,
593 * or otherwise throw an exception.
594 *
595 * <p>
596 * Calling this method also marks the stream as 'consumed'
597 */
598 private boolean unconsumed() {
599 if(payloadLocalName==null)
600 return true; // no payload. can be consumed multiple times.
601
602 if(reader.getEventType()!=XMLStreamReader.START_ELEMENT) {
603 AssertionError error = new AssertionError("StreamMessage has been already consumed. See the nested exception for where it's consumed");
604 error.initCause(consumedAt);
605 throw error;
606 }
607 consumedAt = new Exception().fillInStackTrace();
608 return true;
609 }
610
611 public String getBodyPrologue() {
612 if ( envelopeReader != null ) readEnvelope(this);
613 return bodyPrologue;
614 }
615
616 public String getBodyEpilogue() {
617 if ( envelopeReader != null ) readEnvelope(this);
618 return bodyEpilogue;
619 }
620
621 public XMLStreamReader getReader() {
622 if ( envelopeReader != null ) readEnvelope(this);
623 assert unconsumed();
624 return reader;
625 }
626
627
628 private static final String SOAP_ENVELOPE = "Envelope";
629 private static final String SOAP_HEADER = "Header";
630 private static final String SOAP_BODY = "Body";
631
632 protected interface StreamHeaderDecoder {
633 public Header decodeHeader(XMLStreamReader reader, XMLStreamBuffer mark);
634 }
635
636 static final StreamHeaderDecoder SOAP12StreamHeaderDecoder = new StreamHeaderDecoder() {
637 @Override
638 public Header decodeHeader(XMLStreamReader reader, XMLStreamBuffer mark) {
639 return new StreamHeader12(reader, mark);
640 }
641 };
642
643 static final StreamHeaderDecoder SOAP11StreamHeaderDecoder = new StreamHeaderDecoder() {
644 @Override
645 public Header decodeHeader(XMLStreamReader reader, XMLStreamBuffer mark) {
646 return new StreamHeader11(reader, mark);
647 }
648 };
649
650 static private void readEnvelope(StreamMessage message) {
651 if ( message.envelopeReader == null ) return;
652 XMLStreamReader reader = message.envelopeReader;
653 message.envelopeReader = null;
654 SOAPVersion soapVersion = message.soapVersion;
655 // Move to soap:Envelope and verify
656 if(reader.getEventType()!=XMLStreamConstants.START_ELEMENT)
657 XMLStreamReaderUtil.nextElementContent(reader);
658 XMLStreamReaderUtil.verifyReaderState(reader,XMLStreamConstants.START_ELEMENT);
659 if (SOAP_ENVELOPE.equals(reader.getLocalName()) && !soapVersion.nsUri.equals(reader.getNamespaceURI())) {
660 throw new VersionMismatchException(soapVersion, soapVersion.nsUri, reader.getNamespaceURI());
661 }
662 XMLStreamReaderUtil.verifyTag(reader, soapVersion.nsUri, SOAP_ENVELOPE);
663
664 TagInfoset envelopeTag = new TagInfoset(reader);
665
666 // Collect namespaces on soap:Envelope
667 Map<String,String> namespaces = new HashMap<String,String>();
668 for(int i=0; i< reader.getNamespaceCount();i++){
669 namespaces.put(reader.getNamespacePrefix(i), reader.getNamespaceURI(i));
670 }
671
672 // Move to next element
673 XMLStreamReaderUtil.nextElementContent(reader);
674 XMLStreamReaderUtil.verifyReaderState(reader,
675 javax.xml.stream.XMLStreamConstants.START_ELEMENT);
676
677 HeaderList headers = null;
678 TagInfoset headerTag = null;
679
680 if (reader.getLocalName().equals(SOAP_HEADER)
681 && reader.getNamespaceURI().equals(soapVersion.nsUri)) {
682 headerTag = new TagInfoset(reader);
683
684 // Collect namespaces on soap:Header
685 for(int i=0; i< reader.getNamespaceCount();i++){
686 namespaces.put(reader.getNamespacePrefix(i), reader.getNamespaceURI(i));
687 }
688 // skip <soap:Header>
689 XMLStreamReaderUtil.nextElementContent(reader);
690
691 // If SOAP header blocks are present (i.e. not <soap:Header/>)
692 if (reader.getEventType() == XMLStreamConstants.START_ELEMENT) {
693 headers = new HeaderList(soapVersion);
694
695 try {
696 // Cache SOAP header blocks
697 StreamHeaderDecoder headerDecoder = SOAPVersion.SOAP_11.equals(soapVersion) ? SOAP11StreamHeaderDecoder : SOAP12StreamHeaderDecoder;
698 cacheHeaders(reader, namespaces, headers, headerDecoder);
699 } catch (XMLStreamException e) {
700 // TODO need to throw more meaningful exception
701 throw new WebServiceException(e);
702 }
703 }
704
705 // Move to soap:Body
706 XMLStreamReaderUtil.nextElementContent(reader);
707 }
708
709 // Verify that <soap:Body> is present
710 XMLStreamReaderUtil.verifyTag(reader, soapVersion.nsUri, SOAP_BODY);
711 TagInfoset bodyTag = new TagInfoset(reader);
712
713 String bodyPrologue = XMLStreamReaderUtil.nextWhiteSpaceContent(reader);
714 message.init(envelopeTag,headerTag,message.attachmentSet,headers,bodyPrologue,bodyTag,null,reader,soapVersion);
715 // when there's no payload,
716 // it's tempting to use EmptyMessageImpl, but it doesn't preserve the infoset
717 // of <envelope>,<header>, and <body>, so we need to stick to StreamMessage.
718 }
719
720
721 private static XMLStreamBuffer cacheHeaders(XMLStreamReader reader,
722 Map<String, String> namespaces, HeaderList headers,
723 StreamHeaderDecoder headerDecoder) throws XMLStreamException {
724 MutableXMLStreamBuffer buffer = createXMLStreamBuffer();
725 StreamReaderBufferCreator creator = new StreamReaderBufferCreator();
726 creator.setXMLStreamBuffer(buffer);
727
728 // Reader is positioned at the first header block
729 while(reader.getEventType() == javax.xml.stream.XMLStreamConstants.START_ELEMENT) {
730 Map<String,String> headerBlockNamespaces = namespaces;
731
732 // Collect namespaces on SOAP header block
733 if (reader.getNamespaceCount() > 0) {
734 headerBlockNamespaces = new HashMap<String,String>(namespaces);
735 for (int i = 0; i < reader.getNamespaceCount(); i++) {
736 headerBlockNamespaces.put(reader.getNamespacePrefix(i), reader.getNamespaceURI(i));
737 }
738 }
739
740 // Mark
741 XMLStreamBuffer mark = new XMLStreamBufferMark(headerBlockNamespaces, creator);
742 // Create Header
743 headers.add(headerDecoder.decodeHeader(reader, mark));
744
745
746 // Cache the header block
747 // After caching Reader will be positioned at next header block or
748 // the end of the </soap:header>
749 creator.createElementFragment(reader, false);
750 if (reader.getEventType() != XMLStreamConstants.START_ELEMENT &&
751 reader.getEventType() != XMLStreamConstants.END_ELEMENT) {
752 XMLStreamReaderUtil.nextElementContent(reader);
753 }
754 }
755
756 return buffer;
757 }
758
759 private static MutableXMLStreamBuffer createXMLStreamBuffer() {
760 // TODO: Decode should own one MutableXMLStreamBuffer for reuse
761 // since it is more efficient. ISSUE: possible issue with
762 // lifetime of information in the buffer if accessed beyond
763 // the pipe line.
764 return new MutableXMLStreamBuffer();
765 }
766 }
|