View Javadoc
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  
26  package com.sun.xml.internal.ws.encoding;
27  
28  import com.sun.xml.internal.ws.api.SOAPVersion;
29  import com.sun.xml.internal.ws.api.WSFeatureList;
30  import com.sun.xml.internal.ws.api.message.Packet;
31  import com.sun.xml.internal.ws.api.pipe.Codec;
32  import com.sun.xml.internal.ws.api.pipe.ContentType;
33  import com.sun.xml.internal.ws.client.ContentNegotiation;
34  import com.sun.xml.internal.ws.encoding.xml.XMLCodec;
35  import com.sun.xml.internal.ws.encoding.xml.XMLMessage;
36  import com.sun.xml.internal.ws.encoding.xml.XMLMessage.MessageDataSource;
37  import com.sun.xml.internal.ws.encoding.xml.XMLMessage.UnknownContent;
38  import com.sun.xml.internal.ws.encoding.xml.XMLMessage.XMLMultiPart;
39  import com.sun.xml.internal.ws.resources.StreamingMessages;
40  import com.sun.xml.internal.ws.util.ByteArrayBuffer;
41  
42  import javax.activation.DataSource;
43  import javax.xml.ws.WebServiceException;
44  
45  import java.io.IOException;
46  import java.io.InputStream;
47  import java.io.OutputStream;
48  import java.lang.reflect.Method;
49  import java.nio.channels.WritableByteChannel;
50  import 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   */
60  public 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 }