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 com.sun.istack.NotNull;
040: import com.sun.xml.bind.DatatypeConverterImpl;
041: import com.sun.xml.bind.v2.runtime.output.Encoded;
042: import com.sun.xml.ws.api.SOAPVersion;
043: import com.sun.xml.ws.api.WSBinding;
044: import com.sun.xml.ws.api.message.Attachment;
045: import com.sun.xml.ws.api.message.AttachmentSet;
046: import com.sun.xml.ws.api.message.Packet;
047: import com.sun.xml.ws.api.pipe.ContentType;
048: import com.sun.xml.ws.api.pipe.StreamSOAPCodec;
049: import com.sun.xml.ws.api.streaming.XMLStreamReaderFactory;
050: import com.sun.xml.ws.api.streaming.XMLStreamWriterFactory;
051: import com.sun.xml.ws.message.MimeAttachmentSet;
052: import com.sun.xml.ws.util.ByteArrayDataSource;
053: import com.sun.xml.ws.util.xml.XMLStreamReaderFilter;
054: import com.sun.xml.ws.util.xml.XMLStreamWriterFilter;
055: import org.jvnet.staxex.Base64Data;
056: import org.jvnet.staxex.NamespaceContextEx;
057: import org.jvnet.staxex.XMLStreamReaderEx;
058: import org.jvnet.staxex.XMLStreamWriterEx;
059:
060: import javax.activation.DataHandler;
061: import javax.xml.namespace.NamespaceContext;
062: import javax.xml.stream.XMLStreamConstants;
063: import javax.xml.stream.XMLStreamException;
064: import javax.xml.stream.XMLStreamReader;
065: import javax.xml.stream.XMLStreamWriter;
066: import javax.xml.ws.WebServiceException;
067: import javax.xml.ws.WebServiceFeature;
068: import javax.xml.ws.soap.MTOMFeature;
069: import java.io.IOException;
070: import java.io.OutputStream;
071: import java.io.UnsupportedEncodingException;
072: import java.net.URLDecoder;
073: import java.nio.channels.WritableByteChannel;
074: import java.nio.charset.Charset;
075: import java.util.ArrayList;
076: import java.util.Iterator;
077: import java.util.List;
078: import java.util.UUID;
079:
080: /**
081: * Mtom messge Codec. It can be used even for non-soap message's mtom encoding.
082: *
083: * @author Vivek Pandey
084: */
085: public class MtomCodec extends MimeCodec {
086: public static final String XOP_XML_MIME_TYPE = "application/xop+xml";
087:
088: private final StreamSOAPCodec codec;
089:
090: // encoding related parameters
091: private String boundary;
092: private String rootId;
093: private final String soapXopContentType;
094: private String messageContentType;
095: private final MTOMFeature mtomFeature;
096:
097: MtomCodec(SOAPVersion version, StreamSOAPCodec codec,
098: WSBinding binding, WebServiceFeature mtomFeature) {
099: super (version, binding);
100: this .codec = codec;
101: createConteTypeHeader();
102: this .soapXopContentType = XOP_XML_MIME_TYPE
103: + ";charset=utf-8;type=\"" + version.contentType + "\"";
104: if (mtomFeature == null)
105: this .mtomFeature = new MTOMFeature();
106: else
107: this .mtomFeature = (MTOMFeature) mtomFeature;
108: }
109:
110: private void createConteTypeHeader() {
111: String uuid = UUID.randomUUID().toString();
112: boundary = "uuid:" + uuid;
113: rootId = "<rootpart*" + uuid + "@example.jaxws.sun.com>";
114: String boundaryParameter = "boundary=\"" + boundary + "\"";
115: messageContentType = MULTIPART_RELATED_MIME_TYPE + ";start=\""
116: + rootId + "\"" + ";type=\"" + XOP_XML_MIME_TYPE
117: + "\";" + boundaryParameter + ";start-info=\""
118: + version.contentType + "\"";
119: }
120:
121: /**
122: * Return the soap 1.1 and soap 1.2 specific XOP packaged ContentType
123: *
124: * @return A non-null content type for soap11 or soap 1.2 content type
125: */
126: public ContentType getStaticContentType(Packet packet) {
127: return getContentType(packet);
128: }
129:
130: private ContentType getContentType(Packet packet) {
131: switch (version) {
132: case SOAP_11:
133: return new ContentTypeImpl(messageContentType,
134: (packet.soapAction == null) ? ""
135: : packet.soapAction, null);
136: case SOAP_12:
137: if (packet.soapAction != null) {
138: messageContentType += ";action=\"" + packet.soapAction
139: + "\"";
140: }
141: return new ContentTypeImpl(messageContentType, null, null);
142: }
143: //never happens
144: return null;
145: }
146:
147: public ContentType encode(Packet packet, OutputStream out)
148: throws IOException {
149: //get the current boundary thaat will be reaturned from this method
150: ContentType contentType = getContentType(packet);
151:
152: if (packet.getMessage() != null) {
153: try {
154: writeln("--" + boundary, out);
155: writeln("Content-Id: " + rootId, out);
156: writeln("Content-Type: " + soapXopContentType, out);
157: writeln("Content-Transfer-Encoding: binary", out);
158: writeln(out);
159:
160: //mtom attachments that need to be written after the root part
161: List<ByteArrayBuffer> mtomAttachments = new ArrayList<ByteArrayBuffer>();
162: MtomStreamWriter writer = new MtomStreamWriter(
163: XMLStreamWriterFactory.create(out), out,
164: mtomAttachments);
165: packet.getMessage().writeTo(writer);
166: XMLStreamWriterFactory.recycle(writer);
167: writeln(out);
168:
169: for (ByteArrayBuffer bos : mtomAttachments) {
170: bos.write(out);
171: }
172:
173: //now write out the attachments in the message
174: writeAttachments(packet.getMessage().getAttachments(),
175: out);
176:
177: //write out the end boundary
178: writeAsAscii("--" + boundary, out);
179: writeAsAscii("--", out);
180:
181: } catch (XMLStreamException e) {
182: throw new WebServiceException(e);
183: }
184: }
185: //now create the boundary for next encode() call
186: createConteTypeHeader();
187: return contentType;
188: }
189:
190: private class ByteArrayBuffer {
191: final String contentId;
192:
193: private DataHandler dh;
194:
195: ByteArrayBuffer(@NotNull
196: DataHandler dh) {
197: this .dh = dh;
198: this .contentId = encodeCid();
199: }
200:
201: void write(OutputStream os) throws IOException {
202: //build attachment frame
203: writeln("--" + boundary, os);
204: writeMimeHeaders(dh.getContentType(), contentId, os);
205: dh.writeTo(os);
206: writeln(os);
207: }
208: }
209:
210: private void writeMimeHeaders(String contentType, String contentId,
211: OutputStream out) throws IOException {
212: String cid = contentId;
213: if (cid != null && cid.length() > 0 && cid.charAt(0) != '<')
214: cid = '<' + cid + '>';
215: writeln("Content-Id: " + cid, out);
216: writeln("Content-Type: " + contentType, out);
217: writeln("Content-Transfer-Encoding: binary", out);
218: writeln(out);
219: }
220:
221: private void writeAttachments(AttachmentSet attachments,
222: OutputStream out) throws IOException {
223: for (Attachment att : attachments) {
224: //build attachment frame
225: writeln("--" + boundary, out);
226: writeMimeHeaders(att.getContentType(), att.getContentId(),
227: out);
228: att.writeTo(out);
229: writeln(out); // write \r\n
230: }
231: }
232:
233: public ContentType encode(Packet packet, WritableByteChannel buffer) {
234: throw new UnsupportedOperationException();
235: }
236:
237: public MtomCodec copy() {
238: return new MtomCodec(version, (StreamSOAPCodec) codec.copy(),
239: binding, mtomFeature);
240: }
241:
242: private String encodeCid() {
243: String cid = "example.jaxws.sun.com";
244: String name = UUID.randomUUID() + "@";
245: return name + cid;
246: }
247:
248: @Override
249: protected void decode(MimeMultipartParser mpp, Packet packet)
250: throws IOException {
251: // we'd like to reuse those reader objects but unfortunately decoder may be reused
252: // before the decoded message is completely used.
253:
254: XMLStreamReader mtomReader = new MtomXMLStreamReaderEx(mpp,
255: XMLStreamReaderFactory.create(null, mpp.getRootPart()
256: .asInputStream(), true));
257:
258: //TODO: remove this code after {@link StreamSOAPCodec#decode} is modified to
259: //take AttachmentSet.
260: if (codec instanceof com.sun.xml.ws.encoding.StreamSOAPCodec) {
261: packet
262: .setMessage(((com.sun.xml.ws.encoding.StreamSOAPCodec) codec)
263: .decode(mtomReader, new MimeAttachmentSet(
264: mpp)));
265: } else {
266: packet.setMessage(codec.decode(mtomReader));
267: }
268: }
269:
270: private class MtomStreamWriter extends XMLStreamWriterFilter
271: implements XMLStreamWriterEx {
272: private final OutputStream out;
273: private final Encoded encoded = new Encoded();
274: private final List<ByteArrayBuffer> mtomAttachments;
275:
276: public MtomStreamWriter(XMLStreamWriter w, OutputStream out,
277: List<ByteArrayBuffer> mtomAttachments) {
278: super (w);
279: this .out = out;
280: this .mtomAttachments = mtomAttachments;
281: }
282:
283: public void writeBinary(byte[] data, int start, int len,
284: String contentType) throws XMLStreamException {
285: //check threshold and if less write as base64encoded value
286: if (mtomFeature.getThreshold() > len) {
287: writeCharacters(DatatypeConverterImpl
288: ._printBase64Binary(data, start, len));
289: return;
290: }
291: ByteArrayBuffer bab = new ByteArrayBuffer(new DataHandler(
292: new ByteArrayDataSource(data, start, len,
293: contentType)));
294: writeBinary(bab);
295: }
296:
297: public void writeBinary(DataHandler dataHandler)
298: throws XMLStreamException {
299: // TODO how do we check threshold and if less inline the data
300: writeBinary(new ByteArrayBuffer(dataHandler));
301: }
302:
303: public OutputStream writeBinary(String contentType)
304: throws XMLStreamException {
305: throw new UnsupportedOperationException();
306: }
307:
308: public void writePCDATA(CharSequence data)
309: throws XMLStreamException {
310: if (data == null)
311: return;
312: if (data instanceof Base64Data) {
313: Base64Data binaryData = (Base64Data) data;
314: writeBinary(binaryData.getDataHandler());
315: return;
316: }
317: writeCharacters(data.toString());
318: }
319:
320: private void writeBinary(ByteArrayBuffer bab) {
321: try {
322: mtomAttachments.add(bab);
323:
324: writer.writeCharacters(""); // Force completion of open elems
325: writer.flush();
326: //flush the underlying writer to write-out any cached data to the underlying
327: // stream before writing directly to it
328: //write out the xop reference
329: out.write(XOP_PREF);
330: encoded.set(bab.contentId);
331: out.write(encoded.buf, 0, encoded.len);
332: out.write(XOP_SUFF);
333: } catch (IOException e) {
334: throw new WebServiceException(e);
335: } catch (XMLStreamException e) {
336: throw new WebServiceException(e);
337: }
338: }
339:
340: private class MtomNamespaceContextEx implements
341: NamespaceContextEx {
342: private NamespaceContext nsContext;
343:
344: public MtomNamespaceContextEx(NamespaceContext nsContext) {
345: this .nsContext = nsContext;
346: }
347:
348: public Iterator<Binding> iterator() {
349: throw new UnsupportedOperationException();
350: }
351:
352: public String getNamespaceURI(String prefix) {
353: return nsContext.getNamespaceURI(prefix);
354: }
355:
356: public String getPrefix(String namespaceURI) {
357: return nsContext.getPrefix(namespaceURI);
358: }
359:
360: public Iterator getPrefixes(String namespaceURI) {
361: return nsContext.getPrefixes(namespaceURI);
362: }
363: }
364:
365: public NamespaceContextEx getNamespaceContext() {
366: NamespaceContext nsContext = writer.getNamespaceContext();
367: return new MtomNamespaceContextEx(nsContext);
368: }
369: }
370:
371: private static class MtomXMLStreamReaderEx extends
372: XMLStreamReaderFilter implements XMLStreamReaderEx {
373: /**
374: * The parser for the outer MIME 'shell'.
375: */
376: private final MimeMultipartParser mimeMP;
377:
378: private boolean xopReferencePresent = false;
379: private Base64Data base64AttData;
380:
381: //To be used with #getTextCharacters
382: private char[] base64EncodedText;
383:
384: public MtomXMLStreamReaderEx(MimeMultipartParser mimeMP,
385: XMLStreamReader reader) {
386: super (reader);
387: this .mimeMP = mimeMP;
388: }
389:
390: public CharSequence getPCDATA() throws XMLStreamException {
391: if (xopReferencePresent) {
392: return base64AttData;
393: }
394: return reader.getText();
395: }
396:
397: public NamespaceContextEx getNamespaceContext() {
398: NamespaceContext nsContext = reader.getNamespaceContext();
399: return new MtomNamespaceContextEx(nsContext);
400: }
401:
402: public String getElementTextTrim() throws XMLStreamException {
403: throw new UnsupportedOperationException();
404: }
405:
406: private class MtomNamespaceContextEx implements
407: NamespaceContextEx {
408: private NamespaceContext nsContext;
409:
410: public MtomNamespaceContextEx(NamespaceContext nsContext) {
411: this .nsContext = nsContext;
412: }
413:
414: public Iterator<Binding> iterator() {
415: throw new UnsupportedOperationException();
416: }
417:
418: public String getNamespaceURI(String prefix) {
419: return nsContext.getNamespaceURI(prefix);
420: }
421:
422: public String getPrefix(String namespaceURI) {
423: return nsContext.getPrefix(namespaceURI);
424: }
425:
426: public Iterator getPrefixes(String namespaceURI) {
427: return nsContext.getPrefixes(namespaceURI);
428: }
429:
430: }
431:
432: public int getTextLength() {
433: if (xopReferencePresent) {
434: return base64AttData.length();
435: }
436: return reader.getTextLength();
437: }
438:
439: public int getTextStart() {
440: if (xopReferencePresent) {
441: return 0;
442: }
443: return reader.getTextStart();
444: }
445:
446: public int getEventType() {
447: if (xopReferencePresent)
448: return XMLStreamConstants.CHARACTERS;
449: return super .getEventType();
450: }
451:
452: public int next() throws XMLStreamException {
453: int event = reader.next();
454: if ((event == XMLStreamConstants.START_ELEMENT)
455: && reader.getLocalName().equals(XOP_LOCALNAME)
456: && reader.getNamespaceURI()
457: .equals(XOP_NAMESPACEURI)) {
458: //its xop reference, take the URI reference
459: String href = reader.getAttributeValue(null, "href");
460: try {
461: Attachment att = getAttachment(href);
462: if (att != null) {
463: base64AttData = new Base64Data();
464: base64AttData.set(att.asDataHandler());
465: }
466: xopReferencePresent = true;
467: } catch (IOException e) {
468: throw new WebServiceException(e);
469: }
470: //move to the </xop:Include>
471: try {
472: reader.next();
473: } catch (XMLStreamException e) {
474: throw new WebServiceException(e);
475: }
476: return XMLStreamConstants.CHARACTERS;
477: }
478: if (xopReferencePresent) {
479: xopReferencePresent = false;
480: base64EncodedText = null;
481: }
482: return event;
483: }
484:
485: private String decodeCid(String cid) {
486: try {
487: cid = URLDecoder.decode(cid, "utf-8");
488: } catch (UnsupportedEncodingException e) {
489: //on recceiving side lets not fail now, try to look for it
490: }
491: return cid;
492: }
493:
494: private Attachment getAttachment(String cid) throws IOException {
495: if (cid.startsWith("cid:"))
496: cid = cid.substring(4, cid.length());
497: if (cid.indexOf('%') != -1) {
498: cid = decodeCid(cid);
499: return mimeMP.getAttachmentPart(cid);
500: }
501: return mimeMP.getAttachmentPart(cid);
502: }
503:
504: public char[] getTextCharacters() {
505: if (xopReferencePresent) {
506: char[] chars = new char[base64AttData.length()];
507: base64AttData.writeTo(chars, 0);
508: return chars;
509: }
510: return reader.getTextCharacters();
511: }
512:
513: public int getTextCharacters(int sourceStart, char[] target,
514: int targetStart, int length) throws XMLStreamException {
515: if (xopReferencePresent) {
516: int event = reader.getEventType();
517: if (event != XMLStreamConstants.CHARACTERS) {
518: //its invalid state - delegate it to underlying reader to throw the corrrect exception so that user
519: // always sees the uniform exception from the XMLStreamReader
520: throw new XMLStreamException(
521: "Invalid state: Expected CHARACTERS found :");
522: }
523: if (target == null) {
524: throw new NullPointerException(
525: "target char array can't be null");
526: }
527:
528: if (targetStart < 0 || length < 0 || sourceStart < 0
529: || targetStart >= target.length
530: || (targetStart + length) > target.length) {
531: throw new IndexOutOfBoundsException();
532: }
533:
534: int textLength = base64AttData.length();
535: if (sourceStart > textLength)
536: throw new IndexOutOfBoundsException();
537:
538: if (base64EncodedText == null) {
539: base64EncodedText = new char[base64AttData.length()];
540: base64AttData.writeTo(base64EncodedText, 0);
541: }
542:
543: int copiedLength = Math.min(textLength - sourceStart,
544: length);
545: System.arraycopy(base64EncodedText, sourceStart,
546: target, targetStart, copiedLength);
547: return copiedLength;
548: }
549: return reader.getTextCharacters(sourceStart, target,
550: targetStart, length);
551: }
552:
553: public String getText() {
554: if (xopReferencePresent) {
555: return base64AttData.toString();
556: }
557: return reader.getText();
558: }
559: }
560:
561: private static final byte[] XOP_PREF = encode("<Include xmlns=\"http://www.w3.org/2004/08/xop/include\" href=\"cid:");
562:
563: private static byte[] encode(String str) {
564: try {
565: return str.getBytes("UTF-8");
566: } catch (UnsupportedEncodingException e) {
567: throw new Error(e);
568: }
569: }
570:
571: private static final byte[] XOP_SUFF = encode("\"/>");
572: private static final String XOP_LOCALNAME = "Include";
573: private static final String XOP_NAMESPACEURI = "http://www.w3.org/2004/08/xop/include";
574:
575: private static final Charset UTF8 = Charset.forName("UTF-8");
576: }
|