View Javadoc
1   /*
2    * Copyright (c) 1997, 2012, 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.org.jvnet.mimepull;
27  
28  import java.io.File;
29  import java.io.InputStream;
30  import java.nio.ByteBuffer;
31  import java.util.List;
32  import java.util.logging.Level;
33  import java.util.logging.Logger;
34  
35  /**
36   * Represents an attachment part in a MIME message. MIME message parsing is done
37   * lazily using a pull parser, so the part may not have all the data. {@link #read}
38   * and {@link #readOnce} may trigger the actual parsing the message. In fact,
39   * parsing of an attachment part may be triggered by calling {@link #read} methods
40   * on some other attachment parts. All this happens behind the scenes so the
41   * application developer need not worry about these details.
42   *
43   * @author Jitendra Kotamraju, Martin Grebac
44   */
45  public class MIMEPart {
46  
47      private static final Logger LOGGER = Logger.getLogger(MIMEPart.class.getName());
48  
49      private volatile InternetHeaders headers;
50      private volatile String contentId;
51      private String contentType;
52      private String contentTransferEncoding;
53  
54      volatile boolean parsed;    // part is parsed or not
55      final MIMEMessage msg;
56      private final DataHead dataHead;
57  
58      MIMEPart(MIMEMessage msg) {
59          this.msg = msg;
60          this.dataHead = new DataHead(this);
61      }
62  
63      MIMEPart(MIMEMessage msg, String contentId) {
64          this(msg);
65          this.contentId = contentId;
66      }
67  
68      /**
69       * Can get the attachment part's content multiple times. That means
70       * the full content needs to be there in memory or on the file system.
71       * Calling this method would trigger parsing for the part's data. So
72       * do not call this unless it is required(otherwise, just wrap MIMEPart
73       * into a object that returns InputStream for e.g DataHandler)
74       *
75       * @return data for the part's content
76       */
77      public InputStream read() {
78          InputStream is = null;
79          try {
80              is = MimeUtility.decode(dataHead.read(), contentTransferEncoding);
81          } catch (DecodingException ex) { //ignore
82              if (LOGGER.isLoggable(Level.WARNING)) {
83                  LOGGER.log(Level.WARNING, null, ex);
84              }
85          }
86          return is;
87      }
88  
89      /**
90       * Cleans up any resources that are held by this part (for e.g. deletes
91       * the temp file that is used to serve this part's content). After
92       * calling this, one shouldn't call {@link #read()} or {@link #readOnce()}
93       */
94      public void close() {
95          dataHead.close();
96      }
97  
98      /**
99       * Can get the attachment part's content only once. The content
100      * will be lost after the method. Content data is not be stored
101      * on the file system or is not kept in the memory for the
102      * following case:
103      *   - Attachement parts contents are accessed sequentially
104      *
105      * In general, take advantage of this when the data is used only
106      * once.
107      *
108      * @return data for the part's content
109      */
110     public InputStream readOnce() {
111         InputStream is = null;
112         try {
113             is = MimeUtility.decode(dataHead.readOnce(), contentTransferEncoding);
114         } catch (DecodingException ex) { //ignore
115             if (LOGGER.isLoggable(Level.WARNING)) {
116                 LOGGER.log(Level.WARNING, null, ex);
117             }
118         }
119         return is;
120     }
121 
122     public void moveTo(File f) {
123         dataHead.moveTo(f);
124     }
125 
126     /**
127      * Returns Content-ID MIME header for this attachment part
128      *
129      * @return Content-ID of the part
130      */
131     public String getContentId() {
132         if (contentId == null) {
133             getHeaders();
134         }
135         return contentId;
136     }
137 
138     /**
139      * Returns Content-Transfer-Encoding MIME header for this attachment part
140      *
141      * @return Content-Transfer-Encoding of the part
142      */
143     public String getContentTransferEncoding() {
144         if (contentTransferEncoding == null) {
145             getHeaders();
146         }
147         return contentTransferEncoding;
148     }
149 
150     /**
151      * Returns Content-Type MIME header for this attachment part
152      *
153      * @return Content-Type of the part
154      */
155     public String getContentType() {
156         if (contentType == null) {
157             getHeaders();
158         }
159         return contentType;
160     }
161 
162     private void getHeaders() {
163         // Trigger parsing for the part headers
164         while(headers == null) {
165             if (!msg.makeProgress()) {
166                 if (headers == null) {
167                     throw new IllegalStateException("Internal Error. Didn't get Headers even after complete parsing.");
168                 }
169             }
170         }
171     }
172 
173     /**
174      * Return all the values for the specified header.
175      * Returns <code>null</code> if no headers with the
176      * specified name exist.
177      *
178      * @param   name header name
179      * @return  list of header values, or null if none
180      */
181     public List<String> getHeader(String name) {
182         getHeaders();
183         assert headers != null;
184         return headers.getHeader(name);
185     }
186 
187     /**
188      * Return all the headers
189      *
190      * @return list of Header objects
191      */
192     public List<? extends Header> getAllHeaders() {
193         getHeaders();
194         assert headers != null;
195         return headers.getAllHeaders();
196     }
197 
198     /**
199      * Callback to set headers
200      *
201      * @param headers MIME headers for the part
202      */
203     void setHeaders(InternetHeaders headers) {
204         this.headers = headers;
205         List<String> ct = getHeader("Content-Type");
206         this.contentType = (ct == null) ? "application/octet-stream" : ct.get(0);
207         List<String> cte = getHeader("Content-Transfer-Encoding");
208         this.contentTransferEncoding = (cte == null) ? "binary" : cte.get(0);
209     }
210 
211     /**
212      * Callback to notify that there is a partial content for the part
213      *
214      * @param buf content data for the part
215      */
216     void addBody(ByteBuffer buf) {
217         dataHead.addBody(buf);
218     }
219 
220     /**
221      * Callback to indicate that parsing is done for this part
222      * (no more update events for this part)
223      */
224     void doneParsing() {
225         parsed = true;
226         dataHead.doneParsing();
227     }
228 
229     /**
230      * Callback to set Content-ID for this part
231      * @param cid Content-ID of the part
232      */
233     void setContentId(String cid) {
234         this.contentId = cid;
235     }
236 
237     /**
238      * Callback to set Content-Transfer-Encoding for this part
239      * @param cte Content-Transfer-Encoding of the part
240      */
241     void setContentTransferEncoding(String cte) {
242         this.contentTransferEncoding = cte;
243     }
244 
245     @Override
246     public String toString() {
247         return "Part="+contentId+":"+contentTransferEncoding;
248     }
249 
250 }