1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20 package org.apache.geronimo.javamail.store.pop3; 21 22 import java.io.ByteArrayInputStream; 23 import java.io.ByteArrayOutputStream; 24 import java.io.IOException; 25 import java.io.InputStream; 26 import java.io.OutputStream; 27 import java.util.Enumeration; 28 29 import javax.mail.Flags; 30 import javax.mail.Folder; 31 import javax.mail.IllegalWriteException; 32 import javax.mail.MessagingException; 33 import javax.mail.event.MessageChangedEvent; 34 import javax.mail.internet.InternetHeaders; 35 import javax.mail.internet.MimeMessage; 36 37 import org.apache.geronimo.javamail.store.pop3.connection.POP3Connection; 38 39 /** 40 * POP3 implementation of javax.mail.internet.MimeMessage 41 * 42 * Only the most basic information is given and Message objects created here is 43 * a light-weight reference to the actual Message As per the JavaMail spec items 44 * from the actual message will get filled up on demand 45 * 46 * If some other items are obtained from the server as a result of one call, 47 * then the other details are also processed and filled in. For ex if RETR is 48 * called then header information will also be processed in addition to the 49 * content 50 * 51 * @version $Rev: 597135 $ $Date: 2007-11-21 11:26:57 -0500 (Wed, 21 Nov 2007) $ 52 */ 53 public class POP3Message extends MimeMessage { 54 // the size of the message, in bytes 55 protected int msgSize = -1; 56 // the size of the headers. We keep this around, as it's needed to 57 // properly calculate the size of the message 58 protected int headerSize = -1; 59 // the UID value retrieved from the server 60 protected String uid; 61 // the raw message data from loading the message 62 protected byte[] messageData; 63 64 /** 65 * Create a new POP3 message associated with a folder. 66 * 67 * @param folder The owning folder. 68 * @param msgnum The message sequence number in the folder. 69 */ 70 protected POP3Message(Folder folder, int msgnum) { 71 super(folder, msgnum); 72 this.session = session; 73 // force the headers to empty so we'll load them the first time they're referenced. 74 this.headers = null; 75 } 76 77 /** 78 * Get an InputStream for reading the message content. 79 * 80 * @return An InputStream instance initialized to read the message 81 * content. 82 * @exception MessagingException 83 */ 84 protected InputStream getContentStream() throws MessagingException { 85 // make sure the content is loaded first 86 loadContent(); 87 // allow the super class to handle creating it from the loaded content. 88 return super.getContentStream(); 89 } 90 91 92 /** 93 * Write out the byte data to the provided output stream. 94 * 95 * @param out The target stream. 96 * 97 * @exception IOException 98 * @exception MessagingException 99 */ 100 public void writeTo(OutputStream out) throws IOException, MessagingException { 101 // make sure we have everything loaded 102 loadContent(); 103 // just write out the raw message data 104 out.write(messageData); 105 } 106 107 108 /** 109 * Set a flag value for this Message. The flags are 110 * only set locally, not the server. When the folder 111 * is closed, any messages with the Deleted flag set 112 * will be removed from the server. 113 * 114 * @param newFlags The new flag values. 115 * @param set Indicates whether this is a set or an unset operation. 116 * 117 * @exception MessagingException 118 */ 119 public void setFlags(Flags newFlags, boolean set) throws MessagingException { 120 Flags oldFlags = (Flags) flags.clone(); 121 super.setFlags(newFlags, set); 122 123 if (!flags.equals(oldFlags)) { 124 ((POP3Folder) folder).notifyMessageChangedListeners(MessageChangedEvent.FLAGS_CHANGED, this); 125 } 126 } 127 128 /** 129 * Unconditionally load the headers from an inputstream. 130 * When retrieving content, we get back the entire message, 131 * including the headers. This allows us to skip over 132 * them to reach the content, even if we already have 133 * headers loaded. 134 * 135 * @param in The InputStream with the header data. 136 * 137 * @exception MessagingException 138 */ 139 protected void loadHeaders(InputStream in) throws MessagingException { 140 try { 141 headerSize = in.available(); 142 // just load and replace the haders 143 headers = new InternetHeaders(in); 144 headerSize -= in.available(); 145 } catch (IOException e) { 146 // reading from a ByteArrayInputStream...this should never happen. 147 } 148 } 149 150 /** 151 * Lazy loading of the message content. 152 * 153 * @exception MessagingException 154 */ 155 protected void loadContent() throws MessagingException { 156 if (content == null) { 157 POP3Connection connection = getConnection(); 158 try { 159 // retrieve (and save the raw message data 160 messageData = connection.retrieveMessageData(msgnum); 161 } finally { 162 // done with the connection 163 releaseConnection(connection); 164 } 165 // now create a input stream for splitting this into headers and 166 // content 167 ByteArrayInputStream in = new ByteArrayInputStream(messageData); 168 169 // the Sun implementation has an option that forces headers loaded using TOP 170 // should be forgotten when retrieving the message content. This is because 171 // some POP3 servers return different results for TOP and RETR. Since we need to 172 // retrieve the headers anyway, and this set should be the most complete, we'll 173 // just replace the headers unconditionally. 174 loadHeaders(in); 175 // load headers stops loading at the header terminator. Everything 176 // after that is content. 177 loadContent(in); 178 } 179 } 180 181 /** 182 * Load the message content from the server. 183 * 184 * @param stream A ByteArrayInputStream containing the message content. 185 * We explicitly use ByteArrayInputStream because 186 * there are some optimizations that can take advantage 187 * of the fact it is such a stream. 188 * 189 * @exception MessagingException 190 */ 191 protected void loadContent(ByteArrayInputStream stream) throws MessagingException { 192 // since this is a byte array input stream, available() returns reliable value. 193 content = new byte[stream.available()]; 194 try { 195 // just read everything in to the array 196 stream.read(content); 197 } catch (IOException e) { 198 // should never happen 199 throw new MessagingException("Error loading content info", e); 200 } 201 } 202 203 /** 204 * Get the size of the message. 205 * 206 * @return The calculated message size, in bytes. 207 * @exception MessagingException 208 */ 209 public int getSize() throws MessagingException { 210 if (msgSize < 0) { 211 // we need to get the headers loaded, since we need that information to calculate the total 212 // content size without retrieving the content. 213 loadHeaders(); 214 215 POP3Connection connection = getConnection(); 216 try { 217 218 // get the total message size, and adjust by size of the headers to get the content size. 219 msgSize = connection.retrieveMessageSize(msgnum) - headerSize; 220 } finally { 221 // done with the connection 222 releaseConnection(connection); 223 } 224 } 225 return msgSize; 226 } 227 228 /** 229 * notice that we pass zero as the no of lines from the message,as it 230 * doesn't serv any purpose to get only a certain number of lines. 231 * 232 * However this maybe important if a mail client only shows 3 or 4 lines of 233 * the message in the list and then when the user clicks they would load the 234 * message on demand. 235 * 236 */ 237 protected void loadHeaders() throws MessagingException { 238 if (headers == null) { 239 POP3Connection connection = getConnection(); 240 try { 241 loadHeaders(connection.retrieveMessageHeaders(msgnum)); 242 } finally { 243 // done with the connection 244 releaseConnection(connection); 245 } 246 } 247 } 248 249 /** 250 * Retrieve the message UID from the server. 251 * 252 * @return The string UID value. 253 * @exception MessagingException 254 */ 255 protected String getUID() throws MessagingException { 256 if (uid == null) { 257 POP3Connection connection = getConnection(); 258 try { 259 uid = connection.retrieveMessageUid(msgnum); 260 } finally { 261 // done with the connection 262 releaseConnection(connection); 263 } 264 } 265 return uid; 266 } 267 268 // The following are methods that deal with all header accesses. Most of the 269 // methods that retrieve information from the headers funnel through these, so we 270 // can lazy-retrieve the header information. 271 272 public String[] getHeader(String name) throws MessagingException { 273 // make sure the headers are loaded 274 loadHeaders(); 275 // allow the super class to handle everything from here 276 return super.getHeader(name); 277 } 278 279 public String getHeader(String name, String delimiter) throws MessagingException { 280 // make sure the headers are loaded 281 loadHeaders(); 282 // allow the super class to handle everything from here 283 return super.getHeader(name, delimiter); 284 } 285 286 public Enumeration getAllHeaders() throws MessagingException { 287 // make sure the headers are loaded 288 loadHeaders(); 289 // allow the super class to handle everything from here 290 return super.getAllHeaders(); 291 } 292 293 public Enumeration getMatchingHeaders(String[] names) throws MessagingException { 294 // make sure the headers are loaded 295 loadHeaders(); 296 // allow the super class to handle everything from here 297 return super.getMatchingHeaders(names); 298 } 299 300 public Enumeration getNonMatchingHeaders(String[] names) throws MessagingException { 301 // make sure the headers are loaded 302 loadHeaders(); 303 // allow the super class to handle everything from here 304 return super.getNonMatchingHeaders(names); 305 } 306 307 public Enumeration getAllHeaderLines() throws MessagingException { 308 // make sure the headers are loaded 309 loadHeaders(); 310 // allow the super class to handle everything from here 311 return super.getAllHeaderLines(); 312 } 313 314 public Enumeration getMatchingHeaderLines(String[] names) throws MessagingException { 315 // make sure the headers are loaded 316 loadHeaders(); 317 // allow the super class to handle everything from here 318 return super.getMatchingHeaderLines(names); 319 } 320 321 public Enumeration getNonMatchingHeaderLines(String[] names) throws MessagingException { 322 // make sure the headers are loaded 323 loadHeaders(); 324 // allow the super class to handle everything from here 325 return super.getNonMatchingHeaderLines(names); 326 } 327 328 // the following are overrides for header modification methods. These 329 // messages are read only, 330 // so the headers cannot be modified. 331 public void addHeader(String name, String value) throws MessagingException { 332 throw new IllegalWriteException("POP3 messages are read-only"); 333 } 334 335 public void setHeader(String name, String value) throws MessagingException { 336 throw new IllegalWriteException("POP3 messages are read-only"); 337 } 338 339 public void removeHeader(String name) throws MessagingException { 340 throw new IllegalWriteException("POP3 messages are read-only"); 341 } 342 343 public void addHeaderLine(String line) throws MessagingException { 344 throw new IllegalWriteException("POP3 messages are read-only"); 345 } 346 347 /** 348 * We cannot modify these messages 349 */ 350 public void saveChanges() throws MessagingException { 351 throw new IllegalWriteException("POP3 messages are read-only"); 352 } 353 354 355 /** 356 * get the current connection pool attached to the folder. We need 357 * to do this dynamically, to A) ensure we're only accessing an 358 * currently open folder, and B) to make sure we're using the 359 * correct connection attached to the folder. 360 * 361 * @return A connection attached to the hosting folder. 362 */ 363 protected POP3Connection getConnection() throws MessagingException { 364 // the folder owns everything. 365 return ((POP3Folder)folder).getMessageConnection(); 366 } 367 368 /** 369 * Release the connection back to the Folder after performing an operation 370 * that requires a connection. 371 * 372 * @param connection The previously acquired connection. 373 */ 374 protected void releaseConnection(POP3Connection connection) throws MessagingException { 375 // the folder owns everything. 376 ((POP3Folder)folder).releaseMessageConnection(connection); 377 } 378 }