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 */ 25 26 package com.sun.xml.internal.ws.encoding; 27 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.XMLStreamReaderFilter; 45 import com.sun.xml.internal.ws.util.xml.XMLStreamWriterFilter; 46 import com.sun.xml.internal.ws.streaming.MtomStreamWriter; 47 import com.sun.xml.internal.ws.streaming.XMLStreamReaderUtil; 48 import com.sun.xml.internal.ws.server.UnsupportedMediaException; 49 import com.sun.xml.internal.org.jvnet.staxex.Base64Data; 50 import com.sun.xml.internal.org.jvnet.staxex.NamespaceContextEx; 51 import com.sun.xml.internal.org.jvnet.staxex.XMLStreamReaderEx; 52 import com.sun.xml.internal.org.jvnet.staxex.XMLStreamWriterEx; 53 54 import javax.activation.DataHandler; 55 import javax.xml.namespace.NamespaceContext; 56 import javax.xml.stream.XMLStreamConstants; 57 import javax.xml.stream.XMLStreamException; 58 import javax.xml.stream.XMLStreamReader; 59 import javax.xml.stream.XMLStreamWriter; 60 import javax.xml.ws.WebServiceException; 61 import javax.xml.ws.soap.MTOMFeature; 62 import javax.xml.bind.attachment.AttachmentMarshaller; 63 import java.io.IOException; 64 import java.io.OutputStream; 65 import java.io.UnsupportedEncodingException; 66 import java.net.URLDecoder; 67 import java.nio.channels.WritableByteChannel; 68 import java.nio.charset.Charset; 69 import java.util.ArrayList; 70 import java.util.Iterator; 71 import java.util.List; 72 import java.util.Map; 73 import java.util.UUID; 74 75 /** 76 * Mtom message Codec. It can be used even for non-soap message's mtom encoding. 77 * 78 * @author Vivek Pandey 79 * @author Jitendra Kotamraju 80 */ 81 public class MtomCodec extends MimeCodec { 82 83 public static final String XOP_XML_MIME_TYPE = "application/xop+xml"; 84 public static final String XOP_LOCALNAME = "Include"; 85 public static final String XOP_NAMESPACEURI = "http://www.w3.org/2004/08/xop/include"; 86 87 private final StreamSOAPCodec codec; 88 private final MTOMFeature mtomFeature; 89 private final SerializationFeature sf; 90 private final static String DECODED_MESSAGE_CHARSET = "decodedMessageCharset"; 91 92 MtomCodec(SOAPVersion version, StreamSOAPCodec codec, WSFeatureList features){ 93 super(version, features); 94 this.codec = codec; 95 sf = features.get(SerializationFeature.class); 96 MTOMFeature mtom = features.get(MTOMFeature.class); 97 if(mtom == null) 98 this.mtomFeature = new MTOMFeature(); 99 else 100 this.mtomFeature = mtom; 101 } 102 103 /** 104 * Return the soap 1.1 and soap 1.2 specific XOP packaged ContentType 105 * 106 * @return A non-null content type for soap11 or soap 1.2 content type 107 */ 108 @Override 109 public ContentType getStaticContentType(Packet packet) { 110 return getStaticContentTypeStatic(packet, version); 111 } 112 113 public static ContentType getStaticContentTypeStatic(Packet packet, SOAPVersion version) { 114 ContentType ct = (ContentType) packet.getInternalContentType(); 115 if ( ct != null ) return ct; 116 117 String uuid = UUID.randomUUID().toString(); 118 String boundary = "uuid:" + uuid; 119 String rootId = "<rootpart*"+uuid+"@example.jaxws.sun.com>"; 120 String soapActionParameter = SOAPVersion.SOAP_11.equals(version) ? null : createActionParameter(packet); 121 122 String boundaryParameter = "boundary=\"" + boundary +"\""; 123 String messageContentType = MULTIPART_RELATED_MIME_TYPE + 124 ";start=\""+rootId +"\"" + 125 ";type=\"" + XOP_XML_MIME_TYPE + "\";" + 126 boundaryParameter + 127 ";start-info=\"" + version.contentType + 128 (soapActionParameter == null? "" : soapActionParameter) + 129 "\""; 130 131 ContentTypeImpl ctImpl = SOAPVersion.SOAP_11.equals(version) ? 132 new ContentTypeImpl(messageContentType, (packet.soapAction == null)?"":packet.soapAction, null) : 133 new ContentTypeImpl(messageContentType, null, null); 134 ctImpl.setBoundary(boundary); 135 ctImpl.setRootId(rootId); 136 packet.setContentType(ctImpl); 137 return ctImpl; 138 } 139 140 private static String createActionParameter(Packet packet) { 141 return packet.soapAction != null? ";action=\\\""+packet.soapAction+"\\\"" : ""; 142 } 143 144 @Override 145 public ContentType encode(Packet packet, OutputStream out) throws IOException { 146 ContentTypeImpl ctImpl = (ContentTypeImpl) this.getStaticContentType(packet); 147 String boundary = ctImpl.getBoundary(); 148 String rootId = ctImpl.getRootId(); 149 150 if(packet.getMessage() != null){ 151 try { 152 String encoding = getPacketEncoding(packet); 153 packet.invocationProperties.remove(DECODED_MESSAGE_CHARSET); 154 155 String actionParameter = getActionParameter(packet, version); 156 String soapXopContentType = getSOAPXopContentType(encoding, version, actionParameter); 157 158 writeln("--"+boundary, out); 159 writeMimeHeaders(soapXopContentType, rootId, out); 160 161 //mtom attachments that need to be written after the root part 162 List<ByteArrayBuffer> mtomAttachments = new ArrayList<ByteArrayBuffer>(); 163 MtomStreamWriterImpl writer = new MtomStreamWriterImpl( 164 XMLStreamWriterFactory.create(out, encoding), mtomAttachments, boundary, mtomFeature); 165 166 packet.getMessage().writeTo(writer); 167 XMLStreamWriterFactory.recycle(writer); 168 writeln(out); 169 170 for(ByteArrayBuffer bos : mtomAttachments){ 171 bos.write(out); 172 } 173 174 // now write out the attachments in the message that weren't 175 // previously written 176 writeNonMtomAttachments(packet.getMessage().getAttachments(), 177 out, boundary); 178 179 //write out the end boundary 180 writeAsAscii("--"+boundary, out); 181 writeAsAscii("--", out); 182 183 } catch (XMLStreamException e) { 184 throw new WebServiceException(e); 185 } 186 } 187 //now create the boundary for next encode() call 188 // createConteTypeHeader(); 189 return ctImpl; 190 } 191 192 public static String getSOAPXopContentType(String encoding, SOAPVersion version, 193 String actionParameter) { 194 return XOP_XML_MIME_TYPE +";charset="+encoding+";type=\""+version.contentType+ actionParameter + "\""; 195 } 196 197 public static String getActionParameter(Packet packet, SOAPVersion version) { 198 return (version == SOAPVersion.SOAP_11) ? "" : createActionParameter(packet); 199 } 200 201 public static class ByteArrayBuffer{ 202 final String contentId; 203 204 private final DataHandler dh; 205 private final String boundary; 206 207 ByteArrayBuffer(@NotNull DataHandler dh, String b) { 208 this.dh = dh; 209 String cid = null; 210 if (dh instanceof StreamingDataHandler) { 211 StreamingDataHandler sdh = (StreamingDataHandler) dh; 212 if (sdh.getHrefCid() != null) 213 cid = sdh.getHrefCid(); 214 } 215 this.contentId = cid != null ? cid : encodeCid(); 216 boundary = b; 217 } 218 219 public void write(OutputStream os) throws IOException { 220 //build attachment frame 221 writeln("--"+boundary, os); 222 writeMimeHeaders(dh.getContentType(), contentId, os); 223 dh.writeTo(os); 224 writeln(os); 225 } 226 } 227 228 public static void writeMimeHeaders(String contentType, String contentId, OutputStream out) throws IOException { 229 String cid = contentId; 230 if(cid != null && cid.length() >0 && cid.charAt(0) != '<') 231 cid = '<' + cid + '>'; 232 writeln("Content-Id: " + cid, out); 233 writeln("Content-Type: " + contentType, out); 234 writeln("Content-Transfer-Encoding: binary", out); 235 writeln(out); 236 } 237 238 // Compiler warning for not calling close, but cannot call close, 239 // will consume attachment bytes. 240 @SuppressWarnings("resource") 241 private void writeNonMtomAttachments(AttachmentSet attachments, 242 OutputStream out, String boundary) throws IOException { 243 244 for (Attachment att : attachments) { 245 246 DataHandler dh = att.asDataHandler(); 247 if (dh instanceof StreamingDataHandler) { 248 StreamingDataHandler sdh = (StreamingDataHandler) dh; 249 // If DataHandler has href Content-ID, it is MTOM, so skip. 250 if (sdh.getHrefCid() != null) 251 continue; 252 } 253 254 // build attachment frame 255 writeln("--" + boundary, out); 256 writeMimeHeaders(att.getContentType(), att.getContentId(), out); 257 att.writeTo(out); 258 writeln(out); // write \r\n 259 } 260 } 261 262 @Override 263 public ContentType encode(Packet packet, WritableByteChannel buffer) { 264 throw new UnsupportedOperationException(); 265 } 266 267 @Override 268 public MtomCodec copy() { 269 return new MtomCodec(version, (StreamSOAPCodec)codec.copy(), features); 270 } 271 272 private static String encodeCid(){ 273 String cid="example.jaxws.sun.com"; 274 String name = UUID.randomUUID()+"@"; 275 return name + cid; 276 } 277 278 @Override 279 protected void decode(MimeMultipartParser mpp, Packet packet) throws IOException { 280 //TODO shouldn't we check for SOAP1.1/SOAP1.2 and throw 281 //TODO UnsupportedMediaException like StreamSOAPCodec 282 String charset = null; 283 String ct = mpp.getRootPart().getContentType(); 284 if (ct != null) { 285 charset = new ContentTypeImpl(ct).getCharSet(); 286 } 287 if (charset != null && !Charset.isSupported(charset)) { 288 throw new UnsupportedMediaException(charset); 289 } 290 291 if (charset != null) { 292 packet.invocationProperties.put(DECODED_MESSAGE_CHARSET, charset); 293 } else { 294 packet.invocationProperties.remove(DECODED_MESSAGE_CHARSET); 295 } 296 297 // we'd like to reuse those reader objects but unfortunately decoder may be reused 298 // before the decoded message is completely used. 299 XMLStreamReader mtomReader = new MtomXMLStreamReaderEx( mpp, 300 XMLStreamReaderFactory.create(null, mpp.getRootPart().asInputStream(), charset, true) 301 ); 302 303 packet.setMessage(codec.decode(mtomReader, new MimeAttachmentSet(mpp))); 304 packet.setMtomFeature(mtomFeature); 305 packet.setContentType(mpp.getContentType()); 306 } 307 308 private String getPacketEncoding(Packet packet) { 309 // If SerializationFeature is set, just use that encoding 310 if (sf != null && sf.getEncoding() != null) { 311 return sf.getEncoding().equals("") ? SOAPBindingCodec.DEFAULT_ENCODING : sf.getEncoding(); 312 } 313 return determinePacketEncoding(packet); 314 } 315 316 public static String determinePacketEncoding(Packet packet) { 317 if (packet != null && packet.endpoint != null) { 318 // Use request message's encoding for Server-side response messages 319 String charset = (String)packet.invocationProperties.get(DECODED_MESSAGE_CHARSET); 320 return charset == null 321 ? SOAPBindingCodec.DEFAULT_ENCODING : charset; 322 } 323 324 // Use default encoding for client-side request messages 325 return SOAPBindingCodec.DEFAULT_ENCODING; 326 } 327 328 public static class MtomStreamWriterImpl extends XMLStreamWriterFilter implements XMLStreamWriterEx, 329 MtomStreamWriter, HasEncoding { 330 private final List<ByteArrayBuffer> mtomAttachments; 331 private final String boundary; 332 private final MTOMFeature myMtomFeature; 333 public MtomStreamWriterImpl(XMLStreamWriter w, List<ByteArrayBuffer> mtomAttachments, String b, MTOMFeature myMtomFeature) { 334 super(w); 335 this.mtomAttachments = mtomAttachments; 336 this.boundary = b; 337 this.myMtomFeature = myMtomFeature; 338 } 339 340 @Override 341 public void writeBinary(byte[] data, int start, int len, String contentType) throws XMLStreamException { 342 //check threshold and if less write as base64encoded value 343 if(myMtomFeature.getThreshold() > len){ 344 writeCharacters(DatatypeConverterImpl._printBase64Binary(data, start, len)); 345 return; 346 } 347 ByteArrayBuffer bab = new ByteArrayBuffer(new DataHandler(new ByteArrayDataSource(data, start, len, contentType)), boundary); 348 writeBinary(bab); 349 } 350 351 @Override 352 public void writeBinary(DataHandler dataHandler) throws XMLStreamException { 353 // TODO how do we check threshold and if less inline the data 354 writeBinary(new ByteArrayBuffer(dataHandler, boundary)); 355 } 356 357 @Override 358 public OutputStream writeBinary(String contentType) throws XMLStreamException { 359 throw new UnsupportedOperationException(); 360 } 361 362 @Override 363 public void writePCDATA(CharSequence data) throws XMLStreamException { 364 if(data == null) 365 return; 366 if(data instanceof Base64Data){ 367 Base64Data binaryData = (Base64Data)data; 368 writeBinary(binaryData.getDataHandler()); 369 return; 370 } 371 writeCharacters(data.toString()); 372 } 373 374 private void writeBinary(ByteArrayBuffer bab) { 375 try { 376 mtomAttachments.add(bab); 377 writer.setPrefix("xop", XOP_NAMESPACEURI); 378 writer.writeNamespace("xop", XOP_NAMESPACEURI); 379 writer.writeStartElement(XOP_NAMESPACEURI, XOP_LOCALNAME); 380 writer.writeAttribute("href", "cid:"+bab.contentId); 381 writer.writeEndElement(); 382 writer.flush(); 383 } catch (XMLStreamException e) { 384 throw new WebServiceException(e); 385 } 386 } 387 388 @Override 389 public Object getProperty(String name) throws IllegalArgumentException { 390 // Hack for JDK6's SJSXP 391 if (name.equals("sjsxp-outputstream") && writer instanceof Map) { 392 Object obj = ((Map) writer).get("sjsxp-outputstream"); 393 if (obj != null) { 394 return obj; 395 } 396 } 397 return super.getProperty(name); 398 } 399 400 /** 401 * JAXBMessage writes envelope directly to the OutputStream(for SJSXP, woodstox). 402 * While writing, it calls the AttachmentMarshaller methods for adding attachments. 403 * JAXB writes xop:Include in this case. 404 */ 405 @Override 406 public AttachmentMarshaller getAttachmentMarshaller() { 407 return new AttachmentMarshaller() { 408 409 @Override 410 public String addMtomAttachment(DataHandler data, String elementNamespace, String elementLocalName) { 411 // Should we do the threshold processing on DataHandler ? But that would be 412 // expensive as DataHolder need to read the data again from its source 413 ByteArrayBuffer bab = new ByteArrayBuffer(data, boundary); 414 mtomAttachments.add(bab); 415 return "cid:"+bab.contentId; 416 } 417 418 @Override 419 public String addMtomAttachment(byte[] data, int offset, int length, String mimeType, String elementNamespace, String elementLocalName) { 420 // inline the data based on the threshold 421 if (myMtomFeature.getThreshold() > length) { 422 return null; // JAXB inlines the attachment data 423 } 424 ByteArrayBuffer bab = new ByteArrayBuffer(new DataHandler(new ByteArrayDataSource(data, offset, length, mimeType)), boundary); 425 mtomAttachments.add(bab); 426 return "cid:"+bab.contentId; 427 } 428 429 @Override 430 public String addSwaRefAttachment(DataHandler data) { 431 ByteArrayBuffer bab = new ByteArrayBuffer(data, boundary); 432 mtomAttachments.add(bab); 433 return "cid:"+bab.contentId; 434 } 435 436 @Override 437 public boolean isXOPPackage() { 438 return true; 439 } 440 }; 441 } 442 443 public List<ByteArrayBuffer> getMtomAttachments() { 444 return this.mtomAttachments; 445 } 446 447 @Override 448 public String getEncoding() { 449 return XMLStreamWriterUtil.getEncoding(writer); 450 } 451 452 private static class MtomNamespaceContextEx implements NamespaceContextEx { 453 private final NamespaceContext nsContext; 454 455 public MtomNamespaceContextEx(NamespaceContext nsContext) { 456 this.nsContext = nsContext; 457 } 458 459 @Override 460 public Iterator<Binding> iterator() { 461 throw new UnsupportedOperationException(); 462 } 463 464 @Override 465 public String getNamespaceURI(String prefix) { 466 return nsContext.getNamespaceURI(prefix); 467 } 468 469 @Override 470 public String getPrefix(String namespaceURI) { 471 return nsContext.getPrefix(namespaceURI); 472 } 473 474 @Override 475 public Iterator getPrefixes(String namespaceURI) { 476 return nsContext.getPrefixes(namespaceURI); 477 } 478 } 479 480 @Override 481 public NamespaceContextEx getNamespaceContext() { 482 NamespaceContext nsContext = writer.getNamespaceContext(); 483 return new MtomNamespaceContextEx(nsContext); 484 } 485 } 486 487 public static class MtomXMLStreamReaderEx extends XMLStreamReaderFilter implements XMLStreamReaderEx { 488 /** 489 * The parser for the outer MIME 'shell'. 490 */ 491 private final MimeMultipartParser mimeMP; 492 493 private boolean xopReferencePresent = false; 494 private Base64Data base64AttData; 495 496 //To be used with #getTextCharacters 497 private char[] base64EncodedText; 498 499 private String xopHref; 500 501 public MtomXMLStreamReaderEx(MimeMultipartParser mimeMP, XMLStreamReader reader) { 502 super(reader); 503 this.mimeMP = mimeMP; 504 } 505 506 @Override 507 public CharSequence getPCDATA() throws XMLStreamException { 508 if(xopReferencePresent){ 509 return base64AttData; 510 } 511 return reader.getText(); 512 } 513 514 @Override 515 public NamespaceContextEx getNamespaceContext() { 516 NamespaceContext nsContext = reader.getNamespaceContext(); 517 return new MtomNamespaceContextEx(nsContext); 518 } 519 520 @Override 521 public String getElementTextTrim() throws XMLStreamException { 522 throw new UnsupportedOperationException(); 523 } 524 525 private static class MtomNamespaceContextEx implements NamespaceContextEx { 526 private final NamespaceContext nsContext; 527 528 public MtomNamespaceContextEx(NamespaceContext nsContext) { 529 this.nsContext = nsContext; 530 } 531 532 @Override 533 public Iterator<Binding> iterator() { 534 throw new UnsupportedOperationException(); 535 } 536 537 @Override 538 public String getNamespaceURI(String prefix) { 539 return nsContext.getNamespaceURI(prefix); 540 } 541 542 @Override 543 public String getPrefix(String namespaceURI) { 544 return nsContext.getPrefix(namespaceURI); 545 } 546 547 @Override 548 public Iterator getPrefixes(String namespaceURI) { 549 return nsContext.getPrefixes(namespaceURI); 550 } 551 552 } 553 554 @Override 555 public int getTextLength() { 556 if (xopReferencePresent) { 557 return base64AttData.length(); 558 } 559 return reader.getTextLength(); 560 } 561 562 @Override 563 public int getTextStart() { 564 if (xopReferencePresent) { 565 return 0; 566 } 567 return reader.getTextStart(); 568 } 569 570 @Override 571 public int getEventType() { 572 if(xopReferencePresent) 573 return XMLStreamConstants.CHARACTERS; 574 return super.getEventType(); 575 } 576 577 @Override 578 public int next() throws XMLStreamException { 579 int event = reader.next(); 580 if (event == XMLStreamConstants.START_ELEMENT && reader.getLocalName().equals(XOP_LOCALNAME) && reader.getNamespaceURI().equals(XOP_NAMESPACEURI)) { 581 //its xop reference, take the URI reference 582 String href = reader.getAttributeValue(null, "href"); 583 try { 584 xopHref = href; 585 Attachment att = getAttachment(href); 586 if(att != null){ 587 DataHandler dh = att.asDataHandler(); 588 if (dh instanceof StreamingDataHandler) { 589 ((StreamingDataHandler)dh).setHrefCid(att.getContentId()); 590 } 591 base64AttData = new Base64Data(); 592 base64AttData.set(dh); 593 } 594 xopReferencePresent = true; 595 } catch (IOException e) { 596 throw new WebServiceException(e); 597 } 598 //move to the </xop:Include> 599 XMLStreamReaderUtil.nextElementContent(reader); 600 return XMLStreamConstants.CHARACTERS; 601 } 602 if(xopReferencePresent){ 603 xopReferencePresent = false; 604 base64EncodedText = null; 605 xopHref = null; 606 } 607 return event; 608 } 609 610 private String decodeCid(String cid) { 611 try { 612 cid = URLDecoder.decode(cid, "utf-8"); 613 } catch (UnsupportedEncodingException e) { 614 //on recceiving side lets not fail now, try to look for it 615 } 616 return cid; 617 } 618 619 private Attachment getAttachment(String cid) throws IOException { 620 if (cid.startsWith("cid:")) 621 cid = cid.substring(4, cid.length()); 622 if (cid.indexOf('%') != -1) { 623 cid = decodeCid(cid); 624 return mimeMP.getAttachmentPart(cid); 625 } 626 return mimeMP.getAttachmentPart(cid); 627 } 628 629 @Override 630 public char[] getTextCharacters() { 631 if (xopReferencePresent) { 632 char[] chars = new char[base64AttData.length()]; 633 base64AttData.writeTo(chars, 0); 634 return chars; 635 } 636 return reader.getTextCharacters(); 637 } 638 639 @Override 640 public int getTextCharacters(int sourceStart, char[] target, int targetStart, int length) throws XMLStreamException { 641 if(xopReferencePresent){ 642 if(target == null){ 643 throw new NullPointerException("target char array can't be null") ; 644 } 645 646 if(targetStart < 0 || length < 0 || sourceStart < 0 || targetStart >= target.length || 647 (targetStart + length ) > target.length) { 648 throw new IndexOutOfBoundsException(); 649 } 650 651 int textLength = base64AttData.length(); 652 if(sourceStart > textLength) 653 throw new IndexOutOfBoundsException(); 654 655 if(base64EncodedText == null){ 656 base64EncodedText = new char[base64AttData.length()]; 657 base64AttData.writeTo(base64EncodedText, 0); 658 } 659 660 int copiedLength = Math.min(textLength - sourceStart, length); 661 System.arraycopy(base64EncodedText, sourceStart , target, targetStart, copiedLength); 662 return copiedLength; 663 } 664 return reader.getTextCharacters(sourceStart, target, targetStart, length); 665 } 666 667 @Override 668 public String getText() { 669 if (xopReferencePresent) { 670 return base64AttData.toString(); 671 } 672 return reader.getText(); 673 } 674 675 protected boolean isXopReference() throws XMLStreamException { 676 return xopReferencePresent; 677 } 678 679 protected String getXopHref() { 680 return xopHref; 681 } 682 683 public MimeMultipartParser getMimeMultipartParser() { 684 return mimeMP; 685 } 686 } 687 688 }