001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019 020 package org.apache.geronimo.javamail.store.pop3; 021 022 import java.util.List; 023 024 import javax.mail.FetchProfile; 025 import javax.mail.Flags; 026 import javax.mail.Folder; 027 import javax.mail.FolderClosedException; 028 import javax.mail.Message; 029 import javax.mail.MessagingException; 030 import javax.mail.MethodNotSupportedException; 031 import javax.mail.Session; 032 import javax.mail.Store; 033 import javax.mail.URLName; 034 import javax.mail.event.ConnectionEvent; 035 036 import org.apache.geronimo.javamail.store.pop3.connection.POP3Connection; 037 import org.apache.geronimo.javamail.store.pop3.connection.POP3StatusResponse; 038 039 /** 040 * The POP3 implementation of the javax.mail.Folder Note that only INBOX is 041 * supported in POP3 042 * <p> 043 * <url>http://www.faqs.org/rfcs/rfc1939.html</url> 044 * </p> 045 * 046 * @see javax.mail.Folder 047 * 048 * @version $Rev: 689140 $ $Date: 2008-08-26 13:20:01 -0400 (Tue, 26 Aug 2008) $ 049 */ 050 public class POP3Folder extends Folder { 051 052 protected boolean isFolderOpen = false; 053 054 protected int mode; 055 056 protected int msgCount; 057 058 private POP3Message[] messageCache; 059 // The fully qualified name of the folder. For a POP3 folder, this is either "" for the root or 060 // "INPUT" for the in-basket. It is possible to create other folders, but they will report that 061 // they don't exist. 062 protected String fullName; 063 // indicates whether this folder exists or not 064 protected boolean exists = false; 065 // indicates the type of folder this is. 066 protected int folderType; 067 068 /** 069 * Create a new folder associate with a POP3 store instance. 070 * 071 * @param store The owning Store. 072 * @param name The name of the folder. Note that POP3 stores only 073 * have 2 real folders, the root ("") and the in-basket 074 * ("INBOX"). It is possible to create other instances 075 * of Folder associated with the Store, but they will 076 * be non-functional. 077 */ 078 public POP3Folder(POP3Store store, String name) { 079 super(store); 080 this.fullName = name; 081 // if this is the input folder, this exists 082 if (name.equalsIgnoreCase("INPUT")) { 083 exists = true; 084 } 085 // by default, we're holding messages. 086 folderType = Folder.HOLDS_MESSAGES; 087 } 088 089 090 /** 091 * Retrieve the folder name. This is the simple folder 092 * name at the its hiearchy level. This can be invoked when the folder is closed. 093 * 094 * @return The folder's name. 095 */ 096 public String getName() { 097 // the name and the full name are always the same 098 return fullName; 099 } 100 101 /** 102 * Retrieve the folder's full name (including hierarchy information). 103 * This can be invoked when the folder is closed. 104 * 105 * @return The full name value. 106 */ 107 public String getFullName() { 108 return fullName; 109 } 110 111 112 /** 113 * Never return "this" as the parent folder. Somebody not familliar with 114 * POP3 may do something like while(getParent() != null) or something 115 * simmilar which will result in an infinte loop 116 */ 117 public Folder getParent() throws MessagingException { 118 // the default folder returns null. We return the default 119 // folder 120 return store.getDefaultFolder(); 121 } 122 123 /** 124 * Indicate whether a folder exists. Only the root 125 * folder and "INBOX" will ever return true. 126 * 127 * @return true for real POP3 folders, false for any other 128 * instances that have been created. 129 * @exception MessagingException 130 */ 131 public boolean exists() throws MessagingException { 132 // only one folder truely exists...this might be it. 133 return exists; 134 } 135 136 public Folder[] list(String pattern) throws MessagingException { 137 throw new MethodNotSupportedException("Only INBOX is supported in POP3, no sub folders"); 138 } 139 140 /** 141 * No sub folders, hence there is no notion of a seperator. This is always a null character. 142 */ 143 public char getSeparator() throws MessagingException { 144 return '\0'; 145 } 146 147 /** 148 * There's no hierarchy in POP3, so the only type 149 * is HOLDS_MESSAGES (and only one of those exists). 150 * 151 * @return Always returns HOLDS_MESSAGES. 152 * @exception MessagingException 153 */ 154 public int getType() throws MessagingException { 155 return folderType; 156 } 157 158 /** 159 * Always returns false as any creation operation must 160 * fail. 161 * 162 * @param type The type of folder to create. This is ignored. 163 * 164 * @return Always returns false. 165 * @exception MessagingException 166 */ 167 public boolean create(int type) throws MessagingException { 168 return false; 169 } 170 171 /** 172 * No way to detect new messages, so always return false. 173 * 174 * @return Always returns false. 175 * @exception MessagingException 176 */ 177 public boolean hasNewMessages() throws MessagingException { 178 return false; 179 } 180 181 public Folder getFolder(String name) throws MessagingException { 182 throw new MethodNotSupportedException("Only INBOX is supported in POP3, no sub folders"); 183 } 184 185 public boolean delete(boolean recurse) throws MessagingException { 186 throw new MethodNotSupportedException("Only INBOX is supported in POP3 and INBOX cannot be deleted"); 187 } 188 189 public boolean renameTo(Folder f) throws MessagingException { 190 throw new MethodNotSupportedException("Only INBOX is supported in POP3 and INBOX cannot be renamed"); 191 } 192 193 /** 194 * @see javax.mail.Folder#open(int) 195 */ 196 public void open(int mode) throws MessagingException { 197 // Can only be performed on a closed folder 198 checkClosed(); 199 200 // get a connection object 201 POP3Connection connection = getConnection(); 202 203 try { 204 POP3StatusResponse res = connection.retrieveMailboxStatus(); 205 this.mode = mode; 206 this.isFolderOpen = true; 207 this.msgCount = res.getNumMessages(); 208 // JavaMail API has no method in Folder to expose the total 209 // size (no of bytes) of the mail drop; 210 211 // NB: We use the actual message number to access the messages from 212 // the cache, which is origin 1. Vectors are origin 0, so we have to subtract each time 213 // we access a messagge. 214 messageCache = new POP3Message[msgCount]; 215 } catch (Exception e) { 216 throw new MessagingException("Unable to execute STAT command", e); 217 } 218 finally { 219 // return the connection when finished 220 releaseConnection(connection); 221 } 222 223 notifyConnectionListeners(ConnectionEvent.OPENED); 224 } 225 226 /** 227 * Close a POP3 folder. 228 * 229 * @param expunge The expunge flag (ignored for POP3). 230 * 231 * @exception MessagingException 232 */ 233 public void close(boolean expunge) throws MessagingException { 234 // Can only be performed on an open folder 235 checkOpen(); 236 237 // get a connection object 238 POP3Connection connection = getConnection(); 239 try { 240 // we might need to reset the connection before we 241 // process deleted messages and send the QUIT. The 242 // connection knows if we need to do this. 243 connection.reset(); 244 // clean up any messages marked for deletion 245 expungeDeletedMessages(connection); 246 } finally { 247 // return the connection when finished 248 releaseConnection(connection); 249 // cleanup the the state even if exceptions occur when deleting the 250 // messages. 251 cleanupFolder(false); 252 } 253 } 254 255 /** 256 * Mark any messages we've flagged as deleted from the 257 * POP3 server before closing. 258 * 259 * @exception MessagingException 260 */ 261 protected void expungeDeletedMessages(POP3Connection connection) throws MessagingException { 262 if (mode == READ_WRITE) { 263 for (int i = 0; i < messageCache.length; i++) { 264 POP3Message msg = messageCache[i]; 265 if (msg != null) { 266 // if the deleted flag is set, go delete this 267 // message. NB: We adjust the index back to an 268 // origin 1 value 269 if (msg.isSet(Flags.Flag.DELETED)) { 270 try { 271 connection.deleteMessage(i + 1); 272 } catch (MessagingException e) { 273 throw new MessagingException("Exception deleting message number " + (i + 1), e); 274 } 275 } 276 } 277 } 278 } 279 } 280 281 282 /** 283 * Do folder cleanup. This is used both for normal 284 * close operations, and adnormal closes where the 285 * server has sent us a BYE message. 286 * 287 * @param expunge Indicates whether open messages should be expunged. 288 * @param disconnected 289 * The disconnected flag. If true, the server has cut 290 * us off, which means our connection can not be returned 291 * to the connection pool. 292 * 293 * @exception MessagingException 294 */ 295 protected void cleanupFolder(boolean disconnected) throws MessagingException { 296 messageCache = null; 297 isFolderOpen = false; 298 notifyConnectionListeners(ConnectionEvent.CLOSED); 299 } 300 301 302 /** 303 * Obtain a connection object for a Message attached to this Folder. This 304 * will be the Folder's connection, which is only available if the Folder 305 * is currently open. 306 * 307 * @return The connection object for the Message instance to use. 308 * @exception MessagingException 309 */ 310 synchronized POP3Connection getMessageConnection() throws MessagingException { 311 // we always get one from the store. If we're fully single threaded, then 312 // we can get away with just a single one. 313 return getConnection(); 314 } 315 316 317 /** 318 * Release the connection object back to the Folder instance. 319 * 320 * @param connection The connection being released. 321 * 322 * @exception MessagingException 323 */ 324 void releaseMessageConnection(POP3Connection connection) throws MessagingException { 325 // give this back to the store 326 releaseConnection(connection); 327 } 328 329 public boolean isOpen() { 330 // if we're not open, we're not open 331 if (!isFolderOpen) { 332 return false; 333 } 334 335 try { 336 // we might be open, but the Store has been closed. In which case, we're not any more 337 // closing also changes the isFolderOpen flag. 338 if (!((POP3Store)store).isConnected()) { 339 close(false); 340 } 341 } catch (MessagingException e) { 342 } 343 return isFolderOpen; 344 } 345 346 public Flags getPermanentFlags() { 347 // unfortunately doesn't have a throws clause for this method 348 // throw new MethodNotSupportedException("POP3 doesn't support permanent 349 // flags"); 350 351 // Better than returning null, save the extra condition from a user to 352 // check for null 353 // and avoids a NullPointerException for the careless. 354 return new Flags(); 355 } 356 357 /** 358 * Get the folder message count. 359 * 360 * @return The number of messages in the folder. 361 * @exception MessagingException 362 */ 363 public int getMessageCount() throws MessagingException { 364 // NB: returns -1 if the folder isn't open. 365 return msgCount; 366 } 367 368 /** 369 * Checks wether the message is in cache, if not will create a new message 370 * object and return it. 371 * 372 * @see javax.mail.Folder#getMessage(int) 373 */ 374 public Message getMessage(int msgNum) throws MessagingException { 375 // Can only be performed on an Open folder 376 checkOpen(); 377 if (msgNum < 1 || msgNum > getMessageCount()) { 378 throw new MessagingException("Invalid Message number"); 379 } 380 381 Message msg = messageCache[msgNum - 1]; 382 if (msg == null) { 383 msg = new POP3Message(this, msgNum); 384 messageCache[msgNum - 1] = (POP3Message)msg; 385 } 386 387 return msg; 388 } 389 390 public void appendMessages(Message[] msgs) throws MessagingException { 391 throw new MethodNotSupportedException("Message appending is not supported in POP3"); 392 393 } 394 395 public Message[] expunge() throws MessagingException { 396 throw new MethodNotSupportedException("Expunge is not supported in POP3"); 397 } 398 399 public int getMode() throws IllegalStateException { 400 // Can only be performed on an Open folder 401 checkOpen(); 402 return mode; 403 } 404 405 /** 406 * @see javax.mail.Folder#fetch(javax.mail.Message[], 407 * javax.mail.FetchProfile) 408 * 409 * The JavaMail API recommends that this method be overrident to provide a 410 * meaningfull implementation. 411 */ 412 public synchronized void fetch(Message[] msgs, FetchProfile fp) throws MessagingException { 413 // Can only be performed on an Open folder 414 checkOpen(); 415 for (int i = 0; i < msgs.length; i++) { 416 Message msg = msgs[i]; 417 418 if (fp.contains(FetchProfile.Item.ENVELOPE)) { 419 // fetching the size and the subject will force all of the 420 // envelope information to load 421 msg.getHeader("Subject"); 422 msg.getSize(); 423 } 424 if (fp.contains(FetchProfile.Item.CONTENT_INFO)) { 425 // force the content to load...this also fetches the header information. 426 // C'est la vie. 427 ((POP3Message)msg).loadContent(); 428 msg.getSize(); 429 } 430 // force flag loading for this message 431 if (fp.contains(FetchProfile.Item.FLAGS)) { 432 msg.getFlags(); 433 } 434 435 if (fp.getHeaderNames().length > 0) { 436 // loading any header loads all headers, so just grab the header set. 437 msg.getHeader("Subject"); 438 } 439 } 440 } 441 442 /** 443 * Retrieve the UID for a given message. 444 * 445 * @param msg The message of interest. 446 * 447 * @return The String UID value for this message. 448 * @exception MessagingException 449 */ 450 public synchronized String getUID(Message msg) throws MessagingException { 451 checkOpen(); 452 // the Message knows how to do this 453 return ((POP3Message)msg).getUID(); 454 } 455 456 457 /** 458 * Below is a list of covinience methods that avoid repeated checking for a 459 * value and throwing an exception 460 */ 461 462 /** Ensure the folder is open */ 463 private void checkOpen() throws IllegalStateException { 464 if (!isFolderOpen) { 465 throw new IllegalStateException("Folder is not Open"); 466 } 467 } 468 469 /** Ensure the folder is not open */ 470 private void checkClosed() throws IllegalStateException { 471 if (isFolderOpen) { 472 throw new IllegalStateException("Folder is Open"); 473 } 474 } 475 476 /** 477 * @see javax.mail.Folder#notifyMessageChangedListeners(int, 478 * javax.mail.Message) 479 * 480 * this method is protected and cannot be used outside of Folder, therefore 481 * had to explicitly expose it via a method in POP3Folder, so that 482 * POP3Message has access to it 483 * 484 * Bad design on the part of the Java Mail API. 485 */ 486 public void notifyMessageChangedListeners(int type, Message m) { 487 super.notifyMessageChangedListeners(type, m); 488 } 489 490 491 /** 492 * Retrieve the connection attached to this folder. Throws an 493 * exception if we don't have an active connection. 494 * 495 * @return The current connection object. 496 * @exception MessagingException 497 */ 498 protected synchronized POP3Connection getConnection() throws MessagingException { 499 // request a connection from the central store. 500 return ((POP3Store)store).getFolderConnection(this); 501 } 502 503 504 /** 505 * Release our connection back to the Store. 506 * 507 * @param connection The connection to release. 508 * 509 * @exception MessagingException 510 */ 511 protected void releaseConnection(POP3Connection connection) throws MessagingException { 512 // we need to release the connection to the Store once we're finished with it 513 ((POP3Store)store).releaseFolderConnection(this, connection); 514 } 515 }