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