/*
 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.xml.internal.ws.encoding;

import com.sun.xml.internal.ws.api.SOAPVersion;
import com.sun.xml.internal.ws.api.WSFeatureList;
import com.sun.xml.internal.ws.api.client.SelectOptimalEncodingFeature;
import com.sun.xml.internal.ws.api.fastinfoset.FastInfosetFeature;
import com.sun.xml.internal.ws.api.message.Message;
import com.sun.xml.internal.ws.api.message.Packet;
import com.sun.xml.internal.ws.api.message.ExceptionHasMessage;
import com.sun.xml.internal.ws.api.pipe.Codec;
import com.sun.xml.internal.ws.api.pipe.Codecs;
import com.sun.xml.internal.ws.api.pipe.ContentType;
import com.sun.xml.internal.ws.api.pipe.StreamSOAPCodec;
import com.sun.xml.internal.ws.client.ContentNegotiation;
import com.sun.xml.internal.ws.protocol.soap.MessageCreationException;
import com.sun.xml.internal.ws.resources.StreamingMessages;
import com.sun.xml.internal.ws.server.UnsupportedMediaException;
import static com.sun.xml.internal.ws.binding.WebServiceFeatureList.getSoapVersion;

import javax.xml.ws.WebServiceException;
import javax.xml.ws.WebServiceFeature;
import javax.xml.ws.soap.MTOMFeature;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
//import java.util.StringTokenizer;

SOAP binding Codec that can handle MTOM, SwA, and SOAP messages encoded using XML or Fast Infoset.

This is used when we need to determine the encoding from what we received (for decoding) and from configuration and Message contents (for encoding)

TODO: Split this Codec into two, one that supports FI and one that does not. Then further split the FI Codec into two, one for client and one for server. This will simplify the logic and make it easier to understand/maintain.

Author:Vivek Pandey, Kohsuke Kawaguchi
/** * SOAP binding {@link Codec} that can handle MTOM, SwA, and SOAP messages * encoded using XML or Fast Infoset. * * <p> * This is used when we need to determine the encoding from what we received (for decoding) * and from configuration and {@link Message} contents (for encoding) * * <p> * TODO: Split this Codec into two, one that supports FI and one that does not. * Then further split the FI Codec into two, one for client and one for * server. This will simplify the logic and make it easier to understand/maintain. * * @author Vivek Pandey * @author Kohsuke Kawaguchi */
public class SOAPBindingCodec extends MimeCodec implements com.sun.xml.internal.ws.api.pipe.SOAPBindingCodec { public static final String UTF8_ENCODING = "utf-8"; public static final String DEFAULT_ENCODING = UTF8_ENCODING;
True if Fast Infoset functionality has been configured to be disabled, or the Fast Infoset runtime is not available.
/** * True if Fast Infoset functionality has been * configured to be disabled, or the Fast Infoset * runtime is not available. */
private boolean isFastInfosetDisabled;
True if the Fast Infoset codec should be used for encoding.
/** * True if the Fast Infoset codec should be used for encoding. */
private boolean useFastInfosetForEncoding;
True if the content negotiation property should be ignored by the client. This will be used in the case of Fast Infoset being configured to be disabled or automatically selected.
/** * True if the content negotiation property should * be ignored by the client. This will be used in * the case of Fast Infoset being configured to be * disabled or automatically selected. */
private boolean ignoreContentNegotiationProperty; // The XML SOAP codec private final StreamSOAPCodec xmlSoapCodec; // The Fast Infoset SOAP codec private final Codec fiSoapCodec; // The XML MTOM codec private final MimeCodec xmlMtomCodec; // The XML SWA codec private final MimeCodec xmlSwaCodec; // The Fast Infoset SWA codec private final MimeCodec fiSwaCodec;
The XML SOAP MIME type
/** * The XML SOAP MIME type */
private final String xmlMimeType;
The Fast Infoset SOAP MIME type
/** * The Fast Infoset SOAP MIME type */
private final String fiMimeType;
The Accept header for XML encodings
/** * The Accept header for XML encodings */
private final String xmlAccept;
The Accept header for Fast Infoset and XML encodings
/** * The Accept header for Fast Infoset and XML encodings */
private final String connegXmlAccept; public StreamSOAPCodec getXMLCodec() { return xmlSoapCodec; } private ContentTypeImpl setAcceptHeader(Packet p, ContentTypeImpl c) { String _accept; if (!ignoreContentNegotiationProperty && p.contentNegotiation != ContentNegotiation.none) { _accept = connegXmlAccept; } else { _accept = xmlAccept; } c.setAcceptHeader(_accept); return c; } public SOAPBindingCodec(WSFeatureList features) { this(features, Codecs.createSOAPEnvelopeXmlCodec(features)); } public SOAPBindingCodec(WSFeatureList features, StreamSOAPCodec xmlSoapCodec) { super(getSoapVersion(features), features); this.xmlSoapCodec = xmlSoapCodec; xmlMimeType = xmlSoapCodec.getMimeType(); xmlMtomCodec = new MtomCodec(version, xmlSoapCodec, features); xmlSwaCodec = new SwACodec(version, features, xmlSoapCodec); String clientAcceptedContentTypes = xmlSoapCodec.getMimeType() + ", " + xmlMtomCodec.getMimeType(); WebServiceFeature fi = features.get(FastInfosetFeature.class); isFastInfosetDisabled = (fi != null && !fi.isEnabled()); if (!isFastInfosetDisabled) { fiSoapCodec = getFICodec(xmlSoapCodec, version); if (fiSoapCodec != null) { fiMimeType = fiSoapCodec.getMimeType(); fiSwaCodec = new SwACodec(version, features, fiSoapCodec); connegXmlAccept = fiMimeType + ", " + clientAcceptedContentTypes; /** * This feature will only be present on the client side. * * Fast Infoset is enabled on the client if the service * explicitly supports Fast Infoset. */ WebServiceFeature select = features.get(SelectOptimalEncodingFeature.class); if (select != null) { // if the client FI feature is set - ignore negotiation property ignoreContentNegotiationProperty = true; if (select.isEnabled()) { // If the client's FI encoding feature is enabled, and server's is not disabled if (fi != null) { // if server's FI feature also enabled useFastInfosetForEncoding = true; } clientAcceptedContentTypes = connegXmlAccept; } else { // If client FI feature is disabled isFastInfosetDisabled = true; } } } else { // Fast Infoset could not be loaded by the runtime isFastInfosetDisabled = true; fiSwaCodec = null; fiMimeType = ""; connegXmlAccept = clientAcceptedContentTypes; ignoreContentNegotiationProperty = true; } } else { // Fast Infoset is explicitly not supported by the service fiSoapCodec = fiSwaCodec = null; fiMimeType = ""; connegXmlAccept = clientAcceptedContentTypes; ignoreContentNegotiationProperty = true; } xmlAccept = clientAcceptedContentTypes; if(getSoapVersion(features) == null) throw new WebServiceException("Expecting a SOAP binding but found "); } public String getMimeType() { return null; } public ContentType getStaticContentType(Packet packet) { ContentType toAdapt = getEncoder(packet).getStaticContentType(packet); return setAcceptHeader(packet, (ContentTypeImpl)toAdapt); } public ContentType encode(Packet packet, OutputStream out) throws IOException { preEncode(packet); ContentType ct = getEncoder(packet).encode(packet, out); ct = setAcceptHeader(packet, (ContentTypeImpl)ct); postEncode(); return ct; } public ContentType encode(Packet packet, WritableByteChannel buffer) { preEncode(packet); ContentType ct = getEncoder(packet).encode(packet, buffer); ct = setAcceptHeader(packet, (ContentTypeImpl)ct); postEncode(); return ct; }
Should be called before encode(). Set the state so that such state is used by encode process.
/** * Should be called before encode(). * Set the state so that such state is used by encode process. */
private void preEncode(Packet p) { }
Should be called after encode() Reset the encoding state.
/** * Should be called after encode() * Reset the encoding state. */
private void postEncode() { }
Should be called before decode(). Set the state so that such state is used by decode().
/** * Should be called before decode(). * Set the state so that such state is used by decode(). */
private void preDecode(Packet p) { if (p.contentNegotiation == null) useFastInfosetForEncoding = false; }
Should be called after decode(). Set the state so that such state is used by encode().
/** * Should be called after decode(). * Set the state so that such state is used by encode(). */
private void postDecode(Packet p) { p.setFastInfosetDisabled(isFastInfosetDisabled); if(features.isEnabled(MTOMFeature.class)) p.checkMtomAcceptable(); // p.setMtomAcceptable( isMtomAcceptable(p.acceptableMimeTypes) ); MTOMFeature mtomFeature = features.get(MTOMFeature.class); if (mtomFeature != null) { p.setMtomFeature(mtomFeature); } if (!useFastInfosetForEncoding) { useFastInfosetForEncoding = p.getFastInfosetAcceptable(fiMimeType); // useFastInfosetForEncoding = isFastInfosetAcceptable(p.acceptableMimeTypes); } } public void decode(InputStream in, String contentType, Packet packet) throws IOException { if (contentType == null) { contentType = xmlMimeType; } packet.setContentType(new ContentTypeImpl(contentType)); preDecode(packet); try { if(isMultipartRelated(contentType)) // parse the multipart portion and then decide whether it's MTOM or SwA super.decode(in, contentType, packet); else if(isFastInfoset(contentType)) { if (!ignoreContentNegotiationProperty && packet.contentNegotiation == ContentNegotiation.none) throw noFastInfosetForDecoding(); useFastInfosetForEncoding = true; fiSoapCodec.decode(in, contentType, packet); } else xmlSoapCodec.decode(in, contentType, packet); } catch(RuntimeException we) { if (we instanceof ExceptionHasMessage || we instanceof UnsupportedMediaException) { throw we; } else { throw new MessageCreationException(version, we); } } postDecode(packet); } public void decode(ReadableByteChannel in, String contentType, Packet packet) { if (contentType == null) { throw new UnsupportedMediaException(); } preDecode(packet); try { if(isMultipartRelated(contentType)) super.decode(in, contentType, packet); else if(isFastInfoset(contentType)) { if (packet.contentNegotiation == ContentNegotiation.none) throw noFastInfosetForDecoding(); useFastInfosetForEncoding = true; fiSoapCodec.decode(in, contentType, packet); } else xmlSoapCodec.decode(in, contentType, packet); } catch(RuntimeException we) { if (we instanceof ExceptionHasMessage || we instanceof UnsupportedMediaException) { throw we; } else { throw new MessageCreationException(version, we); } } postDecode(packet); } public SOAPBindingCodec copy() { return new SOAPBindingCodec(features, (StreamSOAPCodec)xmlSoapCodec.copy()); } @Override protected void decode(MimeMultipartParser mpp, Packet packet) throws IOException { // is this SwA or XOP? final String rootContentType = mpp.getRootPart().getContentType(); boolean isMTOM = isApplicationXopXml(rootContentType); packet.setMtomRequest(isMTOM); if(isMTOM) { xmlMtomCodec.decode(mpp,packet); } else if (isFastInfoset(rootContentType)) { if (packet.contentNegotiation == ContentNegotiation.none) throw noFastInfosetForDecoding(); useFastInfosetForEncoding = true; fiSwaCodec.decode(mpp,packet); } else if (isXml(rootContentType)) xmlSwaCodec.decode(mpp,packet); else { // TODO localize exception throw new IOException(""); } // checkDuplicateKnownHeaders(packet); } private boolean isMultipartRelated(String contentType) { return compareStrings(contentType, MimeCodec.MULTIPART_RELATED_MIME_TYPE); } private boolean isApplicationXopXml(String contentType) { return compareStrings(contentType, MtomCodec.XOP_XML_MIME_TYPE); } private boolean isXml(String contentType) { return compareStrings(contentType, xmlMimeType); } private boolean isFastInfoset(String contentType) { if (isFastInfosetDisabled) return false; return compareStrings(contentType, fiMimeType); } private boolean compareStrings(String a, String b) { return a.length() >= b.length() && b.equalsIgnoreCase( a.substring(0, b.length())); } // private boolean isFastInfosetAcceptable(String accept) { // if (accept == null || isFastInfosetDisabled) return false; // // StringTokenizer st = new StringTokenizer(accept, ","); // while (st.hasMoreTokens()) { // final String token = st.nextToken().trim(); // if (token.equalsIgnoreCase(fiMimeType)) { // return true; // } // } // return false; // } /* * Just check if the Accept header contains application/xop+xml, * no need to worry about q values. */ // private boolean isMtomAcceptable(String accept) { // if (accept == null || isFastInfosetDisabled) return false; // StringTokenizer st = new StringTokenizer(accept, ","); // while (st.hasMoreTokens()) { // final String token = st.nextToken().trim(); // if (token.toLowerCase().contains(MtomCodec.XOP_XML_MIME_TYPE)) { // return true; // } // } // return false; // }
Determines the encoding codec.
/** * Determines the encoding codec. */
private Codec getEncoder(Packet p) { /** * The following logic is only for outbound packets * to be encoded by a client. * For a server the p.contentNegotiation == null. */ if (!ignoreContentNegotiationProperty) { if (p.contentNegotiation == ContentNegotiation.none) { // The client may have changed the negotiation property from // pessismistic to none between invocations useFastInfosetForEncoding = false; } else if (p.contentNegotiation == ContentNegotiation.optimistic) { // Always encode using Fast Infoset if in optimisitic mode useFastInfosetForEncoding = true; } } // Override the MTOM binding for now // Note: Using FI with MTOM does not make sense if (useFastInfosetForEncoding) { final Message m = p.getMessage(); if(m==null || m.getAttachments().isEmpty() || features.isEnabled(MTOMFeature.class)) return fiSoapCodec; else return fiSwaCodec; } //If the packet does not have a binding, explicitly set the MTOMFeature //on the packet so that it has a way to determine whether to use MTOM if (p.getBinding() == null) { if (features != null) { p.setMtomFeature(features.get(MTOMFeature.class)); } } if (p.shouldUseMtom()) { return xmlMtomCodec; } Message m = p.getMessage(); if(m==null || m.getAttachments().isEmpty()) return xmlSoapCodec; else return xmlSwaCodec; } private RuntimeException noFastInfosetForDecoding() { return new RuntimeException(StreamingMessages.FASTINFOSET_DECODING_NOT_ACCEPTED()); }
Obtain an FI SOAP codec instance using reflection.
/** * Obtain an FI SOAP codec instance using reflection. */
private static Codec getFICodec(StreamSOAPCodec soapCodec, SOAPVersion version) { try { Class c = Class.forName("com.sun.xml.internal.ws.encoding.fastinfoset.FastInfosetStreamSOAPCodec"); Method m = c.getMethod("create", StreamSOAPCodec.class, SOAPVersion.class); return (Codec)m.invoke(null, soapCodec, version); } catch (Exception e) { // TODO Log that FI cannot be loaded return null; } } }