| /* |
| * 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 {@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. |
| */ |
| private boolean isFastInfosetDisabled; |
| |
| /** |
| * 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. |
| */ |
| 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 |
| */ |
| private final String xmlMimeType; |
| |
| /** |
| * The Fast Infoset SOAP MIME type |
| */ |
| private final String fiMimeType; |
| |
| /** |
| * The Accept header for XML encodings |
| */ |
| private final String xmlAccept; |
| |
| /** |
| * 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. |
| */ |
| private void preEncode(Packet p) { |
| } |
| |
| /** |
| * 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(). |
| */ |
| 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(). |
| */ |
| 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. |
| */ |
| 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. |
| */ |
| 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; |
| } |
| } |
| } |