ohair@286: /* alanb@368: * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. ohair@286: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. ohair@286: * ohair@286: * This code is free software; you can redistribute it and/or modify it ohair@286: * under the terms of the GNU General Public License version 2 only, as ohair@286: * published by the Free Software Foundation. Oracle designates this ohair@286: * particular file as subject to the "Classpath" exception as provided ohair@286: * by Oracle in the LICENSE file that accompanied this code. ohair@286: * ohair@286: * This code is distributed in the hope that it will be useful, but WITHOUT ohair@286: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ohair@286: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License ohair@286: * version 2 for more details (a copy is included in the LICENSE file that ohair@286: * accompanied this code). ohair@286: * ohair@286: * You should have received a copy of the GNU General Public License version ohair@286: * 2 along with this work; if not, write to the Free Software Foundation, ohair@286: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. ohair@286: * ohair@286: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA ohair@286: * or visit www.oracle.com if you need additional information or have any ohair@286: * questions. ohair@286: */ ohair@286: ohair@286: package com.sun.xml.internal.ws.encoding; ohair@286: ohair@286: import com.sun.istack.internal.NotNull; ohair@286: import com.sun.xml.internal.bind.DatatypeConverterImpl; ohair@286: import com.sun.xml.internal.ws.api.SOAPVersion; ohair@286: import com.sun.xml.internal.ws.api.WSFeatureList; ohair@286: import com.sun.xml.internal.ws.api.message.Attachment; ohair@286: import com.sun.xml.internal.ws.api.message.AttachmentSet; ohair@286: import com.sun.xml.internal.ws.api.message.Packet; ohair@286: import com.sun.xml.internal.ws.api.pipe.ContentType; ohair@286: import com.sun.xml.internal.ws.api.pipe.StreamSOAPCodec; ohair@286: import com.sun.xml.internal.ws.api.streaming.XMLStreamReaderFactory; ohair@286: import com.sun.xml.internal.ws.api.streaming.XMLStreamWriterFactory; ohair@286: import com.sun.xml.internal.ws.developer.SerializationFeature; alanb@368: import com.sun.xml.internal.ws.developer.StreamingDataHandler; ohair@286: import com.sun.xml.internal.ws.message.MimeAttachmentSet; ohair@286: import com.sun.xml.internal.ws.streaming.XMLStreamWriterUtil; ohair@286: import com.sun.xml.internal.ws.util.ByteArrayDataSource; mkos@397: import com.sun.xml.internal.ws.util.xml.NamespaceContextExAdaper; ohair@286: import com.sun.xml.internal.ws.util.xml.XMLStreamReaderFilter; ohair@286: import com.sun.xml.internal.ws.util.xml.XMLStreamWriterFilter; ohair@286: import com.sun.xml.internal.ws.streaming.MtomStreamWriter; ohair@286: import com.sun.xml.internal.ws.streaming.XMLStreamReaderUtil; ohair@286: import com.sun.xml.internal.ws.server.UnsupportedMediaException; ohair@286: import com.sun.xml.internal.org.jvnet.staxex.Base64Data; ohair@286: import com.sun.xml.internal.org.jvnet.staxex.NamespaceContextEx; ohair@286: import com.sun.xml.internal.org.jvnet.staxex.XMLStreamReaderEx; ohair@286: import com.sun.xml.internal.org.jvnet.staxex.XMLStreamWriterEx; ohair@286: ohair@286: import javax.activation.DataHandler; ohair@286: import javax.xml.namespace.NamespaceContext; ohair@286: import javax.xml.stream.XMLStreamConstants; ohair@286: import javax.xml.stream.XMLStreamException; ohair@286: import javax.xml.stream.XMLStreamReader; ohair@286: import javax.xml.stream.XMLStreamWriter; ohair@286: import javax.xml.ws.WebServiceException; ohair@286: import javax.xml.ws.soap.MTOMFeature; ohair@286: import javax.xml.bind.attachment.AttachmentMarshaller; ohair@286: import java.io.IOException; ohair@286: import java.io.OutputStream; ohair@286: import java.io.UnsupportedEncodingException; ohair@286: import java.net.URLDecoder; ohair@286: import java.nio.channels.WritableByteChannel; ohair@286: import java.nio.charset.Charset; ohair@286: import java.util.ArrayList; ohair@286: import java.util.Iterator; ohair@286: import java.util.List; ohair@286: import java.util.Map; ohair@286: import java.util.UUID; ohair@286: ohair@286: /** alanb@368: * Mtom message Codec. It can be used even for non-soap message's mtom encoding. ohair@286: * ohair@286: * @author Vivek Pandey ohair@286: * @author Jitendra Kotamraju ohair@286: */ ohair@286: public class MtomCodec extends MimeCodec { ohair@286: ohair@286: public static final String XOP_XML_MIME_TYPE = "application/xop+xml"; mkos@384: public static final String XOP_LOCALNAME = "Include"; mkos@384: public static final String XOP_NAMESPACEURI = "http://www.w3.org/2004/08/xop/include"; ohair@286: ohair@286: private final StreamSOAPCodec codec; ohair@286: private final MTOMFeature mtomFeature; ohair@286: private final SerializationFeature sf; ohair@286: private final static String DECODED_MESSAGE_CHARSET = "decodedMessageCharset"; ohair@286: ohair@286: MtomCodec(SOAPVersion version, StreamSOAPCodec codec, WSFeatureList features){ ohair@286: super(version, features); ohair@286: this.codec = codec; ohair@286: sf = features.get(SerializationFeature.class); ohair@286: MTOMFeature mtom = features.get(MTOMFeature.class); ohair@286: if(mtom == null) ohair@286: this.mtomFeature = new MTOMFeature(); ohair@286: else ohair@286: this.mtomFeature = mtom; ohair@286: } ohair@286: alanb@368: /** alanb@368: * Return the soap 1.1 and soap 1.2 specific XOP packaged ContentType alanb@368: * alanb@368: * @return A non-null content type for soap11 or soap 1.2 content type alanb@368: */ alanb@368: @Override alanb@368: public ContentType getStaticContentType(Packet packet) { alanb@368: return getStaticContentTypeStatic(packet, version); ohair@286: } ohair@286: alanb@368: public static ContentType getStaticContentTypeStatic(Packet packet, SOAPVersion version) { alanb@368: ContentType ct = (ContentType) packet.getInternalContentType(); alanb@368: if ( ct != null ) return ct; ohair@286: alanb@368: String uuid = UUID.randomUUID().toString(); alanb@368: String boundary = "uuid:" + uuid; alanb@368: String rootId = ""; alanb@368: String soapActionParameter = SOAPVersion.SOAP_11.equals(version) ? null : createActionParameter(packet); alanb@368: ohair@286: String boundaryParameter = "boundary=\"" + boundary +"\""; alanb@368: String messageContentType = MULTIPART_RELATED_MIME_TYPE + ohair@286: ";start=\""+rootId +"\"" + ohair@286: ";type=\"" + XOP_XML_MIME_TYPE + "\";" + ohair@286: boundaryParameter + ohair@286: ";start-info=\"" + version.contentType + ohair@286: (soapActionParameter == null? "" : soapActionParameter) + ohair@286: "\""; alanb@368: alanb@368: ContentTypeImpl ctImpl = SOAPVersion.SOAP_11.equals(version) ? alanb@368: new ContentTypeImpl(messageContentType, (packet.soapAction == null)?"":packet.soapAction, null) : alanb@368: new ContentTypeImpl(messageContentType, null, null); alanb@368: ctImpl.setBoundary(boundary); alanb@368: ctImpl.setRootId(rootId); alanb@368: packet.setContentType(ctImpl); alanb@368: return ctImpl; ohair@286: } ohair@286: alanb@368: private static String createActionParameter(Packet packet) { ohair@286: return packet.soapAction != null? ";action=\\\""+packet.soapAction+"\\\"" : ""; ohair@286: } ohair@286: alanb@368: @Override ohair@286: public ContentType encode(Packet packet, OutputStream out) throws IOException { alanb@368: ContentTypeImpl ctImpl = (ContentTypeImpl) this.getStaticContentType(packet); alanb@368: String boundary = ctImpl.getBoundary(); alanb@368: String rootId = ctImpl.getRootId(); ohair@286: ohair@286: if(packet.getMessage() != null){ ohair@286: try { ohair@286: String encoding = getPacketEncoding(packet); ohair@286: packet.invocationProperties.remove(DECODED_MESSAGE_CHARSET); ohair@286: alanb@368: String actionParameter = getActionParameter(packet, version); alanb@368: String soapXopContentType = getSOAPXopContentType(encoding, version, actionParameter); ohair@286: ohair@286: writeln("--"+boundary, out); alanb@368: writeMimeHeaders(soapXopContentType, rootId, out); ohair@286: ohair@286: //mtom attachments that need to be written after the root part ohair@286: List mtomAttachments = new ArrayList(); ohair@286: MtomStreamWriterImpl writer = new MtomStreamWriterImpl( alanb@368: XMLStreamWriterFactory.create(out, encoding), mtomAttachments, boundary, mtomFeature); ohair@286: ohair@286: packet.getMessage().writeTo(writer); ohair@286: XMLStreamWriterFactory.recycle(writer); ohair@286: writeln(out); ohair@286: ohair@286: for(ByteArrayBuffer bos : mtomAttachments){ ohair@286: bos.write(out); ohair@286: } ohair@286: mkos@384: // now write out the attachments in the message that weren't mkos@384: // previously written mkos@384: writeNonMtomAttachments(packet.getMessage().getAttachments(), mkos@384: out, boundary); ohair@286: ohair@286: //write out the end boundary ohair@286: writeAsAscii("--"+boundary, out); ohair@286: writeAsAscii("--", out); ohair@286: ohair@286: } catch (XMLStreamException e) { ohair@286: throw new WebServiceException(e); ohair@286: } ohair@286: } ohair@286: //now create the boundary for next encode() call alanb@368: // createConteTypeHeader(); alanb@368: return ctImpl; ohair@286: } ohair@286: alanb@368: public static String getSOAPXopContentType(String encoding, SOAPVersion version, alanb@368: String actionParameter) { alanb@368: return XOP_XML_MIME_TYPE +";charset="+encoding+";type=\""+version.contentType+ actionParameter + "\""; alanb@368: } alanb@368: alanb@368: public static String getActionParameter(Packet packet, SOAPVersion version) { alanb@368: return (version == SOAPVersion.SOAP_11) ? "" : createActionParameter(packet); alanb@368: } alanb@368: alanb@368: public static class ByteArrayBuffer{ ohair@286: final String contentId; ohair@286: alanb@368: private final DataHandler dh; alanb@368: private final String boundary; ohair@286: alanb@368: ByteArrayBuffer(@NotNull DataHandler dh, String b) { ohair@286: this.dh = dh; mkos@384: String cid = null; mkos@384: if (dh instanceof StreamingDataHandler) { mkos@384: StreamingDataHandler sdh = (StreamingDataHandler) dh; mkos@384: if (sdh.getHrefCid() != null) mkos@384: cid = sdh.getHrefCid(); mkos@384: } mkos@384: this.contentId = cid != null ? cid : encodeCid(); alanb@368: boundary = b; ohair@286: } ohair@286: alanb@368: public void write(OutputStream os) throws IOException { ohair@286: //build attachment frame ohair@286: writeln("--"+boundary, os); ohair@286: writeMimeHeaders(dh.getContentType(), contentId, os); ohair@286: dh.writeTo(os); ohair@286: writeln(os); ohair@286: } ohair@286: } ohair@286: alanb@368: public static void writeMimeHeaders(String contentType, String contentId, OutputStream out) throws IOException { ohair@286: String cid = contentId; ohair@286: if(cid != null && cid.length() >0 && cid.charAt(0) != '<') ohair@286: cid = '<' + cid + '>'; ohair@286: writeln("Content-Id: " + cid, out); ohair@286: writeln("Content-Type: " + contentType, out); ohair@286: writeln("Content-Transfer-Encoding: binary", out); ohair@286: writeln(out); ohair@286: } ohair@286: mkos@384: // Compiler warning for not calling close, but cannot call close, mkos@384: // will consume attachment bytes. mkos@384: @SuppressWarnings("resource") mkos@384: private void writeNonMtomAttachments(AttachmentSet attachments, mkos@384: OutputStream out, String boundary) throws IOException { mkos@384: mkos@384: for (Attachment att : attachments) { mkos@384: mkos@384: DataHandler dh = att.asDataHandler(); mkos@384: if (dh instanceof StreamingDataHandler) { mkos@384: StreamingDataHandler sdh = (StreamingDataHandler) dh; mkos@384: // If DataHandler has href Content-ID, it is MTOM, so skip. mkos@384: if (sdh.getHrefCid() != null) mkos@384: continue; mkos@384: } mkos@384: mkos@384: // build attachment frame mkos@384: writeln("--" + boundary, out); ohair@286: writeMimeHeaders(att.getContentType(), att.getContentId(), out); ohair@286: att.writeTo(out); mkos@384: writeln(out); // write \r\n ohair@286: } ohair@286: } ohair@286: alanb@368: @Override ohair@286: public ContentType encode(Packet packet, WritableByteChannel buffer) { ohair@286: throw new UnsupportedOperationException(); ohair@286: } ohair@286: alanb@368: @Override ohair@286: public MtomCodec copy() { ohair@286: return new MtomCodec(version, (StreamSOAPCodec)codec.copy(), features); ohair@286: } ohair@286: alanb@368: private static String encodeCid(){ ohair@286: String cid="example.jaxws.sun.com"; ohair@286: String name = UUID.randomUUID()+"@"; ohair@286: return name + cid; ohair@286: } ohair@286: ohair@286: @Override ohair@286: protected void decode(MimeMultipartParser mpp, Packet packet) throws IOException { ohair@286: //TODO shouldn't we check for SOAP1.1/SOAP1.2 and throw ohair@286: //TODO UnsupportedMediaException like StreamSOAPCodec ohair@286: String charset = null; ohair@286: String ct = mpp.getRootPart().getContentType(); ohair@286: if (ct != null) { ohair@286: charset = new ContentTypeImpl(ct).getCharSet(); ohair@286: } ohair@286: if (charset != null && !Charset.isSupported(charset)) { ohair@286: throw new UnsupportedMediaException(charset); ohair@286: } ohair@286: ohair@286: if (charset != null) { ohair@286: packet.invocationProperties.put(DECODED_MESSAGE_CHARSET, charset); ohair@286: } else { ohair@286: packet.invocationProperties.remove(DECODED_MESSAGE_CHARSET); ohair@286: } ohair@286: ohair@286: // we'd like to reuse those reader objects but unfortunately decoder may be reused ohair@286: // before the decoded message is completely used. ohair@286: XMLStreamReader mtomReader = new MtomXMLStreamReaderEx( mpp, ohair@286: XMLStreamReaderFactory.create(null, mpp.getRootPart().asInputStream(), charset, true) ohair@286: ); ohair@286: ohair@286: packet.setMessage(codec.decode(mtomReader, new MimeAttachmentSet(mpp))); alanb@368: packet.setMtomFeature(mtomFeature); alanb@368: packet.setContentType(mpp.getContentType()); ohair@286: } ohair@286: ohair@286: private String getPacketEncoding(Packet packet) { ohair@286: // If SerializationFeature is set, just use that encoding ohair@286: if (sf != null && sf.getEncoding() != null) { ohair@286: return sf.getEncoding().equals("") ? SOAPBindingCodec.DEFAULT_ENCODING : sf.getEncoding(); ohair@286: } alanb@368: return determinePacketEncoding(packet); alanb@368: } ohair@286: alanb@368: public static String determinePacketEncoding(Packet packet) { ohair@286: if (packet != null && packet.endpoint != null) { ohair@286: // Use request message's encoding for Server-side response messages ohair@286: String charset = (String)packet.invocationProperties.get(DECODED_MESSAGE_CHARSET); ohair@286: return charset == null ohair@286: ? SOAPBindingCodec.DEFAULT_ENCODING : charset; ohair@286: } ohair@286: ohair@286: // Use default encoding for client-side request messages ohair@286: return SOAPBindingCodec.DEFAULT_ENCODING; ohair@286: } ohair@286: alanb@368: public static class MtomStreamWriterImpl extends XMLStreamWriterFilter implements XMLStreamWriterEx, ohair@286: MtomStreamWriter, HasEncoding { ohair@286: private final List mtomAttachments; alanb@368: private final String boundary; alanb@368: private final MTOMFeature myMtomFeature; alanb@368: public MtomStreamWriterImpl(XMLStreamWriter w, List mtomAttachments, String b, MTOMFeature myMtomFeature) { ohair@286: super(w); ohair@286: this.mtomAttachments = mtomAttachments; alanb@368: this.boundary = b; alanb@368: this.myMtomFeature = myMtomFeature; ohair@286: } ohair@286: alanb@368: @Override ohair@286: public void writeBinary(byte[] data, int start, int len, String contentType) throws XMLStreamException { ohair@286: //check threshold and if less write as base64encoded value alanb@368: if(myMtomFeature.getThreshold() > len){ ohair@286: writeCharacters(DatatypeConverterImpl._printBase64Binary(data, start, len)); ohair@286: return; ohair@286: } alanb@368: ByteArrayBuffer bab = new ByteArrayBuffer(new DataHandler(new ByteArrayDataSource(data, start, len, contentType)), boundary); ohair@286: writeBinary(bab); ohair@286: } ohair@286: alanb@368: @Override ohair@286: public void writeBinary(DataHandler dataHandler) throws XMLStreamException { ohair@286: // TODO how do we check threshold and if less inline the data alanb@368: writeBinary(new ByteArrayBuffer(dataHandler, boundary)); ohair@286: } ohair@286: alanb@368: @Override ohair@286: public OutputStream writeBinary(String contentType) throws XMLStreamException { ohair@286: throw new UnsupportedOperationException(); ohair@286: } ohair@286: alanb@368: @Override ohair@286: public void writePCDATA(CharSequence data) throws XMLStreamException { ohair@286: if(data == null) ohair@286: return; ohair@286: if(data instanceof Base64Data){ ohair@286: Base64Data binaryData = (Base64Data)data; ohair@286: writeBinary(binaryData.getDataHandler()); ohair@286: return; ohair@286: } ohair@286: writeCharacters(data.toString()); ohair@286: } ohair@286: ohair@286: private void writeBinary(ByteArrayBuffer bab) { ohair@286: try { ohair@286: mtomAttachments.add(bab); mkos@397: String prefix = writer.getPrefix(XOP_NAMESPACEURI); mkos@397: if (prefix == null || !prefix.equals("xop")) { mkos@397: writer.setPrefix("xop", XOP_NAMESPACEURI); mkos@397: writer.writeNamespace("xop", XOP_NAMESPACEURI); mkos@397: } ohair@286: writer.writeStartElement(XOP_NAMESPACEURI, XOP_LOCALNAME); ohair@286: writer.writeAttribute("href", "cid:"+bab.contentId); ohair@286: writer.writeEndElement(); ohair@286: writer.flush(); ohair@286: } catch (XMLStreamException e) { ohair@286: throw new WebServiceException(e); ohair@286: } ohair@286: } ohair@286: ohair@286: @Override ohair@286: public Object getProperty(String name) throws IllegalArgumentException { ohair@286: // Hack for JDK6's SJSXP ohair@286: if (name.equals("sjsxp-outputstream") && writer instanceof Map) { ohair@286: Object obj = ((Map) writer).get("sjsxp-outputstream"); ohair@286: if (obj != null) { ohair@286: return obj; ohair@286: } ohair@286: } ohair@286: return super.getProperty(name); ohair@286: } ohair@286: ohair@286: /** ohair@286: * JAXBMessage writes envelope directly to the OutputStream(for SJSXP, woodstox). ohair@286: * While writing, it calls the AttachmentMarshaller methods for adding attachments. ohair@286: * JAXB writes xop:Include in this case. ohair@286: */ alanb@368: @Override ohair@286: public AttachmentMarshaller getAttachmentMarshaller() { ohair@286: return new AttachmentMarshaller() { ohair@286: alanb@368: @Override ohair@286: public String addMtomAttachment(DataHandler data, String elementNamespace, String elementLocalName) { ohair@286: // Should we do the threshold processing on DataHandler ? But that would be ohair@286: // expensive as DataHolder need to read the data again from its source alanb@368: ByteArrayBuffer bab = new ByteArrayBuffer(data, boundary); ohair@286: mtomAttachments.add(bab); ohair@286: return "cid:"+bab.contentId; ohair@286: } ohair@286: alanb@368: @Override ohair@286: public String addMtomAttachment(byte[] data, int offset, int length, String mimeType, String elementNamespace, String elementLocalName) { ohair@286: // inline the data based on the threshold alanb@368: if (myMtomFeature.getThreshold() > length) { ohair@286: return null; // JAXB inlines the attachment data ohair@286: } alanb@368: ByteArrayBuffer bab = new ByteArrayBuffer(new DataHandler(new ByteArrayDataSource(data, offset, length, mimeType)), boundary); ohair@286: mtomAttachments.add(bab); ohair@286: return "cid:"+bab.contentId; ohair@286: } ohair@286: alanb@368: @Override ohair@286: public String addSwaRefAttachment(DataHandler data) { alanb@368: ByteArrayBuffer bab = new ByteArrayBuffer(data, boundary); ohair@286: mtomAttachments.add(bab); ohair@286: return "cid:"+bab.contentId; ohair@286: } ohair@286: ohair@286: @Override ohair@286: public boolean isXOPPackage() { ohair@286: return true; ohair@286: } ohair@286: }; ohair@286: } ohair@286: alanb@368: public List getMtomAttachments() { alanb@368: return this.mtomAttachments; alanb@368: } alanb@368: alanb@368: @Override ohair@286: public String getEncoding() { ohair@286: return XMLStreamWriterUtil.getEncoding(writer); ohair@286: } ohair@286: alanb@368: private static class MtomNamespaceContextEx implements NamespaceContextEx { alanb@368: private final NamespaceContext nsContext; ohair@286: ohair@286: public MtomNamespaceContextEx(NamespaceContext nsContext) { ohair@286: this.nsContext = nsContext; ohair@286: } ohair@286: alanb@368: @Override ohair@286: public Iterator iterator() { ohair@286: throw new UnsupportedOperationException(); ohair@286: } ohair@286: alanb@368: @Override ohair@286: public String getNamespaceURI(String prefix) { ohair@286: return nsContext.getNamespaceURI(prefix); ohair@286: } ohair@286: alanb@368: @Override ohair@286: public String getPrefix(String namespaceURI) { ohair@286: return nsContext.getPrefix(namespaceURI); ohair@286: } ohair@286: alanb@368: @Override ohair@286: public Iterator getPrefixes(String namespaceURI) { ohair@286: return nsContext.getPrefixes(namespaceURI); ohair@286: } ohair@286: } ohair@286: ohair@286: @Override ohair@286: public NamespaceContextEx getNamespaceContext() { ohair@286: NamespaceContext nsContext = writer.getNamespaceContext(); ohair@286: return new MtomNamespaceContextEx(nsContext); ohair@286: } ohair@286: } ohair@286: alanb@368: public static class MtomXMLStreamReaderEx extends XMLStreamReaderFilter implements XMLStreamReaderEx { ohair@286: /** ohair@286: * The parser for the outer MIME 'shell'. ohair@286: */ ohair@286: private final MimeMultipartParser mimeMP; ohair@286: ohair@286: private boolean xopReferencePresent = false; ohair@286: private Base64Data base64AttData; ohair@286: ohair@286: //To be used with #getTextCharacters ohair@286: private char[] base64EncodedText; ohair@286: alanb@368: private String xopHref; alanb@368: ohair@286: public MtomXMLStreamReaderEx(MimeMultipartParser mimeMP, XMLStreamReader reader) { ohair@286: super(reader); ohair@286: this.mimeMP = mimeMP; ohair@286: } ohair@286: alanb@368: @Override ohair@286: public CharSequence getPCDATA() throws XMLStreamException { ohair@286: if(xopReferencePresent){ ohair@286: return base64AttData; ohair@286: } ohair@286: return reader.getText(); ohair@286: } ohair@286: alanb@368: @Override ohair@286: public NamespaceContextEx getNamespaceContext() { mkos@397: return new NamespaceContextExAdaper(reader.getNamespaceContext()); ohair@286: } ohair@286: alanb@368: @Override ohair@286: public String getElementTextTrim() throws XMLStreamException { ohair@286: throw new UnsupportedOperationException(); ohair@286: } ohair@286: alanb@368: @Override ohair@286: public int getTextLength() { ohair@286: if (xopReferencePresent) { ohair@286: return base64AttData.length(); ohair@286: } ohair@286: return reader.getTextLength(); ohair@286: } ohair@286: alanb@368: @Override ohair@286: public int getTextStart() { ohair@286: if (xopReferencePresent) { ohair@286: return 0; ohair@286: } ohair@286: return reader.getTextStart(); ohair@286: } ohair@286: alanb@368: @Override ohair@286: public int getEventType() { ohair@286: if(xopReferencePresent) ohair@286: return XMLStreamConstants.CHARACTERS; ohair@286: return super.getEventType(); ohair@286: } ohair@286: alanb@368: @Override ohair@286: public int next() throws XMLStreamException { ohair@286: int event = reader.next(); ohair@286: if (event == XMLStreamConstants.START_ELEMENT && reader.getLocalName().equals(XOP_LOCALNAME) && reader.getNamespaceURI().equals(XOP_NAMESPACEURI)) { ohair@286: //its xop reference, take the URI reference ohair@286: String href = reader.getAttributeValue(null, "href"); ohair@286: try { alanb@368: xopHref = href; ohair@286: Attachment att = getAttachment(href); ohair@286: if(att != null){ alanb@368: DataHandler dh = att.asDataHandler(); alanb@368: if (dh instanceof StreamingDataHandler) { alanb@368: ((StreamingDataHandler)dh).setHrefCid(att.getContentId()); alanb@368: } ohair@286: base64AttData = new Base64Data(); alanb@368: base64AttData.set(dh); ohair@286: } ohair@286: xopReferencePresent = true; ohair@286: } catch (IOException e) { ohair@286: throw new WebServiceException(e); ohair@286: } ohair@286: //move to the ohair@286: XMLStreamReaderUtil.nextElementContent(reader); ohair@286: return XMLStreamConstants.CHARACTERS; ohair@286: } ohair@286: if(xopReferencePresent){ ohair@286: xopReferencePresent = false; ohair@286: base64EncodedText = null; alanb@368: xopHref = null; ohair@286: } ohair@286: return event; ohair@286: } ohair@286: ohair@286: private String decodeCid(String cid) { ohair@286: try { ohair@286: cid = URLDecoder.decode(cid, "utf-8"); ohair@286: } catch (UnsupportedEncodingException e) { ohair@286: //on recceiving side lets not fail now, try to look for it ohair@286: } ohair@286: return cid; ohair@286: } ohair@286: ohair@286: private Attachment getAttachment(String cid) throws IOException { ohair@286: if (cid.startsWith("cid:")) ohair@286: cid = cid.substring(4, cid.length()); ohair@286: if (cid.indexOf('%') != -1) { ohair@286: cid = decodeCid(cid); ohair@286: return mimeMP.getAttachmentPart(cid); ohair@286: } ohair@286: return mimeMP.getAttachmentPart(cid); ohair@286: } ohair@286: alanb@368: @Override ohair@286: public char[] getTextCharacters() { ohair@286: if (xopReferencePresent) { ohair@286: char[] chars = new char[base64AttData.length()]; ohair@286: base64AttData.writeTo(chars, 0); ohair@286: return chars; ohair@286: } ohair@286: return reader.getTextCharacters(); ohair@286: } ohair@286: alanb@368: @Override ohair@286: public int getTextCharacters(int sourceStart, char[] target, int targetStart, int length) throws XMLStreamException { ohair@286: if(xopReferencePresent){ ohair@286: if(target == null){ ohair@286: throw new NullPointerException("target char array can't be null") ; ohair@286: } ohair@286: ohair@286: if(targetStart < 0 || length < 0 || sourceStart < 0 || targetStart >= target.length || ohair@286: (targetStart + length ) > target.length) { ohair@286: throw new IndexOutOfBoundsException(); ohair@286: } ohair@286: ohair@286: int textLength = base64AttData.length(); ohair@286: if(sourceStart > textLength) ohair@286: throw new IndexOutOfBoundsException(); ohair@286: ohair@286: if(base64EncodedText == null){ ohair@286: base64EncodedText = new char[base64AttData.length()]; ohair@286: base64AttData.writeTo(base64EncodedText, 0); ohair@286: } ohair@286: ohair@286: int copiedLength = Math.min(textLength - sourceStart, length); ohair@286: System.arraycopy(base64EncodedText, sourceStart , target, targetStart, copiedLength); ohair@286: return copiedLength; ohair@286: } ohair@286: return reader.getTextCharacters(sourceStart, target, targetStart, length); ohair@286: } ohair@286: alanb@368: @Override ohair@286: public String getText() { ohair@286: if (xopReferencePresent) { ohair@286: return base64AttData.toString(); ohair@286: } ohair@286: return reader.getText(); ohair@286: } alanb@368: alanb@368: protected boolean isXopReference() throws XMLStreamException { alanb@368: return xopReferencePresent; alanb@368: } alanb@368: alanb@368: protected String getXopHref() { alanb@368: return xopHref; alanb@368: } alanb@368: alanb@368: public MimeMultipartParser getMimeMultipartParser() { alanb@368: return mimeMP; alanb@368: } ohair@286: } ohair@286: ohair@286: }