1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved. 5 * 6 * The contents of this file are subject to the terms of either the GNU 7 * General Public License Version 2 only ("GPL") or the Common Development 8 * and Distribution License("CDDL") (collectively, the "License"). You 9 * may not use this file except in compliance with the License. You can obtain 10 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html 11 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific 12 * language governing permissions and limitations under the License. 13 * 14 * When distributing the software, include this License Header Notice in each 15 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt. 16 * Sun designates this particular file as subject to the "Classpath" exception 17 * as provided by Sun in the GPL Version 2 section of the License file that 18 * accompanied this code. If applicable, add the following below the License 19 * Header, with the fields enclosed by brackets [] replaced by your own 20 * identifying information: "Portions Copyrighted [year] 21 * [name of copyright owner]" 22 * 23 * Contributor(s): 24 * 25 * If you wish your version of this file to be governed by only the CDDL or 26 * only the GPL Version 2, indicate your decision by adding "[Contributor] 27 * elects to include this software in this distribution under the [CDDL or GPL 28 * Version 2] license." If you don't indicate a single choice of license, a 29 * recipient has the option to distribute your version of this file under 30 * either the CDDL, the GPL Version 2 or to extend the choice of license to 31 * its licensees as provided above. However, if you add GPL Version 2 code 32 * and therefore, elected the GPL Version 2 license, then the option applies 33 * only if the new code is made subject to such option by the copyright 34 * holder. 35 */ 36 37 /* 38 * @(#)IMAPFolder.java 1.85 07/09/05 39 */ 40 41 package com.sun.mail.imap; 42 43 import java.util.Date; 44 import java.util.Vector; 45 import java.util.Hashtable; 46 import java.util.NoSuchElementException; 47 import java.io; 48 49 import javax.mail; 50 import javax.mail.event; 51 import javax.mail.internet; 52 import javax.mail.search; 53 54 import com.sun.mail.util; 55 import com.sun.mail.iap; 56 import com.sun.mail.imap.protocol; 57 58 /** 59 * This class implements an IMAP folder. <p> 60 * 61 * A closed IMAPFolder object shares a protocol connection with its IMAPStore 62 * object. When the folder is opened, it gets its own protocol connection. <p> 63 * 64 * Applications that need to make use of IMAP-specific features may cast 65 * a <code>Folder</code> object to an <code>IMAPFolder</code> object and 66 * use the methods on this class. The {@link #getQuota getQuota} and 67 * {@link #setQuota setQuota} methods support the IMAP QUOTA extension. 68 * Refer to <A HREF="http://www.ietf.org/rfc/rfc2087.txt">RFC 2087</A> 69 * for more information. <p> 70 * 71 * The {@link #getACL getACL}, {@link #addACL addACL}, 72 * {@link #removeACL removeACL}, {@link #addRights addRights}, 73 * {@link #removeRights removeRights}, {@link #listRights listRights}, and 74 * {@link #myRights myRights} methods support the IMAP ACL extension. 75 * Refer to <A HREF="http://www.ietf.org/rfc/rfc2086.txt">RFC 2086</A> 76 * for more information. <p> 77 * 78 * The {@link #doCommand doCommand} method and 79 * {@link IMAPFolder.ProtocolCommand IMAPFolder.ProtocolCommand} 80 * interface support use of arbitrary IMAP protocol commands. <p> 81 * 82 * See the <a href="package-summary.html">com.sun.mail.imap</a> package 83 * documentation for further information on the IMAP protocol provider. <p> 84 * 85 * <strong>WARNING:</strong> The APIs unique to this class should be 86 * considered <strong>EXPERIMENTAL</strong>. They may be changed in the 87 * future in ways that are incompatible with applications using the 88 * current APIs. 89 * 90 * @version 1.85, 07/09/05 91 * @author John Mani 92 * @author Bill Shannon 93 * @author Jim Glennon 94 */ 95 96 /* 97 * The folder object itself serves as a lock for the folder's state 98 * EXCEPT for the message cache (see below), typically by using 99 * synchronized methods. When checking that a folder is open or 100 * closed, the folder's lock must be held. It's important that the 101 * folder's lock is acquired before the messageCacheLock (see below). 102 * Thus, the locking hierarchy is that the folder lock, while optional, 103 * must be acquired before the messageCacheLock, if it's acquired at 104 * all. Be especially careful of callbacks that occur while holding 105 * the messageCacheLock into (e.g.) superclass Folder methods that are 106 * synchronized. Note that methods in IMAPMessage will acquire the 107 * messageCacheLock without acquiring the folder lock. <p> 108 * 109 * When a folder is opened, it creates a messageCache (a Vector) of 110 * empty IMAPMessage objects. Each Message has a messageNumber - which 111 * is its index into the messageCache, and a sequenceNumber - which is 112 * its IMAP sequence-number. All operations on a Message which involve 113 * communication with the server, use the message's sequenceNumber. <p> 114 * 115 * The most important thing to note here is that the server can send 116 * unsolicited EXPUNGE notifications as part of the responses for "most" 117 * commands. Refer RFC2060, sections 5.3 & 5.5 for gory details. Also, 118 * the server sends these notifications AFTER the message has been 119 * expunged. And once a message is expunged, the sequence-numbers of 120 * those messages after the expunged one are renumbered. This essentially 121 * means that the mapping between *any* Message and its sequence-number 122 * can change in the period when a IMAP command is issued and its responses 123 * are processed. Hence we impose a strict locking model as follows: <p> 124 * 125 * We define one mutex per folder - this is just a Java Object (named 126 * messageCacheLock). Any time a command is to be issued to the IMAP 127 * server (i.e., anytime the corresponding IMAPProtocol method is 128 * invoked), follow the below style: 129 * 130 * synchronized (messageCacheLock) { // ACQUIRE LOCK 131 * issue command () 132 * 133 * // The response processing is typically done within 134 * // the handleResponse() callback. A few commands (Fetch, 135 * // Expunge) return *all* responses and hence their 136 * // processing is done here itself. Now, as part of the 137 * // processing unsolicited EXPUNGE responses, we renumber 138 * // the necessary sequence-numbers. Thus the renumbering 139 * // happens within this critical-region, surrounded by 140 * // locks. 141 * process responses () 142 * } // RELEASE LOCK 143 * 144 * This technique is used both by methods in IMAPFolder and by methods 145 * in IMAPMessage and other classes that operate on data in the folder. 146 * Note that holding the messageCacheLock has the side effect of 147 * preventing the folder from being closed, and thus ensuring that the 148 * folder's protocol object is still valid. The protocol object should 149 * only be accessed while holding the messageCacheLock (except for calls 150 * to IMAPProtocol.isREV1(), which don't need to be protected because it 151 * doesn't access the server). 152 * 153 * Note that interactions with the Store's protocol connection do 154 * not have to be protected as above, since the Store's protocol is 155 * never in a "meaningful" SELECT-ed state. 156 */ 157 158 public class IMAPFolder extends Folder implements UIDFolder, ResponseHandler { 159 160 protected String fullName; // full name 161 protected String name; // name 162 protected int type; // folder type. 163 protected char separator; // separator 164 protected Flags availableFlags; // available flags 165 protected Flags permanentFlags; // permanent flags 166 protected boolean exists = false; // whether this folder really exists ? 167 protected boolean isNamespace = false; // folder is a namespace name 168 protected String[] attributes; // name attributes from LIST response 169 170 protected IMAPProtocol protocol; // this folder's own protocol object 171 protected Vector messageCache; // message cache 172 protected Object messageCacheLock; // accessor lock for message cache 173 174 protected Hashtable uidTable; // UID->Message hashtable 175 176 /* An IMAP delimiter is a 7bit US-ASCII character. (except NUL). 177 * We use '\uffff' (a non 7bit character) to indicate that we havent 178 * yet determined what the separator character is. 179 * We use '\u0000' (NUL) to indicate that no separator character 180 * exists, i.e., a flat hierarchy 181 */ 182 static final protected char UNKNOWN_SEPARATOR = '\uffff'; 183 184 private boolean opened = false; // is this folder opened ? 185 186 /* This field tracks the state of this folder. If the folder is closed 187 * due to external causes (i.e, not thru the close() method), then 188 * this field will remain false. If the folder is closed thru the 189 * close() method, then this field is set to true. 190 * 191 * If reallyClosed is false, then a FolderClosedException is 192 * generated when a method is invoked on any Messaging object 193 * owned by this folder. If reallyClosed is true, then the 194 * IllegalStateException runtime exception is thrown. 195 */ 196 private boolean reallyClosed = true; 197 198 /* 199 * The idleState field supports the IDLE command. 200 * Normally when executing an IMAP command we hold the 201 * messageCacheLock and often the folder lock (see above). 202 * While executing the IDLE command we can't hold either 203 * of these locks or it would prevent other threads from 204 * entering Folder methods even far enough to check whether 205 * an IDLE command is in progress. We need to check before 206 * issuing another command so that we can abort the IDLE 207 * command. 208 * 209 * The idleState field is protected by the messageCacheLock. 210 * The RUNNING state is the normal state and means no IDLE 211 * command is in progress. The IDLE state means we've issued 212 * an IDLE command and are reading responses. The ABORTING 213 * state means we've sent the DONE continuation command and 214 * are waiting for the thread running the IDLE command to 215 * break out of its read loop. 216 * 217 * When an IDLE command is in progress, the thread calling 218 * the idle method will be reading from the IMAP connection 219 * while holding neither the folder lock nor the messageCacheLock. 220 * It's obviously critical that no other thread try to send a 221 * command or read from the connection while in this state. 222 * However, other threads can send the DONE continuation 223 * command that will cause the server to break out of the IDLE 224 * loop and send the ending tag response to the IDLE command. 225 * The thread in the idle method that's reading the responses 226 * from the IDLE command will see this ending response and 227 * complete the idle method, setting the idleState field back 228 * to RUNNING, and notifying any threads waiting to use the 229 * connection. 230 * 231 * All uses of the IMAP connection (IMAPProtocol object) must 232 * be done while holding the messageCacheLock and must be 233 * preceeded by a check to make sure an IDLE command is not 234 * running, and abort the IDLE command if necessary. While 235 * waiting for the IDLE command to complete, these other threads 236 * will give up the messageCacheLock, but might still be holding 237 * the folder lock. This check is done by the getProtocol() 238 * method, resulting in a typical usage pattern of: 239 * 240 * synchronized (messageCacheLock) { 241 * IMAPProtocol p = getProtocol(); // may block waiting for IDLE 242 * // ... use protocol 243 * } 244 */ 245 private static final int RUNNING = 0; // not doing IDLE command 246 private static final int IDLE = 1; // IDLE command in effect 247 private static final int ABORTING = 2; // IDLE command aborting 248 private int idleState = RUNNING; 249 250 private int total = -1; // total number of messages in the 251 // message cache 252 private int recent = -1; // number of recent messages 253 private int realTotal = -1; // total number of messages on 254 // the server 255 private long uidvalidity = -1; // UIDValidity 256 private long uidnext = -1; // UIDNext 257 private boolean doExpungeNotification = true; // used in expunge handler 258 259 private Status cachedStatus = null; 260 private long cachedStatusTime = 0; 261 262 private boolean debug = false; 263 private PrintStream out; // debug output stream 264 265 private boolean connectionPoolDebug; 266 267 /** 268 * A fetch profile item for fetching headers. 269 * This inner class extends the <code>FetchProfile.Item</code> 270 * class to add new FetchProfile item types, specific to IMAPFolders. 271 * 272 * @see FetchProfile 273 */ 274 public static class FetchProfileItem extends FetchProfile.Item { 275 protected FetchProfileItem(String name) { 276 super(name); 277 } 278 279 /** 280 * HEADERS is a fetch profile item that can be included in a 281 * <code>FetchProfile</code> during a fetch request to a Folder. 282 * This item indicates that the headers for messages in the specified 283 * range are desired to be prefetched. <p> 284 * 285 * An example of how a client uses this is below: <p> 286 * <blockquote><pre> 287 * 288 * FetchProfile fp = new FetchProfile(); 289 * fp.add(IMAPFolder.FetchProfileItem.HEADERS); 290 * folder.fetch(msgs, fp); 291 * 292 * </pre></blockquote><p> 293 */ 294 public static final FetchProfileItem HEADERS = 295 new FetchProfileItem("HEADERS"); 296 297 /** 298 * SIZE is a fetch profile item that can be included in a 299 * <code>FetchProfile</code> during a fetch request to a Folder. 300 * This item indicates that the sizes of the messages in the specified 301 * range are desired to be prefetched. <p> 302 * 303 * SIZE should move to FetchProfile.Item in JavaMail 1.3. 304 */ 305 public static final FetchProfileItem SIZE = 306 new FetchProfileItem("SIZE"); 307 } 308 309 /** 310 * Constructor used to create a possibly non-existent folder. 311 * 312 * @param fullName fullname of this folder 313 * @param separator the default separator character for this 314 * folder's namespace 315 * @param store the Store 316 */ 317 protected IMAPFolder(String fullName, char separator, IMAPStore store) { 318 super(store); 319 if (fullName == null) 320 throw new NullPointerException("Folder name is null"); 321 this.fullName = fullName; 322 this.separator = separator; 323 messageCacheLock = new Object(); 324 debug = store.getSession().getDebug(); 325 connectionPoolDebug = ((IMAPStore)store).getConnectionPoolDebug(); 326 out = store.getSession().getDebugOut(); 327 if (out == null) // should never happen 328 out = System.out; 329 330 /* 331 * Work around apparent bug in Exchange. Exchange 332 * will return a name of "Public Folders/" from 333 * LIST "%". 334 * 335 * If name has one separator, and it's at the end, 336 * assume this is a namespace name and treat it 337 * accordingly. Usually this will happen as a result 338 * of the list method, but this also allows getFolder 339 * to work with namespace names. 340 */ 341 this.isNamespace = false; 342 if (separator != UNKNOWN_SEPARATOR && separator != '\0') { 343 int i = this.fullName.indexOf(separator); 344 if (i > 0 && i == this.fullName.length() - 1) { 345 this.fullName = this.fullName.substring(0, i); 346 this.isNamespace = true; 347 } 348 } 349 } 350 351 /** 352 * Constructor used to create a possibly non-existent folder. 353 * 354 * @param fullName fullname of this folder 355 * @param separator the default separator character for this 356 * folder's namespace 357 * @param store the Store 358 */ 359 protected IMAPFolder(String fullName, char separator, IMAPStore store, 360 boolean isNamespace) { 361 this(fullName, separator, store); 362 this.isNamespace = isNamespace; 363 } 364 365 /** 366 * Constructor used to create an existing folder. 367 */ 368 protected IMAPFolder(ListInfo li, IMAPStore store) { 369 this(li.name, li.separator, store); 370 371 if (li.hasInferiors) 372 type |= HOLDS_FOLDERS; 373 if (li.canOpen) 374 type |= HOLDS_MESSAGES; 375 exists = true; 376 attributes = li.attrs; 377 } 378 379 /* 380 * Ensure that this folder exists. If 'exists' has been set to true, 381 * we don't attempt to validate it with the server again. Note that 382 * this can result in a possible loss of sync with the server. 383 */ 384 private void checkExists() throws MessagingException { 385 // If the boolean field 'exists' is false, check with the 386 // server by invoking exists() .. 387 if (!exists && !exists()) 388 throw new FolderNotFoundException( 389 this, fullName + " not found"); 390 } 391 392 /* 393 * Ensure the folder is closed. 394 * ASSERT: Must be called with this folder's synchronization lock held. 395 */ 396 private void checkClosed() { 397 if (opened) 398 throw new IllegalStateException( 399 "This operation is not allowed on an open folder" 400 ); 401 } 402 403 /* 404 * Ensure the folder is open. 405 * ASSERT: Must be called with this folder's synchronization lock held. 406 */ 407 private void checkOpened() throws FolderClosedException { 408 assert Thread.holdsLock(this); 409 if (!opened) { 410 if (reallyClosed) 411 throw new IllegalStateException( 412 "This operation is not allowed on a closed folder" 413 ); 414 else // Folder was closed "implicitly" 415 throw new FolderClosedException(this, 416 "Lost folder connection to server" 417 ); 418 } 419 } 420 421 /* 422 * Check that the given message number is within the range 423 * of messages present in this folder. If the message 424 * number is out of range, we ping the server to obtain any 425 * pending new message notifications from the server. 426 */ 427 private void checkRange(int msgno) throws MessagingException { 428 if (msgno < 1) // message-numbers start at 1 429 throw new IndexOutOfBoundsException(); 430 431 if (msgno <= total) 432 return; 433 434 // Out of range, let's ping the server and see if 435 // the server has more messages for us. 436 437 synchronized(messageCacheLock) { // Acquire lock 438 try { 439 keepConnectionAlive(false); 440 } catch (ConnectionException cex) { 441 // Oops, lost connection 442 throw new FolderClosedException(this, cex.getMessage()); 443 } catch (ProtocolException pex) { 444 throw new MessagingException(pex.getMessage(), pex); 445 } 446 } // Release lock 447 448 if (msgno > total) // Still out of range ? Throw up ... 449 throw new IndexOutOfBoundsException(); 450 } 451 452 /* 453 * Check whether the given flags are supported by this server, 454 * and also verify that the folder allows setting flags. 455 */ 456 private void checkFlags(Flags flags) throws MessagingException { 457 assert Thread.holdsLock(this); 458 if (mode != READ_WRITE) 459 throw new IllegalStateException( 460 "Cannot change flags on READ_ONLY folder: " + fullName 461 ); 462 /* 463 if (!availableFlags.contains(flags)) 464 throw new MessagingException( 465 "These flags are not supported by this implementation" 466 ); 467 */ 468 } 469 470 /** 471 * Get the name of this folder. 472 */ 473 public synchronized String getName() { 474 /* Return the last component of this Folder's full name. 475 * Folder components are delimited by the separator character. 476 */ 477 if (name == null) { 478 try { 479 name = fullName.substring( 480 fullName.lastIndexOf(getSeparator()) + 1 481 ); 482 } catch (MessagingException mex) { } 483 } 484 return name; 485 } 486 487 /** 488 * Get the fullname of this folder. 489 */ 490 public synchronized String getFullName() { 491 return fullName; 492 } 493 494 /** 495 * Get this folder's parent. 496 */ 497 public synchronized Folder getParent() throws MessagingException { 498 char c = getSeparator(); 499 int index; 500 if ((index = fullName.lastIndexOf(c)) != -1) 501 return new IMAPFolder(fullName.substring(0, index), 502 c, (IMAPStore)store); 503 else 504 return new DefaultFolder((IMAPStore)store); 505 } 506 507 /** 508 * Check whether this folder really exists on the server. 509 */ 510 public synchronized boolean exists() throws MessagingException { 511 // Check whether this folder exists .. 512 ListInfo[] li = null; 513 final String lname; 514 if (isNamespace && separator != '\0') 515 lname = fullName + separator; 516 else 517 lname = fullName; 518 519 li = (ListInfo[])doCommand(new ProtocolCommand() { 520 public Object doCommand(IMAPProtocol p) throws ProtocolException { 521 return p.list("", lname); 522 } 523 }); 524 525 if (li != null) { 526 int i = findName(li, lname); 527 fullName = li[i].name; 528 separator = li[i].separator; 529 int len = fullName.length(); 530 if (separator != '\0' && len > 0 && 531 fullName.charAt(len - 1) == separator) { 532 fullName = fullName.substring(0, len - 1); 533 } 534 type = 0; 535 if (li[i].hasInferiors) 536 type |= HOLDS_FOLDERS; 537 if (li[i].canOpen) 538 type |= HOLDS_MESSAGES; 539 exists = true; 540 attributes = li[i].attrs; 541 } else { 542 exists = opened; 543 attributes = null; 544 } 545 546 return exists; 547 } 548 549 /** 550 * Which entry in <code>li</code> matches <code>lname</code>? 551 * If the name contains wildcards, more than one entry may be 552 * returned. 553 */ 554 private int findName(ListInfo[] li, String lname) { 555 int i; 556 // if the name contains a wildcard, there might be more than one 557 for (i = 0; i < li.length; i++) { 558 if (li[i].name.equals(lname)) 559 break; 560 } 561 if (i >= li.length) { // nothing matched exactly 562 // XXX - possibly should fail? But what if server 563 // is case insensitive and returns the preferred 564 // case of the name here? 565 i = 0; // use first one 566 } 567 return i; 568 } 569 570 /** 571 * List all subfolders matching the specified pattern. 572 */ 573 public Folder[] list(String pattern) throws MessagingException { 574 return doList(pattern, false); 575 } 576 577 /** 578 * List all subscribed subfolders matching the specified pattern. 579 */ 580 public Folder[] listSubscribed(String pattern) throws MessagingException { 581 return doList(pattern, true); 582 } 583 584 private synchronized Folder[] doList(final String pattern, 585 final boolean subscribed) throws MessagingException { 586 checkExists(); // insure that this folder does exist. 587 588 if (!isDirectory()) // Why waste a roundtrip to the server ? 589 return new Folder[0]; 590 591 final char c = getSeparator(); 592 593 ListInfo[] li = (ListInfo[])doCommandIgnoreFailure( 594 new ProtocolCommand() { 595 public Object doCommand(IMAPProtocol p) 596 throws ProtocolException { 597 if (subscribed) 598 return p.lsub("", fullName + c + pattern); 599 else 600 return p.list("", fullName + c + pattern); 601 } 602 }); 603 604 if (li == null) 605 return new Folder[0]; 606 607 /* 608 * The UW based IMAP4 servers (e.g. SIMS2.0) include 609 * current folder (terminated with the separator), when 610 * the LIST pattern is '%' or '*'. i.e, <LIST "" mail/%> 611 * returns "mail/" as the first LIST response. 612 * 613 * Doesn't make sense to include the current folder in this 614 * case, so we filter it out. Note that I'm assuming that 615 * the offending response is the *first* one, my experiments 616 * with the UW & SIMS2.0 servers indicate that .. 617 */ 618 int start = 0; 619 // Check the first LIST response. 620 if (li.length > 0 && li[0].name.equals(fullName + c)) 621 start = 1; // start from index = 1 622 623 IMAPFolder[] folders = new IMAPFolder[li.length - start]; 624 for (int i = start; i < li.length; i++) 625 folders[i-start] = new IMAPFolder(li[i], (IMAPStore)store); 626 return folders; 627 } 628 629 /** 630 * Get the separator character. 631 */ 632 public synchronized char getSeparator() throws MessagingException { 633 if (separator == UNKNOWN_SEPARATOR) { 634 ListInfo[] li = null; 635 636 li = (ListInfo[])doCommand(new ProtocolCommand() { 637 public Object doCommand(IMAPProtocol p) 638 throws ProtocolException { 639 // REV1 allows the following LIST format to obtain 640 // the hierarchy delimiter of non-existent folders 641 if (p.isREV1()) // IMAP4rev1 642 return p.list(fullName, ""); 643 else // IMAP4, note that this folder must exist for this 644 // to work :( 645 return p.list("", fullName); 646 } 647 }); 648 649 if (li != null) 650 separator = li[0].separator; 651 else 652 separator = '/'; // punt ! 653 } 654 return separator; 655 } 656 657 /** 658 * Get the type of this folder. 659 */ 660 public synchronized int getType() throws MessagingException { 661 if (opened) { 662 // never throw FolderNotFoundException if folder is open 663 if (attributes == null) 664 exists(); // try to fetch attributes 665 } else { 666 checkExists(); 667 } 668 return type; 669 } 670 671 /** 672 * Check whether this folder is subscribed. <p> 673 */ 674 public synchronized boolean isSubscribed() { 675 ListInfo[] li = null; 676 final String lname; 677 if (isNamespace && separator != '\0') 678 lname = fullName + separator; 679 else 680 lname = fullName; 681 682 try { 683 li = (ListInfo[])doProtocolCommand(new ProtocolCommand() { 684 public Object doCommand(IMAPProtocol p) 685 throws ProtocolException { 686 return p.lsub("", lname); 687 } 688 }); 689 } catch (ProtocolException pex) { 690 } 691 692 if (li != null) { 693 int i = findName(li, lname); 694 return li[i].canOpen; 695 } else 696 return false; 697 } 698 699 /** 700 * Subscribe/Unsubscribe this folder. 701 */ 702 public synchronized void setSubscribed(final boolean subscribe) 703 throws MessagingException { 704 doCommandIgnoreFailure(new ProtocolCommand() { 705 public Object doCommand(IMAPProtocol p) throws ProtocolException { 706 if (subscribe) 707 p.subscribe(fullName); 708 else 709 p.unsubscribe(fullName); 710 return null; 711 } 712 }); 713 } 714 715 /** 716 * Create this folder, with the specified type. 717 */ 718 public synchronized boolean create(final int type) 719 throws MessagingException { 720 721 char c = 0; 722 if ((type & HOLDS_MESSAGES) == 0) // only holds folders 723 c = getSeparator(); 724 final char sep = c; 725 Object ret = doCommandIgnoreFailure(new ProtocolCommand() { 726 public Object doCommand(IMAPProtocol p) 727 throws ProtocolException { 728 if ((type & HOLDS_MESSAGES) == 0) // only holds folders 729 p.create(fullName + sep); 730 else { 731 p.create(fullName); 732 733 // Certain IMAP servers do not allow creation of folders 734 // that can contain messages *and* subfolders. So, if we 735 // were asked to create such a folder, we should verify 736 // that we could indeed do so. 737 if ((type & HOLDS_FOLDERS) != 0) { 738 // we want to hold subfolders and messages. Check 739 // whether we could create such a folder. 740 ListInfo[] li = p.list("", fullName); 741 if (li != null && !li[0].hasInferiors) { 742 // Hmm ..the new folder 743 // doesn't support Inferiors ? Fail 744 p.delete(fullName); 745 throw new ProtocolException("Unsupported type"); 746 } 747 } 748 } 749 return Boolean.TRUE; 750 } 751 }); 752 753 if (ret == null) 754 return false; // CREATE failure, maybe this 755 // folder already exists ? 756 757 // exists = true; 758 // this.type = type; 759 boolean retb = exists(); // set exists, type, and attributes 760 if (retb) // Notify listeners on self and our Store 761 notifyFolderListeners(FolderEvent.CREATED); 762 return retb; 763 } 764 765 /** 766 * Check whether this folder has new messages. 767 */ 768 public synchronized boolean hasNewMessages() throws MessagingException { 769 if (opened) { // If we are open, we already have this information 770 // Folder is open, make sure information is up to date 771 synchronized(messageCacheLock) { 772 // tickle the folder and store connections. 773 try { 774 keepConnectionAlive(true); 775 } catch (ConnectionException cex) { 776 throw new FolderClosedException(this, cex.getMessage()); 777 } catch (ProtocolException pex) { 778 throw new MessagingException(pex.getMessage(), pex); 779 } 780 return recent > 0 ? true : false; 781 } 782 } 783 784 // First, the cheap way - use LIST and look for the \Marked 785 // or \Unmarked tag 786 787 ListInfo[] li = null; 788 final String lname; 789 if (isNamespace && separator != '\0') 790 lname = fullName + separator; 791 else 792 lname = fullName; 793 li = (ListInfo[])doCommandIgnoreFailure(new ProtocolCommand() { 794 public Object doCommand(IMAPProtocol p) throws ProtocolException { 795 return p.list("", lname); 796 } 797 }); 798 799 // if folder doesn't exist, throw exception 800 if (li == null) 801 throw new FolderNotFoundException(this, fullName + " not found"); 802 803 int i = findName(li, lname); 804 if (li[i].changeState == ListInfo.CHANGED) 805 return true; 806 else if (li[i].changeState == ListInfo.UNCHANGED) 807 return false; 808 809 // LIST didn't work. Try the hard way, using STATUS 810 try { 811 Status status = getStatus(); 812 if (status.recent > 0) 813 return true; 814 else 815 return false; 816 } catch (BadCommandException bex) { 817 // Probably doesn't support STATUS, tough luck. 818 return false; 819 } catch (ConnectionException cex) { 820 throw new StoreClosedException(store, cex.getMessage()); 821 } catch (ProtocolException pex) { 822 throw new MessagingException(pex.getMessage(), pex); 823 } 824 } 825 826 /** 827 * Get the named subfolder. <p> 828 */ 829 public Folder getFolder(String name) throws MessagingException { 830 // If we know that this folder is *not* a directory, don't 831 // send the request to the server at all ... 832 if (attributes != null && !isDirectory()) 833 throw new MessagingException("Cannot contain subfolders"); 834 835 char c = getSeparator(); 836 return new IMAPFolder(fullName + c + name, c, (IMAPStore)store); 837 } 838 839 /** 840 * Delete this folder. 841 */ 842 public synchronized boolean delete(boolean recurse) 843 throws MessagingException { 844 checkClosed(); // insure that this folder is closed. 845 846 if (recurse) { 847 // Delete all subfolders. 848 Folder[] f = list(); 849 for (int i = 0; i < f.length; i++) 850 f[i].delete(recurse); // ignore intermediate failures 851 } 852 853 // Attempt to delete this folder 854 855 Object ret = doCommandIgnoreFailure(new ProtocolCommand() { 856 public Object doCommand(IMAPProtocol p) throws ProtocolException { 857 p.delete(fullName); 858 return Boolean.TRUE; 859 } 860 }); 861 862 if (ret == null) 863 // Non-existent folder/No permission ?? 864 return false; 865 866 // DELETE succeeded. 867 exists = false; 868 attributes = null; 869 870 // Notify listeners on self and our Store 871 notifyFolderListeners(FolderEvent.DELETED); 872 return true; 873 } 874 875 /** 876 * Rename this folder. <p> 877 */ 878 public synchronized boolean renameTo(final Folder f) 879 throws MessagingException { 880 checkClosed(); // insure that we are closed. 881 checkExists(); 882 if (f.getStore() != store) 883 throw new MessagingException("Can't rename across Stores"); 884 885 886 Object ret = doCommandIgnoreFailure(new ProtocolCommand() { 887 public Object doCommand(IMAPProtocol p) throws ProtocolException { 888 p.rename(fullName, f.getFullName()); 889 return Boolean.TRUE; 890 } 891 }); 892 893 if (ret == null) 894 return false; 895 896 exists = false; 897 attributes = null; 898 notifyFolderRenamedListeners(f); 899 return true; 900 } 901 902 /** 903 * Open this folder in the given mode. 904 */ 905 public synchronized void open(int mode) throws MessagingException { 906 checkClosed(); // insure that we are not already open 907 908 MailboxInfo mi = null; 909 // Request store for our own protocol connection. 910 protocol = ((IMAPStore)store).getProtocol(this); 911 912 CommandFailedException exc = null; 913 lock: 914 synchronized(messageCacheLock) { // Acquire messageCacheLock 915 916 /* 917 * Add response handler right away so we get any alerts or 918 * notifications that occur during the SELECT or EXAMINE. 919 * Have to be sure to remove it if we fail to open the 920 * folder. 921 */ 922 protocol.addResponseHandler(this); 923 924 try { 925 if (mode == READ_ONLY) 926 mi = protocol.examine(fullName); 927 else 928 mi = protocol.select(fullName); 929 } catch (CommandFailedException cex) { 930 // got a NO; connection still good, return it 931 releaseProtocol(true); 932 protocol = null; 933 exc = cex; 934 break lock; 935 } catch (ProtocolException pex) { 936 // got a BAD or a BYE; connection may be bad, close it 937 try { 938 protocol.logout(); 939 } catch (ProtocolException pex2) { 940 // ignore 941 } finally { 942 releaseProtocol(false); 943 protocol = null; 944 throw new MessagingException(pex.getMessage(), pex); 945 } 946 } 947 948 if (mi.mode != mode) { 949 if (mode == READ_WRITE && mi.mode == READ_ONLY && 950 ((IMAPStore)store).allowReadOnlySelect()) { 951 ; // all ok, allow it 952 } else { // otherwise, it's an error 953 try { 954 // close mailbox and return connection 955 protocol.close(); 956 releaseProtocol(true); 957 } catch (ProtocolException pex) { 958 // something went wrong, close connection 959 try { 960 protocol.logout(); 961 } catch (ProtocolException pex2) { 962 // ignore 963 } finally { 964 releaseProtocol(false); 965 } 966 } finally { 967 protocol = null; 968 throw new ReadOnlyFolderException(this, 969 "Cannot open in desired mode"); 970 } 971 972 } 973 } 974 975 // Initialize stuff. 976 opened = true; 977 reallyClosed = false; 978 this.mode = mi.mode; 979 availableFlags = mi.availableFlags; 980 permanentFlags = mi.permanentFlags; 981 total = realTotal = mi.total; 982 recent = mi.recent; 983 uidvalidity = mi.uidvalidity; 984 uidnext = mi.uidnext; 985 986 // Create the message cache vector of appropriate size 987 messageCache = new Vector(total); 988 // Fill up the cache with light-weight IMAPMessage objects 989 for (int i = 0; i < total; i++) 990 messageCache.addElement(new IMAPMessage(this, i+1, i+1)); 991 992 } // Release lock 993 994 /* 995 * Handle SELECT or EXAMINE failure after lock is released. 996 * Try to figure out why the operation failed so we can 997 * report a more reasonable exception. 998 */ 999 if (exc != null) { 1000 checkExists(); // throw exception if folder doesn't exist 1001 1002 if ((type & HOLDS_MESSAGES) == 0) 1003 throw new MessagingException("folder cannot contain messages"); 1004 throw new MessagingException(exc.getMessage(), exc); 1005 } 1006 1007 exists = true; // if we opened it, it must exist 1008 attributes = null; // but we don't yet know its attributes 1009 type = HOLDS_MESSAGES; // lacking more info, we know at least this much 1010 1011 // notify listeners 1012 notifyConnectionListeners(ConnectionEvent.OPENED); 1013 } 1014 1015 /** 1016 * Prefetch attributes, based on the given FetchProfile. 1017 */ 1018 public synchronized void fetch(Message[] msgs, FetchProfile fp) 1019 throws MessagingException { 1020 checkOpened(); 1021 IMAPMessage.fetch(this, msgs, fp); 1022 } 1023 1024 /** 1025 * Set the specified flags for the given array of messages. 1026 */ 1027 public synchronized void setFlags(Message[] msgs, Flags flag, boolean value) 1028 throws MessagingException { 1029 checkOpened(); 1030 checkFlags(flag); // validate flags 1031 1032 if (msgs.length == 0) // boundary condition 1033 return; 1034 1035 synchronized(messageCacheLock) { 1036 try { 1037 IMAPProtocol p = getProtocol(); 1038 MessageSet[] ms = Utility.toMessageSet(msgs, null); 1039 if (ms == null) 1040 throw new MessageRemovedException( 1041 "Messages have been removed"); 1042 p.storeFlags(ms, flag, value); 1043 } catch (ConnectionException cex) { 1044 throw new FolderClosedException(this, cex.getMessage()); 1045 } catch (ProtocolException pex) { 1046 throw new MessagingException(pex.getMessage(), pex); 1047 } 1048 } 1049 } 1050 1051 /** 1052 * Close this folder. 1053 */ 1054 public synchronized void close(boolean expunge) throws MessagingException { 1055 close(expunge, false); 1056 } 1057 1058 /** 1059 * Close this folder without waiting for the server. 1060 */ 1061 public synchronized void forceClose() throws MessagingException { 1062 close(false, true); 1063 } 1064 1065 /* 1066 * Common close method. 1067 */ 1068 private void close(boolean expunge, boolean force) 1069 throws MessagingException { 1070 assert Thread.holdsLock(this); 1071 synchronized(messageCacheLock) { 1072 /* 1073 * If we already know we're closed, this is illegal. 1074 * Can't use checkOpened() because if we were forcibly 1075 * closed asynchronously we just want to complete the 1076 * closing here. 1077 */ 1078 if (!opened && reallyClosed) 1079 throw new IllegalStateException( 1080 "This operation is not allowed on a closed folder" 1081 ); 1082 1083 reallyClosed = true; // Ok, lets reset 1084 1085 // Maybe this folder is already closed, or maybe another 1086 // thread which had the messageCacheLock earlier, found 1087 // that our server connection is dead and cleaned up 1088 // everything .. 1089 if (!opened) 1090 return; 1091 1092 try { 1093 waitIfIdle(); 1094 if (force) { 1095 if (debug) 1096 out.println("DEBUG: forcing folder " + fullName + 1097 " to close"); 1098 if (protocol != null) 1099 protocol.disconnect(); 1100 } else if (((IMAPStore)store).isConnectionPoolFull()) { 1101 // If the connection pool is full, logout the connection 1102 if (debug) 1103 out.println("DEBUG: pool is full, not adding " + 1104 "an Authenticated connection"); 1105 1106 // If the expunge flag is set, close the folder first. 1107 if (expunge) 1108 protocol.close(); 1109 1110 if (protocol != null) 1111 protocol.logout(); 1112 } else { 1113 // If the expunge flag is set or we're open read-only we 1114 // can just close the folder, otherwise open it read-only 1115 // before closing. 1116 if (!expunge && mode == READ_WRITE) { 1117 try { 1118 MailboxInfo mi = protocol.examine(fullName); 1119 } catch (ProtocolException pex2) { 1120 if (protocol != null) 1121 protocol.disconnect(); 1122 } 1123 } 1124 if (protocol != null) 1125 protocol.close(); 1126 } 1127 } catch (ProtocolException pex) { 1128 throw new MessagingException(pex.getMessage(), pex); 1129 } finally { 1130 // cleanup if we haven't already 1131 if (opened) 1132 cleanup(true); 1133 } 1134 } 1135 } 1136 1137 // NOTE: this method can currently be invoked from close() or 1138 // from handleResponses(). Both invocations are conditional, 1139 // based on the "opened" flag, so we are sure that multiple 1140 // Connection.CLOSED events are not generated. Also both 1141 // invocations are from within messageCacheLock-ed areas. 1142 private void cleanup(boolean returnToPool) { 1143 releaseProtocol(returnToPool); 1144 protocol = null; 1145 messageCache = null; 1146 uidTable = null; 1147 exists = false; // to force a recheck in exists(). 1148 attributes = null; 1149 opened = false; 1150 idleState = RUNNING; // just in case 1151 notifyConnectionListeners(ConnectionEvent.CLOSED); 1152 } 1153 1154 /** 1155 * Check whether this connection is really open. 1156 */ 1157 public synchronized boolean isOpen() { 1158 synchronized(messageCacheLock) { 1159 // Probe the connection to make sure its really open. 1160 if (opened) { 1161 try { 1162 keepConnectionAlive(false); 1163 } catch (ProtocolException pex) { } 1164 } 1165 } 1166 1167 return opened; 1168 } 1169 1170 /** 1171 * Return the permanent flags supported by the server. 1172 */ 1173 public synchronized Flags getPermanentFlags() { 1174 return (Flags)(permanentFlags.clone()); 1175 } 1176 1177 /** 1178 * Get the total message count. 1179 */ 1180 public synchronized int getMessageCount() throws MessagingException { 1181 if (!opened) { 1182 checkExists(); 1183 // If this folder is not yet open, we use STATUS to 1184 // get the total message count 1185 try { 1186 Status status = getStatus(); 1187 return status.total; 1188 } catch (BadCommandException bex) { 1189 // doesn't support STATUS, probably vanilla IMAP4 .. 1190 // lets try EXAMINE 1191 IMAPProtocol p = null; 1192 1193 try { 1194 p = getStoreProtocol(); // XXX 1195 MailboxInfo minfo = p.examine(fullName); 1196 p.close(); 1197 return minfo.total; 1198 } catch (ProtocolException pex) { 1199 // Give up. 1200 throw new MessagingException(pex.getMessage(), pex); 1201 } finally { 1202 releaseStoreProtocol(p); 1203 } 1204 } catch (ConnectionException cex) { 1205 throw new StoreClosedException(store, cex.getMessage()); 1206 } catch (ProtocolException pex) { 1207 throw new MessagingException(pex.getMessage(), pex); 1208 } 1209 } 1210 1211 // Folder is open, we know what the total message count is .. 1212 synchronized(messageCacheLock) { 1213 // tickle the folder and store connections. 1214 try { 1215 keepConnectionAlive(true); 1216 return total; 1217 } catch (ConnectionException cex) { 1218 throw new FolderClosedException(this, cex.getMessage()); 1219 } catch (ProtocolException pex) { 1220 throw new MessagingException(pex.getMessage(), pex); 1221 } 1222 } 1223 } 1224 1225 /** 1226 * Get the new message count. 1227 */ 1228 public synchronized int getNewMessageCount() 1229 throws MessagingException { 1230 if (!opened) { 1231 checkExists(); 1232 // If this folder is not yet open, we use STATUS to 1233 // get the new message count 1234 try { 1235 Status status = getStatus(); 1236 return status.recent; 1237 } catch (BadCommandException bex) { 1238 // doesn't support STATUS, probably vanilla IMAP4 .. 1239 // lets try EXAMINE 1240 IMAPProtocol p = null; 1241 1242 try { 1243 p = getStoreProtocol(); // XXX 1244 MailboxInfo minfo = p.examine(fullName); 1245 p.close(); 1246 return minfo.recent; 1247 } catch (ProtocolException pex) { 1248 // Give up. 1249 throw new MessagingException(pex.getMessage(), pex); 1250 } finally { 1251 releaseStoreProtocol(p); 1252 } 1253 } catch (ConnectionException cex) { 1254 throw new StoreClosedException(store, cex.getMessage()); 1255 } catch (ProtocolException pex) { 1256 throw new MessagingException(pex.getMessage(), pex); 1257 } 1258 } 1259 1260 // Folder is open, we know what the new message count is .. 1261 synchronized(messageCacheLock) { 1262 // tickle the folder and store connections. 1263 try { 1264 keepConnectionAlive(true); 1265 return recent; 1266 } catch (ConnectionException cex) { 1267 throw new FolderClosedException(this, cex.getMessage()); 1268 } catch (ProtocolException pex) { 1269 throw new MessagingException(pex.getMessage(), pex); 1270 } 1271 } 1272 } 1273 1274 /** 1275 * Get the unread message count. 1276 */ 1277 public synchronized int getUnreadMessageCount() 1278 throws MessagingException { 1279 if (!opened) { 1280 checkExists(); 1281 // If this folder is not yet open, we use STATUS to 1282 // get the unseen message count 1283 try { 1284 Status status = getStatus(); 1285 return status.unseen; 1286 } catch (BadCommandException bex) { 1287 // doesn't support STATUS, probably vanilla IMAP4 .. 1288 // Could EXAMINE, SEARCH for UNREAD messages and 1289 // return the count .. bah, not worth it. 1290 return -1; 1291 } catch (ConnectionException cex) { 1292 throw new StoreClosedException(store, cex.getMessage()); 1293 } catch (ProtocolException pex) { 1294 throw new MessagingException(pex.getMessage(), pex); 1295 } 1296 } 1297 1298 // if opened, issue server-side search for messages that do 1299 // *not* have the SEEN flag. 1300 Flags f = new Flags(); 1301 f.add(Flags.Flag.SEEN); 1302 try { 1303 synchronized(messageCacheLock) { 1304 int[] matches = getProtocol().search(new FlagTerm(f, false)); 1305 return matches.length; // NOTE: 'matches' is never null 1306 } 1307 } catch (ConnectionException cex) { 1308 throw new FolderClosedException(this, cex.getMessage()); 1309 } catch (ProtocolException pex) { 1310 // Shouldn't happen 1311 throw new MessagingException(pex.getMessage(), pex); 1312 } 1313 } 1314 1315 /** 1316 * Get the deleted message count. 1317 */ 1318 public synchronized int getDeletedMessageCount() 1319 throws MessagingException { 1320 if (!opened) { 1321 checkExists(); 1322 // no way to do this on closed folders 1323 return -1; 1324 } 1325 1326 // if opened, issue server-side search for messages that do 1327 // have the DELETED flag. 1328 Flags f = new Flags(); 1329 f.add(Flags.Flag.DELETED); 1330 try { 1331 synchronized(messageCacheLock) { 1332 int[] matches = getProtocol().search(new FlagTerm(f, true)); 1333 return matches.length; // NOTE: 'matches' is never null 1334 } 1335 } catch (ConnectionException cex) { 1336 throw new FolderClosedException(this, cex.getMessage()); 1337 } catch (ProtocolException pex) { 1338 // Shouldn't happen 1339 throw new MessagingException(pex.getMessage(), pex); 1340 } 1341 } 1342 1343 /* 1344 * Get results of STATUS command for this folder, checking cache first. 1345 * ASSERT: Must be called with this folder's synchronization lock held. 1346 * ASSERT: The folder must be closed. 1347 */ 1348 private Status getStatus() throws ProtocolException { 1349 int statusCacheTimeout = ((IMAPStore)store).getStatusCacheTimeout(); 1350 1351 // if allowed to cache and our cache is still valid, return it 1352 if (statusCacheTimeout > 0 && cachedStatus != null && 1353 System.currentTimeMillis() - cachedStatusTime < statusCacheTimeout) 1354 return cachedStatus; 1355 1356 IMAPProtocol p = null; 1357 1358 try { 1359 p = getStoreProtocol(); // XXX 1360 Status s = p.status(fullName, null); 1361 // if allowed to cache, do so 1362 if (statusCacheTimeout > 0) { 1363 cachedStatus = s; 1364 cachedStatusTime = System.currentTimeMillis(); 1365 } 1366 return s; 1367 } finally { 1368 releaseStoreProtocol(p); 1369 } 1370 } 1371 1372 /** 1373 * Get the specified message. 1374 */ 1375 public synchronized Message getMessage(int msgnum) 1376 throws MessagingException { 1377 checkOpened(); 1378 checkRange(msgnum); 1379 1380 return (Message)messageCache.elementAt(msgnum-1); 1381 } 1382 1383 /** 1384 * Append the given messages into this folder. 1385 */ 1386 public synchronized void appendMessages(Message[] msgs) 1387 throws MessagingException { 1388 checkExists(); // verify that self exists 1389 1390 // XXX - have to verify that messages are in a different 1391 // store (if any) than target folder, otherwise could 1392 // deadlock trying to fetch messages on the same connection 1393 // we're using for the append. 1394 1395 int maxsize = ((IMAPStore)store).getAppendBufferSize(); 1396 1397 for (int i = 0; i < msgs.length; i++) { 1398 final Message m = msgs[i]; 1399 final MessageLiteral mos; 1400 1401 try { 1402 // if we know the message is too big, don't buffer any of it 1403 mos = new MessageLiteral(m, 1404 m.getSize() > maxsize ? 0 : maxsize); 1405 } catch (IOException ex) { 1406 throw new MessagingException( 1407 "IOException while appending messages", ex); 1408 } catch (MessageRemovedException mrex) { 1409 continue; // just skip this expunged message 1410 } 1411 1412 Date d = m.getReceivedDate(); // retain dates 1413 if (d == null) 1414 d = m.getSentDate(); 1415 final Date dd = d; 1416 final Flags f = m.getFlags(); 1417 doCommand(new ProtocolCommand() { 1418 public Object doCommand(IMAPProtocol p) 1419 throws ProtocolException { 1420 p.append(fullName, f, dd, mos); 1421 return null; 1422 } 1423 }); 1424 } 1425 } 1426 1427 /** 1428 * Append the given messages into this folder. 1429 * Return array of AppendUID objects containing 1430 * UIDs of these messages in the destination folder. 1431 * Each element of the returned array corresponds to 1432 * an element of the <code>msgs</code> array. A null 1433 * element means the server didn't return UID information 1434 * for the appended message. <p> 1435 * 1436 * Depends on the APPENDUID response code defined by the 1437 * UIDPLUS extension - 1438 * <A HREF="http://www.ietf.org/rfc/rfc2359.txt">RFC 2359</A>. 1439 * 1440 * @since JavaMail 1.4 1441 */ 1442 public synchronized AppendUID[] appendUIDMessages(Message[] msgs) 1443 throws MessagingException { 1444 checkExists(); // verify that self exists 1445 1446 // XXX - have to verify that messages are in a different 1447 // store (if any) than target folder, otherwise could 1448 // deadlock trying to fetch messages on the same connection 1449 // we're using for the append. 1450 1451 int maxsize = ((IMAPStore)store).getAppendBufferSize(); 1452 1453 AppendUID[] uids = new AppendUID[msgs.length]; 1454 for (int i = 0; i < msgs.length; i++) { 1455 final Message m = msgs[i]; 1456 final MessageLiteral mos; 1457 1458 try { 1459 // if we know the message is too big, don't buffer any of it 1460 mos = new MessageLiteral(m, 1461 m.getSize() > maxsize ? 0 : maxsize); 1462 } catch (IOException ex) { 1463 throw new MessagingException( 1464 "IOException while appending messages", ex); 1465 } catch (MessageRemovedException mrex) { 1466 continue; // just skip this expunged message 1467 } 1468 1469 Date d = m.getReceivedDate(); // retain dates 1470 if (d == null) 1471 d = m.getSentDate(); 1472 final Date dd = d; 1473 final Flags f = m.getFlags(); 1474 AppendUID auid = (AppendUID)doCommand(new ProtocolCommand() { 1475 public Object doCommand(IMAPProtocol p) 1476 throws ProtocolException { 1477 return p.appenduid(fullName, f, dd, mos); 1478 } 1479 }); 1480 uids[i] = auid; 1481 } 1482 return uids; 1483 } 1484 1485 /** 1486 * Append the given messages into this folder. 1487 * Return array of Message objects representing 1488 * the messages in the destination folder. Note 1489 * that the folder must be open. 1490 * Each element of the returned array corresponds to 1491 * an element of the <code>msgs</code> array. A null 1492 * element means the server didn't return UID information 1493 * for the appended message. <p> 1494 * 1495 * Depends on the APPENDUID response code defined by the 1496 * UIDPLUS extension - 1497 * <A HREF="http://www.ietf.org/rfc/rfc2359.txt">RFC 2359</A>. 1498 * 1499 * @since JavaMail 1.4 1500 */ 1501 public synchronized Message[] addMessages(Message[] msgs) 1502 throws MessagingException { 1503 checkOpened(); 1504 Message[] rmsgs = new MimeMessage[msgs.length]; 1505 AppendUID[] uids = appendUIDMessages(msgs); 1506 for (int i = 0; i < uids.length; i++) { 1507 AppendUID auid = uids[i]; 1508 if (auid != null) { 1509 if (auid.uidvalidity == uidvalidity) { 1510 try { 1511 rmsgs[i] = getMessageByUID(auid.uid); 1512 } catch (MessagingException mex) { 1513 // ignore errors at this stage 1514 } 1515 } 1516 } 1517 } 1518 return rmsgs; 1519 } 1520 1521 /** 1522 * Copy the specified messages from this folder, to the 1523 * specified destination. 1524 */ 1525 public synchronized void copyMessages(Message[] msgs, Folder folder) 1526 throws MessagingException { 1527 checkOpened(); 1528 1529 if (msgs.length == 0) // boundary condition 1530 return; 1531 1532 // If the destination belongs to our same store, optimize 1533 if (folder.getStore() == store) { 1534 synchronized(messageCacheLock) { 1535 try { 1536 IMAPProtocol p = getProtocol(); 1537 MessageSet[] ms = Utility.toMessageSet(msgs, null); 1538 if (ms == null) 1539 throw new MessageRemovedException( 1540 "Messages have been removed"); 1541 p.copy(ms, folder.getFullName()); 1542 } catch (CommandFailedException cfx) { 1543 if (cfx.getMessage().indexOf("TRYCREATE") != -1) 1544 throw new FolderNotFoundException( 1545 folder, 1546 folder.getFullName() + " does not exist" 1547 ); 1548 else 1549 throw new MessagingException(cfx.getMessage(), cfx); 1550 } catch (ConnectionException cex) { 1551 throw new FolderClosedException(this, cex.getMessage()); 1552 } catch (ProtocolException pex) { 1553 throw new MessagingException(pex.getMessage(), pex); 1554 } 1555 } 1556 } else // destination is a different store. 1557 super.copyMessages(msgs, folder); 1558 } 1559 1560 /** 1561 * Expunge all messages marked as DELETED. 1562 */ 1563 public synchronized Message[] expunge() throws MessagingException { 1564 return expunge(null); 1565 } 1566 1567 /** 1568 * Expunge the indicated messages, which must have been marked as DELETED. 1569 */ 1570 public synchronized Message[] expunge(Message[] msgs) 1571 throws MessagingException { 1572 checkOpened(); 1573 1574 Vector v = new Vector(); // to collect expunged messages 1575 1576 if (msgs != null) { 1577 // call fetch to make sure we have all the UIDs 1578 FetchProfile fp = new FetchProfile(); 1579 fp.add(UIDFolder.FetchProfileItem.UID); 1580 fetch(msgs, fp); 1581 } 1582 1583 synchronized(messageCacheLock) { 1584 doExpungeNotification = false; // We do this ourselves later 1585 try { 1586 IMAPProtocol p = getProtocol(); 1587 if (msgs != null) 1588 p.uidexpunge(Utility.toUIDSet(msgs)); 1589 else 1590 p.expunge(); 1591 } catch (CommandFailedException cfx) { 1592 // expunge not allowed, perhaps due to a permission problem? 1593 if (mode != READ_WRITE) 1594 throw new IllegalStateException( 1595 "Cannot expunge READ_ONLY folder: " + fullName); 1596 else 1597 throw new MessagingException(cfx.getMessage(), cfx); 1598 } catch (ConnectionException cex) { 1599 throw new FolderClosedException(this, cex.getMessage()); 1600 } catch (ProtocolException pex) { 1601 // Bad bad server .. 1602 throw new MessagingException(pex.getMessage(), pex); 1603 } finally { 1604 doExpungeNotification = true; 1605 } 1606 1607 // Cleanup expunged messages and sync messageCache with 1608 // reality. 1609 for (int i = 0; i < messageCache.size(); ) { 1610 IMAPMessage m = (IMAPMessage)messageCache.elementAt(i); 1611 if (m.isExpunged()) { 1612 v.addElement(m); // add into vector of expunged messages 1613 1614 /* remove this message from the messageCache. 1615 * 1616 * Note that this also causes all succeeding messages 1617 * in the cache to shifted downward in the vector, 1618 * therby decrementing the vector's size. (and hence 1619 * we need to do messageCache.size() at the top of 1620 * this loop. 1621 */ 1622 messageCache.removeElementAt(i); 1623 1624 /* remove this message from the UIDTable */ 1625 if (uidTable != null) { 1626 long uid = m.getUID(); 1627 if (uid != -1) 1628 uidTable.remove(new Long(uid)); 1629 } 1630 } else { 1631 /* Valid message, sync its message number with 1632 * its sequence number. 1633 */ 1634 m.setMessageNumber(m.getSequenceNumber()); 1635 i++; // done; increment index, go check next message 1636 } 1637 } 1638 } 1639 1640 // Update 'total' 1641 total = messageCache.size(); 1642 1643 // Notify listeners. This time its for real, guys. 1644 Message[] rmsgs = new Message[v.size()]; 1645 v.copyInto(rmsgs); 1646 if (rmsgs.length > 0) 1647 notifyMessageRemovedListeners(true, rmsgs); 1648 return rmsgs; 1649 } 1650 1651 /** 1652 * Search whole folder for messages matching the given term. 1653 */ 1654 public synchronized Message[] search(SearchTerm term) 1655 throws MessagingException { 1656 checkOpened(); 1657 1658 try { 1659 Message[] matchMsgs = null; 1660 1661 synchronized(messageCacheLock) { 1662 int[] matches = getProtocol().search(term); 1663 if (matches != null) { 1664 matchMsgs = new IMAPMessage[matches.length]; 1665 // Map seq-numbers into actual Messages. 1666 for (int i = 0; i < matches.length; i++) 1667 matchMsgs[i] = getMessageBySeqNumber(matches[i]); 1668 } 1669 } 1670 return matchMsgs; 1671 1672 } catch (CommandFailedException cfx) { 1673 // unsupported charset or search criterion 1674 return super.search(term); 1675 } catch (SearchException sex) { 1676 // too complex for IMAP 1677 return super.search(term); 1678 } catch (ConnectionException cex) { 1679 throw new FolderClosedException(this, cex.getMessage()); 1680 } catch (ProtocolException pex) { 1681 // bug in our IMAP layer ? 1682 throw new MessagingException(pex.getMessage(), pex); 1683 } 1684 } 1685 1686 /** 1687 * Search the folder for messages matching the given term. Returns 1688 * array of matching messages. Returns an empty array if no matching 1689 * messages are found. 1690 */ 1691 public synchronized Message[] search(SearchTerm term, Message[] msgs) 1692 throws MessagingException { 1693 checkOpened(); 1694 1695 if (msgs.length == 0) 1696 // need to return an empty array (not null!) 1697 return msgs; 1698 1699 try { 1700 Message[] matchMsgs = null; 1701 1702 synchronized(messageCacheLock) { 1703 IMAPProtocol p = getProtocol(); 1704 MessageSet[] ms = Utility.toMessageSet(msgs, null); 1705 if (ms == null) 1706 throw new MessageRemovedException( 1707 "Messages have been removed"); 1708 int[] matches = p.search(ms, term); 1709 if (matches != null) { 1710 matchMsgs = new IMAPMessage[matches.length]; 1711 for (int i = 0; i < matches.length; i++) 1712 matchMsgs[i] = getMessageBySeqNumber(matches[i]); 1713 } 1714 } 1715 return matchMsgs; 1716 1717 } catch (CommandFailedException cfx) { 1718 // unsupported charset or search criterion 1719 return super.search(term, msgs); 1720 } catch (SearchException sex) { 1721 // too complex for IMAP 1722 return super.search(term, msgs); 1723 } catch (ConnectionException cex) { 1724 throw new FolderClosedException(this, cex.getMessage()); 1725 } catch (ProtocolException pex) { 1726 // bug in our IMAP layer ? 1727 throw new MessagingException(pex.getMessage(), pex); 1728 } 1729 } 1730 1731 /*********************************************************** 1732 * UIDFolder interface methods 1733 **********************************************************/ 1734 1735 /** 1736 * Returns the UIDValidity for this folder. 1737 */ 1738 public synchronized long getUIDValidity() throws MessagingException { 1739 if (opened) // we already have this information 1740 return uidvalidity; 1741 1742 IMAPProtocol p = null; 1743 Status status = null; 1744 1745 try { 1746 p = getStoreProtocol(); // XXX 1747 String[] item = { "UIDVALIDITY" }; 1748 status = p.status(fullName, item); 1749 } catch (BadCommandException bex) { 1750 // Probably a RFC1730 server 1751 throw new MessagingException("Cannot obtain UIDValidity", bex); 1752 } catch (ConnectionException cex) { 1753 // Oops, the store or folder died on us. 1754 throwClosedException(cex); 1755 } catch (ProtocolException pex) { 1756 throw new MessagingException(pex.getMessage(), pex); 1757 } finally { 1758 releaseStoreProtocol(p); 1759 } 1760 1761 return status.uidvalidity; 1762 } 1763 1764 /** 1765 * Returns the predicted UID that will be assigned to the 1766 * next message that is appended to this folder. 1767 * If the folder is closed, the STATUS command is used to 1768 * retrieve this value. If the folder is open, the value 1769 * returned from the SELECT or EXAMINE command is returned. 1770 * Note that messages may have been appended to the folder 1771 * while it was open and thus this value may be out of 1772 * date. <p> 1773 * 1774 * Servers implementing RFC2060 likely won't return this value 1775 * when a folder is opened. Servers implementing RFC3501 1776 * should return this value when a folder is opened. <p> 1777 * 1778 * @return the UIDNEXT value, or -1 if unknown 1779 * @since JavaMail 1.3.3 1780 */ 1781 // Not a UIDFolder method, but still useful 1782 public synchronized long getUIDNext() throws MessagingException { 1783 if (opened) // we already have this information 1784 return uidnext; 1785 1786 IMAPProtocol p = null; 1787 Status status = null; 1788 1789 try { 1790 p = getStoreProtocol(); // XXX 1791 String[] item = { "UIDNEXT" }; 1792 status = p.status(fullName, item); 1793 } catch (BadCommandException bex) { 1794 // Probably a RFC1730 server 1795 throw new MessagingException("Cannot obtain UIDNext", bex); 1796 } catch (ConnectionException cex) { 1797 // Oops, the store or folder died on us. 1798 throwClosedException(cex); 1799 } catch (ProtocolException pex) { 1800 throw new MessagingException(pex.getMessage(), pex); 1801 } finally { 1802 releaseStoreProtocol(p); 1803 } 1804 1805 return status.uidnext; 1806 } 1807 1808 /** 1809 * Get the Message corresponding to the given UID. 1810 * If no such message exists, <code> null </code> is returned. 1811 */ 1812 public synchronized Message getMessageByUID(long uid) 1813 throws MessagingException { 1814 checkOpened(); // insure folder is open 1815 1816 IMAPMessage m = null; 1817 1818 try { 1819 synchronized(messageCacheLock) { 1820 Long l = new Long(uid); 1821 1822 if (uidTable != null) { 1823 // Check in uidTable 1824 m = (IMAPMessage)uidTable.get(l); 1825 if (m != null) // found it 1826 return m; 1827 } else 1828 uidTable = new Hashtable(); 1829 1830 // Check with the server 1831 // Issue UID FETCH command 1832 UID u = getProtocol().fetchSequenceNumber(uid); 1833 1834 if (u != null && u.seqnum <= total) { // Valid UID 1835 m = getMessageBySeqNumber(u.seqnum); 1836 m.setUID(u.uid); // set this message's UID .. 1837 // .. and put this into the hashtable 1838 uidTable.put(l, m); 1839 } 1840 } 1841 } catch(ConnectionException cex) { 1842 throw new FolderClosedException(this, cex.getMessage()); 1843 } catch (ProtocolException pex) { 1844 throw new MessagingException(pex.getMessage(), pex); 1845 } 1846 1847 return m; 1848 } 1849 1850 /** 1851 * Get the Messages specified by the given range. <p> 1852 * Returns Message objects for all valid messages in this range. 1853 * Returns an empty array if no messages are found. 1854 */ 1855 public synchronized Message[] getMessagesByUID(long start, long end) 1856 throws MessagingException { 1857 checkOpened(); // insure that folder is open 1858 1859 Message[] msgs; // array of messages to be returned 1860 1861 try { 1862 synchronized(messageCacheLock) { 1863 if (uidTable == null) 1864 uidTable = new Hashtable(); 1865 1866 // Issue UID FETCH for given range 1867 UID[] ua = getProtocol().fetchSequenceNumbers(start, end); 1868 1869 msgs = new Message[ua.length]; 1870 IMAPMessage m; 1871 // NOTE: Below must be within messageCacheLock region 1872 for (int i = 0; i < ua.length; i++) { 1873 m = getMessageBySeqNumber(ua[i].seqnum); 1874 m.setUID(ua[i].uid); 1875 msgs[i] = m; 1876 uidTable.put(new Long(ua[i].uid), m); 1877 } 1878 } 1879 } catch(ConnectionException cex) { 1880 throw new FolderClosedException(this, cex.getMessage()); 1881 } catch (ProtocolException pex) { 1882 throw new MessagingException(pex.getMessage(), pex); 1883 } 1884 1885 return msgs; 1886 } 1887 1888 /** 1889 * Get the Messages specified by the given array. <p> 1890 * 1891 * <code>uids.length()</code> elements are returned. 1892 * If any UID in the array is invalid, a <code>null</code> entry 1893 * is returned for that element. 1894 */ 1895 public synchronized Message[] getMessagesByUID(long[] uids) 1896 throws MessagingException { 1897 checkOpened(); // insure that folder is open 1898 1899 try { 1900 synchronized(messageCacheLock) { 1901 long[] unavailUids = uids; 1902 if (uidTable != null) { 1903 Vector v = new Vector(); // to collect unavailable UIDs 1904 Long l; 1905 for (int i = 0; i < uids.length; i++) { 1906 if (!uidTable.containsKey(l = new Long(uids[i]))) 1907 // This UID has not been loaded yet. 1908 v.addElement(l); 1909 } 1910 1911 int vsize = v.size(); 1912 unavailUids = new long[vsize]; 1913 for (int i = 0; i < vsize; i++) 1914 unavailUids[i] = ((Long)v.elementAt(i)).longValue(); 1915 } else 1916 uidTable = new Hashtable(); 1917 1918 if (unavailUids.length > 0) { 1919 // Issue UID FETCH request for given uids 1920 UID[] ua = getProtocol().fetchSequenceNumbers(unavailUids); 1921 IMAPMessage m; 1922 for (int i = 0; i < ua.length; i++) { 1923 m = getMessageBySeqNumber(ua[i].seqnum); 1924 m.setUID(ua[i].uid); 1925 uidTable.put(new Long(ua[i].uid), m); 1926 } 1927 } 1928 1929 // Return array of size = uids.length 1930 Message[] msgs = new Message[uids.length]; 1931 for (int i = 0; i < uids.length; i++) 1932 msgs[i] = (Message)uidTable.get(new Long(uids[i])); 1933 return msgs; 1934 } 1935 } catch(ConnectionException cex) { 1936 throw new FolderClosedException(this, cex.getMessage()); 1937 } catch (ProtocolException pex) { 1938 throw new MessagingException(pex.getMessage(), pex); 1939 } 1940 } 1941 1942 /** 1943 * Get the UID for the specified message. 1944 */ 1945 public synchronized long getUID(Message message) 1946 throws MessagingException { 1947 if (message.getFolder() != this) 1948 throw new NoSuchElementException( 1949 "Message does not belong to this folder"); 1950 1951 checkOpened(); // insure that folder is open 1952 1953 IMAPMessage m = (IMAPMessage)message; 1954 // If the message already knows its UID, great .. 1955 long uid; 1956 if ((uid = m.getUID()) != -1) 1957 return uid; 1958 1959 synchronized(messageCacheLock) { // Acquire Lock 1960 try { 1961 IMAPProtocol p = getProtocol(); 1962 m.checkExpunged(); // insure that message is not expunged 1963 UID u = p.fetchUID(m.getSequenceNumber()); 1964 1965 if (u != null) { 1966 uid = u.uid; 1967 m.setUID(uid); // set message's UID 1968 1969 // insert this message into uidTable 1970 if (uidTable == null) 1971 uidTable = new Hashtable(); 1972 uidTable.put(new Long(uid), m); 1973 } 1974 } catch (ConnectionException cex) { 1975 throw new FolderClosedException(this, cex.getMessage()); 1976 } catch (ProtocolException pex) { 1977 throw new MessagingException(pex.getMessage(), pex); 1978 } 1979 } 1980 1981 return uid; 1982 } 1983 1984 /** 1985 * Get the quotas for the quotaroot associated with this 1986 * folder. Note that many folders may have the same quotaroot. 1987 * Quotas are controlled on the basis of a quotaroot, not 1988 * (necessarily) a folder. The relationship between folders 1989 * and quotaroots depends on the IMAP server. Some servers 1990 * might implement a single quotaroot for all folders owned by 1991 * a user. Other servers might implement a separate quotaroot 1992 * for each folder. A single folder can even have multiple 1993 * quotaroots, perhaps controlling quotas for different 1994 * resources. 1995 * 1996 * @return array of Quota objects for the quotaroots associated with 1997 * this folder 1998 * @exception MessagingException if the server doesn't support the 1999 * QUOTA extension 2000 */ 2001 public Quota[] getQuota() throws MessagingException { 2002 return (Quota[])doOptionalCommand("QUOTA not supported", 2003 new ProtocolCommand() { 2004 public Object doCommand(IMAPProtocol p) 2005 throws ProtocolException { 2006 return p.getQuotaRoot(fullName); 2007 } 2008 }); 2009 } 2010 2011 /** 2012 * Set the quotas for the quotaroot specified in the quota argument. 2013 * Typically this will be one of the quotaroots associated with this 2014 * folder, as obtained from the <code>getQuota</code> method, but it 2015 * need not be. 2016 * 2017 * @param quota the quota to set 2018 * @exception MessagingException if the server doesn't support the 2019 * QUOTA extension 2020 */ 2021 public void setQuota(final Quota quota) throws MessagingException { 2022 doOptionalCommand("QUOTA not supported", 2023 new ProtocolCommand() { 2024 public Object doCommand(IMAPProtocol p) 2025 throws ProtocolException { 2026 p.setQuota(quota); 2027 return null; 2028 } 2029 }); 2030 } 2031 2032 /** 2033 * Get the access control list entries for this folder. 2034 * 2035 * @return array of access control list entries 2036 * @exception MessagingException if the server doesn't support the 2037 * ACL extension 2038 */ 2039 public ACL[] getACL() throws MessagingException { 2040 return (ACL[])doOptionalCommand("ACL not supported", 2041 new ProtocolCommand() { 2042 public Object doCommand(IMAPProtocol p) 2043 throws ProtocolException { 2044 return p.getACL(fullName); 2045 } 2046 }); 2047 } 2048 2049 /** 2050 * Add an access control list entry to the access control list 2051 * for this folder. 2052 * 2053 * @param acl the access control list entry to add 2054 * @exception MessagingException if the server doesn't support the 2055 * ACL extension 2056 */ 2057 public void addACL(ACL acl) throws MessagingException { 2058 setACL(acl, '\0'); 2059 } 2060 2061 /** 2062 * Remove any access control list entry for the given identifier 2063 * from the access control list for this folder. 2064 * 2065 * @param name the identifier for which to remove all ACL entries 2066 * @exception MessagingException if the server doesn't support the 2067 * ACL extension 2068 */ 2069 public void removeACL(final String name) throws MessagingException { 2070 doOptionalCommand("ACL not supported", 2071 new ProtocolCommand() { 2072 public Object doCommand(IMAPProtocol p) 2073 throws ProtocolException { 2074 p.deleteACL(fullName, name); 2075 return null; 2076 } 2077 }); 2078 } 2079 2080 /** 2081 * Add the rights specified in the ACL to the entry for the 2082 * identifier specified in the ACL. If an entry for the identifier 2083 * doesn't already exist, add one. 2084 * 2085 * @param acl the identifer and rights to add 2086 * @exception MessagingException if the server doesn't support the 2087 * ACL extension 2088 */ 2089 public void addRights(ACL acl) throws MessagingException { 2090 setACL(acl, '+'); 2091 } 2092 2093 /** 2094 * Remove the rights specified in the ACL from the entry for the 2095 * identifier specified in the ACL. 2096 * 2097 * @param acl the identifer and rights to remove 2098 * @exception MessagingException if the server doesn't support the 2099 * ACL extension 2100 */ 2101 public void removeRights(ACL acl) throws MessagingException { 2102 setACL(acl, '-'); 2103 } 2104 2105 /** 2106 * Get all the rights that may be allowed to the given identifier. 2107 * Rights are grouped per RFC 2086 and each group is returned as an 2108 * element of the array. The first element of the array is the set 2109 * of rights that are always granted to the identifier. Later 2110 * elements are rights that may be optionally granted to the 2111 * identifier. <p> 2112 * 2113 * Note that this method lists the rights that it is possible to 2114 * assign to the given identifier, <em>not</em> the rights that are 2115 * actually granted to the given identifier. For the latter, see 2116 * the <code>getACL</code> method. 2117 * 2118 * @param name the identifier to list rights for 2119 * @return array of Rights objects representing possible 2120 * rights for the identifier 2121 * @exception MessagingException if the server doesn't support the 2122 * ACL extension 2123 */ 2124 public Rights[] listRights(final String name) throws MessagingException { 2125 return (Rights[])doOptionalCommand("ACL not supported", 2126 new ProtocolCommand() { 2127 public Object doCommand(IMAPProtocol p) 2128 throws ProtocolException { 2129 return p.listRights(fullName, name); 2130 } 2131 }); 2132 } 2133 2134 /** 2135 * Get the rights allowed to the currently authenticated user. 2136 * 2137 * @return the rights granted to the current user 2138 * @exception MessagingException if the server doesn't support the 2139 * ACL extension 2140 */ 2141 public Rights myRights() throws MessagingException { 2142 return (Rights)doOptionalCommand("ACL not supported", 2143 new ProtocolCommand() { 2144 public Object doCommand(IMAPProtocol p) 2145 throws ProtocolException { 2146 return p.myRights(fullName); 2147 } 2148 }); 2149 } 2150 2151 private void setACL(final ACL acl, final char mod) 2152 throws MessagingException { 2153 doOptionalCommand("ACL not supported", 2154 new ProtocolCommand() { 2155 public Object doCommand(IMAPProtocol p) 2156 throws ProtocolException { 2157 p.setACL(fullName, mod, acl); 2158 return null; 2159 } 2160 }); 2161 } 2162 2163 /** 2164 * Get the attributes that the IMAP server returns with the 2165 * LIST response. 2166 * 2167 * @since JavaMail 1.3.3 2168 */ 2169 public String[] getAttributes() throws MessagingException { 2170 if (attributes == null) 2171 exists(); // do a LIST to set the attributes 2172 return (String[])(attributes.clone()); 2173 } 2174 2175 /** 2176 * Use the IMAP IDLE command (see 2177 * <A HREF="http://www.ietf.org/rfc/rfc2177.txt">RFC 2177</A>), 2178 * if supported by the server, to enter idle mode so that the server 2179 * can send unsolicited notifications of new messages arriving, etc. 2180 * without the need for the client to constantly poll the server. 2181 * Use an appropriate listener to be notified of new messages or 2182 * other events. When another thread (e.g., the listener thread) 2183 * needs to issue an IMAP comand for this folder, the idle mode will 2184 * be terminated and this method will return. Typically the caller 2185 * will invoke this method in a loop. <p> 2186 * 2187 * The mail.imap.minidletime property enforces a minimum delay 2188 * before returning from this method, to ensure that other threads 2189 * have a chance to issue commands before the caller invokes this 2190 * method again. The default delay is 10 milliseconds. 2191 * 2192 * @exception MessagingException if the server doesn't support the 2193 * IDLE extension 2194 * @exception IllegalStateException if the folder isn't open 2195 * 2196 * @since JavaMail 1.4.1 2197 */ 2198 public void idle() throws MessagingException { 2199 // ASSERT: Must NOT be called with this folder's 2200 // synchronization lock held. 2201 assert !Thread.holdsLock(this); 2202 synchronized(this) { 2203 checkOpened(); 2204 Boolean started = (Boolean)doOptionalCommand("IDLE not supported", 2205 new ProtocolCommand() { 2206 public Object doCommand(IMAPProtocol p) 2207 throws ProtocolException { 2208 if (idleState == RUNNING) { 2209 p.idleStart(); 2210 idleState = IDLE; 2211 return Boolean.TRUE; 2212 } else { 2213 // some other thread must be running the IDLE 2214 // command, we'll just wait for it to finish 2215 // without aborting it ourselves 2216 try { 2217 // give up lock and wait to be not idle 2218 messageCacheLock.wait(); 2219 } catch (InterruptedException ex) { } 2220 return Boolean.FALSE; 2221 } 2222 } 2223 }); 2224 if (!started.booleanValue()) 2225 return; 2226 } 2227 2228 /* 2229 * We gave up the folder lock so that other threads 2230 * can get into the folder far enough to see that we're 2231 * in IDLE and abort the IDLE. 2232 * 2233 * Now we read responses from the IDLE command, especially 2234 * including unsolicited notifications from the server. 2235 * We don't hold the messageCacheLock while reading because 2236 * it protects the idleState and other threads need to be 2237 * able to examine the state. 2238 * 2239 * We hold the messageCacheLock while processing the 2240 * responses so that we can update the number of messages 2241 * in the folder (for example). 2242 */ 2243 for (;;) { 2244 Response r = protocol.readIdleResponse(); 2245 try { 2246 synchronized (messageCacheLock) { 2247 if (r == null || protocol == null || 2248 !protocol.processIdleResponse(r)) { 2249 idleState = RUNNING; 2250 messageCacheLock.notifyAll(); 2251 break; 2252 } 2253 } 2254 } catch (ConnectionException cex) { 2255 // Oops, the store or folder died on us. 2256 throwClosedException(cex); 2257 } catch (ProtocolException pex) { 2258 throw new MessagingException(pex.getMessage(), pex); 2259 } 2260 } 2261 2262 /* 2263 * Enforce a minimum delay to give time to threads 2264 * processing the responses that came in while we 2265 * were idle. 2266 */ 2267 int minidle = ((IMAPStore)store).getMinIdleTime(); 2268 if (minidle > 0) { 2269 try { 2270 Thread.sleep(minidle); 2271 } catch (InterruptedException ex) { } 2272 } 2273 } 2274 2275 /* 2276 * If an IDLE command is in progress, abort it if necessary, 2277 * and wait until it completes. 2278 * ASSERT: Must be called with the message cache lock held. 2279 */ 2280 void waitIfIdle() throws ProtocolException { 2281 assert Thread.holdsLock(messageCacheLock); 2282 while (idleState != RUNNING) { 2283 if (idleState == IDLE) { 2284 protocol.idleAbort(); 2285 idleState = ABORTING; 2286 } 2287 try { 2288 // give up lock and wait to be not idle 2289 messageCacheLock.wait(); 2290 } catch (InterruptedException ex) { } 2291 } 2292 } 2293 2294 /** 2295 * The response handler. This is the callback routine that is 2296 * invoked by the protocol layer. 2297 */ 2298 /* 2299 * ASSERT: This method must be called only when holding the 2300 * messageCacheLock. 2301 * ASSERT: This method must *not* invoke any other method that 2302 * might grab the 'folder' lock or 'message' lock (i.e., any 2303 * synchronized methods on IMAPFolder or IMAPMessage) 2304 * since that will result in violating the locking hierarchy. 2305 */ 2306 public void handleResponse(Response r) { 2307 assert Thread.holdsLock(messageCacheLock); 2308 2309 /* 2310 * First, delegate possible ALERT or notification to the Store. 2311 */ 2312 if (r.isOK() || r.isNO() || r.isBAD() || r.isBYE()) 2313 ((IMAPStore)store).handleResponseCode(r); 2314 2315 /* 2316 * Now check whether this is a BYE or OK response and 2317 * handle appropriately. 2318 */ 2319 if (r.isBYE()) { 2320 if (opened) // XXX - accessed without holding folder lock 2321 cleanup(false); 2322 return; 2323 } else if (r.isOK()) { 2324 return; 2325 } else if (!r.isUnTagged()) { 2326 return; // XXX - should never happen 2327 } 2328 2329 /* Now check whether this is an IMAP specific response */ 2330 if (!(r instanceof IMAPResponse)) { 2331 // Probably a bug in our code ! 2332 // XXX - should be an assert 2333 out.println("UNEXPECTED RESPONSE : " + r.toString()); 2334 out.println("CONTACT javamail@sun.com"); 2335 return; 2336 } 2337 2338 IMAPResponse ir = (IMAPResponse)r; 2339 2340 if (ir.keyEquals("EXISTS")) { // EXISTS 2341 int exists = ir.getNumber(); 2342 if (exists <= realTotal) 2343 // Could be the EXISTS following EXPUNGE, ignore 'em 2344 return; 2345 2346 int count = exists - realTotal; // number of new messages 2347 Message[] msgs = new Message[count]; 2348 2349 // Add 'count' new IMAPMessage objects into the messageCache 2350 for (int i = 0; i < count; i++) { 2351 // Note that as a side-effect, we also increment 2352 // total & realTotal 2353 IMAPMessage msg = new IMAPMessage(this, ++total, ++realTotal); 2354 msgs[i] = msg; 2355 messageCache.addElement(msg); 2356 } 2357 2358 // Notify listeners. 2359 notifyMessageAddedListeners(msgs); 2360 2361 } else if (ir.keyEquals("EXPUNGE")) { 2362 // EXPUNGE response. 2363 2364 IMAPMessage msg = getMessageBySeqNumber(ir.getNumber()); 2365 msg.setExpunged(true); // mark this message expunged. 2366 2367 // Renumber the cache, starting from just beyond 2368 // the expunged message. 2369 for (int i = msg.getMessageNumber(); i < total; i++) { 2370 // Note that 'i' actually indexes the message 2371 // beyond the expunged message. 2372 IMAPMessage m = (IMAPMessage)messageCache.elementAt(i); 2373 if (m.isExpunged()) // an expunged message, skip 2374 continue; 2375 2376 // Decrement this message's seqnum 2377 m.setSequenceNumber(m.getSequenceNumber() - 1); 2378 } // Whew, done. 2379 2380 // decrement 'realTotal'; but leave 'total' unchanged 2381 realTotal--; 2382 2383 if (doExpungeNotification) { 2384 // Do the notification here. 2385 Message[] msgs = {msg}; 2386 notifyMessageRemovedListeners(false, msgs); 2387 } 2388 2389 } else if (ir.keyEquals("FETCH")) { 2390 // The only unsolicited FETCH response that makes sense 2391 // to me (for now) is FLAGS updates. Ignore any other junk. 2392 assert ir instanceof FetchResponse : "!ir instanceof FetchResponse"; 2393 FetchResponse f = (FetchResponse)ir; 2394 // Get FLAGS response, if present 2395 Flags flags = (Flags)f.getItem(Flags.class); 2396 2397 if (flags != null) { 2398 IMAPMessage msg = getMessageBySeqNumber(f.getNumber()); 2399 if (msg != null) { // should always be true 2400 msg._setFlags(flags); 2401 notifyMessageChangedListeners( 2402 MessageChangedEvent.FLAGS_CHANGED, msg); 2403 } 2404 } 2405 2406 } else if (ir.keyEquals("RECENT")) { 2407 // update 'recent' 2408 recent = ir.getNumber(); 2409 } 2410 } 2411 2412 /** 2413 * Handle the given array of Responses. 2414 * 2415 * ASSERT: This method must be called only when holding the 2416 * messageCacheLock 2417 */ 2418 void handleResponses(Response[] r) { 2419 for (int i = 0; i < r.length; i++) { 2420 if (r[i] != null) 2421 handleResponse(r[i]); 2422 } 2423 } 2424 2425 /** 2426 * Get this folder's Store's protocol connection. 2427 * 2428 * When acquiring a store protocol object, it is important to 2429 * use the following steps: 2430 * 2431 * IMAPProtocol p = null; 2432 * try { 2433 * p = getStoreProtocol(); 2434 * // perform the command 2435 * } catch (WhateverException ex) { 2436 * // handle it 2437 * } finally { 2438 * releaseStoreProtocol(p); 2439 * } 2440 */ 2441 protected synchronized IMAPProtocol getStoreProtocol() 2442 throws ProtocolException { 2443 if (connectionPoolDebug) { 2444 out.println("DEBUG: getStoreProtocol() - " + 2445 "borrowing a connection"); 2446 } 2447 return ((IMAPStore)store).getStoreProtocol(); 2448 } 2449 2450 /** 2451 * Throw the appropriate 'closed' exception. 2452 */ 2453 private synchronized void throwClosedException(ConnectionException cex) 2454 throws FolderClosedException, StoreClosedException { 2455 // If it's the folder's protocol object, throw a FolderClosedException; 2456 // otherwise, throw a StoreClosedException. 2457 // If a command has failed because the connection is closed, 2458 // the folder will have already been forced closed by the 2459 // time we get here and our protocol object will have been 2460 // released, so if we no longer have a protocol object we base 2461 // this decision on whether we *think* the folder is open. 2462 if ((protocol != null && cex.getProtocol() == protocol) || 2463 (protocol == null && !reallyClosed)) 2464 throw new FolderClosedException(this, cex.getMessage()); 2465 else 2466 throw new StoreClosedException(store, cex.getMessage()); 2467 } 2468 2469 /** 2470 * Return the IMAPProtocol object for this folder. <p> 2471 * 2472 * This method will block if necessary to wait for an IDLE 2473 * command to finish. 2474 * 2475 * @return the IMAPProtocol object used when the folder is open 2476 */ 2477 private IMAPProtocol getProtocol() throws ProtocolException { 2478 assert Thread.holdsLock(messageCacheLock); 2479 waitIfIdle(); 2480 return protocol; 2481 } 2482 2483 /** 2484 * A simple interface for user-defined IMAP protocol commands. 2485 */ 2486 public static interface ProtocolCommand { 2487 /** 2488 * Execute the user-defined command using the supplied IMAPProtocol 2489 * object. 2490 */ 2491 public Object doCommand(IMAPProtocol protocol) throws ProtocolException; 2492 } 2493 2494 /** 2495 * Execute a user-supplied IMAP command. The command is executed 2496 * in the appropriate context with the necessary locks held and 2497 * using the appropriate <code>IMAPProtocol</code> object. <p> 2498 * 2499 * This method returns whatever the <code>ProtocolCommand</code> 2500 * object's <code>doCommand</code> method returns. If the 2501 * <code>doCommand</code> method throws a <code>ConnectionException</code> 2502 * it is translated into a <code>StoreClosedException</code> or 2503 * <code>FolderClosedException</code> as appropriate. If the 2504 * <code>doCommand</code> method throws a <code>ProtocolException</code> 2505 * it is translated into a <code>MessagingException</code>. <p> 2506 * 2507 * The following example shows how to execute the IMAP NOOP command. 2508 * Executing more complex IMAP commands requires intimate knowledge 2509 * of the <code>com.sun.mail.iap</code> and 2510 * <code>com.sun.mail.imap.protocol</code> packages, best acquired by 2511 * reading the source code. <p> 2512 * 2513 * <blockquote><pre> 2514 * import com.sun.mail.iap.*; 2515 * import com.sun.mail.imap.*; 2516 * import com.sun.mail.imap.protocol.*; 2517 * 2518 * ... 2519 * 2520 * IMAPFolder f = (IMAPFolder)folder; 2521 * Object val = f.doCommand(new IMAPFolder.ProtocolCommand() { 2522 * public Object doCommand(IMAPProtocol p) 2523 * throws ProtocolException { 2524 * p.simpleCommand("NOOP", null); 2525 * return null; 2526 * } 2527 * }); 2528 * </pre></blockquote> 2529 * <p> 2530 * 2531 * Here's a more complex example showing how to use the proposed 2532 * IMAP SORT extension: <p> 2533 * 2534 * <pre><blockquote> 2535 * import com.sun.mail.iap.*; 2536 * import com.sun.mail.imap.*; 2537 * import com.sun.mail.imap.protocol.*; 2538 * 2539 * ... 2540 * 2541 * IMAPFolder f = (IMAPFolder)folder; 2542 * Object val = f.doCommand(new IMAPFolder.ProtocolCommand() { 2543 * public Object doCommand(IMAPProtocol p) 2544 * throws ProtocolException { 2545 * // Issue command 2546 * Argument args = new Argument(); 2547 * Argument list = new Argument(); 2548 * list.writeString("SUBJECT"); 2549 * args.writeArgument(list); 2550 * args.writeString("UTF-8"); 2551 * args.writeString("ALL"); 2552 * Response[] r = p.command("SORT", args); 2553 * Response response = r[r.length-1]; 2554 * 2555 * // Grab response 2556 * Vector v = new Vector(); 2557 * if (response.isOK()) { // command succesful 2558 * for (int i = 0, len = r.length; i < len; i++) { 2559 * if (!(r[i] instanceof IMAPResponse)) 2560 * continue; 2561 * 2562 * IMAPResponse ir = (IMAPResponse)r[i]; 2563 * if (ir.keyEquals("SORT")) { 2564 * String num; 2565 * while ((num = ir.readAtomString()) != null) 2566 * System.out.println(num); 2567 * r[i] = null; 2568 * } 2569 * } 2570 * } 2571 * 2572 * // dispatch remaining untagged responses 2573 * p.notifyResponseHandlers(r); 2574 * p.handleResult(response); 2575 * 2576 * return null; 2577 * } 2578 * }); 2579 * </pre></blockquote> 2580 */ 2581 public Object doCommand(ProtocolCommand cmd) throws MessagingException { 2582 try { 2583 return doProtocolCommand(cmd); 2584 } catch (ConnectionException cex) { 2585 // Oops, the store or folder died on us. 2586 throwClosedException(cex); 2587 } catch (ProtocolException pex) { 2588 throw new MessagingException(pex.getMessage(), pex); 2589 } 2590 return null; 2591 } 2592 2593 public Object doOptionalCommand(String err, ProtocolCommand cmd) 2594 throws MessagingException { 2595 try { 2596 return doProtocolCommand(cmd); 2597 } catch (BadCommandException bex) { 2598 throw new MessagingException(err, bex); 2599 } catch (ConnectionException cex) { 2600 // Oops, the store or folder died on us. 2601 throwClosedException(cex); 2602 } catch (ProtocolException pex) { 2603 throw new MessagingException(pex.getMessage(), pex); 2604 } 2605 return null; 2606 } 2607 2608 public Object doCommandIgnoreFailure(ProtocolCommand cmd) 2609 throws MessagingException { 2610 try { 2611 return doProtocolCommand(cmd); 2612 } catch (CommandFailedException cfx) { 2613 return null; 2614 } catch (ConnectionException cex) { 2615 // Oops, the store or folder died on us. 2616 throwClosedException(cex); 2617 } catch (ProtocolException pex) { 2618 throw new MessagingException(pex.getMessage(), pex); 2619 } 2620 return null; 2621 } 2622 2623 protected Object doProtocolCommand(ProtocolCommand cmd) 2624 throws ProtocolException { 2625 synchronized (this) { 2626 // XXX - why does "&& !hasSeparateStoreConnection" make sense? 2627 if (opened && !((IMAPStore)store).hasSeparateStoreConnection()) { 2628 synchronized (messageCacheLock) { 2629 return cmd.doCommand(getProtocol()); 2630 } 2631 } 2632 } 2633 2634 // only get here if using store's connection 2635 IMAPProtocol p = null; 2636 2637 try { 2638 p = getStoreProtocol(); 2639 return cmd.doCommand(p); 2640 } finally { 2641 releaseStoreProtocol(p); 2642 } 2643 } 2644 2645 /** 2646 * Release the store protocol object. If we borrowed a protocol 2647 * object from the connection pool, give it back. If we used our 2648 * own protocol object, nothing to do. 2649 */ 2650 protected synchronized void releaseStoreProtocol(IMAPProtocol p) { 2651 if (p != protocol) 2652 ((IMAPStore)store).releaseStoreProtocol(p); 2653 } 2654 2655 /** 2656 * Release the protocol object. 2657 * 2658 * ASSERT: This method must be called only when holding the 2659 * messageCacheLock 2660 */ 2661 private void releaseProtocol(boolean returnToPool) { 2662 if (protocol != null) { 2663 protocol.removeResponseHandler(this); 2664 2665 if (returnToPool) 2666 ((IMAPStore)store).releaseProtocol(this, protocol); 2667 else 2668 ((IMAPStore)store).releaseProtocol(this, null); 2669 } 2670 } 2671 2672 /** 2673 * Issue a noop command for the connection if the connection has not been 2674 * used in more than a second. If <code>keepStoreAlive</code> is true, 2675 * also issue a noop over the store's connection. 2676 * 2677 * ASSERT: This method must be called only when holding the 2678 * messageCacheLock 2679 */ 2680 private void keepConnectionAlive(boolean keepStoreAlive) 2681 throws ProtocolException { 2682 2683 if (System.currentTimeMillis() - protocol.getTimestamp() > 1000) { 2684 waitIfIdle(); 2685 protocol.noop(); 2686 } 2687 2688 if (keepStoreAlive && ((IMAPStore)store).hasSeparateStoreConnection()) { 2689 IMAPProtocol p = null; 2690 try { 2691 p = ((IMAPStore)store).getStoreProtocol(); 2692 if (System.currentTimeMillis() - p.getTimestamp() > 1000) 2693 p.noop(); 2694 } finally { 2695 ((IMAPStore)store).releaseStoreProtocol(p); 2696 } 2697 } 2698 } 2699 2700 /** 2701 * Get the message object for the given sequence number. If 2702 * none found, null is returned. 2703 * 2704 * ASSERT: This method must be called only when holding the 2705 * messageCacheLock 2706 */ 2707 IMAPMessage getMessageBySeqNumber(int seqnum) { 2708 /* Check messageCache for message matching the given 2709 * sequence number. We start the search from position (seqnum-1) 2710 * and continue down the vector till we get a match. 2711 */ 2712 for (int i = seqnum-1; i < total; i++) { 2713 IMAPMessage msg = (IMAPMessage)messageCache.elementAt(i); 2714 if (msg.getSequenceNumber() == seqnum) 2715 return msg; 2716 } 2717 return null; 2718 } 2719 2720 private boolean isDirectory() { 2721 return ((type & HOLDS_FOLDERS) != 0); 2722 } 2723 } 2724 2725 /** 2726 * An object that holds a Message object 2727 * and reports its size and writes it to another OutputStream 2728 * on demand. Used by appendMessages to avoid the need to 2729 * buffer the entire message in memory in a single byte array 2730 * before sending it to the server. 2731 */ 2732 class MessageLiteral implements Literal { 2733 private Message msg; 2734 private int msgSize = -1; 2735 private byte[] buf; // the buffered message, if not null 2736 2737 public MessageLiteral(Message msg, int maxsize) 2738 throws MessagingException, IOException { 2739 this.msg = msg; 2740 // compute the size here so exceptions can be returned immediately 2741 LengthCounter lc = new LengthCounter(maxsize); 2742 OutputStream os = new CRLFOutputStream(lc); 2743 msg.writeTo(os); 2744 os.flush(); 2745 msgSize = lc.getSize(); 2746 buf = lc.getBytes(); 2747 } 2748 2749 public int size() { 2750 return msgSize; 2751 } 2752 2753 public void writeTo(OutputStream os) throws IOException { 2754 // the message should not change between the constructor and this call 2755 try { 2756 if (buf != null) 2757 os.write(buf, 0, msgSize); 2758 else { 2759 os = new CRLFOutputStream(os); 2760 msg.writeTo(os); 2761 } 2762 } catch (MessagingException mex) { 2763 // exceptions here are bad, "should" never happen 2764 throw new IOException("MessagingException while appending message: " 2765 + mex); 2766 } 2767 } 2768 } 2769 2770 /** 2771 * Count the number of bytes written to the stream. 2772 * Also, save a copy of small messages to avoid having to process 2773 * the data again. 2774 */ 2775 class LengthCounter extends OutputStream { 2776 private int size = 0; 2777 private byte[] buf; 2778 private int maxsize; 2779 2780 public LengthCounter(int maxsize) { 2781 buf = new byte[8192]; 2782 this.maxsize = maxsize; 2783 } 2784 2785 public void write(int b) { 2786 int newsize = size + 1; 2787 if (buf != null) { 2788 if (newsize > maxsize && maxsize >= 0) { 2789 buf = null; 2790 } else if (newsize > buf.length) { 2791 byte newbuf[] = new byte[Math.max(buf.length << 1, newsize)]; 2792 System.arraycopy(buf, 0, newbuf, 0, size); 2793 buf = newbuf; 2794 buf[size] = (byte)b; 2795 } else { 2796 buf[size] = (byte)b; 2797 } 2798 } 2799 size = newsize; 2800 } 2801 2802 public void write(byte b[], int off, int len) { 2803 if ((off < 0) || (off > b.length) || (len < 0) || 2804 ((off + len) > b.length) || ((off + len) < 0)) { 2805 throw new IndexOutOfBoundsException(); 2806 } else if (len == 0) { 2807 return; 2808 } 2809 int newsize = size + len; 2810 if (buf != null) { 2811 if (newsize > maxsize && maxsize >= 0) { 2812 buf = null; 2813 } else if (newsize > buf.length) { 2814 byte newbuf[] = new byte[Math.max(buf.length << 1, newsize)]; 2815 System.arraycopy(buf, 0, newbuf, 0, size); 2816 buf = newbuf; 2817 System.arraycopy(b, off, buf, size, len); 2818 } else { 2819 System.arraycopy(b, off, buf, size, len); 2820 } 2821 } 2822 size = newsize; 2823 } 2824 2825 public void write(byte[] b) throws IOException { 2826 write(b, 0, b.length); 2827 } 2828 2829 public int getSize() { 2830 return size; 2831 } 2832 2833 public byte[] getBytes() { 2834 return buf; 2835 } 2836 }