/* * Copyright 2005-2006 Sun Microsystems, Inc. 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. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package com.sun.xml.internal.ws.encoding; 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; import javax.xml.namespace.QName; import javax.xml.soap.SOAPException; import javax.xml.soap.SOAPFault; import javax.xml.ws.WebServiceFeature; import javax.xml.ws.soap.MTOMFeature; import com.sun.xml.internal.ws.api.SOAPVersion; import com.sun.xml.internal.ws.api.WSBinding; 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.Header; import com.sun.xml.internal.ws.api.message.HeaderList; import com.sun.xml.internal.ws.api.message.Message; import com.sun.xml.internal.ws.api.message.Messages; import com.sun.xml.internal.ws.api.message.Packet; import com.sun.xml.internal.ws.api.pipe.Codec; import com.sun.xml.internal.ws.api.pipe.ContentType; import com.sun.xml.internal.ws.api.pipe.StreamSOAPCodec; import com.sun.xml.internal.ws.api.pipe.Codecs; import com.sun.xml.internal.ws.binding.SOAPBindingImpl; import com.sun.xml.internal.ws.client.ContentNegotiation; import com.sun.xml.internal.ws.resources.ServerMessages; import com.sun.xml.internal.ws.resources.StreamingMessages; import com.sun.xml.internal.ws.server.ServerRtException; import com.sun.xml.internal.ws.server.UnsupportedMediaException; import com.sun.xml.internal.ws.transport.http.WSHTTPConnection; /** * 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 { /** * Base HTTP Accept request-header. */ private static final String BASE_ACCEPT_VALUE = "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"; /** * 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; private final SOAPBindingImpl binding; /** * 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 class AcceptContentType implements ContentType { private ContentType _c; private String _accept; public AcceptContentType set(Packet p, ContentType c) { if (!ignoreContentNegotiationProperty && p.contentNegotiation != ContentNegotiation.none) { _accept = connegXmlAccept; } else { _accept = xmlAccept; } _c = c; return this; } public String getContentType() { return _c.getContentType(); } public String getSOAPActionHeader() { return _c.getSOAPActionHeader(); } public String getAcceptHeader() { return _accept; } } private AcceptContentType _adaptingContentType = new AcceptContentType(); public SOAPBindingCodec(WSBinding binding) { this(binding, Codecs.createSOAPEnvelopeXmlCodec(binding.getSOAPVersion())); } public SOAPBindingCodec(WSBinding binding, StreamSOAPCodec xmlSoapCodec) { super(binding.getSOAPVersion()); this.xmlSoapCodec = xmlSoapCodec; xmlMimeType = xmlSoapCodec.getMimeType(); xmlMtomCodec = new MtomCodec(version, xmlSoapCodec, binding.getFeature(MTOMFeature.class)); xmlSwaCodec = new SwACodec(version, xmlSoapCodec); String clientAcceptedContentTypes = xmlSoapCodec.getMimeType() + ", " + xmlMtomCodec.getMimeType() + ", " + BASE_ACCEPT_VALUE; WebServiceFeature fi = binding.getFeature(FastInfosetFeature.class); isFastInfosetDisabled = (fi != null && !fi.isEnabled()); if (!isFastInfosetDisabled) { fiSoapCodec = getFICodec(xmlSoapCodec, version); if (fiSoapCodec != null) { fiMimeType = fiSoapCodec.getMimeType(); fiSwaCodec = new SwACodec(version, 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 = binding.getFeature(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; this.binding = (SOAPBindingImpl)binding; } public String getMimeType() { return null; } public ContentType getStaticContentType(Packet packet) { ContentType toAdapt = getEncoder(packet).getStaticContentType(packet); return (toAdapt != null) ? _adaptingContentType.set(packet, toAdapt) : null; } public ContentType encode(Packet packet, OutputStream out) throws IOException { return _adaptingContentType.set(packet, getEncoder(packet).encode(packet, out)); } public ContentType encode(Packet packet, WritableByteChannel buffer) { return _adaptingContentType.set(packet, getEncoder(packet).encode(packet, buffer)); } public void decode(InputStream in, String contentType, Packet packet) throws IOException { if (contentType == null) { throw new UnsupportedMediaException(); } /** * Reset the encoding state when on the server side for each * decode/encode step. */ if (packet.contentNegotiation == null) useFastInfosetForEncoding = false; 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); if (!useFastInfosetForEncoding) { useFastInfosetForEncoding = isFastInfosetAcceptable(packet.acceptableMimeTypes); } } public void decode(ReadableByteChannel in, String contentType, Packet packet) { if (contentType == null) { throw new UnsupportedMediaException(); } /** * Reset the encoding state when on the server side for each * decode/encode step. */ if (packet.contentNegotiation == null) useFastInfosetForEncoding = false; 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); // checkDuplicateKnownHeaders(packet); if (!useFastInfosetForEncoding) { useFastInfosetForEncoding = isFastInfosetAcceptable(packet.acceptableMimeTypes); } } public SOAPBindingCodec copy() { return new SOAPBindingCodec(binding, (StreamSOAPCodec)xmlSoapCodec.copy()); } @Override protected void decode(MimeMultipartParser mpp, Packet packet) throws IOException { // is this SwA or XOP? final String rootContentType = mpp.getRootPart().getContentType(); if(isApplicationXopXml(rootContentType)) 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; } /** * 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() || binding.isFeatureEnabled(MTOMFeature.class)) return fiSoapCodec; else return fiSwaCodec; } if(binding.isFeatureEnabled(MTOMFeature.class)) 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; } } }