src/share/jaxws_classes/com/sun/xml/internal/ws/encoding/SOAPBindingCodec.java

changeset 286
f50545b5e2f1
child 368
0989ad8c0860
equal deleted inserted replaced
284:88b85470e72c 286:f50545b5e2f1
1 /*
2 * Copyright (c) 1997, 2011, 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.client.SelectOptimalEncodingFeature;
31 import com.sun.xml.internal.ws.api.fastinfoset.FastInfosetFeature;
32 import com.sun.xml.internal.ws.api.message.Message;
33 import com.sun.xml.internal.ws.api.message.Packet;
34 import com.sun.xml.internal.ws.api.message.ExceptionHasMessage;
35 import com.sun.xml.internal.ws.api.pipe.Codec;
36 import com.sun.xml.internal.ws.api.pipe.Codecs;
37 import com.sun.xml.internal.ws.api.pipe.ContentType;
38 import com.sun.xml.internal.ws.api.pipe.StreamSOAPCodec;
39 import com.sun.xml.internal.ws.client.ContentNegotiation;
40 import com.sun.xml.internal.ws.protocol.soap.MessageCreationException;
41 import com.sun.xml.internal.ws.resources.StreamingMessages;
42 import com.sun.xml.internal.ws.server.UnsupportedMediaException;
43 import static com.sun.xml.internal.ws.binding.WebServiceFeatureList.getSoapVersion;
44
45 import javax.xml.ws.WebServiceException;
46 import javax.xml.ws.WebServiceFeature;
47 import javax.xml.ws.soap.MTOMFeature;
48 import java.io.IOException;
49 import java.io.InputStream;
50 import java.io.OutputStream;
51 import java.lang.reflect.Method;
52 import java.nio.channels.ReadableByteChannel;
53 import java.nio.channels.WritableByteChannel;
54 import java.util.StringTokenizer;
55
56 /**
57 * SOAP binding {@link Codec} that can handle MTOM, SwA, and SOAP messages
58 * encoded using XML or Fast Infoset.
59 *
60 * <p>
61 * This is used when we need to determine the encoding from what we received (for decoding)
62 * and from configuration and {@link Message} contents (for encoding)
63 *
64 * <p>
65 * TODO: Split this Codec into two, one that supports FI and one that does not.
66 * Then further split the FI Codec into two, one for client and one for
67 * server. This will simplify the logic and make it easier to understand/maintain.
68 *
69 * @author Vivek Pandey
70 * @author Kohsuke Kawaguchi
71 */
72 public class SOAPBindingCodec extends MimeCodec implements com.sun.xml.internal.ws.api.pipe.SOAPBindingCodec {
73
74 public static final String UTF8_ENCODING = "utf-8";
75 public static final String DEFAULT_ENCODING = UTF8_ENCODING;
76
77 /**
78 * Based on request's Accept header this is set.
79 * Currently only set if MTOMFeature is enabled.
80 *
81 * Should be used on server-side, for encoding the response.
82 */
83 private boolean acceptMtomMessages;
84
85 /**
86 * If the request's Content-Type is multipart/related; type=application/xop+xml, then this set to to true
87 *
88 * Used on server-side, for encoding the repsonse.
89 */
90 private boolean isRequestMtomMessage;
91
92 private enum TriState {UNSET,TRUE,FALSE}
93
94 /**
95 * This captures is decode is called before encode,
96 * if true, infers that this is being used on Server-side
97 */
98 private TriState decodeFirst = TriState.UNSET;
99
100 /**
101 * True if Fast Infoset functionality has been
102 * configured to be disabled, or the Fast Infoset
103 * runtime is not available.
104 */
105 private boolean isFastInfosetDisabled;
106
107 /**
108 * True if the Fast Infoset codec should be used for encoding.
109 */
110 private boolean useFastInfosetForEncoding;
111
112 /**
113 * True if the content negotiation property should
114 * be ignored by the client. This will be used in
115 * the case of Fast Infoset being configured to be
116 * disabled or automatically selected.
117 */
118 private boolean ignoreContentNegotiationProperty;
119
120 // The XML SOAP codec
121 private final StreamSOAPCodec xmlSoapCodec;
122
123 // The Fast Infoset SOAP codec
124 private final Codec fiSoapCodec;
125
126 // The XML MTOM codec
127 private final MimeCodec xmlMtomCodec;
128
129 // The XML SWA codec
130 private final MimeCodec xmlSwaCodec;
131
132 // The Fast Infoset SWA codec
133 private final MimeCodec fiSwaCodec;
134
135 // private final SOAPBindingImpl binding;
136 // private final WebServiceFeature[] feature;
137
138 /**
139 * The XML SOAP MIME type
140 */
141 private final String xmlMimeType;
142
143 /**
144 * The Fast Infoset SOAP MIME type
145 */
146 private final String fiMimeType;
147
148 /**
149 * The Accept header for XML encodings
150 */
151 private final String xmlAccept;
152
153 /**
154 * The Accept header for Fast Infoset and XML encodings
155 */
156 private final String connegXmlAccept;
157
158 public StreamSOAPCodec getXMLCodec() {
159 return xmlSoapCodec;
160 }
161
162 private class AcceptContentType implements ContentType {
163 private ContentType _c;
164 private String _accept;
165
166 public AcceptContentType set(Packet p, ContentType c) {
167 if (!ignoreContentNegotiationProperty && p.contentNegotiation != ContentNegotiation.none) {
168 _accept = connegXmlAccept;
169 } else {
170 _accept = xmlAccept;
171 }
172 _c = c;
173 return this;
174 }
175
176 public String getContentType() {
177 return _c.getContentType();
178 }
179
180 public String getSOAPActionHeader() {
181 return _c.getSOAPActionHeader();
182 }
183
184 public String getAcceptHeader() {
185 return _accept;
186 }
187 }
188
189 private AcceptContentType _adaptingContentType = new AcceptContentType();
190
191 public SOAPBindingCodec(WSFeatureList features) {
192 this(features, Codecs.createSOAPEnvelopeXmlCodec(features));
193 }
194
195 public SOAPBindingCodec(WSFeatureList features, StreamSOAPCodec xmlSoapCodec) {
196 super(getSoapVersion(features), features);
197
198 this.xmlSoapCodec = xmlSoapCodec;
199 xmlMimeType = xmlSoapCodec.getMimeType();
200
201 xmlMtomCodec = new MtomCodec(version, xmlSoapCodec, features);
202
203 xmlSwaCodec = new SwACodec(version, features, xmlSoapCodec);
204
205 String clientAcceptedContentTypes = xmlSoapCodec.getMimeType() + ", " +
206 xmlMtomCodec.getMimeType();
207
208 WebServiceFeature fi = features.get(FastInfosetFeature.class);
209 isFastInfosetDisabled = (fi != null && !fi.isEnabled());
210 if (!isFastInfosetDisabled) {
211 fiSoapCodec = getFICodec(xmlSoapCodec, version);
212 if (fiSoapCodec != null) {
213 fiMimeType = fiSoapCodec.getMimeType();
214 fiSwaCodec = new SwACodec(version, features, fiSoapCodec);
215 connegXmlAccept = fiMimeType + ", " + clientAcceptedContentTypes;
216
217 /**
218 * This feature will only be present on the client side.
219 *
220 * Fast Infoset is enabled on the client if the service
221 * explicitly supports Fast Infoset.
222 */
223 WebServiceFeature select = features.get(SelectOptimalEncodingFeature.class);
224 if (select != null) { // if the client FI feature is set - ignore negotiation property
225 ignoreContentNegotiationProperty = true;
226 if (select.isEnabled()) {
227 // If the client's FI encoding feature is enabled, and server's is not disabled
228 if (fi != null) { // if server's FI feature also enabled
229 useFastInfosetForEncoding = true;
230 }
231
232 clientAcceptedContentTypes = connegXmlAccept;
233 } else { // If client FI feature is disabled
234 isFastInfosetDisabled = true;
235 }
236 }
237 } else {
238 // Fast Infoset could not be loaded by the runtime
239 isFastInfosetDisabled = true;
240 fiSwaCodec = null;
241 fiMimeType = "";
242 connegXmlAccept = clientAcceptedContentTypes;
243 ignoreContentNegotiationProperty = true;
244 }
245 } else {
246 // Fast Infoset is explicitly not supported by the service
247 fiSoapCodec = fiSwaCodec = null;
248 fiMimeType = "";
249 connegXmlAccept = clientAcceptedContentTypes;
250 ignoreContentNegotiationProperty = true;
251 }
252
253 xmlAccept = clientAcceptedContentTypes;
254
255 // if(!(binding instanceof SOAPBindingImpl))
256 if(getSoapVersion(features) == null)
257 throw new WebServiceException("Expecting a SOAP binding but found ");
258 // this.binding = (SOAPBindingImpl)binding;
259 }
260
261 public String getMimeType() {
262 return null;
263 }
264
265 public ContentType getStaticContentType(Packet packet) {
266 ContentType toAdapt = getEncoder(packet).getStaticContentType(packet);
267 return (toAdapt != null) ? _adaptingContentType.set(packet, toAdapt) : null;
268 }
269
270 public ContentType encode(Packet packet, OutputStream out) throws IOException {
271 preEncode(packet);
272 ContentType ct = _adaptingContentType.set(packet, getEncoder(packet).encode(packet, out));
273 postEncode();
274 return ct;
275 }
276
277 public ContentType encode(Packet packet, WritableByteChannel buffer) {
278 preEncode(packet);
279 ContentType ct = _adaptingContentType.set(packet, getEncoder(packet).encode(packet, buffer));
280 postEncode();
281 return ct;
282 }
283
284 /**
285 * Should be called before encode().
286 * Set the state so that such state is used by encode process.
287 */
288 private void preEncode(Packet p) {
289 if (decodeFirst == TriState.UNSET)
290 decodeFirst = TriState.FALSE;
291 }
292
293 /**
294 * Should be called after encode()
295 * Reset the encoding state.
296 */
297 private void postEncode() {
298 decodeFirst = TriState.UNSET;
299 acceptMtomMessages = false;
300 isRequestMtomMessage = false;
301 }
302
303 /**
304 * Should be called before decode().
305 * Set the state so that such state is used by decode().
306 */
307 private void preDecode(Packet p) {
308 if (p.contentNegotiation == null)
309 useFastInfosetForEncoding = false;
310 }
311
312 /**
313 * Should be called after decode().
314 * Set the state so that such state is used by encode().
315 */
316 private void postDecode(Packet p) {
317 if(decodeFirst == TriState.UNSET)
318 decodeFirst = TriState.TRUE;
319 if(features.isEnabled(MTOMFeature.class))
320 acceptMtomMessages =isMtomAcceptable(p.acceptableMimeTypes);
321 if (!useFastInfosetForEncoding) {
322 useFastInfosetForEncoding = isFastInfosetAcceptable(p.acceptableMimeTypes);
323 }
324 }
325
326
327 private boolean isServerSide() {
328 return decodeFirst == TriState.TRUE;
329 }
330
331 public void decode(InputStream in, String contentType, Packet packet) throws IOException {
332 if (contentType == null) {
333 contentType = xmlMimeType;
334 }
335
336 preDecode(packet);
337 try {
338 if(isMultipartRelated(contentType))
339 // parse the multipart portion and then decide whether it's MTOM or SwA
340 super.decode(in, contentType, packet);
341 else if(isFastInfoset(contentType)) {
342 if (!ignoreContentNegotiationProperty && packet.contentNegotiation == ContentNegotiation.none)
343 throw noFastInfosetForDecoding();
344
345 useFastInfosetForEncoding = true;
346 fiSoapCodec.decode(in, contentType, packet);
347 } else
348 xmlSoapCodec.decode(in, contentType, packet);
349 } catch(RuntimeException we) {
350 if (we instanceof ExceptionHasMessage || we instanceof UnsupportedMediaException) {
351 throw we;
352 } else {
353 throw new MessageCreationException(version, we);
354 }
355 }
356 postDecode(packet);
357 }
358
359 public void decode(ReadableByteChannel in, String contentType, Packet packet) {
360 if (contentType == null) {
361 throw new UnsupportedMediaException();
362 }
363
364 preDecode(packet);
365 try {
366 if(isMultipartRelated(contentType))
367 super.decode(in, contentType, packet);
368 else if(isFastInfoset(contentType)) {
369 if (packet.contentNegotiation == ContentNegotiation.none)
370 throw noFastInfosetForDecoding();
371
372 useFastInfosetForEncoding = true;
373 fiSoapCodec.decode(in, contentType, packet);
374 } else
375 xmlSoapCodec.decode(in, contentType, packet);
376 } catch(RuntimeException we) {
377 if (we instanceof ExceptionHasMessage || we instanceof UnsupportedMediaException) {
378 throw we;
379 } else {
380 throw new MessageCreationException(version, we);
381 }
382 }
383 postDecode(packet);
384 }
385
386 public SOAPBindingCodec copy() {
387 return new SOAPBindingCodec(features, (StreamSOAPCodec)xmlSoapCodec.copy());
388 }
389
390 @Override
391 protected void decode(MimeMultipartParser mpp, Packet packet) throws IOException {
392 // is this SwA or XOP?
393 final String rootContentType = mpp.getRootPart().getContentType();
394
395 if(isApplicationXopXml(rootContentType)) {
396 isRequestMtomMessage = true;
397 xmlMtomCodec.decode(mpp,packet);
398 } else if (isFastInfoset(rootContentType)) {
399 if (packet.contentNegotiation == ContentNegotiation.none)
400 throw noFastInfosetForDecoding();
401
402 useFastInfosetForEncoding = true;
403 fiSwaCodec.decode(mpp,packet);
404 } else if (isXml(rootContentType))
405 xmlSwaCodec.decode(mpp,packet);
406 else {
407 // TODO localize exception
408 throw new IOException("");
409 }
410 // checkDuplicateKnownHeaders(packet);
411 }
412
413 private boolean isMultipartRelated(String contentType) {
414 return compareStrings(contentType, MimeCodec.MULTIPART_RELATED_MIME_TYPE);
415 }
416
417 private boolean isApplicationXopXml(String contentType) {
418 return compareStrings(contentType, MtomCodec.XOP_XML_MIME_TYPE);
419 }
420
421 private boolean isXml(String contentType) {
422 return compareStrings(contentType, xmlMimeType);
423 }
424
425 private boolean isFastInfoset(String contentType) {
426 if (isFastInfosetDisabled) return false;
427
428 return compareStrings(contentType, fiMimeType);
429 }
430
431 private boolean compareStrings(String a, String b) {
432 return a.length() >= b.length() &&
433 b.equalsIgnoreCase(
434 a.substring(0,
435 b.length()));
436 }
437
438 private boolean isFastInfosetAcceptable(String accept) {
439 if (accept == null || isFastInfosetDisabled) return false;
440
441 StringTokenizer st = new StringTokenizer(accept, ",");
442 while (st.hasMoreTokens()) {
443 final String token = st.nextToken().trim();
444 if (token.equalsIgnoreCase(fiMimeType)) {
445 return true;
446 }
447 }
448 return false;
449 }
450
451 /*
452 * Just check if the Accept header contains application/xop+xml,
453 * no need to worry about q values.
454 */
455 private boolean isMtomAcceptable(String accept) {
456 if (accept == null || isFastInfosetDisabled) return false;
457
458 StringTokenizer st = new StringTokenizer(accept, ",");
459 while (st.hasMoreTokens()) {
460 final String token = st.nextToken().trim();
461 if (token.toLowerCase().contains(MtomCodec.XOP_XML_MIME_TYPE)) {
462 return true;
463 }
464 }
465 return false;
466 }
467
468 /**
469 * Determines the encoding codec.
470 */
471 private Codec getEncoder(Packet p) {
472 /**
473 * The following logic is only for outbound packets
474 * to be encoded by a client.
475 * For a server the p.contentNegotiation == null.
476 */
477 if (!ignoreContentNegotiationProperty) {
478 if (p.contentNegotiation == ContentNegotiation.none) {
479 // The client may have changed the negotiation property from
480 // pessismistic to none between invocations
481 useFastInfosetForEncoding = false;
482 } else if (p.contentNegotiation == ContentNegotiation.optimistic) {
483 // Always encode using Fast Infoset if in optimisitic mode
484 useFastInfosetForEncoding = true;
485 }
486 }
487
488 // Override the MTOM binding for now
489 // Note: Using FI with MTOM does not make sense
490 if (useFastInfosetForEncoding) {
491 final Message m = p.getMessage();
492 if(m==null || m.getAttachments().isEmpty() || features.isEnabled(MTOMFeature.class))
493 return fiSoapCodec;
494 else
495 return fiSwaCodec;
496 }
497
498 if(features.isEnabled(MTOMFeature.class)) {
499 //On client, always use XOP encoding if MTOM is enabled
500 // On Server, use XOP encoding if either request is XOP encoded or client accepts XOP encoding
501 if(!isServerSide() || isRequestMtomMessage || acceptMtomMessages)
502 return xmlMtomCodec;
503 }
504
505 Message m = p.getMessage();
506 if(m==null || m.getAttachments().isEmpty())
507 return xmlSoapCodec;
508 else
509 return xmlSwaCodec;
510 }
511
512 private RuntimeException noFastInfosetForDecoding() {
513 return new RuntimeException(StreamingMessages.FASTINFOSET_DECODING_NOT_ACCEPTED());
514 }
515
516 /**
517 * Obtain an FI SOAP codec instance using reflection.
518 */
519 private static Codec getFICodec(StreamSOAPCodec soapCodec, SOAPVersion version) {
520 try {
521 Class c = Class.forName("com.sun.xml.internal.ws.encoding.fastinfoset.FastInfosetStreamSOAPCodec");
522 Method m = c.getMethod("create", StreamSOAPCodec.class, SOAPVersion.class);
523 return (Codec)m.invoke(null, soapCodec, version);
524 } catch (Exception e) {
525 // TODO Log that FI cannot be loaded
526 return null;
527 }
528 }
529 }

mercurial