1 /* 2 * Copyright (c) 1997, 2014, 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.ws.encoding.xml; 27 28 import com.sun.istack.internal.NotNull; 29 import com.sun.xml.internal.bind.api.Bridge; 30 import com.sun.xml.internal.ws.api.SOAPVersion; 31 import com.sun.xml.internal.ws.api.WSFeatureList; 32 import com.sun.xml.internal.ws.api.message.*; 33 import com.sun.xml.internal.ws.api.model.wsdl.WSDLPort; 34 import com.sun.xml.internal.ws.api.pipe.Codec; 35 import com.sun.xml.internal.ws.api.streaming.XMLStreamWriterFactory; 36 import com.sun.xml.internal.ws.developer.StreamingAttachmentFeature; 37 import com.sun.xml.internal.ws.encoding.ContentType; 38 import com.sun.xml.internal.ws.encoding.MimeMultipartParser; 39 import com.sun.xml.internal.ws.encoding.XMLHTTPBindingCodec; 40 import com.sun.xml.internal.ws.message.AbstractMessageImpl; 41 import com.sun.xml.internal.ws.message.EmptyMessageImpl; 42 import com.sun.xml.internal.ws.message.MimeAttachmentSet; 43 import com.sun.xml.internal.ws.message.source.PayloadSourceMessage; 44 import com.sun.xml.internal.ws.util.ByteArrayBuffer; 45 import com.sun.xml.internal.ws.util.StreamUtils; 46 import org.xml.sax.ContentHandler; 47 import org.xml.sax.ErrorHandler; 48 import org.xml.sax.SAXException; 49 50 import javax.activation.DataSource; 51 import javax.xml.bind.JAXBException; 52 import javax.xml.bind.Unmarshaller; 53 import javax.xml.soap.SOAPException; 54 import javax.xml.soap.SOAPMessage; 55 import javax.xml.stream.XMLStreamException; 56 import javax.xml.stream.XMLStreamReader; 57 import javax.xml.stream.XMLStreamWriter; 58 import javax.xml.transform.Source; 59 import javax.xml.transform.stream.StreamSource; 60 import javax.xml.ws.WebServiceException; 61 62 import java.io.IOException; 63 import java.io.InputStream; 64 import java.io.OutputStream; 65 66 /** 67 * 68 * @author Jitendra Kotamraju 69 */ 70 public final class XMLMessage { 71 72 private static final int PLAIN_XML_FLAG = 1; // 00001 73 private static final int MIME_MULTIPART_FLAG = 2; // 00010 74 private static final int FI_ENCODED_FLAG = 16; // 10000 75 76 /* 77 * Construct a message given a content type and an input stream. 78 */ 79 public static Message create(final String ct, InputStream in, WSFeatureList f) { 80 Message data; 81 try { 82 in = StreamUtils.hasSomeData(in); 83 if (in == null) { 84 return Messages.createEmpty(SOAPVersion.SOAP_11); 85 } 86 87 if (ct != null) { 88 final ContentType contentType = new ContentType(ct); 89 final int contentTypeId = identifyContentType(contentType); 90 if ((contentTypeId & MIME_MULTIPART_FLAG) != 0) { 91 data = new XMLMultiPart(ct, in, f); 92 } else if ((contentTypeId & PLAIN_XML_FLAG) != 0) { 93 data = new XmlContent(ct, in, f); 94 } else { 95 data = new UnknownContent(ct, in); 96 } 97 } else { 98 // According to HTTP spec 7.2.1, if the media type remain 99 // unknown, treat as application/octet-stream 100 data = new UnknownContent("application/octet-stream", in); 101 } 102 } catch(Exception ex) { 103 throw new WebServiceException(ex); 104 } 105 return data; 106 } 107 108 109 public static Message create(Source source) { 110 return (source == null) ? 111 Messages.createEmpty(SOAPVersion.SOAP_11) : 112 Messages.createUsingPayload(source, SOAPVersion.SOAP_11); 113 } 114 115 public static Message create(DataSource ds, WSFeatureList f) { 116 try { 117 return (ds == null) ? 118 Messages.createEmpty(SOAPVersion.SOAP_11) : 119 create(ds.getContentType(), ds.getInputStream(), f); 120 } catch(IOException ioe) { 121 throw new WebServiceException(ioe); 122 } 123 } 124 125 public static Message create(Exception e) { 126 return new FaultMessage(SOAPVersion.SOAP_11); 127 } 128 129 /* 130 * Get the content type ID from the content type. 131 */ 132 private static int getContentId(String ct) { 133 try { 134 final ContentType contentType = new ContentType(ct); 135 return identifyContentType(contentType); 136 } catch(Exception ex) { 137 throw new WebServiceException(ex); 138 } 139 } 140 141 /** 142 * Return true if the content uses fast infoset. 143 */ 144 public static boolean isFastInfoset(String ct) { 145 return (getContentId(ct) & FI_ENCODED_FLAG) != 0; 146 } 147 148 /* 149 * Verify a contentType. 150 * 151 * @return 152 * MIME_MULTIPART_FLAG | PLAIN_XML_FLAG 153 * MIME_MULTIPART_FLAG | FI_ENCODED_FLAG; 154 * PLAIN_XML_FLAG 155 * FI_ENCODED_FLAG 156 * 157 */ 158 public static int identifyContentType(ContentType contentType) { 159 String primary = contentType.getPrimaryType(); 160 String sub = contentType.getSubType(); 161 162 if (primary.equalsIgnoreCase("multipart") && sub.equalsIgnoreCase("related")) { 163 String type = contentType.getParameter("type"); 164 if (type != null) { 165 if (isXMLType(type)) { 166 return MIME_MULTIPART_FLAG | PLAIN_XML_FLAG; 167 } else if (isFastInfosetType(type)) { 168 return MIME_MULTIPART_FLAG | FI_ENCODED_FLAG; 169 } 170 } 171 return 0; 172 } else if (isXMLType(primary, sub)) { 173 return PLAIN_XML_FLAG; 174 } else if (isFastInfosetType(primary, sub)) { 175 return FI_ENCODED_FLAG; 176 } 177 return 0; 178 } 179 180 protected static boolean isXMLType(@NotNull String primary, @NotNull String sub) { 181 return (primary.equalsIgnoreCase("text") && sub.equalsIgnoreCase("xml")) 182 || (primary.equalsIgnoreCase("application") && sub.equalsIgnoreCase("xml")) 183 || (primary.equalsIgnoreCase("application") && sub.toLowerCase().endsWith("+xml")); 184 } 185 186 protected static boolean isXMLType(String type) { 187 String lowerType = type.toLowerCase(); 188 return lowerType.startsWith("text/xml") 189 || lowerType.startsWith("application/xml") 190 || (lowerType.startsWith("application/") && (lowerType.indexOf("+xml") != -1)); 191 } 192 193 protected static boolean isFastInfosetType(String primary, String sub) { 194 return primary.equalsIgnoreCase("application") && sub.equalsIgnoreCase("fastinfoset"); 195 } 196 197 protected static boolean isFastInfosetType(String type) { 198 return type.toLowerCase().startsWith("application/fastinfoset"); 199 } 200 201 202 /** 203 * Access a {@link Message} as a {@link DataSource}. 204 * <p> 205 * A {@link Message} implementation will implement this if the 206 * messages is to be access as data source. 207 * <p> 208 * TODO: consider putting as part of the API. 209 */ 210 public static interface MessageDataSource { 211 /** 212 * Check if the data source has been consumed. 213 * @return true of the data source has been consumed, otherwise false. 214 */ 215 boolean hasUnconsumedDataSource(); 216 217 /** 218 * Get the data source. 219 * @return the data source. 220 */ 221 DataSource getDataSource(); 222 } 223 224 /** 225 * It's conent-type is some XML type 226 * 227 */ 228 private static class XmlContent extends AbstractMessageImpl implements MessageDataSource { 229 private final XmlDataSource dataSource; 230 private boolean consumed; 231 private Message delegate; 232 private final HeaderList headerList; 233 // private final WSBinding binding; 234 private WSFeatureList features; 235 236 public XmlContent(String ct, InputStream in, WSFeatureList f) { 237 super(SOAPVersion.SOAP_11); 238 dataSource = new XmlDataSource(ct, in); 239 this.headerList = new HeaderList(SOAPVersion.SOAP_11); 240 // this.binding = binding; 241 features = f; 242 } 243 244 private Message getMessage() { 245 if (delegate == null) { 246 InputStream in = dataSource.getInputStream(); 247 assert in != null; 248 delegate = Messages.createUsingPayload(new StreamSource(in), SOAPVersion.SOAP_11); 249 consumed = true; 250 } 251 return delegate; 252 } 253 254 public boolean hasUnconsumedDataSource() { 255 return !dataSource.consumed()&&!consumed; 256 } 257 258 public DataSource getDataSource() { 259 return hasUnconsumedDataSource() ? dataSource : 260 XMLMessage.getDataSource(getMessage(), features); 261 } 262 263 public boolean hasHeaders() { 264 return false; 265 } 266 267 public @NotNull MessageHeaders getHeaders() { 268 return headerList; 269 } 270 271 public String getPayloadLocalPart() { 272 return getMessage().getPayloadLocalPart(); 273 } 274 275 public String getPayloadNamespaceURI() { 276 return getMessage().getPayloadNamespaceURI(); 277 } 278 279 public boolean hasPayload() { 280 return true; 281 } 282 283 public boolean isFault() { 284 return false; 285 } 286 287 public Source readEnvelopeAsSource() { 288 return getMessage().readEnvelopeAsSource(); 289 } 290 291 public Source readPayloadAsSource() { 292 return getMessage().readPayloadAsSource(); 293 } 294 295 public SOAPMessage readAsSOAPMessage() throws SOAPException { 296 return getMessage().readAsSOAPMessage(); 297 } 298 299 public SOAPMessage readAsSOAPMessage(Packet packet, boolean inbound) throws SOAPException { 300 return getMessage().readAsSOAPMessage(packet, inbound); 301 } 302 303 public <T> T readPayloadAsJAXB(Unmarshaller unmarshaller) throws JAXBException { 304 return (T)getMessage().readPayloadAsJAXB(unmarshaller); 305 } 306 /** @deprecated */ 307 public <T> T readPayloadAsJAXB(Bridge<T> bridge) throws JAXBException { 308 return getMessage().readPayloadAsJAXB(bridge); 309 } 310 311 public XMLStreamReader readPayload() throws XMLStreamException { 312 return getMessage().readPayload(); 313 } 314 315 316 public void writePayloadTo(XMLStreamWriter sw) throws XMLStreamException { 317 getMessage().writePayloadTo(sw); 318 } 319 320 public void writeTo(XMLStreamWriter sw) throws XMLStreamException { 321 getMessage().writeTo(sw); 322 } 323 324 public void writeTo(ContentHandler contentHandler, ErrorHandler errorHandler) throws SAXException { 325 getMessage().writeTo(contentHandler, errorHandler); 326 } 327 328 public Message copy() { 329 return getMessage().copy().copyFrom(getMessage()); 330 } 331 332 protected void writePayloadTo(ContentHandler contentHandler, ErrorHandler errorHandler, boolean fragment) throws SAXException { 333 throw new UnsupportedOperationException(); 334 } 335 336 } 337 338 339 340 /** 341 * Data represented as a multi-part MIME message. 342 * <p> 343 * The root part may be an XML or an FI document. This class 344 * parses MIME message lazily. 345 */ 346 public static final class XMLMultiPart extends AbstractMessageImpl implements MessageDataSource { 347 private final DataSource dataSource; 348 private final StreamingAttachmentFeature feature; 349 private Message delegate; 350 private HeaderList headerList;// = new HeaderList(); 351 // private final WSBinding binding; 352 private final WSFeatureList features; 353 354 public XMLMultiPart(final String contentType, final InputStream is, WSFeatureList f) { 355 super(SOAPVersion.SOAP_11); 356 headerList = new HeaderList(SOAPVersion.SOAP_11); 357 dataSource = createDataSource(contentType, is); 358 this.feature = f.get(StreamingAttachmentFeature.class); 359 this.features = f; 360 } 361 362 private Message getMessage() { 363 if (delegate == null) { 364 MimeMultipartParser mpp; 365 try { 366 mpp = new MimeMultipartParser(dataSource.getInputStream(), 367 dataSource.getContentType(), feature); 368 } catch(IOException ioe) { 369 throw new WebServiceException(ioe); 370 } 371 InputStream in = mpp.getRootPart().asInputStream(); 372 assert in != null; 373 delegate = new PayloadSourceMessage(headerList, new StreamSource(in), new MimeAttachmentSet(mpp), SOAPVersion.SOAP_11); 374 } 375 return delegate; 376 } 377 378 public boolean hasUnconsumedDataSource() { 379 return delegate == null; 380 } 381 382 public DataSource getDataSource() { 383 return hasUnconsumedDataSource() ? dataSource : 384 XMLMessage.getDataSource(getMessage(), features); 385 } 386 387 public boolean hasHeaders() { 388 return false; 389 } 390 391 public @NotNull MessageHeaders getHeaders() { 392 return headerList; 393 } 394 395 public String getPayloadLocalPart() { 396 return getMessage().getPayloadLocalPart(); 397 } 398 399 public String getPayloadNamespaceURI() { 400 return getMessage().getPayloadNamespaceURI(); 401 } 402 403 public boolean hasPayload() { 404 return true; 405 } 406 407 public boolean isFault() { 408 return false; 409 } 410 411 public Source readEnvelopeAsSource() { 412 return getMessage().readEnvelopeAsSource(); 413 } 414 415 public Source readPayloadAsSource() { 416 return getMessage().readPayloadAsSource(); 417 } 418 419 public SOAPMessage readAsSOAPMessage() throws SOAPException { 420 return getMessage().readAsSOAPMessage(); 421 } 422 423 public SOAPMessage readAsSOAPMessage(Packet packet, boolean inbound) throws SOAPException { 424 return getMessage().readAsSOAPMessage(packet, inbound); 425 } 426 427 public <T> T readPayloadAsJAXB(Unmarshaller unmarshaller) throws JAXBException { 428 return (T)getMessage().readPayloadAsJAXB(unmarshaller); 429 } 430 431 public <T> T readPayloadAsJAXB(Bridge<T> bridge) throws JAXBException { 432 return getMessage().readPayloadAsJAXB(bridge); 433 } 434 435 public XMLStreamReader readPayload() throws XMLStreamException { 436 return getMessage().readPayload(); 437 } 438 439 public void writePayloadTo(XMLStreamWriter sw) throws XMLStreamException { 440 getMessage().writePayloadTo(sw); 441 } 442 443 public void writeTo(XMLStreamWriter sw) throws XMLStreamException { 444 getMessage().writeTo(sw); 445 } 446 447 public void writeTo(ContentHandler contentHandler, ErrorHandler errorHandler) throws SAXException { 448 getMessage().writeTo(contentHandler, errorHandler); 449 } 450 451 public Message copy() { 452 return getMessage().copy().copyFrom(getMessage()); 453 } 454 455 protected void writePayloadTo(ContentHandler contentHandler, ErrorHandler errorHandler, boolean fragment) throws SAXException { 456 throw new UnsupportedOperationException(); 457 } 458 459 @Override 460 public boolean isOneWay(@NotNull WSDLPort port) { 461 return false; 462 } 463 464 public @NotNull AttachmentSet getAttachments() { 465 return getMessage().getAttachments(); 466 } 467 468 } 469 470 private static class FaultMessage extends EmptyMessageImpl { 471 472 public FaultMessage(SOAPVersion version) { 473 super(version); 474 } 475 476 @Override 477 public boolean isFault() { 478 return true; 479 } 480 } 481 482 483 /** 484 * Don't know about this content. It's conent-type is NOT the XML types 485 * we recognize(text/xml, application/xml, multipart/related;text/xml etc). 486 * 487 * This could be used to represent image/jpeg etc 488 */ 489 public static class UnknownContent extends AbstractMessageImpl implements MessageDataSource { 490 private final DataSource ds; 491 private final HeaderList headerList; 492 493 public UnknownContent(final String ct, final InputStream in) { 494 this(createDataSource(ct,in)); 495 } 496 497 public UnknownContent(DataSource ds) { 498 super(SOAPVersion.SOAP_11); 499 this.ds = ds; 500 this.headerList = new HeaderList(SOAPVersion.SOAP_11); 501 } 502 503 /* 504 * Copy constructor. 505 */ 506 private UnknownContent(UnknownContent that) { 507 super(that.soapVersion); 508 this.ds = that.ds; 509 this.headerList = HeaderList.copy(that.headerList); 510 this.copyFrom(that); 511 } 512 513 public boolean hasUnconsumedDataSource() { 514 return true; 515 } 516 517 public DataSource getDataSource() { 518 assert ds != null; 519 return ds; 520 } 521 522 protected void writePayloadTo(ContentHandler contentHandler, 523 ErrorHandler errorHandler, boolean fragment) throws SAXException { 524 throw new UnsupportedOperationException(); 525 } 526 527 public boolean hasHeaders() { 528 return false; 529 } 530 531 public boolean isFault() { 532 return false; 533 } 534 535 public MessageHeaders getHeaders() { 536 return headerList; 537 } 538 539 public String getPayloadLocalPart() { 540 throw new UnsupportedOperationException(); 541 } 542 543 public String getPayloadNamespaceURI() { 544 throw new UnsupportedOperationException(); 545 } 546 547 public boolean hasPayload() { 548 return false; 549 } 550 551 public Source readPayloadAsSource() { 552 return null; 553 } 554 555 public XMLStreamReader readPayload() throws XMLStreamException { 556 throw new WebServiceException("There isn't XML payload. Shouldn't come here."); 557 } 558 559 public void writePayloadTo(XMLStreamWriter sw) throws XMLStreamException { 560 // No XML. Nothing to do 561 } 562 563 public Message copy() { 564 return new UnknownContent(this).copyFrom(this); 565 } 566 567 } 568 569 public static DataSource getDataSource(Message msg, WSFeatureList f) { 570 if (msg == null) 571 return null; 572 if (msg instanceof MessageDataSource) { 573 return ((MessageDataSource)msg).getDataSource(); 574 } else { 575 AttachmentSet atts = msg.getAttachments(); 576 if (atts != null && !atts.isEmpty()) { 577 final ByteArrayBuffer bos = new ByteArrayBuffer(); 578 try { 579 Codec codec = new XMLHTTPBindingCodec(f); 580 Packet packet = new Packet(msg); 581 com.sun.xml.internal.ws.api.pipe.ContentType ct = codec.getStaticContentType(packet); 582 codec.encode(packet, bos); 583 return createDataSource(ct.getContentType(), bos.newInputStream()); 584 } catch(IOException ioe) { 585 throw new WebServiceException(ioe); 586 } 587 588 } else { 589 final ByteArrayBuffer bos = new ByteArrayBuffer(); 590 XMLStreamWriter writer = XMLStreamWriterFactory.create(bos); 591 try { 592 msg.writePayloadTo(writer); 593 writer.flush(); 594 } catch (XMLStreamException e) { 595 throw new WebServiceException(e); 596 } 597 return XMLMessage.createDataSource("text/xml", bos.newInputStream()); 598 } 599 } 600 } 601 602 public static DataSource createDataSource(final String contentType, final InputStream is) { 603 return new XmlDataSource(contentType, is); 604 } 605 606 private static class XmlDataSource implements DataSource { 607 private final String contentType; 608 private final InputStream is; 609 private boolean consumed; 610 611 XmlDataSource(String contentType, final InputStream is) { 612 this.contentType = contentType; 613 this.is = is; 614 } 615 616 public boolean consumed() { 617 return consumed; 618 } 619 620 public InputStream getInputStream() { 621 consumed = !consumed; 622 return is; 623 } 624 625 public OutputStream getOutputStream() { 626 return null; 627 } 628 629 public String getContentType() { 630 return contentType; 631 } 632 633 public String getName() { 634 return ""; 635 } 636 } 637 }