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