XMLHTTPBindingCodec.java revision 524:dcaa586ab756
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 com.sun.xml.internal.ws.api.SOAPVersion;
29import com.sun.xml.internal.ws.api.WSFeatureList;
30import com.sun.xml.internal.ws.api.message.Packet;
31import com.sun.xml.internal.ws.api.pipe.Codec;
32import com.sun.xml.internal.ws.api.pipe.ContentType;
33import com.sun.xml.internal.ws.client.ContentNegotiation;
34import com.sun.xml.internal.ws.encoding.xml.XMLCodec;
35import com.sun.xml.internal.ws.encoding.xml.XMLMessage;
36import com.sun.xml.internal.ws.encoding.xml.XMLMessage.MessageDataSource;
37import com.sun.xml.internal.ws.encoding.xml.XMLMessage.UnknownContent;
38import com.sun.xml.internal.ws.encoding.xml.XMLMessage.XMLMultiPart;
39import com.sun.xml.internal.ws.resources.StreamingMessages;
40import com.sun.xml.internal.ws.util.ByteArrayBuffer;
41
42import javax.activation.DataSource;
43import javax.xml.ws.WebServiceException;
44
45import java.io.IOException;
46import java.io.InputStream;
47import java.io.OutputStream;
48import java.lang.reflect.Method;
49import java.nio.channels.WritableByteChannel;
50import java.util.StringTokenizer;
51
52/**
53 * XML (infoset) over HTTP binding {@link Codec}.
54 * <p>
55 * TODO: Support FI for multipart/related
56 *       Support FI for MessageDataSource
57 *
58 * @author Jitendra Kotamraju
59 */
60public final class XMLHTTPBindingCodec extends MimeCodec {
61    /**
62     * Base HTTP Accept request-header.
63     */
64    private static final String BASE_ACCEPT_VALUE =
65        "*";
66
67    /**
68     * Fast Infoset MIME type.
69     */
70    private static final String APPLICATION_FAST_INFOSET_MIME_TYPE =
71        "application/fastinfoset";
72
73    /**
74     * True if the Fast Infoset codec should be used
75     */
76    private boolean useFastInfosetForEncoding;
77
78    /**
79     * The XML codec
80     */
81    private final Codec xmlCodec;
82
83    /**
84     * The FI codec
85     */
86    private final Codec fiCodec;
87
88    /**
89     * The Accept header for XML encodings
90     */
91    private static final String xmlAccept = null;
92
93    /**
94     * The Accept header for Fast Infoset and XML encodings
95     */
96    private static final String fiXmlAccept = APPLICATION_FAST_INFOSET_MIME_TYPE + ", " + BASE_ACCEPT_VALUE;
97
98    private ContentTypeImpl setAcceptHeader(Packet p, ContentType c) {
99        ContentTypeImpl ctImpl = (ContentTypeImpl)c;
100        if (p.contentNegotiation == ContentNegotiation.optimistic
101                || p.contentNegotiation == ContentNegotiation.pessimistic) {
102            ctImpl.setAcceptHeader(fiXmlAccept);
103        } else {
104            ctImpl.setAcceptHeader(xmlAccept);
105        }
106        p.setContentType(ctImpl);
107        return ctImpl;
108    }
109
110    public XMLHTTPBindingCodec(WSFeatureList f) {
111        super(SOAPVersion.SOAP_11, f);
112
113        xmlCodec = new XMLCodec(f);
114
115        fiCodec = getFICodec();
116    }
117
118    @Override
119    public String getMimeType() {
120        return null;
121    }
122
123    @Override
124    public ContentType getStaticContentType(Packet packet) {
125        ContentType ct;
126        if (packet.getInternalMessage() instanceof MessageDataSource) {
127            final MessageDataSource mds = (MessageDataSource)packet.getInternalMessage();
128            if (mds.hasUnconsumedDataSource()) {
129                ct = getStaticContentType(mds);
130                return (ct != null)
131                    ? setAcceptHeader(packet, ct) //_adaptingContentType.set(packet, ct)
132                    : null;
133            }
134        }
135
136        ct = super.getStaticContentType(packet);
137        return (ct != null)
138            ? setAcceptHeader(packet, ct) //_adaptingContentType.set(packet, ct)
139            : null;
140    }
141
142    @Override
143    public ContentType encode(Packet packet, OutputStream out) throws IOException {
144        if (packet.getInternalMessage() instanceof MessageDataSource) {
145            final MessageDataSource mds = (MessageDataSource)packet.getInternalMessage();
146            if (mds.hasUnconsumedDataSource())
147                return setAcceptHeader(packet, encode(mds, out));
148        }
149
150        return setAcceptHeader(packet, super.encode(packet, out));
151    }
152
153    @Override
154    public ContentType encode(Packet packet, WritableByteChannel buffer) {
155        throw new UnsupportedOperationException();
156    }
157
158    @Override
159    public void decode(InputStream in, String contentType, Packet packet) throws IOException {
160        /**
161         * Reset the encoding state when on the server side for each
162         * decode/encode step.
163         */
164        if (packet.contentNegotiation == null)
165            useFastInfosetForEncoding = false;
166
167        if (contentType == null) {
168            xmlCodec.decode(in, contentType, packet);
169        } else if (isMultipartRelated(contentType)) {
170            packet.setMessage(new XMLMultiPart(contentType, in, features));
171        } else if(isFastInfoset(contentType)) {
172            if (fiCodec == null) {
173                throw new RuntimeException(StreamingMessages.FASTINFOSET_NO_IMPLEMENTATION());
174            }
175
176            useFastInfosetForEncoding = true;
177            fiCodec.decode(in, contentType, packet);
178        } else if (isXml(contentType)) {
179            xmlCodec.decode(in, contentType, packet);
180        } else {
181            packet.setMessage(new UnknownContent(contentType, in));
182        }
183
184        if (!useFastInfosetForEncoding) {
185            useFastInfosetForEncoding = isFastInfosetAcceptable(packet.acceptableMimeTypes);
186        }
187    }
188
189    @Override
190    protected void decode(MimeMultipartParser mpp, Packet packet) throws IOException {
191        // This method will never be invoked
192    }
193
194    @Override
195    public MimeCodec copy() {
196        return new XMLHTTPBindingCodec(features);
197    }
198
199    private boolean isMultipartRelated(String contentType) {
200        return compareStrings(contentType, MimeCodec.MULTIPART_RELATED_MIME_TYPE);
201    }
202
203    private boolean isXml(String contentType) {
204        return compareStrings(contentType, XMLCodec.XML_APPLICATION_MIME_TYPE)
205                || compareStrings(contentType, XMLCodec.XML_TEXT_MIME_TYPE)
206                || (compareStrings(contentType, "application/")&&(contentType.toLowerCase().indexOf("+xml") != -1));
207    }
208
209    private boolean isFastInfoset(String contentType) {
210        return compareStrings(contentType, APPLICATION_FAST_INFOSET_MIME_TYPE);
211    }
212
213    private boolean compareStrings(String a, String b) {
214        return a.length() >= b.length() &&
215                b.equalsIgnoreCase(
216                    a.substring(0,
217                        b.length()));
218    }
219
220    private boolean isFastInfosetAcceptable(String accept) {
221        if (accept == null) return false;
222
223        StringTokenizer st = new StringTokenizer(accept, ",");
224        while (st.hasMoreTokens()) {
225            final String token = st.nextToken().trim();
226            if (token.equalsIgnoreCase(APPLICATION_FAST_INFOSET_MIME_TYPE)) {
227                return true;
228            }
229        }
230        return false;
231    }
232
233    private ContentType getStaticContentType(MessageDataSource mds) {
234        final String contentType = mds.getDataSource().getContentType();
235        final boolean isFastInfoset = XMLMessage.isFastInfoset(contentType);
236
237        if (!requiresTransformationOfDataSource(isFastInfoset,
238                useFastInfosetForEncoding)) {
239            return new ContentTypeImpl(contentType);
240        } else {
241            return null;
242        }
243    }
244
245    private ContentType encode(MessageDataSource mds, OutputStream out) {
246        try {
247            final boolean isFastInfoset = XMLMessage.isFastInfoset(
248                    mds.getDataSource().getContentType());
249            DataSource ds = transformDataSource(mds.getDataSource(),
250                    isFastInfoset, useFastInfosetForEncoding, features);
251
252            InputStream is = ds.getInputStream();
253            byte[] buf = new byte[1024];
254            int count;
255            while((count=is.read(buf)) != -1) {
256                out.write(buf, 0, count);
257            }
258            return new ContentTypeImpl(ds.getContentType());
259        } catch(IOException ioe) {
260            throw new WebServiceException(ioe);
261        }
262    }
263
264    @Override
265    protected Codec getMimeRootCodec(Packet p) {
266        /**
267         * The following logic is only for outbound packets
268         * to be encoded by client.
269         * On the server the p.contentNegotiation == null.
270         */
271        if (p.contentNegotiation == ContentNegotiation.none) {
272            // The client may have changed the negotiation property from
273            // pessismistic to none between invocations
274            useFastInfosetForEncoding = false;
275        } else if (p.contentNegotiation == ContentNegotiation.optimistic) {
276            // Always encode using Fast Infoset if in optimisitic mode
277            useFastInfosetForEncoding = true;
278        }
279
280        return (useFastInfosetForEncoding && fiCodec != null)? fiCodec : xmlCodec;
281    }
282
283    public static boolean requiresTransformationOfDataSource(
284            boolean isFastInfoset, boolean useFastInfoset) {
285        return (isFastInfoset && !useFastInfoset) || (!isFastInfoset && useFastInfoset);
286    }
287
288    public static DataSource transformDataSource(DataSource in,
289            boolean isFastInfoset, boolean useFastInfoset, WSFeatureList f) {
290        try {
291            if (isFastInfoset && !useFastInfoset) {
292                // Convert from Fast Infoset to XML
293                Codec codec = new XMLHTTPBindingCodec(f);
294                Packet p = new Packet();
295                codec.decode(in.getInputStream(), in.getContentType(), p);
296
297                p.getMessage().getAttachments();
298                codec.getStaticContentType(p);
299
300                ByteArrayBuffer bos = new ByteArrayBuffer();
301                ContentType ct = codec.encode(p, bos);
302                return XMLMessage.createDataSource(ct.getContentType(), bos.newInputStream());
303            } else if (!isFastInfoset && useFastInfoset) {
304                // Convert from XML to Fast Infoset
305                Codec codec = new XMLHTTPBindingCodec(f);
306                Packet p = new Packet();
307                codec.decode(in.getInputStream(), in.getContentType(), p);
308
309                p.contentNegotiation = ContentNegotiation.optimistic;
310                p.getMessage().getAttachments();
311                codec.getStaticContentType(p);
312
313                ByteArrayBuffer bos = new ByteArrayBuffer();
314                com.sun.xml.internal.ws.api.pipe.ContentType ct = codec.encode(p, bos);
315                return XMLMessage.createDataSource(ct.getContentType(), bos.newInputStream());
316            }
317        } catch(Exception ex) {
318            throw new WebServiceException(ex);
319        }
320
321        return in;
322    }
323
324    /**
325     * Obtain an FI SOAP codec instance using reflection.
326     */
327    private static Codec getFICodec() {
328        try {
329            Class c = Class.forName("com.sun.xml.internal.ws.encoding.fastinfoset.FastInfosetCodec");
330            Method m = c.getMethod("create");
331            return (Codec)m.invoke(null);
332        } catch (Exception e) {
333            return null;
334        }
335    }
336}
337