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
26package com.sun.xml.internal.ws.encoding.xml;
27
28import com.sun.istack.internal.NotNull;
29import com.sun.xml.internal.bind.api.Bridge;
30import com.sun.xml.internal.ws.api.SOAPVersion;
31import com.sun.xml.internal.ws.api.WSFeatureList;
32import com.sun.xml.internal.ws.api.message.*;
33import com.sun.xml.internal.ws.api.model.wsdl.WSDLPort;
34import com.sun.xml.internal.ws.api.pipe.Codec;
35import com.sun.xml.internal.ws.api.streaming.XMLStreamWriterFactory;
36import com.sun.xml.internal.ws.developer.StreamingAttachmentFeature;
37import com.sun.xml.internal.ws.encoding.ContentType;
38import com.sun.xml.internal.ws.encoding.MimeMultipartParser;
39import com.sun.xml.internal.ws.encoding.XMLHTTPBindingCodec;
40import com.sun.xml.internal.ws.message.AbstractMessageImpl;
41import com.sun.xml.internal.ws.message.EmptyMessageImpl;
42import com.sun.xml.internal.ws.message.MimeAttachmentSet;
43import com.sun.xml.internal.ws.message.source.PayloadSourceMessage;
44import com.sun.xml.internal.ws.util.ByteArrayBuffer;
45import com.sun.xml.internal.ws.util.StreamUtils;
46import org.xml.sax.ContentHandler;
47import org.xml.sax.ErrorHandler;
48import org.xml.sax.SAXException;
49
50import javax.activation.DataSource;
51import javax.xml.bind.JAXBException;
52import javax.xml.bind.Unmarshaller;
53import javax.xml.soap.SOAPException;
54import javax.xml.soap.SOAPMessage;
55import javax.xml.stream.XMLStreamException;
56import javax.xml.stream.XMLStreamReader;
57import javax.xml.stream.XMLStreamWriter;
58import javax.xml.transform.Source;
59import javax.xml.transform.stream.StreamSource;
60import javax.xml.ws.WebServiceException;
61
62import java.io.IOException;
63import java.io.InputStream;
64import java.io.OutputStream;
65
66/**
67 *
68 * @author Jitendra Kotamraju
69 */
70public 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}
638