001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018 package org.apache.geronimo.javamail.store.imap; 019 020 import java.io.ByteArrayOutputStream; 021 import java.io.IOException; 022 import java.util.ArrayList; 023 import java.util.Date; 024 import java.util.HashMap; 025 import java.util.Iterator; 026 import java.util.LinkedList; 027 import java.util.List; 028 import java.util.Map; 029 import java.util.NoSuchElementException; 030 import java.util.Vector; 031 032 import javax.mail.*; 033 import javax.mail.event.ConnectionEvent; 034 import javax.mail.event.FolderEvent; 035 import javax.mail.event.MessageChangedEvent; 036 import javax.mail.search.FlagTerm; 037 import javax.mail.search.SearchTerm; 038 039 import org.apache.geronimo.javamail.store.imap.connection.IMAPConnection; 040 import org.apache.geronimo.javamail.store.imap.connection.IMAPFetchDataItem; 041 import org.apache.geronimo.javamail.store.imap.connection.IMAPFetchResponse; 042 import org.apache.geronimo.javamail.store.imap.connection.IMAPFlags; 043 import org.apache.geronimo.javamail.store.imap.connection.IMAPListResponse; 044 import org.apache.geronimo.javamail.store.imap.connection.IMAPMailboxStatus; 045 import org.apache.geronimo.javamail.store.imap.connection.IMAPSizeResponse; 046 import org.apache.geronimo.javamail.store.imap.connection.IMAPUid; 047 import org.apache.geronimo.javamail.store.imap.connection.IMAPUntaggedResponse; 048 import org.apache.geronimo.javamail.store.imap.connection.IMAPUntaggedResponseHandler; 049 050 /** 051 * The base IMAP implementation of the javax.mail.Folder 052 * This is a base class for both the Root IMAP server and each IMAP group folder. 053 * @see javax.mail.Folder 054 * 055 * @version $Rev: 594520 $ 056 */ 057 public class IMAPFolder extends Folder implements UIDFolder, IMAPUntaggedResponseHandler { 058 059 /** 060 * Special profile item used for fetching SIZE and HEADER information. 061 * These items are extensions that Sun has added to their IMAPFolder immplementation. 062 * We're supporting the same set. 063 */ 064 public static class FetchProfileItem extends FetchProfile.Item { 065 public static final FetchProfileItem HEADERS = new FetchProfileItem("HEADERS"); 066 public static final FetchProfileItem SIZE = new FetchProfileItem("SIZE"); 067 068 protected FetchProfileItem(String name) { 069 super(name); 070 } 071 } 072 073 // marker that we don't know the separator yet for this folder. 074 // This occurs when we obtain a folder reference from the 075 // default folder. At that point, we've not queried the 076 // server for specifics yet. 077 static final protected char UNDETERMINED = 0; 078 079 // our attached session 080 protected Session session; 081 // retrieved messages, mapped by sequence number. 082 protected LinkedList messages; 083 // mappings of UIDs to retrieved messages. 084 protected Map uidCache; 085 086 // the separator the server indicates is used as the hierarchy separator 087 protected char separator; 088 // the "full" name of the folder. This is the fully qualified path name for the folder returned by 089 // the IMAP server. Elements of the hierarchy are delimited by "separator" characters. 090 protected String fullname; 091 // the name of this folder. The is the last element of the fully qualified name. 092 protected String name; 093 // the folder open state 094 protected boolean folderOpen = false; 095 // the type information on what the folder can hold 096 protected int folderType; 097 // the subscription status 098 protected boolean subscribed = false; 099 100 // the message identifier ticker, used to assign message numbers. 101 protected int nextMessageID = 1; 102 // the current count of messages in our cache. 103 protected int maxSequenceNumber = 0; 104 // the reported count of new messages (updated as a result of untagged message resposes) 105 protected int recentMessages = -1; 106 // the reported count of unseen messages 107 protected int unseenMessages = 0; 108 // the uidValidity value reported back from the server 109 protected long uidValidity = 0; 110 // the uidNext value reported back from the server 111 protected long uidNext = 0; 112 // the persistent flags we save in the store 113 protected Flags permanentFlags; 114 // the settable flags the server reports back to us 115 protected Flags availableFlags; 116 // Our cached status information. We will only hold this for the timeout interval. 117 protected IMAPMailboxStatus cachedStatus; 118 // Folder information retrieved from the server. Good info here indicates the 119 // folder exists. 120 protected IMAPListResponse listInfo; 121 // the configured status cache timeout value. 122 protected long statusCacheTimeout; 123 // the last time we took a status snap shot. 124 protected long lastStatusTimeStamp; 125 // Our current connection. We get one of these when opened, and release it when closed. 126 // We do this because for any folder (and message) operations, the folder must be selected on 127 // the connection. 128 // Note, however, that there are operations which will require us to borrow a connection 129 // temporarily because we need to touch the server when the folder is not open. In those 130 // cases, we grab a connection, then immediately return it to the pool. 131 protected IMAPConnection currentConnection; 132 133 134 135 /** 136 * Super class constructor the base IMAPFolder class. 137 * 138 * @param store The javamail store this folder is attached to. 139 * @param fullname The fully qualified name of this folder. 140 * @param separator The separtor character used to delimit the different 141 * levels of the folder hierarchy. This is used to 142 * decompose the full name into smaller parts and 143 * create the names of subfolders. 144 */ 145 protected IMAPFolder(IMAPStore store, String fullname, char separator) { 146 super(store); 147 this.session = store.getSession(); 148 this.fullname = fullname; 149 this.separator = separator; 150 // get the status timeout value from the folder. 151 statusCacheTimeout = store.statusCacheTimeout; 152 } 153 154 /** 155 * Retrieve the folder name. This is the simple folder 156 * name at the its hiearchy level. This can be invoked when the folder is closed. 157 * 158 * @return The folder's name. 159 */ 160 public String getName() { 161 // At the time we create the folder, we might not know the separator character yet. 162 // Because of this we need to delay creating the name element until 163 // it's required. 164 if (name == null) { 165 // extract the name from the full name 166 int lastLevel = -1; 167 try { 168 lastLevel = fullname.lastIndexOf(getSeparator()); 169 } catch (MessagingException e) { 170 // not likely to occur, but the link could go down before we 171 // get this. Just assume a failure to locate the character 172 // occurred. 173 } 174 if (lastLevel == -1) { 175 name = fullname; 176 } 177 else { 178 name = fullname.substring(lastLevel + 1); 179 } 180 } 181 return name; 182 } 183 184 /** 185 * Retrieve the folder's full name (including hierarchy information). 186 * This can be invoked when the folder is closed. 187 * 188 * @return The full name value. 189 */ 190 public String getFullName() { 191 return fullname; 192 } 193 194 195 196 /** 197 * Return the parent for this folder; if the folder is at the root of a heirarchy 198 * this returns null. 199 * This can be invoked when the folder is closed. 200 * 201 * @return this folder's parent 202 * @throws MessagingException 203 */ 204 public Folder getParent() throws MessagingException { 205 // NB: We need to use the method form because the separator 206 // might not have been retrieved from the server yet. 207 char separator = getSeparator(); 208 // we don't hold a reference to the parent folder, as that would pin the instance in memory 209 // as long as any any leaf item in the hierarchy is still open. 210 int lastLevel = fullname.lastIndexOf(separator); 211 // no parent folder? Get the root one from the Store. 212 if (lastLevel == -1) { 213 return ((IMAPStore)store).getDefaultFolder(); 214 } 215 else { 216 // create a folder for the parent. 217 return new IMAPFolder((IMAPStore)store, fullname.substring(0, lastLevel), separator); 218 } 219 } 220 221 222 /** 223 * Check to see if this folder physically exists in the store. 224 * This can be invoked when the folder is closed. 225 * 226 * @return true if the folder really exists 227 * @throws MessagingException if there was a problem accessing the store 228 */ 229 public synchronized boolean exists() throws MessagingException { 230 IMAPConnection connection = getConnection(); 231 try { 232 return checkExistance(connection); 233 } finally { 234 releaseConnection(connection); 235 } 236 } 237 238 /** 239 * Internal routine for checking existance using an 240 * already obtained connection. Used for situations 241 * where the list information needs updating but 242 * we'd end up acquiring a new connection because 243 * the folder isn't open yet. 244 * 245 * @param connection The connection to use. 246 * 247 * @return true if the folder exists, false for non-existence. 248 * @exception MessagingException 249 */ 250 private boolean checkExistance(IMAPConnection connection) throws MessagingException { 251 // get the list response for this folder. 252 List responses = connection.list("", fullname); 253 // NB, this grabs the latest information and updates 254 // the type information also. Note also that we need to 255 // use the mailbox name, not the full name. This is so 256 // the namespace folders will return the correct response. 257 listInfo = findListResponse(responses, getMailBoxName()); 258 259 if (listInfo == null) { 260 return false; 261 } 262 263 // update the type information from the status. 264 folderType = 0; 265 if (!listInfo.noinferiors) { 266 folderType |= HOLDS_FOLDERS; 267 } 268 if (!listInfo.noselect) { 269 folderType |= HOLDS_MESSAGES; 270 } 271 272 // also update the separator information. This will allow 273 // use to skip a call later 274 separator = listInfo.separator; 275 // this can be omitted in the response, so assume a default 276 if (separator == '\0') { 277 separator = '/'; 278 } 279 280 // updated ok, so it must be there. 281 return true; 282 } 283 284 285 286 /** 287 * Return a list of folders from this Folder's namespace that match the supplied pattern. 288 * Patterns may contain the following wildcards: 289 * <ul><li>'%' which matches any characater except hierarchy delimiters</li> 290 * <li>'*' which matches any character including hierarchy delimiters</li> 291 * </ul> 292 * This can be invoked when the folder is closed. 293 * 294 * @param pattern the pattern to search for 295 * 296 * @return a possibly empty array containing Folders that matched the pattern 297 * @throws MessagingException 298 * if there was a problem accessing the store 299 */ 300 public synchronized Folder[] list(String pattern) throws MessagingException { 301 // go filter the folders based on the pattern. The server does most of the 302 // heavy lifting on the pattern matching. 303 return filterFolders(pattern, false); 304 } 305 306 307 /** 308 * Return a list of folders to which the user is subscribed and which match the supplied pattern. 309 * If the store does not support the concept of subscription then this should match against 310 * all folders; the default implementation of this method achieves this by defaulting to the 311 * {@link #list(String)} method. 312 * 313 * @param pattern the pattern to search for 314 * 315 * @return a possibly empty array containing subscribed Folders that matched the pattern 316 * @throws MessagingException 317 * if there was a problem accessing the store 318 */ 319 public synchronized Folder[] listSubscribed(String pattern) throws MessagingException { 320 // go filter the folders based on the pattern. The server does most of the 321 // heavy lifting on the pattern matching. 322 return filterFolders(pattern, true); 323 } 324 325 326 /** 327 * Return the character used by this folder's Store to separate path components. 328 * 329 * @return the name separater character 330 * @throws MessagingException if there was a problem accessing the store 331 */ 332 public synchronized char getSeparator() throws MessagingException { 333 // not determined yet, we need to ask the server for the information 334 if (separator == UNDETERMINED) { 335 IMAPConnection connection = getConnection(); 336 try { 337 List responses = connection.list("", fullname); 338 IMAPListResponse info = findListResponse(responses, fullname); 339 340 // if we didn't get any hits, then we just assume a reasonable default. 341 if (info == null) { 342 separator = '/'; 343 } 344 else { 345 separator = info.separator; 346 // this can be omitted in the response, so assume a default 347 if (separator == '\0') { 348 separator = '/'; 349 } 350 } 351 } finally { 352 releaseConnection(connection); 353 } 354 } 355 return separator; 356 } 357 358 359 /** 360 * Return whether this folder can hold just messages or also 361 * subfolders. 362 * 363 * @return The combination of Folder.HOLDS_MESSAGES and Folder.HOLDS_FOLDERS, depending 364 * on the folder capabilities. 365 * @exception MessagingException 366 */ 367 public int getType() throws MessagingException { 368 // checking the validity will update the type information 369 // if it succeeds. 370 checkFolderValidity(); 371 return folderType; 372 } 373 374 375 /** 376 * Create a new folder capable of containing subfolder and/or messages as 377 * determined by the type parameter. Any hierarchy defined by the folder 378 * name will be recursively created. 379 * If the folder was sucessfully created, a {@link FolderEvent#CREATED CREATED FolderEvent} 380 * is sent to all FolderListeners registered with this Folder or with the Store. 381 * 382 * @param newType the type, indicating if this folder should contain subfolders, messages or both 383 * 384 * @return true if the folder was sucessfully created 385 * @throws MessagingException 386 * if there was a problem accessing the store 387 */ 388 public synchronized boolean create(int newType) throws MessagingException { 389 IMAPConnection connection = getConnection(); 390 try { 391 392 // by default, just create using the fullname. 393 String newPath = fullname; 394 395 // if this folder is expected to only hold additional folders, we need to 396 // add a separator on to the end when we create this. 397 if ((newType & HOLDS_MESSAGES) == 0) { 398 newPath = fullname + separator; 399 } 400 try { 401 // go create this 402 connection.createMailbox(newPath); 403 // verify this exists...also updates some of the status 404 boolean reallyCreated = checkExistance(connection); 405 // broadcast a creation event. 406 notifyFolderListeners(FolderEvent.CREATED); 407 return reallyCreated; 408 } catch (MessagingException e) { 409 //TODO add folder level debug logging. 410 } 411 // we have a failure 412 return false; 413 } finally { 414 releaseConnection(connection); 415 } 416 } 417 418 419 /** 420 * Return the subscription status of this folder. 421 * 422 * @return true if the folder is marked as subscribed, false for 423 * unsubscribed. 424 */ 425 public synchronized boolean isSubscribed() { 426 try { 427 IMAPConnection connection = getConnection(); 428 try { 429 // get the lsub response for this folder. 430 List responses = connection.listSubscribed("", fullname); 431 432 IMAPListResponse response = findListResponse(responses, fullname); 433 if (response == null) { 434 return false; 435 } 436 else { 437 // a NOSELECT flag response indicates the mailbox is no longer 438 // selectable, so it's also no longer subscribed to. 439 return !response.noselect; 440 } 441 } finally { 442 releaseConnection(connection); 443 } 444 } catch (MessagingException e) { 445 // Can't override to throw a MessagingException on this method, so 446 // just swallow any exceptions and assume false is the answer. 447 } 448 return false; 449 } 450 451 452 /** 453 * Set or clear the subscription status of a file. 454 * 455 * @param flag 456 * The new subscription state. 457 */ 458 public synchronized void setSubscribed(boolean flag) throws MessagingException { 459 IMAPConnection connection = getConnection(); 460 try { 461 if (flag) { 462 connection.subscribe(fullname); 463 } 464 else { 465 connection.unsubscribe(fullname); 466 } 467 } finally { 468 releaseConnection(connection); 469 } 470 } 471 472 /** 473 * Check to see if this Folder conatins messages with the {@link Flag.RECENT} flag set. 474 * This can be used when the folder is closed to perform a light-weight check for new mail; 475 * to perform an incremental check for new mail the folder must be opened. 476 * 477 * @return true if the Store has recent messages 478 * @throws MessagingException if there was a problem accessing the store 479 */ 480 public synchronized boolean hasNewMessages() throws MessagingException { 481 // the folder must exist for this to work. 482 checkFolderValidity(); 483 484 // get the freshest status information. 485 refreshStatus(true); 486 // return the indicator from the message state. 487 return recentMessages > 0; 488 } 489 490 /** 491 * Get the Folder determined by the supplied name; if the name is relative 492 * then it is interpreted relative to this folder. This does not check that 493 * the named folder actually exists. 494 * 495 * @param name the name of the folder to return 496 * @return the named folder 497 * @throws MessagingException if there was a problem accessing the store 498 */ 499 public Folder getFolder(String name) throws MessagingException { 500 // this must be a real, valid folder to hold a subfolder 501 checkFolderValidity(); 502 if (!holdsFolders()) { 503 throw new MessagingException("Folder " + fullname + " cannot hold subfolders"); 504 } 505 // our separator does not get determined until we ping the server for it. We 506 // might need to do that now, so we need to use the getSeparator() method to retrieve this. 507 char separator = getSeparator(); 508 509 return new IMAPFolder((IMAPStore)store, fullname + separator + name, separator); 510 } 511 512 513 /** 514 * Delete this folder and possibly any subfolders. This operation can only be 515 * performed on a closed folder. 516 * If recurse is true, then all subfolders are deleted first, then any messages in 517 * this folder are removed and it is finally deleted; {@link FolderEvent#DELETED} 518 * events are sent as appropriate. 519 * If recurse is false, then the behaviour depends on the folder type and store 520 * implementation as followd: 521 * <ul> 522 * <li>If the folder can only conrain messages, then all messages are removed and 523 * then the folder is deleted; a {@link FolderEvent#DELETED} event is sent.</li> 524 * <li>If the folder can onlu contain subfolders, then if it is empty it will be 525 * deleted and a {@link FolderEvent#DELETED} event is sent; if the folder is not 526 * empty then the delete fails and this method returns false.</li> 527 * <li>If the folder can contain both subfolders and messages, then if the folder 528 * does not contain any subfolders, any messages are deleted, the folder itself 529 * is deleted and a {@link FolderEvent#DELETED} event is sent; if the folder does 530 * contain subfolders then the implementation may choose from the following three 531 * behaviors: 532 * <ol> 533 * <li>it may return false indicting the operation failed</li> 534 * <li>it may remove all messages within the folder, send a {@link FolderEvent#DELETED} 535 * event, and then return true to indicate the delete was performed. Note this does 536 * not delete the folder itself and the {@link #exists()} operation for this folder 537 * will return true</li> 538 * <li>it may remove all messages within the folder as per the previous option; in 539 * addition it may change the type of the Folder to only HOLDS_FOLDERS indictaing 540 * that messages may no longer be added</li> 541 * </li> 542 * </ul> 543 * FolderEvents are sent to all listeners registered with this folder or 544 * with the Store. 545 * 546 * @param recurse whether subfolders should be recursively deleted as well 547 * @return true if the delete operation succeeds 548 * @throws MessagingException if there was a problem accessing the store 549 */ 550 public synchronized boolean delete(boolean recurse) throws MessagingException { 551 // we must be in the closed state. 552 checkClosed(); 553 554 // if recursive, get the list of subfolders and delete them first. 555 if (recurse) { 556 557 Folder[] subfolders = list(); 558 for (int i = 0; i < subfolders.length; i++) { 559 // this is a recursive delete also 560 subfolders[i].delete(true); 561 } 562 } 563 564 IMAPConnection connection = getConnection(); 565 try { 566 // delete this one now. 567 connection.deleteMailbox(fullname); 568 // this folder no longer exists on the server. 569 listInfo = null; 570 571 // notify interested parties about the deletion. 572 notifyFolderListeners(FolderEvent.DELETED); 573 return true; 574 575 } catch (MessagingException e) { 576 // ignored 577 } finally { 578 releaseConnection(connection); 579 } 580 return false; 581 } 582 583 584 /** 585 * Rename this folder; the folder must be closed. 586 * If the rename is successfull, a {@link FolderEvent#RENAMED} event is sent to 587 * all listeners registered with this folder or with the store. 588 * 589 * @param newName the new name for this folder 590 * @return true if the rename succeeded 591 * @throws MessagingException if there was a problem accessing the store 592 */ 593 public synchronized boolean renameTo(Folder f) throws MessagingException { 594 // we must be in the closed state. 595 checkClosed(); 596 // but we must also exist 597 checkFolderValidity(); 598 599 IMAPConnection connection = getConnection(); 600 try { 601 // delete this one now. 602 connection.renameMailbox(fullname, f.getFullName()); 603 // we renamed, so get a fresh set of status 604 refreshStatus(false); 605 606 // notify interested parties about the deletion. 607 notifyFolderRenamedListeners(f); 608 return true; 609 } catch (MessagingException e) { 610 // ignored 611 } finally { 612 releaseConnection(connection); 613 } 614 return false; 615 } 616 617 618 /** 619 * Open this folder; the folder must be able to contain messages and 620 * must currently be closed. If the folder is opened successfully then 621 * a {@link ConnectionEvent#OPENED} event is sent to listeners registered 622 * with this Folder. 623 * <p/> 624 * Whether the Store allows multiple connections or if it allows multiple 625 * writers is implementation defined. 626 * 627 * @param mode READ_ONLY or READ_WRITE 628 * @throws MessagingException if there was a problem accessing the store 629 */ 630 public synchronized void open(int mode) throws MessagingException { 631 632 // we use a synchronized block rather than use a synchronized method so that we 633 // can notify the event listeners while not holding the lock. 634 synchronized(this) { 635 // can only be performed on a closed folder 636 checkClosed(); 637 // ask the store to kindly hook us up with a connection. 638 // We're going to hang on to this until we're closed, so store it in 639 // the Folder field. We need to make sure our mailbox is selected while 640 // we're working things. 641 currentConnection = ((IMAPStore)store).getFolderConnection(this); 642 // we need to make ourselves a handler of unsolicited responses 643 currentConnection.addResponseHandler(this); 644 // record our open mode 645 this.mode = mode; 646 647 648 try { 649 // try to open, which gives us a lot of initial mailbox state. 650 IMAPMailboxStatus status = currentConnection.openMailbox(fullname, mode == Folder.READ_ONLY); 651 652 // not available in the requested mode? 653 if (status.mode != mode) { 654 // trying to open READ_WRITE and this isn't available? 655 if (mode == READ_WRITE) { 656 throw new ReadOnlyFolderException(this, "Cannot open READ_ONLY folder in READ_WRITE mode"); 657 } 658 } 659 660 // save this status and when we got it for later updating. 661 cachedStatus = status; 662 // mark when we got this 663 lastStatusTimeStamp = System.currentTimeMillis(); 664 665 // now copy the status information over and flip over the open sign. 666 this.mode = status.mode; 667 maxSequenceNumber = status.messages; 668 recentMessages = status.recentMessages; 669 uidValidity = status.uidValidity; 670 uidNext = status.uidNext; 671 672 availableFlags = status.availableFlags; 673 permanentFlags = status.permanentFlags; 674 675 // create a our caches 676 messages = new LinkedList(); 677 uidCache = new HashMap(); 678 // this is a real pain, but because we need to track updates 679 // to message sequence numbers while the folder is open, the 680 // messages list needs to be populated with Message objects 681 // to keep track of things. The IMAPMessage objects will not 682 // retrieve information from the server until required, so they're 683 // relatively lightweight at this point. 684 populateMessageCache(); 685 686 // we're open for business folks! 687 folderOpen = true; 688 notifyConnectionListeners(ConnectionEvent.OPENED); 689 } finally { 690 // NB: this doesn't really release this, but it does drive 691 // the processing of any unsolicited responses. 692 releaseConnection(currentConnection); 693 } 694 } 695 } 696 697 698 /** 699 * Populate the message cache with dummy messages, ensuring we're filled 700 * up to the server-reported number of messages. 701 * 702 * @exception MessagingException 703 */ 704 protected void populateMessageCache() { 705 // spin through the server-reported number of messages and add a dummy one to 706 // the cache. The message number is assigned from the id counter, the 707 // sequence number is the cache position + 1. 708 for (int i = messages.size(); i < maxSequenceNumber; i++) { 709 messages.add(new IMAPMessage(this, ((IMAPStore)store), nextMessageID++, i+1)); 710 } 711 } 712 713 714 /** 715 * Close this folder; it must already be open. 716 * A @link ConnectionEvent#CLOSED} event is sent to all listeners registered 717 {* 718 * with this folder. 719 * 720 * @param expunge whether to expunge all deleted messages 721 * @throws MessagingException if there was a problem accessing the store; the folder is still closed 722 */ 723 public synchronized void close(boolean expunge) throws MessagingException { 724 // Can only be performed on an open folder 725 checkOpen(); 726 cleanupFolder(expunge, false); 727 } 728 729 730 /** 731 * Do folder cleanup. This is used both for normal 732 * close operations, and adnormal closes where the 733 * server has sent us a BYE message. 734 * 735 * @param expunge Indicates whether open messages should be expunged. 736 * @param disconnected 737 * The disconnected flag. If true, the server has cut 738 * us off, which means our connection can not be returned 739 * to the connection pool. 740 * 741 * @exception MessagingException 742 */ 743 protected void cleanupFolder(boolean expunge, boolean disconnected) throws MessagingException { 744 folderOpen = false; 745 uidCache = null; 746 messages = null; 747 // if we have a connection active at the moment 748 if (currentConnection != null) { 749 // was this a forced disconnect by the server? 750 if (disconnected) { 751 currentConnection.setClosed(); 752 } 753 else { 754 // The CLOSE operation depends on what mode was used to select the mailbox. 755 // If we're open in READ-WRITE mode, we used a SELECT operation. When CLOSE 756 // is issued, any deleted messages will be expunged. If we've been asked not 757 // to expunge the messages, we have a problem. The solution is to reselect the 758 // mailbox using EXAMINE, which will not expunge messages when closed. 759 if (mode == READ_WRITE && !expunge) { 760 // we can ignore the result...we're just switching modes. 761 currentConnection.openMailbox(fullname, true); 762 } 763 764 // have this close the selected mailbox 765 currentConnection.closeMailbox(); 766 } 767 currentConnection.removeResponseHandler(this); 768 // we need to release the connection to the Store once we're closed 769 ((IMAPStore)store).releaseFolderConnection(this, currentConnection); 770 currentConnection = null; 771 } 772 notifyConnectionListeners(ConnectionEvent.CLOSED); 773 } 774 775 776 /** 777 * Tests the open status of the folder. 778 * 779 * @return true if the folder is open, false otherwise. 780 */ 781 public boolean isOpen() { 782 return folderOpen; 783 } 784 785 /** 786 * Get the permanentFlags 787 * 788 * @return The set of permanent flags we support (only SEEN). 789 */ 790 public synchronized Flags getPermanentFlags() { 791 if (permanentFlags != null) { 792 // we need a copy of our master set. 793 return new Flags(permanentFlags); 794 } 795 else { 796 // a null return is expected if not there. 797 return null; 798 } 799 } 800 801 802 /** 803 * Return the number of messages this folder contains. 804 * If this operation is invoked on a closed folder, the implementation 805 * may choose to return -1 to avoid the expense of opening the folder. 806 * 807 * @return the number of messages, or -1 if unknown 808 * @throws MessagingException if there was a problem accessing the store 809 */ 810 public synchronized int getMessageCount() throws MessagingException { 811 checkFolderValidity(); 812 813 // if we haven't opened the folder yet, we might not have good status information. 814 // go request some, which updates the folder fields also. 815 refreshStatus(false); 816 return maxSequenceNumber; 817 } 818 819 /** 820 * Return the numbew of messages in this folder that have the {@link Flag.RECENT} flag set. 821 * If this operation is invoked on a closed folder, the implementation 822 * may choose to return -1 to avoid the expense of opening the folder. 823 * The default implmentation of this method iterates over all messages 824 * in the folder; subclasses should override if possible to provide a more 825 * efficient implementation. 826 * 827 * NB: This is an override of the default Folder implementation, which 828 * examines each of the messages in the folder. IMAP has more efficient 829 * mechanisms for grabbing the information. 830 * 831 * @return the number of new messages, or -1 if unknown 832 * @throws MessagingException if there was a problem accessing the store 833 */ 834 public synchronized int getNewMessageCount() throws MessagingException { 835 // the folder must be a real one for this to work. 836 checkFolderValidity(); 837 // now get current status from the folder 838 refreshStatus(false); 839 // this should be current now. 840 return recentMessages; 841 } 842 843 844 845 /** 846 * Return the number of messages in this folder that do not have the {@link Flag.SEEN} flag set. 847 * If this operation is invoked on a closed folder, the implementation 848 * may choose to return -1 to avoid the expense of opening the folder. 849 * The default implmentation of this method iterates over all messages 850 * in the folder; subclasses should override if possible to provide a more 851 * efficient implementation. 852 * 853 * NB: This is an override of the default Folder implementation, which 854 * examines each of the messages in the folder. IMAP has more efficient 855 * mechanisms for grabbing the information. 856 * 857 * @return the number of new messages, or -1 if unknown 858 * @throws MessagingException if there was a problem accessing the store 859 */ 860 public synchronized int getUnreadMessageCount() throws MessagingException { 861 checkFolderValidity(); 862 // if we haven't opened the folder yet, we might not have good status information. 863 // go request some, which updates the folder fields also. 864 if (!folderOpen) { 865 refreshStatus(false); 866 } 867 else { 868 // if we have an open connection, then search the folder for any messages 869 // marked UNSEEN. 870 871 // UNSEEN is a false test on SEEN using the search criteria. 872 SearchTerm criteria = new FlagTerm(new Flags(Flags.Flag.SEEN), false); 873 874 // ask the store to kindly hook us up with a connection. 875 IMAPConnection connection = getConnection(); 876 try { 877 // search using the connection directly rather than calling our search() method so we don't 878 // need to instantiate each of the matched messages. We're really only interested in the count 879 // right now. 880 int[] matches = connection.searchMailbox(criteria); 881 // update the unseen count. 882 unseenMessages = matches == null ? 0 : matches.length; 883 } finally { 884 releaseConnection(connection); 885 } 886 } 887 // return our current message count. 888 return unseenMessages; 889 } 890 891 892 893 /** 894 * Return the number of messages in this folder that have the {@link Flag.DELETED} flag set. 895 * If this operation is invoked on a closed folder, the implementation 896 * may choose to return -1 to avoid the expense of opening the folder. 897 * The default implmentation of this method iterates over all messages 898 * in the folder; subclasses should override if possible to provide a more 899 * efficient implementation. 900 * 901 * @return the number of new messages, or -1 if unknown 902 * @throws MessagingException if there was a problem accessing the store 903 */ 904 public synchronized int getDeletedMessageCount() throws MessagingException { 905 checkFolderValidity(); 906 907 // if we haven't opened the folder yet, we might not have good status information. 908 // go request some, which updates the folder fields also. 909 if (!folderOpen) { 910 // the status update doesn't return deleted messages. These can only be obtained by 911 // searching an open folder. Just return a bail-out response 912 return -1; 913 } 914 else { 915 // if we have an open connection, then search the folder for any messages 916 // marked DELETED. 917 918 // UNSEEN is a false test on SEEN using the search criteria. 919 SearchTerm criteria = new FlagTerm(new Flags(Flags.Flag.DELETED), true); 920 921 // ask the store to kindly hook us up with a connection. 922 IMAPConnection connection = getConnection(); 923 try { 924 // search using the connection directly rather than calling our search() method so we don't 925 // need to instantiate each of the matched messages. We're really only interested in the count 926 // right now. 927 int[] matches = connection.searchMailbox(criteria); 928 return matches == null ? 0 : matches.length; 929 } finally { 930 releaseConnection(connection); 931 } 932 } 933 } 934 935 936 /** 937 * Retrieve the message with the specified index in this Folder; 938 * messages indices start at 1 not zero. 939 * Clients should note that the index for a specific message may change 940 * if the folder is expunged; {@link Message} objects should be used as 941 * references instead. 942 * 943 * @param msgNum The message sequence number of the target message. 944 * 945 * @return the message 946 * @throws MessagingException 947 * if there was a problem accessing the store 948 */ 949 public synchronized Message getMessage(int msgNum) throws MessagingException { 950 // Can only be performed on an Open folder 951 checkOpen(); 952 // Check the validity of the message number. This may require pinging the server to 953 // see if there are new messages in the folder. 954 checkMessageValidity(msgNum); 955 // ok, if the message number is within range, we should have this in the 956 // messages list. Just return the element. 957 return (Message)messages.get(msgNum - 1); 958 } 959 960 961 /** 962 * Retrieve a range of messages for this folder. 963 * messages indices start at 1 not zero. 964 * 965 * @param start Index of the first message to fetch, inclusive. 966 * @param end Index of the last message to fetch, inclusive. 967 * 968 * @return An array of the fetched messages. 969 * @throws MessagingException 970 * if there was a problem accessing the store 971 */ 972 public synchronized Message[] getMessages(int start, int end) throws MessagingException { 973 // Can only be performed on an Open folder 974 checkOpen(); 975 Message[] messageRange = new Message[end - start + 1]; 976 977 for (int i = 0; i < messageRange.length; i++) { 978 // NB: getMessage() requires values that are origin 1, so there's 979 // no need to adjust the value by other than the start position. 980 messageRange[i] = getMessage(start + i); 981 } 982 return messageRange; 983 } 984 985 986 /** 987 * Append the supplied messages to this folder. A {@link MessageCountEvent} is sent 988 * to all listeners registered with this folder when all messages have been appended. 989 * If the array contains a previously expunged message, it must be re-appended to the Store 990 * and implementations must not abort this operation. 991 * 992 * @param msgs The array of messages to append to the folder. 993 * 994 * @throws MessagingException 995 * if there was a problem accessing the store 996 */ 997 public synchronized void appendMessages(Message[] msgs) throws MessagingException { 998 checkFolderValidity(); 999 for (int i = 0; i < msgs.length; i++) { 1000 Message msg = msgs[i]; 1001 1002 appendMessage(msg); 1003 } 1004 } 1005 1006 /** 1007 * Hint to the store to prefetch information on the supplied messages. 1008 * Subclasses should override this method to provide an efficient implementation; 1009 * the default implementation in this class simply returns. 1010 * 1011 * @param messages messages for which information should be fetched 1012 * @param profile the information to fetch 1013 * @throws MessagingException if there was a problem accessing the store 1014 * @see FetchProfile 1015 */ 1016 public void fetch(Message[] messages, FetchProfile profile) throws MessagingException { 1017 1018 // we might already have the information being requested, so ask each of the 1019 // messages in the list to evaluate itself against the profile. We'll only ask 1020 // the server to send information that's required. 1021 List fetchSet = new ArrayList(); 1022 1023 for (int i = 0; i < messages.length; i++) { 1024 Message msg = messages[i]; 1025 // the message is missing some of the information still. Keep this in the list. 1026 // even if the message is only missing one piece of information, we still fetch everything. 1027 if (((IMAPMessage)msg).evaluateFetch(profile)) { 1028 fetchSet.add(msg); 1029 } 1030 } 1031 1032 // we've got everything already, no sense bothering the server 1033 if (fetchSet.isEmpty()) { 1034 return; 1035 } 1036 // ask the store to kindly hook us up with a connection. 1037 IMAPConnection connection = getConnection(); 1038 try { 1039 // ok, from this point onward, we don't want any threads messing with the 1040 // message cache. A single processed EXPUNGE could make for a very bad day 1041 synchronized(this) { 1042 // get the message set for this 1043 String messageSet = generateMessageSet(fetchSet); 1044 // fetch all of the responses 1045 List responses = connection.fetch(messageSet, profile); 1046 1047 // IMPORTANT: We must do our updates while synchronized to keep the 1048 // cache from getting updated underneath us. This includes 1049 // not releasing the connection until we're done to delay processing any 1050 // pending expunge responses. 1051 for (int i = 0; i < responses.size(); i++) { 1052 IMAPFetchResponse response = (IMAPFetchResponse)responses.get(i); 1053 Message msg = getMessage(response.getSequenceNumber()); 1054 // Belt and Braces. This should never be false. 1055 if (msg != null) { 1056 // have the message apply this to itself. 1057 ((IMAPMessage)msg).updateMessageInformation(response); 1058 } 1059 } 1060 } 1061 } finally { 1062 releaseConnection(connection); 1063 } 1064 return; 1065 } 1066 1067 /** 1068 * Set flags on the messages to the supplied value; all messages must belong to this folder. 1069 * This method may be overridden by subclasses that can optimize the setting 1070 * of flags on multiple messages at once; the default implementation simply calls 1071 * {@link Message#setFlags(Flags, boolean)} for each supplied messages. 1072 * 1073 * @param messages whose flags should be set 1074 * @param flags the set of flags to modify 1075 * @param set Indicates whether the flags should be set or cleared. 1076 * 1077 * @throws MessagingException 1078 * if there was a problem accessing the store 1079 */ 1080 public void setFlags(Message[] messages, Flags flags, boolean set) throws MessagingException { 1081 // this is a list of messages for the change broadcast after the update 1082 List updatedMessages = new ArrayList(); 1083 1084 synchronized(this) { 1085 // the folder must be open and writeable. 1086 checkOpenReadWrite(); 1087 1088 // now make sure these are settable flags. 1089 if (!availableFlags.contains(flags)) 1090 { 1091 throw new MessagingException("The IMAP server does not support changing of this flag set"); 1092 } 1093 1094 // turn this into a set of message numbers 1095 String messageSet = generateMessageSet(messages); 1096 // if all of the messages have been expunged, nothing to do. 1097 if (messageSet == null) { 1098 return; 1099 } 1100 // ask the store to kindly hook us up with a connection. 1101 IMAPConnection connection = getConnection(); 1102 1103 try { 1104 // and have the connection set this 1105 List responses = connection.setFlags(messageSet, flags, set); 1106 // retrieve each of the messages from our cache, and do the flag update. 1107 // we need to keep the list so we can broadcast a change update event 1108 // when we're finished. 1109 for (int i = 0; i < responses.size(); i++) { 1110 IMAPFetchResponse response = (IMAPFetchResponse)responses.get(i); 1111 1112 // get the updated message and update the internal state. 1113 Message message = getMessage(response.sequenceNumber); 1114 // this shouldn't happen, but it might have been expunged too. 1115 if (message != null) { 1116 ((IMAPMessage)message).updateMessageInformation(response); 1117 updatedMessages.add(message); 1118 } 1119 } 1120 } finally { 1121 releaseConnection(connection); 1122 } 1123 } 1124 1125 // ok, we're no longer holding the lock. Now go broadcast the update for each 1126 // of the affected messages. 1127 for (int i = 0; i < updatedMessages.size(); i++) { 1128 Message message = (Message)updatedMessages.get(i); 1129 notifyMessageChangedListeners(MessageChangedEvent.FLAGS_CHANGED, message); 1130 } 1131 } 1132 1133 1134 /** 1135 * Set flags on a range of messages to the supplied value. 1136 * This method may be overridden by subclasses that can optimize the setting 1137 * of flags on multiple messages at once; the default implementation simply 1138 * gets each message and then calls {@link Message#setFlags(Flags, boolean)}. 1139 * 1140 * @param start first message end set 1141 * @param end last message end set 1142 * @param flags the set of flags end modify 1143 * @param value Indicates whether the flags should be set or cleared. 1144 * 1145 * @throws MessagingException 1146 * if there was a problem accessing the store 1147 */ 1148 public synchronized void setFlags(int start, int end, Flags flags, boolean value) throws MessagingException { 1149 Message[] msgs = new Message[end - start + 1]; 1150 1151 for (int i = start; i <= end; i++) { 1152 msgs[i] = getMessage(i); 1153 } 1154 // go do a bulk set operation on these messages 1155 setFlags(msgs, flags, value); 1156 } 1157 1158 /** 1159 * Set flags on a set of messages to the supplied value. 1160 * This method may be overridden by subclasses that can optimize the setting 1161 * of flags on multiple messages at once; the default implementation simply 1162 * gets each message and then calls {@link Message#setFlags(Flags, boolean)}. 1163 * 1164 * @param ids the indexes of the messages to set 1165 * @param flags the set of flags end modify 1166 * @param value Indicates whether the flags should be set or cleared. 1167 * 1168 * @throws MessagingException 1169 * if there was a problem accessing the store 1170 */ 1171 public synchronized void setFlags(int ids[], Flags flags, boolean value) throws MessagingException { 1172 Message[] msgs = new Message[ids.length]; 1173 1174 for (int i = 0; i <ids.length; i++) { 1175 msgs[i] = getMessage(ids[i]); 1176 } 1177 // go do a bulk set operation on these messages 1178 setFlags(msgs, flags, value); 1179 } 1180 1181 1182 /** 1183 * Copy the specified messages to another folder. 1184 * The default implementation simply appends the supplied messages to the 1185 * target folder using {@link #appendMessages(Message[])}. 1186 * @param messages the messages to copy 1187 * @param folder the folder to copy to 1188 * @throws MessagingException if there was a problem accessing the store 1189 */ 1190 public synchronized void copyMessages(Message[] messages, Folder folder) throws MessagingException { 1191 // the default implementation just appends the messages to the target. If 1192 // we're copying between two folders of the same store, we can get the server to 1193 // do most of the work for us without needing to fetch all of the message data. 1194 // If we're dealing with two different Store instances, we need to do this the 1195 // hardway. 1196 if (getStore() != folder.getStore()) { 1197 super.copyMessages(messages, folder); 1198 return; 1199 } 1200 1201 // turn this into a set of message numbers 1202 String messageSet = generateMessageSet(messages); 1203 // if all of the messages have been expunged, nothing to do. 1204 if (messageSet == null) { 1205 return; 1206 } 1207 // ask the store to kindly hook us up with a connection. 1208 IMAPConnection connection = getConnection(); 1209 1210 try { 1211 // ask the server to copy this information over to the other mailbox. 1212 connection.copyMessages(messageSet, folder.getFullName()); 1213 } finally { 1214 releaseConnection(connection); 1215 } 1216 } 1217 1218 1219 1220 /** 1221 * Permanently delete all supplied messages that have the DELETED flag set from the Store. 1222 * The original message indices of all messages actually deleted are returned and a 1223 * {@link MessageCountEvent} event is sent to all listeners with this folder. The expunge 1224 * may cause the indices of all messaged that remain in the folder to change. 1225 * 1226 * @return the original indices of messages that were actually deleted 1227 * @throws MessagingException if there was a problem accessing the store 1228 */ 1229 public synchronized Message[] expunge() throws MessagingException { 1230 // must be open to do this. 1231 checkOpen(); 1232 // and changes need to be allowed 1233 checkReadWrite(); 1234 1235 // ask the store to kindly hook us up with a connection. 1236 IMAPConnection connection = getConnection(); 1237 List expunges = null; 1238 1239 try { 1240 // send the expunge notification. This operation results in "nn EXPUNGE" responses getting returned 1241 // for each expunged messages. These will be dispatched to our response handler, which will process 1242 // the expunge operation. We could process this directly, but we may have received asynchronous 1243 // expunge messages that also marked messages as expunged. 1244 expunges = connection.expungeMailbox(); 1245 } finally { 1246 releaseConnection(connection); 1247 } 1248 1249 // we get one EXPUNGE message for each message that's expunged. They MUST be processed in 1250 // order, as the message sequence numbers represent a relative position that takes into account 1251 // previous expunge operations. For example, if message sequence numbers 5, 6, and 7 are 1252 // expunged, we receive 3 expunge messages, all indicating that message 5 has been expunged. 1253 Message[] messages = new Message[expunges.size()]; 1254 1255 // now we need to protect the internal structures 1256 synchronized (this) { 1257 // expunge all of the messages from the message cache. This keeps the sequence 1258 // numbers up to-date. 1259 for (int i = 0; i < expunges.size(); i++) { 1260 IMAPSizeResponse response = (IMAPSizeResponse)expunges.get(i); 1261 messages[i] = expungeMessage(response.getSize()); 1262 } 1263 } 1264 // if we have messages that have been removed, broadcast the notification. 1265 if (messages.length > 0) { 1266 notifyMessageRemovedListeners(true, messages); 1267 } 1268 1269 // note, we're expected to return an array in all cases, even if the expunged count was zero. 1270 return messages; 1271 } 1272 1273 1274 1275 /** 1276 * Search the supplied messages for those that match the supplied criteria; 1277 * messages must belong to this folder. 1278 * The default implementation iterates through the messages, returning those 1279 * whose {@link Message#match(javax.mail.search.SearchTerm)} method returns true; 1280 * subclasses may provide a more efficient implementation. 1281 * 1282 * @param term the search criteria 1283 * @param messages the messages to search 1284 * @return an array containing messages that match the criteria 1285 * @throws MessagingException if there was a problem accessing the store 1286 */ 1287 public synchronized Message[] search(SearchTerm term) throws MessagingException { 1288 // only allowed on open folders 1289 checkOpen(); 1290 1291 // ask the store to kindly hook us up with a connection. 1292 IMAPConnection connection = getConnection(); 1293 1294 try { 1295 // just search everything 1296 int[] messageNumbers = connection.searchMailbox(term); 1297 return resolveMessages(messageNumbers); 1298 } finally { 1299 releaseConnection(connection); 1300 } 1301 } 1302 1303 1304 1305 /** 1306 * Search the supplied messages for those that match the supplied criteria; 1307 * messages must belong to this folder. 1308 * The default implementation iterates through the messages, returning those 1309 * whose {@link Message#match(javax.mail.search.SearchTerm)} method returns true; 1310 * subclasses may provide a more efficient implementation. 1311 * 1312 * @param term the search criteria 1313 * @param messages the messages to search 1314 * @return an array containing messages that match the criteria 1315 * @throws MessagingException if there was a problem accessing the store 1316 */ 1317 public synchronized Message[] search(SearchTerm term, Message[] messages) throws MessagingException { 1318 // only allowed on open folders 1319 checkOpen(); 1320 1321 // turn this into a string specifier for these messages. We'll weed out the expunged messages first. 1322 String messageSet = generateMessageSet(messages); 1323 1324 // If we have no "live" messages to search, just return now. We're required to return a non-null 1325 // value, so give an empy array back. 1326 if (messageSet == null) { 1327 return new Message[0]; 1328 } 1329 1330 // ask the store to kindly hook us up with a connection. 1331 IMAPConnection connection = getConnection(); 1332 1333 try { 1334 1335 // now go do the search. 1336 int[] messageNumbers = connection.searchMailbox(messageSet, term); 1337 return resolveMessages(messageNumbers); 1338 } finally { 1339 releaseConnection(connection); 1340 } 1341 } 1342 1343 /** 1344 * Get the UID validity value for this Folder. 1345 * 1346 * @return The current UID validity value, as a long. 1347 * @exception MessagingException 1348 */ 1349 public synchronized long getUIDValidity() throws MessagingException 1350 { 1351 // get the latest status to make sure we have the 1352 // most current. 1353 refreshStatus(true); 1354 return uidValidity; 1355 } 1356 1357 /** 1358 * Retrieve a message using the UID rather than the 1359 * message sequence number. Returns null if the message 1360 * doesn't exist. 1361 * 1362 * @param uid The target UID. 1363 * 1364 * @return the Message object. Returns null if the message does 1365 * not exist. 1366 * @exception MessagingException 1367 */ 1368 public synchronized Message getMessageByUID(long uid) throws MessagingException 1369 { 1370 // only allowed on open folders 1371 checkOpen(); 1372 1373 Long key = new Long(uid); 1374 // first check to see if we have a cached value for this 1375 synchronized(messages) { 1376 Message msg = (Message)uidCache.get(key); 1377 if (msg != null) { 1378 return msg; 1379 } 1380 } 1381 1382 // ask the store to kindly hook us up with a connection. 1383 IMAPConnection connection = getConnection(); 1384 1385 try { 1386 // locate the message identifier 1387 IMAPUid imapuid = connection.getSequenceNumberForUid(uid); 1388 // if nothing is returned, the message doesn't exist 1389 if (imapuid == null) { 1390 return null; 1391 } 1392 1393 1394 // retrieve the actual message object and place this in the UID cache 1395 return retrieveMessageByUid(key, imapuid.messageNumber); 1396 } finally { 1397 releaseConnection(connection); 1398 } 1399 } 1400 1401 /** 1402 * Get a series of messages using a UID range. The 1403 * special value LASTUID can be used to mark the 1404 * last available message. 1405 * 1406 * @param start The start of the UID range. 1407 * @param end The end of the UID range. The special value 1408 * LASTUID can be used to request all messages up 1409 * to the last UID. 1410 * 1411 * @return An array containing all of the messages in the 1412 * range. 1413 * @exception MessagingException 1414 */ 1415 public synchronized Message[] getMessagesByUID(long start, long end) throws MessagingException 1416 { 1417 // only allowed on open folders 1418 checkOpen(); 1419 // ask the store to kindly hook us up with a connection. 1420 IMAPConnection connection = getConnection(); 1421 1422 try { 1423 // locate the message identifier 1424 List uids = connection.getSequenceNumbersForUids(start, end); 1425 Message[] msgs = new Message[uids.size()]; 1426 1427 // fill in each of the messages based on the returned value 1428 for (int i = 0; i < msgs.length; i++) { 1429 IMAPUid uid = (IMAPUid)uids.get(i); 1430 msgs[i] = retrieveMessageByUid(new Long(uid.uid), uid.messageNumber); 1431 } 1432 1433 return msgs; 1434 } finally { 1435 releaseConnection(connection); 1436 } 1437 1438 1439 } 1440 1441 /** 1442 * Retrieve a set of messages by explicit UIDs. If 1443 * any message in the list does not exist, null 1444 * will be returned for the corresponding item. 1445 * 1446 * @param ids An array of UID values to be retrieved. 1447 * 1448 * @return An array of Message items the same size as the ids 1449 * argument array. This array will contain null 1450 * entries for any UIDs that do not exist. 1451 * @exception MessagingException 1452 */ 1453 public synchronized Message[] getMessagesByUID(long[] ids) throws MessagingException 1454 { 1455 // only allowed on open folders 1456 checkOpen(); 1457 1458 Message[] msgs = new Message[ids.length]; 1459 1460 for (int i = 0; i < msgs.length; i++) { 1461 msgs[i] = getMessageByUID(ids[i]); 1462 } 1463 1464 return msgs; 1465 } 1466 1467 /** 1468 * Retrieve the UID for a message from this Folder. 1469 * The argument Message MUST belong to this Folder 1470 * instance, otherwise a NoSuchElementException will 1471 * be thrown. 1472 * 1473 * @param message The target message. 1474 * 1475 * @return The UID associated with this message. 1476 * @exception MessagingException 1477 */ 1478 public synchronized long getUID(Message message) throws MessagingException 1479 { 1480 // verify this actually is in this folder. 1481 checkMessageFolder(message); 1482 IMAPMessage msg = (IMAPMessage)message; 1483 1484 // we might already know this bit of information 1485 if (msg.getUID() != -1) { 1486 return msg.getUID(); 1487 } 1488 1489 // ask the store to kindly hook us up with a connection. 1490 IMAPConnection connection = getConnection(); 1491 1492 try { 1493 // locate the message identifier 1494 IMAPUid imapuid = connection.getUidForSequenceNumber(msg.getMessageNumber()); 1495 // if nothing is returned, the message doesn't exist 1496 if (imapuid == null) { 1497 return -1; 1498 } 1499 // cache this information now that we've gotten it. 1500 addToUidCache(new Long(imapuid.uid), getMessage(imapuid.messageNumber)); 1501 // return the UID information. 1502 return imapuid.uid; 1503 } finally { 1504 releaseConnection(connection); 1505 } 1506 } 1507 1508 /** 1509 * Retrieve a message from a UID/message mapping. 1510 * 1511 * @param key The UID key used for the mapping. 1512 * @param msgNumber The message sequence number. 1513 * 1514 * @return The Message object corresponding to the message. 1515 * @exception MessagingException 1516 */ 1517 protected synchronized Message retrieveMessageByUid(Long key, int msgNumber) throws MessagingException 1518 { 1519 synchronized (messages) { 1520 // first check the cache...this might have already been added. 1521 Message msg = (Message)uidCache.get(key); 1522 if (msg != null) { 1523 return msg; 1524 } 1525 1526 // retrieve the message by sequence number 1527 msg = getMessage(msgNumber); 1528 // add this to our UID mapping cache. 1529 addToUidCache(key, msg); 1530 return msg; 1531 } 1532 } 1533 1534 1535 /** 1536 * Add a message to the UID mapping cache, ensuring that 1537 * the UID value is updated. 1538 * 1539 * @param key The UID key. 1540 * @param msg The message to add. 1541 */ 1542 protected void addToUidCache(Long key, Message msg) { 1543 synchronized (messages) { 1544 ((IMAPMessage)msg).setUID(key.longValue()); 1545 uidCache.put(key, msg); 1546 } 1547 } 1548 1549 1550 /** 1551 * Append a single message to the IMAP Folder. 1552 * 1553 * @param msg The message to append. 1554 * 1555 * @exception MessagingException 1556 */ 1557 protected synchronized void appendMessage(Message msg) throws MessagingException 1558 { 1559 // sort out the dates. If no received date, use the sent date. 1560 Date date = msg.getReceivedDate(); 1561 if (date == null) { 1562 date = msg.getSentDate(); 1563 } 1564 1565 Flags flags = msg.getFlags(); 1566 1567 // convert the message into an array of bytes we can attach as a literal. 1568 ByteArrayOutputStream out = new ByteArrayOutputStream(); 1569 1570 try { 1571 msg.writeTo(out); 1572 } catch (IOException e) { 1573 } 1574 1575 // now issue the append command 1576 IMAPConnection connection = getConnection(); 1577 try { 1578 connection.appendMessage(getFullName(), date, flags, out.toByteArray()); 1579 } finally { 1580 releaseConnection(connection); 1581 } 1582 } 1583 1584 1585 /** 1586 * Retrieve the list of matching groups from the IMAP server using the LIST 1587 * or LSUB command. The server does the wildcard matching for us. 1588 * 1589 * @param pattern 1590 * The pattern string (in wildmat format) used to match. 1591 * 1592 * @return An array of folders for the matching groups. 1593 */ 1594 protected synchronized Folder[] filterFolders(String pattern, boolean subscribed) throws MessagingException { 1595 IMAPConnection connection = getConnection(); 1596 // this is used to filter out our own folder from the search 1597 String root = fullname + getSeparator(); 1598 1599 List responses = null; 1600 try { 1601 1602 1603 if (subscribed) { 1604 // get the lsub response for this folder. 1605 responses = connection.listSubscribed(root, pattern); 1606 } 1607 else { 1608 // grab using the LIST command. 1609 responses = connection.list(root, pattern); 1610 } 1611 } finally { 1612 releaseConnection(connection); 1613 } 1614 1615 List folders = new ArrayList(); 1616 1617 for (int i = 0; i < responses.size(); i++) { 1618 IMAPListResponse response = (IMAPListResponse)responses.get(i); 1619 // if a full wildcard is specified, the root folder can be returned too. Make sure we 1620 // filter that one out. 1621 if (!response.mailboxName.equals(root)) { 1622 IMAPFolder folder = new IMAPFolder((IMAPStore)store, response.mailboxName, response.separator); 1623 folders.add(folder); 1624 } 1625 } 1626 1627 // convert into an array and return 1628 return (Folder[])folders.toArray(new Folder[folders.size()]); 1629 } 1630 1631 1632 /** 1633 * Test if a folder can hold sub folders. 1634 * 1635 * @return True if the folder is allowed to have subfolders. 1636 */ 1637 protected synchronized boolean holdsFolders() throws MessagingException { 1638 checkFolderValidity(); 1639 return (folderType & HOLDS_FOLDERS) != 0; 1640 } 1641 1642 1643 /** 1644 * Validate that a target message number is considered valid 1645 * by the IMAP server. If outside of the range we currently 1646 * are a ware of, we'll ping the IMAP server to see if there 1647 * have been any updates. 1648 * 1649 * @param messageNumber 1650 * The message number we're checking. 1651 * 1652 * @exception MessagingException 1653 */ 1654 protected void checkMessageValidity(int messageNumber) throws MessagingException { 1655 // lower range for a message is 1. 1656 if (messageNumber < 1) { 1657 throw new MessagingException("Invalid message number for IMAP folder: " + messageNumber); 1658 } 1659 // if within our current known range, we'll accept this 1660 if (messageNumber <= maxSequenceNumber) { 1661 return; 1662 } 1663 1664 IMAPConnection connection = getConnection(); 1665 1666 synchronized (this) { 1667 try { 1668 // ping the server to see if there's any updates to process. The updates are handled 1669 // by the response handlers. 1670 connection.updateMailboxStatus(); 1671 } finally { 1672 releaseConnection(connection); 1673 } 1674 } 1675 1676 // still out of range? 1677 if (messageNumber > maxSequenceNumber) { 1678 throw new MessagingException("Message " + messageNumber + " does not exist on server"); 1679 } 1680 } 1681 1682 1683 /** 1684 * Below is a list of convenience methods that avoid repeated checking for a 1685 * value and throwing an exception 1686 */ 1687 1688 /** 1689 * Ensure the folder is open. Throws a MessagingException 1690 * if not in the correct state for the operation. 1691 * 1692 * @exception IllegalStateException 1693 */ 1694 protected void checkOpen() throws IllegalStateException { 1695 if (!folderOpen){ 1696 throw new IllegalStateException("Folder is not Open"); 1697 } 1698 } 1699 1700 /** 1701 * Ensure the folder is not open for operations 1702 * that require the folder to be closed. 1703 * 1704 * @exception IllegalStateException 1705 */ 1706 protected void checkClosed() throws IllegalStateException { 1707 if (folderOpen){ 1708 throw new IllegalStateException("Folder is Open"); 1709 } 1710 } 1711 1712 /** 1713 * Ensure that the folder is open for read/write mode before doing 1714 * an operation that would make a change. 1715 * 1716 * @exception IllegalStateException 1717 */ 1718 protected void checkReadWrite() throws IllegalStateException { 1719 if (mode != READ_WRITE) { 1720 throw new IllegalStateException("Folder is opened READY_ONLY"); 1721 } 1722 } 1723 1724 1725 /** 1726 * Check that the folder is open and in read/write mode. 1727 * 1728 * @exception IllegalStateException 1729 */ 1730 protected void checkOpenReadWrite() throws IllegalStateException { 1731 checkOpen(); 1732 checkReadWrite(); 1733 } 1734 1735 1736 1737 /** 1738 * Notify the message changed listeners that a 1739 * message contained in the folder has been updated. 1740 * 1741 * @param type The type of update made to the message. 1742 * @param m The message that was updated. 1743 * 1744 * @see javax.mail.Folder#notifyMessageChangedListeners(int, javax.mail.Message) 1745 */ 1746 public void notifyMessageChangedListeners(int type, Message m) { 1747 super.notifyMessageChangedListeners(type, m); 1748 } 1749 1750 1751 /** 1752 * Retrieve the connection attached to this folder. Throws an 1753 * exception if we don't have an active connection. 1754 * 1755 * @return The current connection object. 1756 * @exception MessagingException 1757 */ 1758 protected synchronized IMAPConnection getConnection() throws MessagingException { 1759 // don't have an open connection yet? Just request a pool connection. 1760 if (currentConnection == null) { 1761 // request a connection from the central store. 1762 IMAPConnection connection = ((IMAPStore)store).getFolderConnection(this); 1763 // we need to make ourselves a handler of unsolicited responses 1764 connection.addResponseHandler(this); 1765 return connection; 1766 } 1767 // we have a connection for our use. Just return it. 1768 return currentConnection; 1769 } 1770 1771 1772 /** 1773 * Release our connection back to the Store. 1774 * 1775 * @param connection The connection to release. 1776 * 1777 * @exception MessagingException 1778 */ 1779 protected void releaseConnection(IMAPConnection connection) throws MessagingException { 1780 // This is a bit of a pain. We need to delay processing of the 1781 // unsolicited responses until after each user of the connection has 1782 // finished processing the expected responses. We need to do this because 1783 // the unsolicited responses may include EXPUNGED messages. The EXPUNGED 1784 // messages will alter the message sequence numbers for the messages in the 1785 // cache. Processing the EXPUNGED messages too early will result in 1786 // updates getting applied to the wrong message instances. So, as a result, 1787 // we delay that stage of the processing until all expected responses have 1788 // been handled. 1789 1790 // process any pending messages before returning. 1791 connection.processPendingResponses(); 1792 // if no cached connection or this is somehow different from the cached one, just 1793 // return it. 1794 if (currentConnection == null || connection != currentConnection) { 1795 connection.removeResponseHandler(this); 1796 ((IMAPStore)store).releaseFolderConnection(this, connection); 1797 } 1798 // if we're open, then we don't have to worry about returning this connection 1799 // to the Store. This is set up perfectly for our use right now. 1800 } 1801 1802 1803 /** 1804 * Obtain a connection object for a Message attached to this Folder. This 1805 * will be the Folder's connection, which is only available if the Folder 1806 * is currently open. 1807 * 1808 * @return The connection object for the Message instance to use. 1809 * @exception MessagingException 1810 */ 1811 synchronized IMAPConnection getMessageConnection() throws MessagingException { 1812 // if we're not open, the messages can't communicate either 1813 if (currentConnection == null) { 1814 throw new FolderClosedException(this, "No Folder connections available"); 1815 } 1816 // return the current Folder connection. At this point, we'll be sharing the 1817 // connection between the Folder and the Message (and potentially, other messages). The 1818 // command operations on the connection are synchronized so only a single command can be 1819 // issued at one time. 1820 return currentConnection; 1821 } 1822 1823 1824 /** 1825 * Release the connection object back to the Folder instance. 1826 * 1827 * @param connection The connection being released. 1828 * 1829 * @exception MessagingException 1830 */ 1831 void releaseMessageConnection(IMAPConnection connection) throws MessagingException { 1832 // release it back to ourselves...this will drive unsolicited message processing. 1833 releaseConnection(connection); 1834 } 1835 1836 1837 /** 1838 * Refresh the status information on this folder. 1839 * 1840 * @param force Force a status refresh always. 1841 * 1842 * @exception MessagingException 1843 */ 1844 protected void refreshStatus(boolean force) throws MessagingException { 1845 // first check that any cached status we've received has gotten a little moldy. 1846 if (cachedStatus != null) { 1847 // if not forcing, check the time out. 1848 if (!force) { 1849 if (statusCacheTimeout > 0) { 1850 long age = System.currentTimeMillis() - lastStatusTimeStamp; 1851 if (age < statusCacheTimeout) { 1852 return; 1853 } 1854 } 1855 } 1856 // make sure the stale information is cleared out. 1857 cachedStatus = null; 1858 } 1859 1860 IMAPConnection connection = getConnection(); 1861 try { 1862 // ping the server for the list information for this folder 1863 cachedStatus = connection.getMailboxStatus(fullname); 1864 // mark when we got this 1865 lastStatusTimeStamp = System.currentTimeMillis(); 1866 } finally { 1867 releaseConnection(connection); 1868 } 1869 1870 // refresh the internal state from the message information 1871 maxSequenceNumber = cachedStatus.messages; 1872 recentMessages = cachedStatus.recentMessages; 1873 unseenMessages = cachedStatus.unseenMessages; 1874 uidValidity = cachedStatus.uidValidity; 1875 } 1876 1877 1878 /** 1879 * Process an EXPUNGE response for a message, removing the 1880 * message from the message cache. 1881 * 1882 * @param sequenceNumber 1883 * The sequence number for the expunged message. 1884 * 1885 * @return The Message object corresponding to this expunged 1886 * message. 1887 * @exception MessagingException 1888 */ 1889 protected synchronized Message expungeMessage(int sequenceNumber) throws MessagingException { 1890 // we need to remove this from our message list. Deletion of a message by 1891 // sequence number shifts the sequence numbers of every message after 1892 // the target. So we, need to remove the message, update its status, then 1893 // update the sequence numbers of every message after that. This is a serious 1894 // pain! 1895 1896 Message expungedMessage = (Message)messages.remove(sequenceNumber - 1); 1897 // mark the message as expunged. 1898 ((IMAPMessage)expungedMessage).setExpunged(true); 1899 1900 // update the message sequence numbers for every message after the 1901 // expunged one. NB. The sequence number is the cache index + 1 1902 for (int i = sequenceNumber - 1; i < messages.size(); i++) { 1903 IMAPMessage message = (IMAPMessage)messages.get(i); 1904 message.setSequenceNumber(i + 1); 1905 } 1906 1907 // have we retrieved a UID for this message? If we have, then it's in the UID cache and 1908 // needs removal from there also 1909 long uid = ((IMAPMessage)expungedMessage).getUID(); 1910 if (uid >= 0) { 1911 uidCache.remove(new Long(uid)); 1912 } 1913 1914 // adjust the message count downward 1915 maxSequenceNumber--; 1916 return expungedMessage; 1917 } 1918 1919 1920 /** 1921 * Resolve an array of message numbers into an array of the 1922 * referenced messages. 1923 * 1924 * @param messageNumbers 1925 * The array of message numbers (can be null). 1926 * 1927 * @return An array of Message[] containing the resolved messages from 1928 * the list. Returns a zero-length array if there are no 1929 * messages to resolve. 1930 * @exception MessagingException 1931 */ 1932 protected Message[] resolveMessages(int[] messageNumbers) throws MessagingException { 1933 // the connection search returns a null pointer if nothing was found, just convert this into a 1934 // null array. 1935 if (messageNumbers == null) { 1936 return new Message[0]; 1937 } 1938 1939 Message[] messages = new Message[messageNumbers.length]; 1940 1941 // retrieve each of the message numbers in turn. 1942 for (int i = 0; i < messageNumbers.length; i++) { 1943 messages[i] = getMessage(messageNumbers[i]); 1944 } 1945 1946 return messages; 1947 } 1948 1949 /** 1950 * Generate a message set string from a List of messages rather than an 1951 * array. 1952 * 1953 * @param messages The List of messages. 1954 * 1955 * @return The evaluated message set string. 1956 * @exception MessagingException 1957 */ 1958 protected String generateMessageSet(List messages) throws MessagingException { 1959 Message[] msgs = (Message[])messages.toArray(new Message[messages.size()]); 1960 return generateMessageSet(msgs); 1961 } 1962 1963 1964 /** 1965 * Take an array of messages and generate a String <message set> 1966 * argument as specified by RFC 2060. The message set argument 1967 * is a comma-separated list of message number ranges. A 1968 * single element range is just one number. A longer range is 1969 * a pair of numbers separated by a ":". The generated string 1970 * should not have any blanks. This will attempt to locate 1971 * consequetive ranges of message numbers, but will only do this 1972 * for messages that are already ordered in the array (i.e., we 1973 * don't try to sort). Expunged messages are excluded from the 1974 * search, since they don't exist anymore. A valid search string 1975 * will look something like this: 1976 * 1977 * "3,6:10,15,21:35" 1978 * 1979 * @param messages The array of messages we generate from. 1980 * 1981 * @return A string formatted version of these message identifiers that 1982 * can be used on an IMAP command. 1983 */ 1984 protected String generateMessageSet(Message[] messages) throws MessagingException { 1985 StringBuffer set = new StringBuffer(); 1986 1987 for (int i = 0; i < messages.length; i++) { 1988 // first scan the list looking for a "live" message. 1989 IMAPMessage start = (IMAPMessage)messages[i]; 1990 if (!start.isExpunged()) { 1991 1992 // we can go ahead and add this to the list now. If we find this is the start of a 1993 // range, we'll tack on the ":end" bit once we find the last message in the range. 1994 if (set.length() != 0) { 1995 // only append the comma if not the first element of the list 1996 set.append(','); 1997 } 1998 1999 // append the first number. NOTE: We append this directly rather than 2000 // use appendInteger(), which appends it using atom rules. 2001 set.append(Integer.toString(start.getSequenceNumber())); 2002 2003 // ok, we have a live one. Now scan the list from here looking for the end of 2004 // a range of consequetive messages. 2005 int endIndex = -1; ; 2006 // get the number we're checking against. 2007 int previousSequence = start.getSequenceNumber(); 2008 for (int j = i + 1; j < messages.length; j++) { 2009 IMAPMessage message = (IMAPMessage)messages[j]; 2010 if (!message.isExpunged()) { 2011 // still consequetive? 2012 if (message.getSequenceNumber() == previousSequence + 1) { 2013 // step this for the next check. 2014 previousSequence++; 2015 // record this as the current end of the range. 2016 endIndex = j; 2017 } 2018 else { 2019 // found a non-consequetive one, stop here 2020 break; 2021 } 2022 } 2023 } 2024 2025 // have a range end point? Add the range specifier and step the loop index point 2026 // to skip over this 2027 if (endIndex != -1) { 2028 // pick up the scan at the next location 2029 i = endIndex; 2030 2031 set.append(':'); 2032 set.append(Integer.toString(((IMAPMessage)messages[endIndex]).getSequenceNumber())); 2033 } 2034 } 2035 } 2036 2037 // return null for an empty list. This is possible because either an empty array has been handed to 2038 // us or all of the messages in the array have been expunged. 2039 if (set.length() == 0) { 2040 return null; 2041 } 2042 return set.toString(); 2043 } 2044 2045 /** 2046 * Verify that this folder exists on the server before 2047 * performning an operation that requires a valid 2048 * Folder instance. 2049 * 2050 * @exception MessagingException 2051 */ 2052 protected void checkFolderValidity() throws MessagingException { 2053 // if we are holding a current listinfo response, then 2054 // we have chached existance information. In that case, 2055 // all of our status is presumed up-to-date and we can go 2056 // with that. If we don't have the information, then we 2057 // ping the server for it. 2058 if (listInfo == null && !exists()) { 2059 throw new FolderNotFoundException(this, "Folder " + fullname + " not found on server"); 2060 } 2061 } 2062 2063 2064 /** 2065 * Check if a Message is properly within the target 2066 * folder. 2067 * 2068 * @param msg The message we're checking. 2069 * 2070 * @exception MessagingException 2071 */ 2072 protected void checkMessageFolder(Message msg) throws MessagingException { 2073 if (msg.getFolder() != this) { 2074 throw new NoSuchElementException("Message is not within the target Folder"); 2075 } 2076 } 2077 2078 2079 /** 2080 * Search a list of LIST responses for one containing information 2081 * for a particular mailbox name. 2082 * 2083 * @param responses The list of responses. 2084 * @param name The desired mailbox name. 2085 * 2086 * @return The IMAPListResponse information for the requested name. 2087 */ 2088 protected IMAPListResponse findListResponse(List responses, String name) { 2089 for (int i = 0; i < responses.size(); i++) { 2090 IMAPListResponse response = (IMAPListResponse)responses.get(i); 2091 if (response.mailboxName.equals(name)) { 2092 return response; 2093 } 2094 } 2095 return null; 2096 } 2097 2098 2099 /** 2100 * Protected class intended for subclass overrides. For normal folders, 2101 * the mailbox name is fullname. For Namespace root folders, the mailbox 2102 * name is the prefix + separator. 2103 * 2104 * @return The string name to use as the mailbox name for exists() and issubscribed() 2105 * calls. 2106 */ 2107 protected String getMailBoxName() { 2108 return fullname; 2109 } 2110 2111 /** 2112 * Handle an unsolicited response from the server. Most unsolicited responses 2113 * are replies to specific commands sent to the server. The remainder must 2114 * be handled by the Store or the Folder using the connection. These are 2115 * critical to handle, as events such as expunged messages will alter the 2116 * sequence numbers of the live messages. We need to keep things in sync. 2117 * 2118 * @param response The UntaggedResponse to process. 2119 * 2120 * @return true if we handled this response and no further handling is required. false 2121 * means this one wasn't one of ours. 2122 */ 2123 public boolean handleResponse(IMAPUntaggedResponse response) { 2124 // "you've got mail". The message count has been updated. There 2125 // are two posibilities. Either there really are new messages, or 2126 // this is an update following an expunge. If there are new messages, 2127 // we need to update the message cache and broadcast the change to 2128 // any listeners. 2129 if (response.isKeyword("EXISTS")) { 2130 // we need to update our cache, and also retrieve the new messages and 2131 // send them out in a broadcast update. 2132 int oldCount = maxSequenceNumber; 2133 maxSequenceNumber = ((IMAPSizeResponse)response).getSize(); 2134 populateMessageCache(); 2135 // has the size grown? We have to send the "you've got mail" announcement. 2136 if (oldCount < maxSequenceNumber) { 2137 try { 2138 Message[] messages = getMessages(oldCount + 1, maxSequenceNumber); 2139 notifyMessageAddedListeners(messages); 2140 } catch (MessagingException e) { 2141 // should never happen in this context 2142 } 2143 } 2144 return true; 2145 } 2146 // "you had mail". A message was expunged from the server. This MUST 2147 // be processed immediately, as any subsequent expunge messages will 2148 // shift the message numbers as a result of previous messages getting 2149 // removed. We need to keep our internal cache in sync with the server. 2150 else if (response.isKeyword("EXPUNGE")) { 2151 int messageNumber = ((IMAPSizeResponse)response).getSize(); 2152 try { 2153 Message message = expungeMessage(messageNumber); 2154 2155 // broadcast the message update. 2156 notifyMessageRemovedListeners(false, new Message[] {message}); 2157 } catch (MessagingException e) { 2158 } 2159 // we handled this one. 2160 return true; 2161 } 2162 // just an update of recently arrived stuff? Just update the field. 2163 else if (response.isKeyword("RECENT")) { 2164 recentMessages = ((IMAPSizeResponse)response).getSize(); 2165 return true; 2166 } 2167 // The spec is not particularly clear what types of unsolicited 2168 // FETCH response can be sent. The only one that is specifically 2169 // spelled out is flag updates. If this is one of those, then 2170 // handle it. 2171 else if (response.isKeyword("FETCH")) { 2172 IMAPFetchResponse fetch = (IMAPFetchResponse)response; 2173 IMAPFlags flags = (IMAPFlags)fetch.getDataItem(IMAPFetchDataItem.FLAGS); 2174 // if this is a flags response, get the message and update 2175 if (flags != null) { 2176 try { 2177 // get the updated message and update the internal state. 2178 IMAPMessage message = (IMAPMessage)getMessage(fetch.sequenceNumber); 2179 // this shouldn't happen, but it might have been expunged too. 2180 if (message != null) { 2181 message.updateMessageInformation(fetch); 2182 } 2183 notifyMessageChangedListeners(MessageChangedEvent.FLAGS_CHANGED, message); 2184 } catch (MessagingException e) { 2185 } 2186 return true; 2187 } 2188 } 2189 // this is a BYE response on our connection. This forces us to close, but 2190 // when we return the connection, the pool needs to get rid of it. 2191 else if (response.isKeyword("BYE")) { 2192 // this is essentially a close event. We need to clean everything up 2193 // and make sure our connection is not returned to the general pool. 2194 try { 2195 cleanupFolder(false, true); 2196 } catch (MessagingException e) { 2197 } 2198 return true; 2199 } 2200 2201 // not a response the folder knows how to deal with. 2202 return false; 2203 } 2204 2205 // The following set of methods are extensions that exist in the Sun implementation. They 2206 // match the Sun version in intent, but are not 100% compatible because the Sun implementation 2207 // uses com.sun.* class instances as opposed to the org.apache.geronimo.* classes. 2208 2209 2210 2211 /** 2212 * Remove an entry from the access control list for this folder. 2213 * 2214 * @param acl The ACL element to remove. 2215 * 2216 * @exception MessagingException 2217 */ 2218 public synchronized void removeACL(ACL acl) throws MessagingException { 2219 // ask the store to kindly hook us up with a connection. 2220 IMAPConnection connection = getConnection(); 2221 2222 try { 2223 // the connection does the heavy lifting 2224 connection.removeACLRights(fullname, acl); 2225 } finally { 2226 releaseConnection(connection); 2227 } 2228 } 2229 2230 2231 /** 2232 * Add an entry to the access control list for this folder. 2233 * 2234 * @param acl The new ACL to add. 2235 */ 2236 public synchronized void addACL(ACL acl) throws MessagingException { 2237 // ask the store to kindly hook us up with a connection. 2238 IMAPConnection connection = getConnection(); 2239 2240 try { 2241 // the connection does the heavy lifting 2242 connection.setACLRights(fullname, acl); 2243 } finally { 2244 releaseConnection(connection); 2245 } 2246 } 2247 2248 2249 /** 2250 * Add Rights to a given ACL entry. 2251 * 2252 * @param acl The target ACL to update. 2253 * 2254 * @exception MessagingException 2255 */ 2256 public synchronized void addRights(ACL acl) throws MessagingException { 2257 // ask the store to kindly hook us up with a connection. 2258 IMAPConnection connection = getConnection(); 2259 2260 try { 2261 // the connection does the heavy lifting 2262 connection.addACLRights(fullname, acl); 2263 } finally { 2264 releaseConnection(connection); 2265 } 2266 } 2267 2268 2269 /** 2270 * Remove ACL Rights from a folder. 2271 * 2272 * @param acl The ACL describing the Rights to remove. 2273 * 2274 * @exception MessagingException 2275 */ 2276 public synchronized void removeRights(ACL acl) throws MessagingException { 2277 // ask the store to kindly hook us up with a connection. 2278 IMAPConnection connection = getConnection(); 2279 2280 try { 2281 // the connection does the heavy lifting 2282 connection.removeACLRights(fullname, acl); 2283 } finally { 2284 releaseConnection(connection); 2285 } 2286 } 2287 2288 2289 /** 2290 * List the rights associated with a given name. 2291 * 2292 * @param name The user name for the Rights. 2293 * 2294 * @return The set of Rights associated with the user name. 2295 * @exception MessagingException 2296 */ 2297 public synchronized Rights[] listRights(String name) throws MessagingException { 2298 // ask the store to kindly hook us up with a connection. 2299 IMAPConnection connection = getConnection(); 2300 2301 try { 2302 // the connection does the heavy lifting 2303 return connection.listACLRights(fullname, name); 2304 } finally { 2305 releaseConnection(connection); 2306 } 2307 } 2308 2309 2310 /** 2311 * List the rights for the currently authenticated user. 2312 * 2313 * @return The set of Rights for the current user. 2314 * @exception MessagingException 2315 */ 2316 public synchronized Rights myRights() throws MessagingException { 2317 // ask the store to kindly hook us up with a connection. 2318 IMAPConnection connection = getConnection(); 2319 2320 try { 2321 // the connection does the heavy lifting 2322 return connection.getMyRights(fullname); 2323 } finally { 2324 releaseConnection(connection); 2325 } 2326 } 2327 2328 /** 2329 * Get the quota values assigned to the current folder. 2330 * 2331 * @return The Quota information for the folder. 2332 * @exception MessagingException 2333 */ 2334 public synchronized Quota[] getQuota() throws MessagingException { 2335 // ask the store to kindly hook us up with a connection. 2336 IMAPConnection connection = getConnection(); 2337 2338 try { 2339 // the connection does the heavy lifting 2340 return connection.fetchQuotaRoot(fullname); 2341 } finally { 2342 releaseConnection(connection); 2343 } 2344 } 2345 2346 /** 2347 * Set the quota value for a quota root 2348 * 2349 * @param quota The new quota information to set. 2350 * 2351 * @exception MessagingException 2352 */ 2353 public synchronized void setQuota(Quota quota) throws MessagingException { 2354 // ask the store to kindly hook us up with a connection. 2355 IMAPConnection connection = getConnection(); 2356 2357 try { 2358 // the connection does the heavy lifting 2359 connection.setQuota(quota); 2360 } finally { 2361 releaseConnection(connection); 2362 } 2363 } 2364 2365 /** 2366 * Get the set of attributes defined for the folder 2367 * as the set of capabilities returned when the folder 2368 * was opened. 2369 * 2370 * @return The set of attributes associated with the folder. 2371 * @exception MessagingException 2372 */ 2373 public synchronized String[] getAttributes() throws MessagingException { 2374 // if we don't have the LIST command information for this folder yet, 2375 // call exists() to force this to be updated so we can return. 2376 if (listInfo == null) { 2377 // return a null reference if this is not valid. 2378 if (!exists()) { 2379 return null; 2380 } 2381 } 2382 // return a copy of the attributes array. 2383 return (String[])listInfo.attributes.clone(); 2384 } 2385 } 2386