1/*
2 * Copyright (c) 1997, 2013, 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;
27
28import static com.sun.xml.internal.ws.binding.WebServiceFeatureList.getSoapVersion;
29
30import com.oracle.webservices.internal.impl.encoding.StreamDecoderImpl;
31import com.oracle.webservices.internal.impl.internalspi.encoding.StreamDecoder;
32import com.sun.istack.internal.NotNull;
33import com.sun.istack.internal.Nullable;
34import com.sun.xml.internal.stream.buffer.MutableXMLStreamBuffer;
35import com.sun.xml.internal.stream.buffer.XMLStreamBuffer;
36import com.sun.xml.internal.stream.buffer.XMLStreamBufferMark;
37import com.sun.xml.internal.stream.buffer.stax.StreamReaderBufferCreator;
38import com.sun.xml.internal.ws.api.SOAPVersion;
39import com.sun.xml.internal.ws.api.WSBinding;
40import com.sun.xml.internal.ws.api.WSFeatureList;
41import com.sun.xml.internal.ws.api.message.AttachmentSet;
42import com.sun.xml.internal.ws.api.message.Header;
43import com.sun.xml.internal.ws.api.message.HeaderList;
44import com.sun.xml.internal.ws.api.message.Message;
45import com.sun.xml.internal.ws.api.message.Packet;
46import com.sun.xml.internal.ws.api.pipe.ContentType;
47import com.sun.xml.internal.ws.api.streaming.XMLStreamWriterFactory;
48import com.sun.xml.internal.ws.developer.SerializationFeature;
49import com.sun.xml.internal.ws.message.AttachmentSetImpl;
50import com.sun.xml.internal.ws.message.stream.StreamMessage;
51import com.sun.xml.internal.ws.protocol.soap.VersionMismatchException;
52import com.sun.xml.internal.ws.server.UnsupportedMediaException;
53import com.sun.xml.internal.ws.streaming.XMLStreamReaderUtil;
54import com.sun.xml.internal.ws.util.ServiceFinder;
55
56import javax.xml.stream.XMLStreamConstants;
57import javax.xml.stream.XMLStreamException;
58import javax.xml.stream.XMLStreamReader;
59import javax.xml.stream.XMLStreamWriter;
60import javax.xml.ws.WebServiceException;
61import java.io.IOException;
62import java.io.InputStream;
63import java.io.OutputStream;
64import java.nio.channels.ReadableByteChannel;
65import java.nio.channels.WritableByteChannel;
66import java.nio.charset.Charset;
67import java.util.HashMap;
68import java.util.List;
69import java.util.Map;
70
71/**
72 * A stream SOAP codec.
73 *
74 * @author Paul Sandoz
75 */
76@SuppressWarnings({"StringEquality"})
77public abstract class StreamSOAPCodec implements com.sun.xml.internal.ws.api.pipe.StreamSOAPCodec, RootOnlyCodec {
78
79    private static final String SOAP_ENVELOPE = "Envelope";
80    private static final String SOAP_HEADER = "Header";
81    private static final String SOAP_BODY = "Body";
82
83    private final SOAPVersion soapVersion;
84    protected final SerializationFeature serializationFeature;
85
86    private final StreamDecoder streamDecoder;
87
88    // charset of last decoded message. Will be used for encoding server's
89    // response messages with the request message's encoding
90    // it will stored in the packet.invocationProperties
91    private final static String DECODED_MESSAGE_CHARSET = "decodedMessageCharset";
92
93    /*package*/ StreamSOAPCodec(SOAPVersion soapVersion) {
94        this(soapVersion, null);
95    }
96
97    /*package*/ StreamSOAPCodec(WSBinding binding) {
98        this(binding.getSOAPVersion(), binding.getFeature(SerializationFeature.class));
99    }
100
101    StreamSOAPCodec(WSFeatureList features) {
102        this(getSoapVersion(features), features.get(SerializationFeature.class));
103    }
104
105    private StreamSOAPCodec(SOAPVersion soapVersion, @Nullable SerializationFeature sf) {
106        this.soapVersion = soapVersion;
107        this.serializationFeature = sf;
108        this.streamDecoder = selectStreamDecoder();
109    }
110
111    private StreamDecoder selectStreamDecoder() {
112        for (StreamDecoder sd : ServiceFinder.find(StreamDecoder.class)) {
113            return sd;
114        }
115
116        return new StreamDecoderImpl();
117    }
118
119    public ContentType getStaticContentType(Packet packet) {
120        return getContentType(packet);
121    }
122
123    public ContentType encode(Packet packet, OutputStream out) {
124        if (packet.getMessage() != null) {
125            String encoding = getPacketEncoding(packet);
126            packet.invocationProperties.remove(DECODED_MESSAGE_CHARSET);
127            XMLStreamWriter writer = XMLStreamWriterFactory.create(out, encoding);
128            try {
129                packet.getMessage().writeTo(writer);
130                writer.flush();
131            } catch (XMLStreamException e) {
132                throw new WebServiceException(e);
133            }
134            XMLStreamWriterFactory.recycle(writer);
135        }
136        return getContentType(packet);
137    }
138
139    protected abstract ContentType getContentType(Packet packet);
140
141    protected abstract String getDefaultContentType();
142
143    public ContentType encode(Packet packet, WritableByteChannel buffer) {
144        //TODO: not yet implemented
145        throw new UnsupportedOperationException();
146    }
147
148    protected abstract List<String> getExpectedContentTypes();
149
150    public void decode(InputStream in, String contentType, Packet packet) throws IOException {
151        decode(in, contentType, packet, new AttachmentSetImpl());
152    }
153
154    /*
155     * Checks against expected Content-Type headers that is handled by a codec
156     *
157     * @param ct the Content-Type of the request
158     * @param expected expected Content-Types for a codec
159     * @return true if the codec supports this Content-Type
160     *         false otherwise
161     */
162    private static boolean isContentTypeSupported(String ct, List<String> expected) {
163        for(String contentType : expected) {
164            if (ct.contains(contentType)) {
165                return true;
166            }
167        }
168        return false;
169    }
170
171    /**
172     * Decodes a message from {@link XMLStreamReader} that points to
173     * the beginning of a SOAP infoset.
174     *
175     * @param reader
176     *      can point to the start document or the start element.
177     */
178    public final @NotNull Message decode(@NotNull XMLStreamReader reader) {
179        return decode(reader,new AttachmentSetImpl());
180    }
181
182    /**
183     * Decodes a message from {@link XMLStreamReader} that points to
184     * the beginning of a SOAP infoset.
185     *
186     * @param reader
187     *      can point to the start document or the start element.
188     * @param attachmentSet
189     *      {@link StreamSOAPCodec} can take attachments parsed outside,
190     *      so that this codec can be used as a part of a biggre codec
191     *      (like MIME multipart codec.)
192     */
193    public final Message decode(XMLStreamReader reader, @NotNull AttachmentSet attachmentSet) {
194        return decode(soapVersion, reader, attachmentSet);
195    }
196
197    public static final Message decode(SOAPVersion soapVersion, XMLStreamReader reader, @NotNull AttachmentSet attachmentSet) {
198        // Move to soap:Envelope and verify
199        if(reader.getEventType()!=XMLStreamConstants.START_ELEMENT)
200            XMLStreamReaderUtil.nextElementContent(reader);
201        XMLStreamReaderUtil.verifyReaderState(reader,XMLStreamConstants.START_ELEMENT);
202        if (SOAP_ENVELOPE.equals(reader.getLocalName()) && !soapVersion.nsUri.equals(reader.getNamespaceURI())) {
203            throw new VersionMismatchException(soapVersion, soapVersion.nsUri, reader.getNamespaceURI());
204        }
205        XMLStreamReaderUtil.verifyTag(reader, soapVersion.nsUri, SOAP_ENVELOPE);
206        return new StreamMessage(soapVersion, reader, attachmentSet);
207    }
208
209    public void decode(ReadableByteChannel in, String contentType, Packet packet ) {
210        throw new UnsupportedOperationException();
211    }
212
213    public final StreamSOAPCodec copy() {
214        return this;
215    }
216
217    public void decode(InputStream in, String contentType, Packet packet, AttachmentSet att ) throws IOException {
218        List<String> expectedContentTypes = getExpectedContentTypes();
219        if (contentType != null && !isContentTypeSupported(contentType,expectedContentTypes)) {
220            throw new UnsupportedMediaException(contentType, expectedContentTypes);
221        }
222        com.oracle.webservices.internal.api.message.ContentType pct = packet.getInternalContentType();
223        ContentTypeImpl cti = (pct != null && pct instanceof ContentTypeImpl) ?
224                (ContentTypeImpl)pct : new ContentTypeImpl(contentType);
225        String charset = cti.getCharSet();
226        if (charset != null && !Charset.isSupported(charset)) {
227            throw new UnsupportedMediaException(charset);
228        }
229        if (charset != null) {
230            packet.invocationProperties.put(DECODED_MESSAGE_CHARSET, charset);
231        } else {
232            packet.invocationProperties.remove(DECODED_MESSAGE_CHARSET);
233        }
234        packet.setMessage(streamDecoder.decode(in, charset, att, soapVersion));
235    }
236
237    public void decode(ReadableByteChannel in, String contentType, Packet response, AttachmentSet att ) {
238        throw new UnsupportedOperationException();
239    }
240
241    /*
242     * Creates a new {@link StreamSOAPCodec} instance.
243     */
244    public static StreamSOAPCodec create(SOAPVersion version) {
245        if(version==null)
246            // this decoder is for SOAP, not for XML/HTTP
247            throw new IllegalArgumentException();
248        switch(version) {
249            case SOAP_11:
250                return new StreamSOAP11Codec();
251            case SOAP_12:
252                return new StreamSOAP12Codec();
253            default:
254                throw new AssertionError();
255        }
256    }
257
258    /*
259     * Creates a new {@link StreamSOAPCodec} instance using binding
260     */
261    public static StreamSOAPCodec create(WSFeatureList features) {
262        SOAPVersion version = getSoapVersion(features);
263        if(version==null)
264            // this decoder is for SOAP, not for XML/HTTP
265            throw new IllegalArgumentException();
266        switch(version) {
267            case SOAP_11:
268                return new StreamSOAP11Codec(features);
269            case SOAP_12:
270                return new StreamSOAP12Codec(features);
271            default:
272                throw new AssertionError();
273        }
274    }
275
276    /**
277     * Creates a new {@link StreamSOAPCodec} instance using binding
278     *
279     * @deprecated use {@link #create(WSFeatureList)}
280     */
281    public static StreamSOAPCodec create(WSBinding binding) {
282        SOAPVersion version = binding.getSOAPVersion();
283        if(version==null)
284            // this decoder is for SOAP, not for XML/HTTP
285            throw new IllegalArgumentException();
286        switch(version) {
287            case SOAP_11:
288                return new StreamSOAP11Codec(binding);
289            case SOAP_12:
290                return new StreamSOAP12Codec(binding);
291            default:
292                throw new AssertionError();
293        }
294    }
295
296    private String getPacketEncoding(Packet packet) {
297        // If SerializationFeature is set, just use that encoding
298        if (serializationFeature != null && serializationFeature.getEncoding() != null) {
299            return serializationFeature.getEncoding().equals("")
300                    ? SOAPBindingCodec.DEFAULT_ENCODING : serializationFeature.getEncoding();
301        }
302
303        if (packet != null && packet.endpoint != null) {
304            // Use request message's encoding for Server-side response messages
305            String charset = (String)packet.invocationProperties.get(DECODED_MESSAGE_CHARSET);
306            return charset == null
307                    ? SOAPBindingCodec.DEFAULT_ENCODING : charset;
308        }
309
310        // Use default encoding for client-side request messages
311        return SOAPBindingCodec.DEFAULT_ENCODING;
312    }
313
314    protected ContentTypeImpl.Builder getContenTypeBuilder(Packet packet) {
315        ContentTypeImpl.Builder b = new ContentTypeImpl.Builder();
316        String encoding = getPacketEncoding(packet);
317        if (SOAPBindingCodec.DEFAULT_ENCODING.equalsIgnoreCase(encoding)) {
318            b.contentType = getDefaultContentType();
319            b.charset = SOAPBindingCodec.DEFAULT_ENCODING;
320            return b;
321        }
322        b.contentType = getMimeType()+" ;charset="+encoding;
323        b.charset = encoding;
324        return b;
325    }
326
327}
328