Tue, 06 Mar 2012 16:09:35 -0800
7150322: Stop using drop source bundles in jaxws
Reviewed-by: darcy, ohrstrom
1 /*
2 * Copyright (c) 1997, 2011, 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 */
26 package com.sun.xml.internal.ws.message.stream;
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.streaming.XMLStreamReaderFactory;
40 import com.sun.xml.internal.ws.encoding.TagInfoset;
41 import com.sun.xml.internal.ws.message.AbstractMessageImpl;
42 import com.sun.xml.internal.ws.message.AttachmentUnmarshallerImpl;
43 import com.sun.xml.internal.ws.spi.db.XMLBridge;
44 import com.sun.xml.internal.ws.streaming.XMLStreamReaderUtil;
45 import com.sun.xml.internal.ws.util.xml.DummyLocation;
46 import com.sun.xml.internal.ws.util.xml.StAXSource;
47 import com.sun.xml.internal.ws.util.xml.XMLStreamReaderToXMLStreamWriter;
48 import org.xml.sax.ContentHandler;
49 import org.xml.sax.ErrorHandler;
50 import org.xml.sax.SAXException;
51 import org.xml.sax.SAXParseException;
52 import org.xml.sax.helpers.NamespaceSupport;
54 import javax.xml.bind.JAXBException;
55 import javax.xml.bind.Unmarshaller;
56 import javax.xml.stream.*;
57 import static javax.xml.stream.XMLStreamConstants.START_DOCUMENT;
58 import static javax.xml.stream.XMLStreamConstants.START_ELEMENT;
59 import static javax.xml.stream.XMLStreamConstants.END_ELEMENT;
60 import javax.xml.transform.Source;
61 import javax.xml.ws.WebServiceException;
62 import java.util.ArrayList;
63 import java.util.Enumeration;
64 import java.util.List;
66 /**
67 * {@link Message} implementation backed by {@link XMLStreamReader}.
68 *
69 * TODO: we need another message class that keeps {@link XMLStreamReader} that points
70 * at the start of the envelope element.
71 */
72 public final class StreamMessage extends AbstractMessageImpl {
73 /**
74 * The reader will be positioned at
75 * the first child of the SOAP body
76 */
77 private @NotNull XMLStreamReader reader;
79 // lazily created
80 private @Nullable HeaderList headers;
82 /**
83 * Because the StreamMessage leaves out the white spaces around payload
84 * when being instantiated the space characters between soap:Body opening and
85 * payload is stored in this field to be reused later (necessary for message security);
86 * Instantiated after StreamMessage creation
87 */
88 private String bodyPrologue = null;
90 /**
91 * instantiated after writing message to XMLStreamWriter
92 */
93 private String bodyEpilogue = null;
95 private final String payloadLocalName;
97 private final String payloadNamespaceURI;
99 /**
100 * infoset about the SOAP envelope, header, and body.
101 *
102 * <p>
103 * If the creater of this object didn't care about those,
104 * we use stock values.
105 */
106 private @NotNull TagInfoset envelopeTag,headerTag,bodyTag;
108 /**
109 * Used only for debugging. This records where the message was consumed.
110 */
111 private Throwable consumedAt;
113 /**
114 * Default s:Envelope, s:Header, and s:Body tag infoset definitions.
115 *
116 * We need 3 for SOAP 1.1, 3 for SOAP 1.2.
117 */
118 private static final TagInfoset[] DEFAULT_TAGS;
120 static {
121 DEFAULT_TAGS = new TagInfoset[6];
122 create(SOAPVersion.SOAP_11);
123 create(SOAPVersion.SOAP_12);
124 }
126 /**
127 * Creates a {@link StreamMessage} from a {@link XMLStreamReader}
128 * that points at the start element of the payload, and headers.
129 *
130 * <p>
131 * This method creaets a {@link Message} from a payload.
132 *
133 * @param headers
134 * if null, it means no headers. if non-null,
135 * it will be owned by this message.
136 * @param reader
137 * points at the start element/document of the payload (or the end element of the <s:Body>
138 * if there's no payload)
139 */
140 public StreamMessage(@Nullable HeaderList headers, @NotNull AttachmentSet attachmentSet, @NotNull XMLStreamReader reader, @NotNull SOAPVersion soapVersion) {
141 super(soapVersion);
142 this.headers = headers;
143 this.attachmentSet = attachmentSet;
144 this.reader = reader;
146 if(reader.getEventType()== START_DOCUMENT)
147 XMLStreamReaderUtil.nextElementContent(reader);
149 //if the reader is pointing to the end element </soapenv:Body> then its empty message
150 // or no payload
151 if(reader.getEventType() == XMLStreamConstants.END_ELEMENT){
152 String body = reader.getLocalName();
153 String nsUri = reader.getNamespaceURI();
154 assert body != null;
155 assert nsUri != null;
156 //if its not soapenv:Body then throw exception, we received malformed stream
157 if(body.equals("Body") && nsUri.equals(soapVersion.nsUri)){
158 this.payloadLocalName = null;
159 this.payloadNamespaceURI = null;
160 }else{ //TODO: i18n and also we should be throwing better message that this
161 throw new WebServiceException("Malformed stream: {"+nsUri+"}"+body);
162 }
163 }else{
164 this.payloadLocalName = reader.getLocalName();
165 this.payloadNamespaceURI = reader.getNamespaceURI();
166 }
168 // use the default infoset representation for headers
169 int base = soapVersion.ordinal()*3;
170 this.envelopeTag = DEFAULT_TAGS[base];
171 this.headerTag = DEFAULT_TAGS[base+1];
172 this.bodyTag = DEFAULT_TAGS[base+2];
173 }
175 /**
176 * Creates a {@link StreamMessage} from a {@link XMLStreamReader}
177 * and the complete infoset of the SOAP envelope.
178 *
179 * <p>
180 * See {@link #StreamMessage(HeaderList, AttachmentSet, XMLStreamReader, SOAPVersion)} for
181 * the description of the basic parameters.
182 *
183 * @param headerTag
184 * Null if the message didn't have a header tag.
185 *
186 */
187 public StreamMessage(@NotNull TagInfoset envelopeTag, @Nullable TagInfoset headerTag, @NotNull AttachmentSet attachmentSet, @Nullable HeaderList headers, @NotNull TagInfoset bodyTag, @NotNull XMLStreamReader reader, @NotNull SOAPVersion soapVersion) {
188 this(envelopeTag, headerTag, attachmentSet, headers, null, bodyTag, null, reader, soapVersion);
189 }
191 public StreamMessage(@NotNull TagInfoset envelopeTag, @Nullable TagInfoset headerTag, @NotNull AttachmentSet attachmentSet, @Nullable HeaderList headers, @Nullable String bodyPrologue, @NotNull TagInfoset bodyTag, @Nullable String bodyEpilogue, @NotNull XMLStreamReader reader, @NotNull SOAPVersion soapVersion) {
192 this(headers,attachmentSet,reader,soapVersion);
193 if(envelopeTag == null ) {
194 throw new IllegalArgumentException("EnvelopeTag TagInfoset cannot be null");
195 }
196 if(bodyTag == null ) {
197 throw new IllegalArgumentException("BodyTag TagInfoset cannot be null");
198 }
199 this.envelopeTag = envelopeTag;
200 this.headerTag = headerTag!=null ? headerTag :
201 new TagInfoset(envelopeTag.nsUri,"Header",envelopeTag.prefix,EMPTY_ATTS);
202 this.bodyTag = bodyTag;
203 this.bodyPrologue = bodyPrologue;
204 this.bodyEpilogue = bodyEpilogue;
205 }
207 public boolean hasHeaders() {
208 return headers!=null && !headers.isEmpty();
209 }
211 public HeaderList getHeaders() {
212 if (headers == null) {
213 headers = new HeaderList();
214 }
215 return headers;
216 }
218 public String getPayloadLocalPart() {
219 return payloadLocalName;
220 }
222 public String getPayloadNamespaceURI() {
223 return payloadNamespaceURI;
224 }
226 public boolean hasPayload() {
227 return payloadLocalName!=null;
228 }
230 public Source readPayloadAsSource() {
231 if(hasPayload()) {
232 assert unconsumed();
233 return new StAXSource(reader, true, getInscopeNamespaces());
234 } else
235 return null;
236 }
238 /**
239 * There is no way to enumerate inscope namespaces for XMLStreamReader. That means
240 * namespaces declared in envelope, and body tags need to be computed using their
241 * {@link TagInfoset}s.
242 *
243 * @return array of the even length of the form { prefix0, uri0, prefix1, uri1, ... }
244 */
245 private String[] getInscopeNamespaces() {
246 NamespaceSupport nss = new NamespaceSupport();
248 nss.pushContext();
249 for(int i=0; i < envelopeTag.ns.length; i+=2) {
250 nss.declarePrefix(envelopeTag.ns[i], envelopeTag.ns[i+1]);
251 }
253 nss.pushContext();
254 for(int i=0; i < bodyTag.ns.length; i+=2) {
255 nss.declarePrefix(bodyTag.ns[i], bodyTag.ns[i+1]);
256 }
258 List<String> inscope = new ArrayList<String>();
259 for( Enumeration en = nss.getPrefixes(); en.hasMoreElements(); ) {
260 String prefix = (String)en.nextElement();
261 inscope.add(prefix);
262 inscope.add(nss.getURI(prefix));
263 }
264 return inscope.toArray(new String[inscope.size()]);
265 }
267 public Object readPayloadAsJAXB(Unmarshaller unmarshaller) throws JAXBException {
268 if(!hasPayload())
269 return null;
270 assert unconsumed();
271 // TODO: How can the unmarshaller process this as a fragment?
272 if(hasAttachments())
273 unmarshaller.setAttachmentUnmarshaller(new AttachmentUnmarshallerImpl(getAttachments()));
274 try {
275 return unmarshaller.unmarshal(reader);
276 } finally{
277 unmarshaller.setAttachmentUnmarshaller(null);
278 XMLStreamReaderUtil.readRest(reader);
279 XMLStreamReaderUtil.close(reader);
280 XMLStreamReaderFactory.recycle(reader);
281 }
282 }
283 /** @deprecated */
284 public <T> T readPayloadAsJAXB(Bridge<T> bridge) throws JAXBException {
285 if(!hasPayload())
286 return null;
287 assert unconsumed();
288 T r = bridge.unmarshal(reader,
289 hasAttachments() ? new AttachmentUnmarshallerImpl(getAttachments()) : null);
290 XMLStreamReaderUtil.readRest(reader);
291 XMLStreamReaderUtil.close(reader);
292 XMLStreamReaderFactory.recycle(reader);
293 return r;
294 }
296 public <T> T readPayloadAsJAXB(XMLBridge<T> bridge) throws JAXBException {
297 if(!hasPayload())
298 return null;
299 assert unconsumed();
300 T r = bridge.unmarshal(reader,
301 hasAttachments() ? new AttachmentUnmarshallerImpl(getAttachments()) : null);
302 XMLStreamReaderUtil.readRest(reader);
303 XMLStreamReaderUtil.close(reader);
304 XMLStreamReaderFactory.recycle(reader);
305 return r;
306 }
308 @Override
309 public void consume() {
310 assert unconsumed();
311 XMLStreamReaderUtil.readRest(reader);
312 XMLStreamReaderUtil.close(reader);
313 XMLStreamReaderFactory.recycle(reader);
314 }
316 public XMLStreamReader readPayload() {
317 if(!hasPayload())
318 return null;
319 // TODO: What about access at and beyond </soap:Body>
320 assert unconsumed();
321 return this.reader;
322 }
324 public void writePayloadTo(XMLStreamWriter writer)throws XMLStreamException {
325 assert unconsumed();
327 if(payloadLocalName==null) {
328 return; // no body
329 }
331 if (bodyPrologue != null) {
332 writer.writeCharacters(bodyPrologue);
333 }
335 XMLStreamReaderToXMLStreamWriter conv = new XMLStreamReaderToXMLStreamWriter();
337 while(reader.getEventType() != XMLStreamConstants.END_DOCUMENT){
338 String name = reader.getLocalName();
339 String nsUri = reader.getNamespaceURI();
341 // After previous conv.bridge() call the cursor will be at END_ELEMENT.
342 // Check if its not soapenv:Body then move to next ELEMENT
343 if(reader.getEventType() == XMLStreamConstants.END_ELEMENT){
345 if (!isBodyElement(name, nsUri)){
346 // closing payload element: store epilogue for further signing, if applicable
347 // however if there more than one payloads exist - the last one is stored
348 String whiteSpaces = XMLStreamReaderUtil.nextWhiteSpaceContent(reader);
349 if (whiteSpaces != null) {
350 this.bodyEpilogue = whiteSpaces;
351 // write it to the message too
352 writer.writeCharacters(whiteSpaces);
353 }
354 } else {
355 // body closed > exit
356 break;
357 }
359 } else {
360 // payload opening element: copy payload to writer
361 conv.bridge(reader,writer);
362 }
363 }
365 XMLStreamReaderUtil.readRest(reader);
366 XMLStreamReaderUtil.close(reader);
367 XMLStreamReaderFactory.recycle(reader);
368 }
370 private boolean isBodyElement(String name, String nsUri) {
371 return name.equals("Body") && nsUri.equals(soapVersion.nsUri);
372 }
374 public void writeTo(XMLStreamWriter sw) throws XMLStreamException{
375 writeEnvelope(sw);
376 }
378 /**
379 * This method should be called when the StreamMessage is created with a payload
380 * @param writer
381 */
382 private void writeEnvelope(XMLStreamWriter writer) throws XMLStreamException {
383 writer.writeStartDocument();
384 envelopeTag.writeStart(writer);
386 //write headers
387 HeaderList hl = getHeaders();
388 if(hl.size() > 0){
389 headerTag.writeStart(writer);
390 for(Header h:hl){
391 h.writeTo(writer);
392 }
393 writer.writeEndElement();
394 }
395 bodyTag.writeStart(writer);
396 if(hasPayload())
397 writePayloadTo(writer);
398 writer.writeEndElement();
399 writer.writeEndElement();
400 writer.writeEndDocument();
401 }
403 public void writePayloadTo(ContentHandler contentHandler, ErrorHandler errorHandler, boolean fragment) throws SAXException {
404 assert unconsumed();
406 try {
407 if(payloadLocalName==null)
408 return; // no body
410 if (bodyPrologue != null) {
411 char[] chars = bodyPrologue.toCharArray();
412 contentHandler.characters(chars, 0, chars.length);
413 }
415 XMLStreamReaderToContentHandler conv = new XMLStreamReaderToContentHandler(reader,contentHandler,true,fragment,getInscopeNamespaces());
417 while(reader.getEventType() != XMLStreamConstants.END_DOCUMENT){
418 String name = reader.getLocalName();
419 String nsUri = reader.getNamespaceURI();
421 // After previous conv.bridge() call the cursor will be at END_ELEMENT.
422 // Check if its not soapenv:Body then move to next ELEMENT
423 if(reader.getEventType() == XMLStreamConstants.END_ELEMENT){
425 if (!isBodyElement(name, nsUri)){
426 // closing payload element: store epilogue for further signing, if applicable
427 // however if there more than one payloads exist - the last one is stored
428 String whiteSpaces = XMLStreamReaderUtil.nextWhiteSpaceContent(reader);
429 if (whiteSpaces != null) {
430 this.bodyEpilogue = whiteSpaces;
431 // write it to the message too
432 char[] chars = whiteSpaces.toCharArray();
433 contentHandler.characters(chars, 0, chars.length);
434 }
435 } else {
436 // body closed > exit
437 break;
438 }
440 } else {
441 // payload opening element: copy payload to writer
442 conv.bridge();
443 }
444 }
445 XMLStreamReaderUtil.readRest(reader);
446 XMLStreamReaderUtil.close(reader);
447 XMLStreamReaderFactory.recycle(reader);
448 } catch (XMLStreamException e) {
449 Location loc = e.getLocation();
450 if(loc==null) loc = DummyLocation.INSTANCE;
452 SAXParseException x = new SAXParseException(
453 e.getMessage(),loc.getPublicId(),loc.getSystemId(),loc.getLineNumber(),loc.getColumnNumber(),e);
454 errorHandler.error(x);
455 }
456 }
458 // TODO: this method should be probably rewritten to respect spaces between eelements; is it used at all?
459 public Message copy() {
460 try {
461 assert unconsumed();
462 consumedAt = null; // but we don't want to mark it as consumed
463 MutableXMLStreamBuffer xsb = new MutableXMLStreamBuffer();
464 StreamReaderBufferCreator c = new StreamReaderBufferCreator(xsb);
466 // preserving inscope namespaces from envelope, and body. Other option
467 // would be to create a filtering XMLStreamReader from reader+envelopeTag+bodyTag
468 c.storeElement(envelopeTag.nsUri, envelopeTag.localName, envelopeTag.prefix, envelopeTag.ns);
469 c.storeElement(bodyTag.nsUri, bodyTag.localName, bodyTag.prefix, bodyTag.ns);
471 if (hasPayload()) {
472 // Loop all the way for multi payload case
473 while(reader.getEventType() != XMLStreamConstants.END_DOCUMENT){
474 String name = reader.getLocalName();
475 String nsUri = reader.getNamespaceURI();
476 if(isBodyElement(name, nsUri) || (reader.getEventType() == XMLStreamConstants.END_DOCUMENT))
477 break;
478 c.create(reader);
480 // Skip whitespaces in between payload and </Body> or between elements
481 // those won't be in the message itself, but we store them in field bodyEpilogue
482 if (reader.isWhiteSpace()) {
483 bodyEpilogue = XMLStreamReaderUtil.currentWhiteSpaceContent(reader);
484 } else {
485 // clear it in case the existing was not the last one
486 // (we are interested only in the last one?)
487 bodyEpilogue = null;
488 }
489 }
490 }
491 c.storeEndElement(); // create structure element for </Body>
492 c.storeEndElement(); // create structure element for </Envelope>
493 c.storeEndElement(); // create structure element for END_DOCUMENT
495 XMLStreamReaderUtil.readRest(reader);
496 XMLStreamReaderUtil.close(reader);
497 XMLStreamReaderFactory.recycle(reader);
499 reader = xsb.readAsXMLStreamReader();
500 XMLStreamReader clone = xsb.readAsXMLStreamReader();
502 // advance to the start tag of the <Body> first child element
503 proceedToRootElement(reader);
504 proceedToRootElement(clone);
506 return new StreamMessage(envelopeTag, headerTag, attachmentSet, HeaderList.copy(headers), bodyPrologue, bodyTag, bodyEpilogue, clone, soapVersion);
507 } catch (XMLStreamException e) {
508 throw new WebServiceException("Failed to copy a message",e);
509 }
510 }
512 private void proceedToRootElement(XMLStreamReader xsr) throws XMLStreamException {
513 assert xsr.getEventType()==START_DOCUMENT;
514 xsr.nextTag();
515 xsr.nextTag();
516 xsr.nextTag();
517 assert xsr.getEventType()==START_ELEMENT || xsr.getEventType()==END_ELEMENT;
518 }
520 public void writeTo(ContentHandler contentHandler, ErrorHandler errorHandler ) throws SAXException {
521 contentHandler.setDocumentLocator(NULL_LOCATOR);
522 contentHandler.startDocument();
523 envelopeTag.writeStart(contentHandler);
524 headerTag.writeStart(contentHandler);
525 if(hasHeaders()) {
526 HeaderList headers = getHeaders();
527 int len = headers.size();
528 for( int i=0; i<len; i++ ) {
529 // shouldn't JDK be smart enough to use array-style indexing for this foreach!?
530 headers.get(i).writeTo(contentHandler,errorHandler);
531 }
532 }
533 headerTag.writeEnd(contentHandler);
534 bodyTag.writeStart(contentHandler);
535 writePayloadTo(contentHandler,errorHandler, true);
536 bodyTag.writeEnd(contentHandler);
537 envelopeTag.writeEnd(contentHandler);
538 contentHandler.endDocument();
539 }
541 /**
542 * Used for an assertion. Returns true when the message is unconsumed,
543 * or otherwise throw an exception.
544 *
545 * <p>
546 * Calling this method also marks the stream as 'consumed'
547 */
548 private boolean unconsumed() {
549 if(payloadLocalName==null)
550 return true; // no payload. can be consumed multiple times.
552 if(reader.getEventType()!=XMLStreamReader.START_ELEMENT) {
553 AssertionError error = new AssertionError("StreamMessage has been already consumed. See the nested exception for where it's consumed");
554 error.initCause(consumedAt);
555 throw error;
556 }
557 consumedAt = new Exception().fillInStackTrace();
558 return true;
559 }
561 private static void create(SOAPVersion v) {
562 int base = v.ordinal()*3;
563 DEFAULT_TAGS[base ] = new TagInfoset(v.nsUri,"Envelope","S",EMPTY_ATTS,"S",v.nsUri);
564 DEFAULT_TAGS[base+1] = new TagInfoset(v.nsUri,"Header","S",EMPTY_ATTS);
565 DEFAULT_TAGS[base+2] = new TagInfoset(v.nsUri,"Body","S",EMPTY_ATTS);
566 }
568 public String getBodyPrologue() {
569 return bodyPrologue;
570 }
572 public String getBodyEpilogue() {
573 return bodyEpilogue;
574 }
576 }