View Javadoc
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.istack.internal.Nullable;
30  import com.sun.xml.internal.ws.api.message.Attachment;
31  import com.sun.xml.internal.ws.api.message.AttachmentEx;
32  import com.sun.xml.internal.ws.developer.StreamingAttachmentFeature;
33  import com.sun.xml.internal.ws.developer.StreamingDataHandler;
34  import com.sun.xml.internal.ws.util.ByteArrayBuffer;
35  import com.sun.xml.internal.ws.util.ByteArrayDataSource;
36  
37  import com.sun.xml.internal.org.jvnet.mimepull.Header;
38  import com.sun.xml.internal.org.jvnet.mimepull.MIMEMessage;
39  import com.sun.xml.internal.org.jvnet.mimepull.MIMEPart;
40  
41  import javax.activation.DataHandler;
42  import javax.xml.soap.SOAPException;
43  import javax.xml.soap.SOAPMessage;
44  import javax.xml.transform.Source;
45  import javax.xml.transform.stream.StreamSource;
46  import javax.xml.ws.WebServiceException;
47  import java.io.ByteArrayInputStream;
48  import java.io.IOException;
49  import java.io.InputStream;
50  import java.io.OutputStream;
51  import java.util.HashMap;
52  import java.util.Iterator;
53  import java.util.List;
54  import java.util.Map;
55  import java.util.logging.Level;
56  import java.util.logging.Logger;
57  
58  /**
59   * Parses Mime multipart message into primary part and attachment parts. It
60   * parses the stream lazily as and when required.
61   *
62   * @author Vivek Pandey
63   * @author Jitendra Kotamraju
64   */
65  public final class MimeMultipartParser {
66  
67      private final String start;
68      private final MIMEMessage message;
69      private Attachment root;
70      private ContentTypeImpl contentType;
71  
72      // Attachments without root part
73      private final Map<String, Attachment> attachments = new HashMap<String, Attachment>();
74  
75      private boolean gotAll;
76  
77      public MimeMultipartParser(InputStream in, String cType, StreamingAttachmentFeature feature) {
78          this.contentType = new ContentTypeImpl(cType);
79  //        ContentType ct = new ContentType(cType);
80  //        String boundary = ct.getParameter("boundary");
81          String boundary = contentType.getBoundary();
82          if (boundary == null || boundary.equals("")) {
83              throw new WebServiceException("MIME boundary parameter not found" + contentType);
84          }
85          message = (feature != null)
86                  ? new MIMEMessage(in, boundary, feature.getConfig())
87                  : new MIMEMessage(in, boundary);
88          // Strip <...> from root part's Content-ID
89  //        String st = ct.getParameter("start");
90          String st = contentType.getRootId();
91          if (st != null && st.length() > 2 && st.charAt(0) == '<' && st.charAt(st.length()-1) == '>') {
92              st = st.substring(1, st.length()-1);
93          }
94          start = st;
95      }
96  
97      /**
98       * Parses the stream and returns the root part. If start parameter is
99       * present in Content-Type, it is used to determine the root part, otherwise
100      * root part is the first part.
101      *
102      * @return StreamAttachment for root part
103      *         null if root part cannot be found
104      *
105      */
106     public @Nullable Attachment getRootPart() {
107         if (root == null) {
108             root = new PartAttachment((start != null) ? message.getPart(start) : message.getPart(0));
109         }
110         return root;
111     }
112 
113     /**
114      * Parses the entire stream and returns all MIME parts except root MIME part.
115      *
116      * @return Map<String, StreamAttachment> for all attachment parts
117      */
118     public @NotNull Map<String, Attachment> getAttachmentParts() {
119         if (!gotAll) {
120             MIMEPart rootPart = (start != null) ? message.getPart(start) : message.getPart(0);
121             List<MIMEPart> parts = message.getAttachments();
122             for(MIMEPart part : parts) {
123                 if (part != rootPart) {
124                     String cid = part.getContentId();
125                     if (!attachments.containsKey(cid)) {
126                         PartAttachment attach = new PartAttachment(part);
127                         attachments.put(attach.getContentId(), attach);
128                     }
129                 }
130             }
131             gotAll = true;
132         }
133         return attachments;
134     }
135 
136     /**
137      * This method can be called to get a matching MIME attachment part for the
138      * given contentId. It parses the stream until it finds a matching part.
139      *
140      * @return StreamAttachment attachment for contentId
141      *         null if there is no attachment for contentId
142      */
143     public @Nullable Attachment getAttachmentPart(String contentId) throws IOException {
144         //first see if this attachment is already parsed, if so return it
145         Attachment attach = attachments.get(contentId);
146         if (attach == null) {
147             MIMEPart part = message.getPart(contentId);
148             attach = new PartAttachment(part);
149             attachments.put(contentId, attach);
150         }
151         return attach;
152     }
153 
154     static class PartAttachment implements AttachmentEx {
155 
156         final MIMEPart part;
157         byte[] buf;
158         private StreamingDataHandler streamingDataHandler;
159 
160         PartAttachment(MIMEPart part) {
161             this.part = part;
162         }
163 
164         public @NotNull @Override String getContentId() {
165             return part.getContentId();
166         }
167 
168         public @NotNull @Override String getContentType() {
169             return part.getContentType();
170         }
171 
172         @Override
173         public byte[] asByteArray() {
174             if (buf == null) {
175                 ByteArrayBuffer baf = new ByteArrayBuffer();
176                 try {
177                     baf.write(part.readOnce());
178                 } catch(IOException ioe) {
179                     throw new WebServiceException(ioe);
180                 } finally {
181                     if (baf != null) {
182                         try {
183                             baf.close();
184                         } catch (IOException ex) {
185                             Logger.getLogger(MimeMultipartParser.class.getName()).log(Level.FINE, null, ex);
186                         }
187                     }
188                 }
189                 buf = baf.toByteArray();
190             }
191             return buf;
192         }
193 
194         @Override
195         public DataHandler asDataHandler() {
196             if (streamingDataHandler == null) {
197                 streamingDataHandler = (buf != null)
198                     ? new DataSourceStreamingDataHandler(new ByteArrayDataSource(buf,getContentType()))
199                     : new MIMEPartStreamingDataHandler(part);
200             }
201             return streamingDataHandler;
202         }
203 
204         @Override
205         public Source asSource() {
206             return (buf != null)
207                 ? new StreamSource(new ByteArrayInputStream(buf))
208                 : new StreamSource(part.read());
209         }
210 
211         @Override
212         public InputStream asInputStream() {
213             return (buf != null)
214                 ? new ByteArrayInputStream(buf) : part.read();
215         }
216 
217         @Override
218         public void writeTo(OutputStream os) throws IOException {
219             if (buf != null) {
220                 os.write(buf);
221             } else {
222                 InputStream in = part.read();
223                 byte[] temp = new byte[8192];
224                 int len;
225                 while((len=in.read(temp)) != -1) {
226                     os.write(temp, 0, len);
227                 }
228                 in.close();
229             }
230         }
231 
232         @Override
233         public void writeTo(SOAPMessage saaj) throws SOAPException {
234             saaj.createAttachmentPart().setDataHandler(asDataHandler());
235         }
236 
237         // AttachmentEx methods begin here
238         @Override
239         public Iterator<MimeHeader> getMimeHeaders() {
240             final Iterator<? extends Header> ih = part.getAllHeaders()
241                     .iterator();
242             return new Iterator<MimeHeader>() {
243                 @Override
244                 public boolean hasNext() {
245                     return ih.hasNext();
246                 }
247 
248                 @Override
249                 public MimeHeader next() {
250                     final Header hdr = ih.next();
251                     return new AttachmentEx.MimeHeader() {
252                         @Override
253                         public String getValue() {
254                             return hdr.getValue();
255                         }
256                         @Override
257                         public String getName() {
258                             return hdr.getName();
259                         }
260                     };
261                 }
262 
263                 @Override
264                 public void remove() {
265                     throw new UnsupportedOperationException();
266                 }
267             };
268         }
269     }
270 
271     public ContentTypeImpl getContentType() {
272         return contentType;
273     }
274 
275 }