Wed, 27 Apr 2016 01:27:09 +0800
Initial load
http://hg.openjdk.java.net/jdk8u/jdk8u/jaxws/
changeset: 657:d47a47f961ee
tag: jdk8u25-b17
1 /*
2 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
26 package com.sun.xml.internal.ws.encoding;
28 import com.sun.istack.internal.NotNull;
29 import com.sun.xml.internal.bind.DatatypeConverterImpl;
30 import com.sun.xml.internal.ws.api.SOAPVersion;
31 import com.sun.xml.internal.ws.api.WSFeatureList;
32 import com.sun.xml.internal.ws.api.message.Attachment;
33 import com.sun.xml.internal.ws.api.message.AttachmentSet;
34 import com.sun.xml.internal.ws.api.message.Packet;
35 import com.sun.xml.internal.ws.api.pipe.ContentType;
36 import com.sun.xml.internal.ws.api.pipe.StreamSOAPCodec;
37 import com.sun.xml.internal.ws.api.streaming.XMLStreamReaderFactory;
38 import com.sun.xml.internal.ws.api.streaming.XMLStreamWriterFactory;
39 import com.sun.xml.internal.ws.developer.SerializationFeature;
40 import com.sun.xml.internal.ws.developer.StreamingDataHandler;
41 import com.sun.xml.internal.ws.message.MimeAttachmentSet;
42 import com.sun.xml.internal.ws.streaming.XMLStreamWriterUtil;
43 import com.sun.xml.internal.ws.util.ByteArrayDataSource;
44 import com.sun.xml.internal.ws.util.xml.NamespaceContextExAdaper;
45 import com.sun.xml.internal.ws.util.xml.XMLStreamReaderFilter;
46 import com.sun.xml.internal.ws.util.xml.XMLStreamWriterFilter;
47 import com.sun.xml.internal.ws.streaming.MtomStreamWriter;
48 import com.sun.xml.internal.ws.streaming.XMLStreamReaderUtil;
49 import com.sun.xml.internal.ws.server.UnsupportedMediaException;
50 import com.sun.xml.internal.org.jvnet.staxex.Base64Data;
51 import com.sun.xml.internal.org.jvnet.staxex.NamespaceContextEx;
52 import com.sun.xml.internal.org.jvnet.staxex.XMLStreamReaderEx;
53 import com.sun.xml.internal.org.jvnet.staxex.XMLStreamWriterEx;
55 import javax.activation.DataHandler;
56 import javax.xml.namespace.NamespaceContext;
57 import javax.xml.stream.XMLStreamConstants;
58 import javax.xml.stream.XMLStreamException;
59 import javax.xml.stream.XMLStreamReader;
60 import javax.xml.stream.XMLStreamWriter;
61 import javax.xml.ws.WebServiceException;
62 import javax.xml.ws.soap.MTOMFeature;
63 import javax.xml.bind.attachment.AttachmentMarshaller;
64 import java.io.IOException;
65 import java.io.OutputStream;
66 import java.io.UnsupportedEncodingException;
67 import java.net.URLDecoder;
68 import java.nio.channels.WritableByteChannel;
69 import java.nio.charset.Charset;
70 import java.util.ArrayList;
71 import java.util.Iterator;
72 import java.util.List;
73 import java.util.Map;
74 import java.util.UUID;
76 /**
77 * Mtom message Codec. It can be used even for non-soap message's mtom encoding.
78 *
79 * @author Vivek Pandey
80 * @author Jitendra Kotamraju
81 */
82 public class MtomCodec extends MimeCodec {
84 public static final String XOP_XML_MIME_TYPE = "application/xop+xml";
85 public static final String XOP_LOCALNAME = "Include";
86 public static final String XOP_NAMESPACEURI = "http://www.w3.org/2004/08/xop/include";
88 private final StreamSOAPCodec codec;
89 private final MTOMFeature mtomFeature;
90 private final SerializationFeature sf;
91 private final static String DECODED_MESSAGE_CHARSET = "decodedMessageCharset";
93 MtomCodec(SOAPVersion version, StreamSOAPCodec codec, WSFeatureList features){
94 super(version, features);
95 this.codec = codec;
96 sf = features.get(SerializationFeature.class);
97 MTOMFeature mtom = features.get(MTOMFeature.class);
98 if(mtom == null)
99 this.mtomFeature = new MTOMFeature();
100 else
101 this.mtomFeature = mtom;
102 }
104 /**
105 * Return the soap 1.1 and soap 1.2 specific XOP packaged ContentType
106 *
107 * @return A non-null content type for soap11 or soap 1.2 content type
108 */
109 @Override
110 public ContentType getStaticContentType(Packet packet) {
111 return getStaticContentTypeStatic(packet, version);
112 }
114 public static ContentType getStaticContentTypeStatic(Packet packet, SOAPVersion version) {
115 ContentType ct = (ContentType) packet.getInternalContentType();
116 if ( ct != null ) return ct;
118 String uuid = UUID.randomUUID().toString();
119 String boundary = "uuid:" + uuid;
120 String rootId = "<rootpart*"+uuid+"@example.jaxws.sun.com>";
121 String soapActionParameter = SOAPVersion.SOAP_11.equals(version) ? null : createActionParameter(packet);
123 String boundaryParameter = "boundary=\"" + boundary +"\"";
124 String messageContentType = MULTIPART_RELATED_MIME_TYPE +
125 ";start=\""+rootId +"\"" +
126 ";type=\"" + XOP_XML_MIME_TYPE + "\";" +
127 boundaryParameter +
128 ";start-info=\"" + version.contentType +
129 (soapActionParameter == null? "" : soapActionParameter) +
130 "\"";
132 ContentTypeImpl ctImpl = SOAPVersion.SOAP_11.equals(version) ?
133 new ContentTypeImpl(messageContentType, (packet.soapAction == null)?"":packet.soapAction, null) :
134 new ContentTypeImpl(messageContentType, null, null);
135 ctImpl.setBoundary(boundary);
136 ctImpl.setRootId(rootId);
137 packet.setContentType(ctImpl);
138 return ctImpl;
139 }
141 private static String createActionParameter(Packet packet) {
142 return packet.soapAction != null? ";action=\\\""+packet.soapAction+"\\\"" : "";
143 }
145 @Override
146 public ContentType encode(Packet packet, OutputStream out) throws IOException {
147 ContentTypeImpl ctImpl = (ContentTypeImpl) this.getStaticContentType(packet);
148 String boundary = ctImpl.getBoundary();
149 String rootId = ctImpl.getRootId();
151 if(packet.getMessage() != null){
152 try {
153 String encoding = getPacketEncoding(packet);
154 packet.invocationProperties.remove(DECODED_MESSAGE_CHARSET);
156 String actionParameter = getActionParameter(packet, version);
157 String soapXopContentType = getSOAPXopContentType(encoding, version, actionParameter);
159 writeln("--"+boundary, out);
160 writeMimeHeaders(soapXopContentType, rootId, out);
162 //mtom attachments that need to be written after the root part
163 List<ByteArrayBuffer> mtomAttachments = new ArrayList<ByteArrayBuffer>();
164 MtomStreamWriterImpl writer = new MtomStreamWriterImpl(
165 XMLStreamWriterFactory.create(out, encoding), mtomAttachments, boundary, mtomFeature);
167 packet.getMessage().writeTo(writer);
168 XMLStreamWriterFactory.recycle(writer);
169 writeln(out);
171 for(ByteArrayBuffer bos : mtomAttachments){
172 bos.write(out);
173 }
175 // now write out the attachments in the message that weren't
176 // previously written
177 writeNonMtomAttachments(packet.getMessage().getAttachments(),
178 out, boundary);
180 //write out the end boundary
181 writeAsAscii("--"+boundary, out);
182 writeAsAscii("--", out);
184 } catch (XMLStreamException e) {
185 throw new WebServiceException(e);
186 }
187 }
188 //now create the boundary for next encode() call
189 // createConteTypeHeader();
190 return ctImpl;
191 }
193 public static String getSOAPXopContentType(String encoding, SOAPVersion version,
194 String actionParameter) {
195 return XOP_XML_MIME_TYPE +";charset="+encoding+";type=\""+version.contentType+ actionParameter + "\"";
196 }
198 public static String getActionParameter(Packet packet, SOAPVersion version) {
199 return (version == SOAPVersion.SOAP_11) ? "" : createActionParameter(packet);
200 }
202 public static class ByteArrayBuffer{
203 final String contentId;
205 private final DataHandler dh;
206 private final String boundary;
208 ByteArrayBuffer(@NotNull DataHandler dh, String b) {
209 this.dh = dh;
210 String cid = null;
211 if (dh instanceof StreamingDataHandler) {
212 StreamingDataHandler sdh = (StreamingDataHandler) dh;
213 if (sdh.getHrefCid() != null)
214 cid = sdh.getHrefCid();
215 }
216 this.contentId = cid != null ? cid : encodeCid();
217 boundary = b;
218 }
220 public void write(OutputStream os) throws IOException {
221 //build attachment frame
222 writeln("--"+boundary, os);
223 writeMimeHeaders(dh.getContentType(), contentId, os);
224 dh.writeTo(os);
225 writeln(os);
226 }
227 }
229 public static void writeMimeHeaders(String contentType, String contentId, OutputStream out) throws IOException {
230 String cid = contentId;
231 if(cid != null && cid.length() >0 && cid.charAt(0) != '<')
232 cid = '<' + cid + '>';
233 writeln("Content-Id: " + cid, out);
234 writeln("Content-Type: " + contentType, out);
235 writeln("Content-Transfer-Encoding: binary", out);
236 writeln(out);
237 }
239 // Compiler warning for not calling close, but cannot call close,
240 // will consume attachment bytes.
241 @SuppressWarnings("resource")
242 private void writeNonMtomAttachments(AttachmentSet attachments,
243 OutputStream out, String boundary) throws IOException {
245 for (Attachment att : attachments) {
247 DataHandler dh = att.asDataHandler();
248 if (dh instanceof StreamingDataHandler) {
249 StreamingDataHandler sdh = (StreamingDataHandler) dh;
250 // If DataHandler has href Content-ID, it is MTOM, so skip.
251 if (sdh.getHrefCid() != null)
252 continue;
253 }
255 // build attachment frame
256 writeln("--" + boundary, out);
257 writeMimeHeaders(att.getContentType(), att.getContentId(), out);
258 att.writeTo(out);
259 writeln(out); // write \r\n
260 }
261 }
263 @Override
264 public ContentType encode(Packet packet, WritableByteChannel buffer) {
265 throw new UnsupportedOperationException();
266 }
268 @Override
269 public MtomCodec copy() {
270 return new MtomCodec(version, (StreamSOAPCodec)codec.copy(), features);
271 }
273 private static String encodeCid(){
274 String cid="example.jaxws.sun.com";
275 String name = UUID.randomUUID()+"@";
276 return name + cid;
277 }
279 @Override
280 protected void decode(MimeMultipartParser mpp, Packet packet) throws IOException {
281 //TODO shouldn't we check for SOAP1.1/SOAP1.2 and throw
282 //TODO UnsupportedMediaException like StreamSOAPCodec
283 String charset = null;
284 String ct = mpp.getRootPart().getContentType();
285 if (ct != null) {
286 charset = new ContentTypeImpl(ct).getCharSet();
287 }
288 if (charset != null && !Charset.isSupported(charset)) {
289 throw new UnsupportedMediaException(charset);
290 }
292 if (charset != null) {
293 packet.invocationProperties.put(DECODED_MESSAGE_CHARSET, charset);
294 } else {
295 packet.invocationProperties.remove(DECODED_MESSAGE_CHARSET);
296 }
298 // we'd like to reuse those reader objects but unfortunately decoder may be reused
299 // before the decoded message is completely used.
300 XMLStreamReader mtomReader = new MtomXMLStreamReaderEx( mpp,
301 XMLStreamReaderFactory.create(null, mpp.getRootPart().asInputStream(), charset, true)
302 );
304 packet.setMessage(codec.decode(mtomReader, new MimeAttachmentSet(mpp)));
305 packet.setMtomFeature(mtomFeature);
306 packet.setContentType(mpp.getContentType());
307 }
309 private String getPacketEncoding(Packet packet) {
310 // If SerializationFeature is set, just use that encoding
311 if (sf != null && sf.getEncoding() != null) {
312 return sf.getEncoding().equals("") ? SOAPBindingCodec.DEFAULT_ENCODING : sf.getEncoding();
313 }
314 return determinePacketEncoding(packet);
315 }
317 public static String determinePacketEncoding(Packet packet) {
318 if (packet != null && packet.endpoint != null) {
319 // Use request message's encoding for Server-side response messages
320 String charset = (String)packet.invocationProperties.get(DECODED_MESSAGE_CHARSET);
321 return charset == null
322 ? SOAPBindingCodec.DEFAULT_ENCODING : charset;
323 }
325 // Use default encoding for client-side request messages
326 return SOAPBindingCodec.DEFAULT_ENCODING;
327 }
329 public static class MtomStreamWriterImpl extends XMLStreamWriterFilter implements XMLStreamWriterEx,
330 MtomStreamWriter, HasEncoding {
331 private final List<ByteArrayBuffer> mtomAttachments;
332 private final String boundary;
333 private final MTOMFeature myMtomFeature;
334 public MtomStreamWriterImpl(XMLStreamWriter w, List<ByteArrayBuffer> mtomAttachments, String b, MTOMFeature myMtomFeature) {
335 super(w);
336 this.mtomAttachments = mtomAttachments;
337 this.boundary = b;
338 this.myMtomFeature = myMtomFeature;
339 }
341 @Override
342 public void writeBinary(byte[] data, int start, int len, String contentType) throws XMLStreamException {
343 //check threshold and if less write as base64encoded value
344 if(myMtomFeature.getThreshold() > len){
345 writeCharacters(DatatypeConverterImpl._printBase64Binary(data, start, len));
346 return;
347 }
348 ByteArrayBuffer bab = new ByteArrayBuffer(new DataHandler(new ByteArrayDataSource(data, start, len, contentType)), boundary);
349 writeBinary(bab);
350 }
352 @Override
353 public void writeBinary(DataHandler dataHandler) throws XMLStreamException {
354 // TODO how do we check threshold and if less inline the data
355 writeBinary(new ByteArrayBuffer(dataHandler, boundary));
356 }
358 @Override
359 public OutputStream writeBinary(String contentType) throws XMLStreamException {
360 throw new UnsupportedOperationException();
361 }
363 @Override
364 public void writePCDATA(CharSequence data) throws XMLStreamException {
365 if(data == null)
366 return;
367 if(data instanceof Base64Data){
368 Base64Data binaryData = (Base64Data)data;
369 writeBinary(binaryData.getDataHandler());
370 return;
371 }
372 writeCharacters(data.toString());
373 }
375 private void writeBinary(ByteArrayBuffer bab) {
376 try {
377 mtomAttachments.add(bab);
378 String prefix = writer.getPrefix(XOP_NAMESPACEURI);
379 if (prefix == null || !prefix.equals("xop")) {
380 writer.setPrefix("xop", XOP_NAMESPACEURI);
381 writer.writeNamespace("xop", XOP_NAMESPACEURI);
382 }
383 writer.writeStartElement(XOP_NAMESPACEURI, XOP_LOCALNAME);
384 writer.writeAttribute("href", "cid:"+bab.contentId);
385 writer.writeEndElement();
386 writer.flush();
387 } catch (XMLStreamException e) {
388 throw new WebServiceException(e);
389 }
390 }
392 @Override
393 public Object getProperty(String name) throws IllegalArgumentException {
394 // Hack for JDK6's SJSXP
395 if (name.equals("sjsxp-outputstream") && writer instanceof Map) {
396 Object obj = ((Map) writer).get("sjsxp-outputstream");
397 if (obj != null) {
398 return obj;
399 }
400 }
401 return super.getProperty(name);
402 }
404 /**
405 * JAXBMessage writes envelope directly to the OutputStream(for SJSXP, woodstox).
406 * While writing, it calls the AttachmentMarshaller methods for adding attachments.
407 * JAXB writes xop:Include in this case.
408 */
409 @Override
410 public AttachmentMarshaller getAttachmentMarshaller() {
411 return new AttachmentMarshaller() {
413 @Override
414 public String addMtomAttachment(DataHandler data, String elementNamespace, String elementLocalName) {
415 // Should we do the threshold processing on DataHandler ? But that would be
416 // expensive as DataHolder need to read the data again from its source
417 ByteArrayBuffer bab = new ByteArrayBuffer(data, boundary);
418 mtomAttachments.add(bab);
419 return "cid:"+bab.contentId;
420 }
422 @Override
423 public String addMtomAttachment(byte[] data, int offset, int length, String mimeType, String elementNamespace, String elementLocalName) {
424 // inline the data based on the threshold
425 if (myMtomFeature.getThreshold() > length) {
426 return null; // JAXB inlines the attachment data
427 }
428 ByteArrayBuffer bab = new ByteArrayBuffer(new DataHandler(new ByteArrayDataSource(data, offset, length, mimeType)), boundary);
429 mtomAttachments.add(bab);
430 return "cid:"+bab.contentId;
431 }
433 @Override
434 public String addSwaRefAttachment(DataHandler data) {
435 ByteArrayBuffer bab = new ByteArrayBuffer(data, boundary);
436 mtomAttachments.add(bab);
437 return "cid:"+bab.contentId;
438 }
440 @Override
441 public boolean isXOPPackage() {
442 return true;
443 }
444 };
445 }
447 public List<ByteArrayBuffer> getMtomAttachments() {
448 return this.mtomAttachments;
449 }
451 @Override
452 public String getEncoding() {
453 return XMLStreamWriterUtil.getEncoding(writer);
454 }
456 private static class MtomNamespaceContextEx implements NamespaceContextEx {
457 private final NamespaceContext nsContext;
459 public MtomNamespaceContextEx(NamespaceContext nsContext) {
460 this.nsContext = nsContext;
461 }
463 @Override
464 public Iterator<Binding> iterator() {
465 throw new UnsupportedOperationException();
466 }
468 @Override
469 public String getNamespaceURI(String prefix) {
470 return nsContext.getNamespaceURI(prefix);
471 }
473 @Override
474 public String getPrefix(String namespaceURI) {
475 return nsContext.getPrefix(namespaceURI);
476 }
478 @Override
479 public Iterator getPrefixes(String namespaceURI) {
480 return nsContext.getPrefixes(namespaceURI);
481 }
482 }
484 @Override
485 public NamespaceContextEx getNamespaceContext() {
486 NamespaceContext nsContext = writer.getNamespaceContext();
487 return new MtomNamespaceContextEx(nsContext);
488 }
489 }
491 public static class MtomXMLStreamReaderEx extends XMLStreamReaderFilter implements XMLStreamReaderEx {
492 /**
493 * The parser for the outer MIME 'shell'.
494 */
495 private final MimeMultipartParser mimeMP;
497 private boolean xopReferencePresent = false;
498 private Base64Data base64AttData;
500 //To be used with #getTextCharacters
501 private char[] base64EncodedText;
503 private String xopHref;
505 public MtomXMLStreamReaderEx(MimeMultipartParser mimeMP, XMLStreamReader reader) {
506 super(reader);
507 this.mimeMP = mimeMP;
508 }
510 @Override
511 public CharSequence getPCDATA() throws XMLStreamException {
512 if(xopReferencePresent){
513 return base64AttData;
514 }
515 return reader.getText();
516 }
518 @Override
519 public NamespaceContextEx getNamespaceContext() {
520 return new NamespaceContextExAdaper(reader.getNamespaceContext());
521 }
523 @Override
524 public String getElementTextTrim() throws XMLStreamException {
525 throw new UnsupportedOperationException();
526 }
528 @Override
529 public int getTextLength() {
530 if (xopReferencePresent) {
531 return base64AttData.length();
532 }
533 return reader.getTextLength();
534 }
536 @Override
537 public int getTextStart() {
538 if (xopReferencePresent) {
539 return 0;
540 }
541 return reader.getTextStart();
542 }
544 @Override
545 public int getEventType() {
546 if(xopReferencePresent)
547 return XMLStreamConstants.CHARACTERS;
548 return super.getEventType();
549 }
551 @Override
552 public int next() throws XMLStreamException {
553 int event = reader.next();
554 if (event == XMLStreamConstants.START_ELEMENT && reader.getLocalName().equals(XOP_LOCALNAME) && reader.getNamespaceURI().equals(XOP_NAMESPACEURI)) {
555 //its xop reference, take the URI reference
556 String href = reader.getAttributeValue(null, "href");
557 try {
558 xopHref = href;
559 Attachment att = getAttachment(href);
560 if(att != null){
561 DataHandler dh = att.asDataHandler();
562 if (dh instanceof StreamingDataHandler) {
563 ((StreamingDataHandler)dh).setHrefCid(att.getContentId());
564 }
565 base64AttData = new Base64Data();
566 base64AttData.set(dh);
567 }
568 xopReferencePresent = true;
569 } catch (IOException e) {
570 throw new WebServiceException(e);
571 }
572 //move to the </xop:Include>
573 XMLStreamReaderUtil.nextElementContent(reader);
574 return XMLStreamConstants.CHARACTERS;
575 }
576 if(xopReferencePresent){
577 xopReferencePresent = false;
578 base64EncodedText = null;
579 xopHref = null;
580 }
581 return event;
582 }
584 private String decodeCid(String cid) {
585 try {
586 cid = URLDecoder.decode(cid, "utf-8");
587 } catch (UnsupportedEncodingException e) {
588 //on recceiving side lets not fail now, try to look for it
589 }
590 return cid;
591 }
593 private Attachment getAttachment(String cid) throws IOException {
594 if (cid.startsWith("cid:"))
595 cid = cid.substring(4, cid.length());
596 if (cid.indexOf('%') != -1) {
597 cid = decodeCid(cid);
598 return mimeMP.getAttachmentPart(cid);
599 }
600 return mimeMP.getAttachmentPart(cid);
601 }
603 @Override
604 public char[] getTextCharacters() {
605 if (xopReferencePresent) {
606 char[] chars = new char[base64AttData.length()];
607 base64AttData.writeTo(chars, 0);
608 return chars;
609 }
610 return reader.getTextCharacters();
611 }
613 @Override
614 public int getTextCharacters(int sourceStart, char[] target, int targetStart, int length) throws XMLStreamException {
615 if(xopReferencePresent){
616 if(target == null){
617 throw new NullPointerException("target char array can't be null") ;
618 }
620 if(targetStart < 0 || length < 0 || sourceStart < 0 || targetStart >= target.length ||
621 (targetStart + length ) > target.length) {
622 throw new IndexOutOfBoundsException();
623 }
625 int textLength = base64AttData.length();
626 if(sourceStart > textLength)
627 throw new IndexOutOfBoundsException();
629 if(base64EncodedText == null){
630 base64EncodedText = new char[base64AttData.length()];
631 base64AttData.writeTo(base64EncodedText, 0);
632 }
634 int copiedLength = Math.min(textLength - sourceStart, length);
635 System.arraycopy(base64EncodedText, sourceStart , target, targetStart, copiedLength);
636 return copiedLength;
637 }
638 return reader.getTextCharacters(sourceStart, target, targetStart, length);
639 }
641 @Override
642 public String getText() {
643 if (xopReferencePresent) {
644 return base64AttData.toString();
645 }
646 return reader.getText();
647 }
649 protected boolean isXopReference() throws XMLStreamException {
650 return xopReferencePresent;
651 }
653 protected String getXopHref() {
654 return xopHref;
655 }
657 public MimeMultipartParser getMimeMultipartParser() {
658 return mimeMP;
659 }
660 }
662 }