001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common Development
008: * and Distribution License("CDDL") (collectively, the "License"). You
009: * may not use this file except in compliance with the License. You can obtain
010: * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
011: * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
012: * language governing permissions and limitations under the License.
013: *
014: * When distributing the software, include this License Header Notice in each
015: * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
016: * Sun designates this particular file as subject to the "Classpath" exception
017: * as provided by Sun in the GPL Version 2 section of the License file that
018: * accompanied this code. If applicable, add the following below the License
019: * Header, with the fields enclosed by brackets [] replaced by your own
020: * identifying information: "Portions Copyrighted [year]
021: * [name of copyright owner]"
022: *
023: * Contributor(s):
024: *
025: * If you wish your version of this file to be governed by only the CDDL or
026: * only the GPL Version 2, indicate your decision by adding "[Contributor]
027: * elects to include this software in this distribution under the [CDDL or GPL
028: * Version 2] license." If you don't indicate a single choice of license, a
029: * recipient has the option to distribute your version of this file under
030: * either the CDDL, the GPL Version 2 or to extend the choice of license to
031: * its licensees as provided above. However, if you add GPL Version 2 code
032: * and therefore, elected the GPL Version 2 license, then the option applies
033: * only if the new code is made subject to such option by the copyright
034: * holder.
035: */
036:
037: package com.sun.xml.ws.encoding;
038:
039: import java.io.IOException;
040: import java.io.InputStream;
041: import java.io.OutputStream;
042: import java.lang.reflect.Method;
043: import java.nio.channels.ReadableByteChannel;
044: import java.nio.channels.WritableByteChannel;
045: import java.util.StringTokenizer;
046:
047: import javax.xml.namespace.QName;
048: import javax.xml.soap.SOAPException;
049: import javax.xml.soap.SOAPFault;
050: import javax.xml.ws.WebServiceFeature;
051: import javax.xml.ws.soap.MTOMFeature;
052:
053: import com.sun.xml.ws.api.SOAPVersion;
054: import com.sun.xml.ws.api.WSBinding;
055: import com.sun.xml.ws.api.client.SelectOptimalEncodingFeature;
056: import com.sun.xml.ws.api.fastinfoset.FastInfosetFeature;
057: import com.sun.xml.ws.api.message.Header;
058: import com.sun.xml.ws.api.message.HeaderList;
059: import com.sun.xml.ws.api.message.Message;
060: import com.sun.xml.ws.api.message.Messages;
061: import com.sun.xml.ws.api.message.Packet;
062: import com.sun.xml.ws.api.pipe.Codec;
063: import com.sun.xml.ws.api.pipe.ContentType;
064: import com.sun.xml.ws.api.pipe.StreamSOAPCodec;
065: import com.sun.xml.ws.api.pipe.Codecs;
066: import com.sun.xml.ws.binding.SOAPBindingImpl;
067: import com.sun.xml.ws.client.ContentNegotiation;
068: import com.sun.xml.ws.resources.ServerMessages;
069: import com.sun.xml.ws.resources.StreamingMessages;
070: import com.sun.xml.ws.server.ServerRtException;
071: import com.sun.xml.ws.server.UnsupportedMediaException;
072: import com.sun.xml.ws.transport.http.WSHTTPConnection;
073:
074: /**
075: * SOAP binding {@link Codec} that can handle MTOM, SwA, and SOAP messages
076: * encoded using XML or Fast Infoset.
077: *
078: * <p>
079: * This is used when we need to determine the encoding from what we received (for decoding)
080: * and from configuration and {@link Message} contents (for encoding)
081: *
082: * <p>
083: * TODO: Split this Codec into two, one that supports FI and one that does not.
084: * Then further split the FI Codec into two, one for client and one for
085: * server. This will simplify the logic and make it easier to understand/maintain.
086: *
087: * @author Vivek Pandey
088: * @author Kohsuke Kawaguchi
089: */
090: public class SOAPBindingCodec extends MimeCodec implements
091: com.sun.xml.ws.api.pipe.SOAPBindingCodec {
092: /**
093: * Base HTTP Accept request-header.
094: */
095: private static final String BASE_ACCEPT_VALUE = "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2";
096:
097: /**
098: * True if Fast Infoset functionality has been
099: * configured to be disabled, or the Fast Infoset
100: * runtime is not available.
101: */
102: private boolean isFastInfosetDisabled;
103:
104: /**
105: * True if the Fast Infoset codec should be used for encoding.
106: */
107: private boolean useFastInfosetForEncoding;
108:
109: /**
110: * True if the content negotiation property should
111: * be ignored by the client. This will be used in
112: * the case of Fast Infoset being configured to be
113: * disabled or automatically selected.
114: */
115: private boolean ignoreContentNegotiationProperty;
116:
117: // The XML SOAP codec
118: private final StreamSOAPCodec xmlSoapCodec;
119:
120: // The Fast Infoset SOAP codec
121: private final Codec fiSoapCodec;
122:
123: // The XML MTOM codec
124: private final MimeCodec xmlMtomCodec;
125:
126: // The XML SWA codec
127: private final MimeCodec xmlSwaCodec;
128:
129: // The Fast Infoset SWA codec
130: private final MimeCodec fiSwaCodec;
131:
132: private final SOAPBindingImpl binding;
133:
134: /**
135: * The XML SOAP MIME type
136: */
137: private final String xmlMimeType;
138:
139: /**
140: * The Fast Infoset SOAP MIME type
141: */
142: private final String fiMimeType;
143:
144: /**
145: * The Accept header for XML encodings
146: */
147: private final String xmlAccept;
148:
149: /**
150: * The Accept header for Fast Infoset and XML encodings
151: */
152: private final String connegXmlAccept;
153:
154: public StreamSOAPCodec getXMLCodec() {
155: return xmlSoapCodec;
156: }
157:
158: private class AcceptContentType implements ContentType {
159: private ContentType _c;
160: private String _accept;
161:
162: public AcceptContentType set(Packet p, ContentType c) {
163: if (!ignoreContentNegotiationProperty
164: && p.contentNegotiation != ContentNegotiation.none) {
165: _accept = connegXmlAccept;
166: } else {
167: _accept = xmlAccept;
168: }
169: _c = c;
170: return this ;
171: }
172:
173: public String getContentType() {
174: return _c.getContentType();
175: }
176:
177: public String getSOAPActionHeader() {
178: return _c.getSOAPActionHeader();
179: }
180:
181: public String getAcceptHeader() {
182: return _accept;
183: }
184: }
185:
186: private AcceptContentType _adaptingContentType = new AcceptContentType();
187:
188: public SOAPBindingCodec(WSBinding binding) {
189: this (binding, Codecs.createSOAPEnvelopeXmlCodec(binding
190: .getSOAPVersion()));
191: }
192:
193: public SOAPBindingCodec(WSBinding binding,
194: StreamSOAPCodec xmlSoapCodec) {
195: super (binding.getSOAPVersion(), binding);
196:
197: this .xmlSoapCodec = xmlSoapCodec;
198: xmlMimeType = xmlSoapCodec.getMimeType();
199:
200: xmlMtomCodec = new MtomCodec(version, xmlSoapCodec, binding,
201: binding.getFeature(MTOMFeature.class));
202:
203: xmlSwaCodec = new SwACodec(version, binding, xmlSoapCodec);
204:
205: String clientAcceptedContentTypes = xmlSoapCodec.getMimeType()
206: + ", " + xmlMtomCodec.getMimeType() + ", "
207: + BASE_ACCEPT_VALUE;
208:
209: WebServiceFeature fi = binding
210: .getFeature(FastInfosetFeature.class);
211: isFastInfosetDisabled = (fi != null && !fi.isEnabled());
212: if (!isFastInfosetDisabled) {
213: fiSoapCodec = getFICodec(xmlSoapCodec, version);
214: if (fiSoapCodec != null) {
215: fiMimeType = fiSoapCodec.getMimeType();
216: fiSwaCodec = new SwACodec(version, binding, fiSoapCodec);
217: connegXmlAccept = fiMimeType + ", "
218: + clientAcceptedContentTypes;
219:
220: /**
221: * This feature will only be present on the client side.
222: *
223: * Fast Infoset is enabled on the client if the service
224: * explicitly supports Fast Infoset.
225: */
226: WebServiceFeature select = binding
227: .getFeature(SelectOptimalEncodingFeature.class);
228: if (select != null) { // if the client FI feature is set - ignore negotiation property
229: ignoreContentNegotiationProperty = true;
230: if (select.isEnabled()) {
231: // If the client's FI encoding feature is enabled, and server's is not disabled
232: if (fi != null) { // if server's FI feature also enabled
233: useFastInfosetForEncoding = true;
234: }
235:
236: clientAcceptedContentTypes = connegXmlAccept;
237: } else { // If client FI feature is disabled
238: isFastInfosetDisabled = true;
239: }
240: }
241: } else {
242: // Fast Infoset could not be loaded by the runtime
243: isFastInfosetDisabled = true;
244: fiSwaCodec = null;
245: fiMimeType = "";
246: connegXmlAccept = clientAcceptedContentTypes;
247: ignoreContentNegotiationProperty = true;
248: }
249: } else {
250: // Fast Infoset is explicitly not supported by the service
251: fiSoapCodec = fiSwaCodec = null;
252: fiMimeType = "";
253: connegXmlAccept = clientAcceptedContentTypes;
254: ignoreContentNegotiationProperty = true;
255: }
256:
257: xmlAccept = clientAcceptedContentTypes;
258:
259: this .binding = (SOAPBindingImpl) binding;
260: }
261:
262: public String getMimeType() {
263: return null;
264: }
265:
266: public ContentType getStaticContentType(Packet packet) {
267: ContentType toAdapt = getEncoder(packet).getStaticContentType(
268: packet);
269: return (toAdapt != null) ? _adaptingContentType.set(packet,
270: toAdapt) : null;
271: }
272:
273: public ContentType encode(Packet packet, OutputStream out)
274: throws IOException {
275: return _adaptingContentType.set(packet, getEncoder(packet)
276: .encode(packet, out));
277: }
278:
279: public ContentType encode(Packet packet, WritableByteChannel buffer) {
280: return _adaptingContentType.set(packet, getEncoder(packet)
281: .encode(packet, buffer));
282: }
283:
284: public void decode(InputStream in, String contentType, Packet packet)
285: throws IOException {
286: if (contentType == null) {
287: throw new UnsupportedMediaException();
288: }
289:
290: /**
291: * Reset the encoding state when on the server side for each
292: * decode/encode step.
293: */
294: if (packet.contentNegotiation == null)
295: useFastInfosetForEncoding = false;
296:
297: if (isMultipartRelated(contentType))
298: // parse the multipart portion and then decide whether it's MTOM or SwA
299: super .decode(in, contentType, packet);
300: else if (isFastInfoset(contentType)) {
301: if (!ignoreContentNegotiationProperty
302: && packet.contentNegotiation == ContentNegotiation.none)
303: throw noFastInfosetForDecoding();
304:
305: useFastInfosetForEncoding = true;
306: fiSoapCodec.decode(in, contentType, packet);
307: } else
308: xmlSoapCodec.decode(in, contentType, packet);
309:
310: if (!useFastInfosetForEncoding) {
311: useFastInfosetForEncoding = isFastInfosetAcceptable(packet.acceptableMimeTypes);
312: }
313: }
314:
315: public void decode(ReadableByteChannel in, String contentType,
316: Packet packet) {
317: if (contentType == null) {
318: throw new UnsupportedMediaException();
319: }
320: /**
321: * Reset the encoding state when on the server side for each
322: * decode/encode step.
323: */
324: if (packet.contentNegotiation == null)
325: useFastInfosetForEncoding = false;
326:
327: if (isMultipartRelated(contentType))
328: super .decode(in, contentType, packet);
329: else if (isFastInfoset(contentType)) {
330: if (packet.contentNegotiation == ContentNegotiation.none)
331: throw noFastInfosetForDecoding();
332:
333: useFastInfosetForEncoding = true;
334: fiSoapCodec.decode(in, contentType, packet);
335: } else
336: xmlSoapCodec.decode(in, contentType, packet);
337:
338: // checkDuplicateKnownHeaders(packet);
339: if (!useFastInfosetForEncoding) {
340: useFastInfosetForEncoding = isFastInfosetAcceptable(packet.acceptableMimeTypes);
341: }
342: }
343:
344: public SOAPBindingCodec copy() {
345: return new SOAPBindingCodec(binding,
346: (StreamSOAPCodec) xmlSoapCodec.copy());
347: }
348:
349: @Override
350: protected void decode(MimeMultipartParser mpp, Packet packet)
351: throws IOException {
352: // is this SwA or XOP?
353: final String rootContentType = mpp.getRootPart()
354: .getContentType();
355:
356: if (isApplicationXopXml(rootContentType))
357: xmlMtomCodec.decode(mpp, packet);
358: else if (isFastInfoset(rootContentType)) {
359: if (packet.contentNegotiation == ContentNegotiation.none)
360: throw noFastInfosetForDecoding();
361:
362: useFastInfosetForEncoding = true;
363: fiSwaCodec.decode(mpp, packet);
364: } else if (isXml(rootContentType))
365: xmlSwaCodec.decode(mpp, packet);
366: else {
367: // TODO localize exception
368: throw new IOException("");
369: }
370: // checkDuplicateKnownHeaders(packet);
371: }
372:
373: private boolean isMultipartRelated(String contentType) {
374: return compareStrings(contentType,
375: MimeCodec.MULTIPART_RELATED_MIME_TYPE);
376: }
377:
378: private boolean isApplicationXopXml(String contentType) {
379: return compareStrings(contentType, MtomCodec.XOP_XML_MIME_TYPE);
380: }
381:
382: private boolean isXml(String contentType) {
383: return compareStrings(contentType, xmlMimeType);
384: }
385:
386: private boolean isFastInfoset(String contentType) {
387: if (isFastInfosetDisabled)
388: return false;
389:
390: return compareStrings(contentType, fiMimeType);
391: }
392:
393: private boolean compareStrings(String a, String b) {
394: return a.length() >= b.length()
395: && b.equalsIgnoreCase(a.substring(0, b.length()));
396: }
397:
398: private boolean isFastInfosetAcceptable(String accept) {
399: if (accept == null || isFastInfosetDisabled)
400: return false;
401:
402: StringTokenizer st = new StringTokenizer(accept, ",");
403: while (st.hasMoreTokens()) {
404: final String token = st.nextToken().trim();
405: if (token.equalsIgnoreCase(fiMimeType)) {
406: return true;
407: }
408: }
409: return false;
410: }
411:
412: /**
413: * Determines the encoding codec.
414: */
415: private Codec getEncoder(Packet p) {
416: /**
417: * The following logic is only for outbound packets
418: * to be encoded by a client.
419: * For a server the p.contentNegotiation == null.
420: */
421: if (!ignoreContentNegotiationProperty) {
422: if (p.contentNegotiation == ContentNegotiation.none) {
423: // The client may have changed the negotiation property from
424: // pessismistic to none between invocations
425: useFastInfosetForEncoding = false;
426: } else if (p.contentNegotiation == ContentNegotiation.optimistic) {
427: // Always encode using Fast Infoset if in optimisitic mode
428: useFastInfosetForEncoding = true;
429: }
430: }
431:
432: // Override the MTOM binding for now
433: // Note: Using FI with MTOM does not make sense
434: if (useFastInfosetForEncoding) {
435: final Message m = p.getMessage();
436: if (m == null || m.getAttachments().isEmpty()
437: || binding.isFeatureEnabled(MTOMFeature.class))
438: return fiSoapCodec;
439: else
440: return fiSwaCodec;
441: }
442:
443: if (binding.isFeatureEnabled(MTOMFeature.class))
444: return xmlMtomCodec;
445:
446: Message m = p.getMessage();
447: if (m == null || m.getAttachments().isEmpty())
448: return xmlSoapCodec;
449: else
450: return xmlSwaCodec;
451: }
452:
453: private RuntimeException noFastInfosetForDecoding() {
454: return new RuntimeException(StreamingMessages
455: .FASTINFOSET_DECODING_NOT_ACCEPTED());
456: }
457:
458: /**
459: * Obtain an FI SOAP codec instance using reflection.
460: */
461: private static Codec getFICodec(StreamSOAPCodec soapCodec,
462: SOAPVersion version) {
463: try {
464: Class c = Class
465: .forName("com.sun.xml.ws.encoding.fastinfoset.FastInfosetStreamSOAPCodec");
466: Method m = c.getMethod("create", StreamSOAPCodec.class,
467: SOAPVersion.class);
468: return (Codec) m.invoke(null, soapCodec, version);
469: } catch (Exception e) {
470: // TODO Log that FI cannot be loaded
471: return null;
472: }
473: }
474: }
|