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 }