Wed, 12 Jun 2013 14:47:09 +0100
8013021: Rebase 8005432 & 8003542 against the latest jdk8/jaxws
8003542: Improve processing of MTOM attachments
8005432: Update access to JAX-WS
Reviewed-by: mullan
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.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;
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;
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 {
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";
87 private final StreamSOAPCodec codec;
88 private final MTOMFeature mtomFeature;
89 private final SerializationFeature sf;
90 private final static String DECODED_MESSAGE_CHARSET = "decodedMessageCharset";
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 }
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 }
113 public static ContentType getStaticContentTypeStatic(Packet packet, SOAPVersion version) {
114 ContentType ct = (ContentType) packet.getInternalContentType();
115 if ( ct != null ) return ct;
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);
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 "\"";
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 }
140 private static String createActionParameter(Packet packet) {
141 return packet.soapAction != null? ";action=\\\""+packet.soapAction+"\\\"" : "";
142 }
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();
150 if(packet.getMessage() != null){
151 try {
152 String encoding = getPacketEncoding(packet);
153 packet.invocationProperties.remove(DECODED_MESSAGE_CHARSET);
155 String actionParameter = getActionParameter(packet, version);
156 String soapXopContentType = getSOAPXopContentType(encoding, version, actionParameter);
158 writeln("--"+boundary, out);
159 writeMimeHeaders(soapXopContentType, rootId, out);
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);
166 packet.getMessage().writeTo(writer);
167 XMLStreamWriterFactory.recycle(writer);
168 writeln(out);
170 for(ByteArrayBuffer bos : mtomAttachments){
171 bos.write(out);
172 }
174 // now write out the attachments in the message that weren't
175 // previously written
176 writeNonMtomAttachments(packet.getMessage().getAttachments(),
177 out, boundary);
179 //write out the end boundary
180 writeAsAscii("--"+boundary, out);
181 writeAsAscii("--", out);
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 }
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 }
197 public static String getActionParameter(Packet packet, SOAPVersion version) {
198 return (version == SOAPVersion.SOAP_11) ? "" : createActionParameter(packet);
199 }
201 public static class ByteArrayBuffer{
202 final String contentId;
204 private final DataHandler dh;
205 private final String boundary;
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 }
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 }
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 }
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 {
244 for (Attachment att : attachments) {
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 }
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 }
262 @Override
263 public ContentType encode(Packet packet, WritableByteChannel buffer) {
264 throw new UnsupportedOperationException();
265 }
267 @Override
268 public MtomCodec copy() {
269 return new MtomCodec(version, (StreamSOAPCodec)codec.copy(), features);
270 }
272 private static String encodeCid(){
273 String cid="example.jaxws.sun.com";
274 String name = UUID.randomUUID()+"@";
275 return name + cid;
276 }
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 }
291 if (charset != null) {
292 packet.invocationProperties.put(DECODED_MESSAGE_CHARSET, charset);
293 } else {
294 packet.invocationProperties.remove(DECODED_MESSAGE_CHARSET);
295 }
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 );
303 packet.setMessage(codec.decode(mtomReader, new MimeAttachmentSet(mpp)));
304 packet.setMtomFeature(mtomFeature);
305 packet.setContentType(mpp.getContentType());
306 }
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 }
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 }
324 // Use default encoding for client-side request messages
325 return SOAPBindingCodec.DEFAULT_ENCODING;
326 }
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 }
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 }
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 }
357 @Override
358 public OutputStream writeBinary(String contentType) throws XMLStreamException {
359 throw new UnsupportedOperationException();
360 }
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 }
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 }
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 }
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() {
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 }
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 }
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 }
436 @Override
437 public boolean isXOPPackage() {
438 return true;
439 }
440 };
441 }
443 public List<ByteArrayBuffer> getMtomAttachments() {
444 return this.mtomAttachments;
445 }
447 @Override
448 public String getEncoding() {
449 return XMLStreamWriterUtil.getEncoding(writer);
450 }
452 private static class MtomNamespaceContextEx implements NamespaceContextEx {
453 private final NamespaceContext nsContext;
455 public MtomNamespaceContextEx(NamespaceContext nsContext) {
456 this.nsContext = nsContext;
457 }
459 @Override
460 public Iterator<Binding> iterator() {
461 throw new UnsupportedOperationException();
462 }
464 @Override
465 public String getNamespaceURI(String prefix) {
466 return nsContext.getNamespaceURI(prefix);
467 }
469 @Override
470 public String getPrefix(String namespaceURI) {
471 return nsContext.getPrefix(namespaceURI);
472 }
474 @Override
475 public Iterator getPrefixes(String namespaceURI) {
476 return nsContext.getPrefixes(namespaceURI);
477 }
478 }
480 @Override
481 public NamespaceContextEx getNamespaceContext() {
482 NamespaceContext nsContext = writer.getNamespaceContext();
483 return new MtomNamespaceContextEx(nsContext);
484 }
485 }
487 public static class MtomXMLStreamReaderEx extends XMLStreamReaderFilter implements XMLStreamReaderEx {
488 /**
489 * The parser for the outer MIME 'shell'.
490 */
491 private final MimeMultipartParser mimeMP;
493 private boolean xopReferencePresent = false;
494 private Base64Data base64AttData;
496 //To be used with #getTextCharacters
497 private char[] base64EncodedText;
499 private String xopHref;
501 public MtomXMLStreamReaderEx(MimeMultipartParser mimeMP, XMLStreamReader reader) {
502 super(reader);
503 this.mimeMP = mimeMP;
504 }
506 @Override
507 public CharSequence getPCDATA() throws XMLStreamException {
508 if(xopReferencePresent){
509 return base64AttData;
510 }
511 return reader.getText();
512 }
514 @Override
515 public NamespaceContextEx getNamespaceContext() {
516 NamespaceContext nsContext = reader.getNamespaceContext();
517 return new MtomNamespaceContextEx(nsContext);
518 }
520 @Override
521 public String getElementTextTrim() throws XMLStreamException {
522 throw new UnsupportedOperationException();
523 }
525 private static class MtomNamespaceContextEx implements NamespaceContextEx {
526 private final NamespaceContext nsContext;
528 public MtomNamespaceContextEx(NamespaceContext nsContext) {
529 this.nsContext = nsContext;
530 }
532 @Override
533 public Iterator<Binding> iterator() {
534 throw new UnsupportedOperationException();
535 }
537 @Override
538 public String getNamespaceURI(String prefix) {
539 return nsContext.getNamespaceURI(prefix);
540 }
542 @Override
543 public String getPrefix(String namespaceURI) {
544 return nsContext.getPrefix(namespaceURI);
545 }
547 @Override
548 public Iterator getPrefixes(String namespaceURI) {
549 return nsContext.getPrefixes(namespaceURI);
550 }
552 }
554 @Override
555 public int getTextLength() {
556 if (xopReferencePresent) {
557 return base64AttData.length();
558 }
559 return reader.getTextLength();
560 }
562 @Override
563 public int getTextStart() {
564 if (xopReferencePresent) {
565 return 0;
566 }
567 return reader.getTextStart();
568 }
570 @Override
571 public int getEventType() {
572 if(xopReferencePresent)
573 return XMLStreamConstants.CHARACTERS;
574 return super.getEventType();
575 }
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 }
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 }
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 }
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 }
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 }
646 if(targetStart < 0 || length < 0 || sourceStart < 0 || targetStart >= target.length ||
647 (targetStart + length ) > target.length) {
648 throw new IndexOutOfBoundsException();
649 }
651 int textLength = base64AttData.length();
652 if(sourceStart > textLength)
653 throw new IndexOutOfBoundsException();
655 if(base64EncodedText == null){
656 base64EncodedText = new char[base64AttData.length()];
657 base64AttData.writeTo(base64EncodedText, 0);
658 }
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 }
667 @Override
668 public String getText() {
669 if (xopReferencePresent) {
670 return base64AttData.toString();
671 }
672 return reader.getText();
673 }
675 protected boolean isXopReference() throws XMLStreamException {
676 return xopReferencePresent;
677 }
679 protected String getXopHref() {
680 return xopHref;
681 }
683 public MimeMultipartParser getMimeMultipartParser() {
684 return mimeMP;
685 }
686 }
688 }