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.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; 54 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; 75 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 { 83 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"; 87 88 private final StreamSOAPCodec codec; 89 private final MTOMFeature mtomFeature; 90 private final SerializationFeature sf; 91 private final static String DECODED_MESSAGE_CHARSET = "decodedMessageCharset"; 92 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 } 103 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 } 113 114 public static ContentType getStaticContentTypeStatic(Packet packet, SOAPVersion version) { 115 ContentType ct = (ContentType) packet.getInternalContentType(); 116 if ( ct != null ) return ct; 117 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); 122 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 "\""; 131 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 } 140 141 private static String createActionParameter(Packet packet) { 142 return packet.soapAction != null? ";action=\\\""+packet.soapAction+"\\\"" : ""; 143 } 144 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(); 150 151 if(packet.getMessage() != null){ 152 try { 153 String encoding = getPacketEncoding(packet); 154 packet.invocationProperties.remove(DECODED_MESSAGE_CHARSET); 155 156 String actionParameter = getActionParameter(packet, version); 157 String soapXopContentType = getSOAPXopContentType(encoding, version, actionParameter); 158 159 writeln("--"+boundary, out); 160 writeMimeHeaders(soapXopContentType, rootId, out); 161 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); 166 167 packet.getMessage().writeTo(writer); 168 XMLStreamWriterFactory.recycle(writer); 169 writeln(out); 170 171 for(ByteArrayBuffer bos : mtomAttachments){ 172 bos.write(out); 173 } 174 175 // now write out the attachments in the message that weren't 176 // previously written 177 writeNonMtomAttachments(packet.getMessage().getAttachments(), 178 out, boundary); 179 180 //write out the end boundary 181 writeAsAscii("--"+boundary, out); 182 writeAsAscii("--", out); 183 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 } 192 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 } 197 198 public static String getActionParameter(Packet packet, SOAPVersion version) { 199 return (version == SOAPVersion.SOAP_11) ? "" : createActionParameter(packet); 200 } 201 202 public static class ByteArrayBuffer{ 203 final String contentId; 204 205 private final DataHandler dh; 206 private final String boundary; 207 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 } 219 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 } 228 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 } 238 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 { 244 245 for (Attachment att : attachments) { 246 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 } 254 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 } 262 263 @Override 264 public ContentType encode(Packet packet, WritableByteChannel buffer) { 265 throw new UnsupportedOperationException(); 266 } 267 268 @Override 269 public MtomCodec copy() { 270 return new MtomCodec(version, (StreamSOAPCodec)codec.copy(), features); 271 } 272 273 private static String encodeCid(){ 274 String cid="example.jaxws.sun.com"; 275 String name = UUID.randomUUID()+"@"; 276 return name + cid; 277 } 278 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 } 291 292 if (charset != null) { 293 packet.invocationProperties.put(DECODED_MESSAGE_CHARSET, charset); 294 } else { 295 packet.invocationProperties.remove(DECODED_MESSAGE_CHARSET); 296 } 297 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 ); 303 304 packet.setMessage(codec.decode(mtomReader, new MimeAttachmentSet(mpp))); 305 packet.setMtomFeature(mtomFeature); 306 packet.setContentType(mpp.getContentType()); 307 } 308 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 } 316 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 } 324 325 // Use default encoding for client-side request messages 326 return SOAPBindingCodec.DEFAULT_ENCODING; 327 } 328 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 } 340 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 } 351 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 } 357 358 @Override 359 public OutputStream writeBinary(String contentType) throws XMLStreamException { 360 throw new UnsupportedOperationException(); 361 } 362 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 } 374 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 } 391 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 } 403 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() { 412 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 } 421 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 } 432 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 } 439 440 @Override 441 public boolean isXOPPackage() { 442 return true; 443 } 444 }; 445 } 446 447 public List<ByteArrayBuffer> getMtomAttachments() { 448 return this.mtomAttachments; 449 } 450 451 @Override 452 public String getEncoding() { 453 return XMLStreamWriterUtil.getEncoding(writer); 454 } 455 456 private static class MtomNamespaceContextEx implements NamespaceContextEx { 457 private final NamespaceContext nsContext; 458 459 public MtomNamespaceContextEx(NamespaceContext nsContext) { 460 this.nsContext = nsContext; 461 } 462 463 @Override 464 public Iterator<Binding> iterator() { 465 throw new UnsupportedOperationException(); 466 } 467 468 @Override 469 public String getNamespaceURI(String prefix) { 470 return nsContext.getNamespaceURI(prefix); 471 } 472 473 @Override 474 public String getPrefix(String namespaceURI) { 475 return nsContext.getPrefix(namespaceURI); 476 } 477 478 @Override 479 public Iterator getPrefixes(String namespaceURI) { 480 return nsContext.getPrefixes(namespaceURI); 481 } 482 } 483 484 @Override 485 public NamespaceContextEx getNamespaceContext() { 486 NamespaceContext nsContext = writer.getNamespaceContext(); 487 return new MtomNamespaceContextEx(nsContext); 488 } 489 } 490 491 public static class MtomXMLStreamReaderEx extends XMLStreamReaderFilter implements XMLStreamReaderEx { 492 /** 493 * The parser for the outer MIME 'shell'. 494 */ 495 private final MimeMultipartParser mimeMP; 496 497 private boolean xopReferencePresent = false; 498 private Base64Data base64AttData; 499 500 //To be used with #getTextCharacters 501 private char[] base64EncodedText; 502 503 private String xopHref; 504 505 public MtomXMLStreamReaderEx(MimeMultipartParser mimeMP, XMLStreamReader reader) { 506 super(reader); 507 this.mimeMP = mimeMP; 508 } 509 510 @Override 511 public CharSequence getPCDATA() throws XMLStreamException { 512 if(xopReferencePresent){ 513 return base64AttData; 514 } 515 return reader.getText(); 516 } 517 518 @Override 519 public NamespaceContextEx getNamespaceContext() { 520 return new NamespaceContextExAdaper(reader.getNamespaceContext()); 521 } 522 523 @Override 524 public String getElementTextTrim() throws XMLStreamException { 525 throw new UnsupportedOperationException(); 526 } 527 528 @Override 529 public int getTextLength() { 530 if (xopReferencePresent) { 531 return base64AttData.length(); 532 } 533 return reader.getTextLength(); 534 } 535 536 @Override 537 public int getTextStart() { 538 if (xopReferencePresent) { 539 return 0; 540 } 541 return reader.getTextStart(); 542 } 543 544 @Override 545 public int getEventType() { 546 if(xopReferencePresent) 547 return XMLStreamConstants.CHARACTERS; 548 return super.getEventType(); 549 } 550 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 } 583 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 } 592 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 } 602 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 } 612 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 } 619 620 if(targetStart < 0 || length < 0 || sourceStart < 0 || targetStart >= target.length || 621 (targetStart + length ) > target.length) { 622 throw new IndexOutOfBoundsException(); 623 } 624 625 int textLength = base64AttData.length(); 626 if(sourceStart > textLength) 627 throw new IndexOutOfBoundsException(); 628 629 if(base64EncodedText == null){ 630 base64EncodedText = new char[base64AttData.length()]; 631 base64AttData.writeTo(base64EncodedText, 0); 632 } 633 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 } 640 641 @Override 642 public String getText() { 643 if (xopReferencePresent) { 644 return base64AttData.toString(); 645 } 646 return reader.getText(); 647 } 648 649 protected boolean isXopReference() throws XMLStreamException { 650 return xopReferencePresent; 651 } 652 653 protected String getXopHref() { 654 return xopHref; 655 } 656 657 public MimeMultipartParser getMimeMultipartParser() { 658 return mimeMP; 659 } 660 } 661 662 }