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 }