Skip to contentPackage: IMAPStore$1
IMAPStore$1
Coverage
1: /*
2: * Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
3: *
4: * This program and the accompanying materials are made available under the
5: * terms of the Eclipse Public License v. 2.0, which is available at
6: * http://www.eclipse.org/legal/epl-2.0.
7: *
8: * This Source Code may also be made available under the following Secondary
9: * Licenses when the conditions for such availability set forth in the
10: * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
11: * version 2 with the GNU Classpath Exception, which is available at
12: * https://www.gnu.org/software/classpath/license.html.
13: *
14: * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15: */
16:
17: package org.eclipse.angus.mail.imap;
18:
19: import java.lang.reflect.*;
20: import java.util.Vector;
21: import java.util.StringTokenizer;
22: import java.util.Locale;
23: import java.util.Properties;
24: import java.io.IOException;
25: import java.net.InetAddress;
26: import java.net.UnknownHostException;
27: import java.util.Map;
28: import java.util.HashMap;
29: import java.util.logging.Level;
30:
31: import jakarta.mail.*;
32: import jakarta.mail.event.*;
33:
34: import org.eclipse.angus.mail.iap.*;
35: import org.eclipse.angus.mail.imap.protocol.*;
36: import org.eclipse.angus.mail.imap.protocol.IMAPProtocol;
37: import org.eclipse.angus.mail.imap.protocol.IMAPReferralException;
38: import org.eclipse.angus.mail.imap.protocol.ListInfo;
39: import org.eclipse.angus.mail.imap.protocol.Namespaces;
40: import org.eclipse.angus.mail.util.SocketConnectException;
41: import org.eclipse.angus.mail.util.MailConnectException;
42: import org.eclipse.angus.mail.util.MailLogger;
43: import org.eclipse.angus.mail.util.PropUtil;
44: import org.eclipse.angus.mail.iap.BadCommandException;
45: import org.eclipse.angus.mail.iap.CommandFailedException;
46: import org.eclipse.angus.mail.iap.ConnectionException;
47: import org.eclipse.angus.mail.iap.ProtocolException;
48: import org.eclipse.angus.mail.iap.Response;
49: import org.eclipse.angus.mail.iap.ResponseHandler;
50:
51: import java.util.ArrayList;
52: import java.util.List;
53:
54: /**
55: * This class provides access to an IMAP message store. <p>
56: *
57: * Applications that need to make use of IMAP-specific features may cast
58: * a <code>Store</code> object to an <code>IMAPStore</code> object and
59: * use the methods on this class. The {@link #getQuota getQuota} and
60: * {@link #setQuota setQuota} methods support the IMAP QUOTA extension.
61: * Refer to <A HREF="http://www.ietf.org/rfc/rfc2087.txt">RFC 2087</A>
62: * for more information. <p>
63: *
64: * The {@link #id id} method supports the IMAP ID extension;
65: * see <A HREF="http://www.ietf.org/rfc/rfc2971.txt">RFC 2971</A>.
66: * The fields ID_NAME, ID_VERSION, etc. represent the suggested field names
67: * in RFC 2971 section 3.3 and may be used as keys in the Map containing
68: * client values or server values. <p>
69: *
70: * See the <a href="package-summary.html">org.eclipse.angus.mail.imap</a> package
71: * documentation for further information on the IMAP protocol provider. <p>
72: *
73: * <strong>WARNING:</strong> The APIs unique to this class should be
74: * considered <strong>EXPERIMENTAL</strong>. They may be changed in the
75: * future in ways that are incompatible with applications using the
76: * current APIs.
77: *
78: * @author John Mani
79: * @author Bill Shannon
80: * @author Jim Glennon
81: */
82: /*
83: * This package is implemented over the "imap.protocol" package, which
84: * implements the protocol-level commands. <p>
85: *
86: * A connected IMAPStore maintains a pool of IMAP protocol objects for
87: * use in communicating with the IMAP server. The IMAPStore will create
88: * the initial AUTHENTICATED connection and seed the pool with this
89: * connection. As folders are opened and new IMAP protocol objects are
90: * needed, the IMAPStore will provide them from the connection pool,
91: * or create them if none are available. When a folder is closed,
92: * its IMAP protocol object is returned to the connection pool if the
93: * pool is not over capacity. The pool size can be configured by setting
94: * the mail.imap.connectionpoolsize property. <p>
95: *
96: * Note that all connections in the connection pool have their response
97: * handler set to be the Store. When the connection is removed from the
98: * pool for use by a folder, the response handler is removed and then set
99: * to either the Folder or to the special nonStoreResponseHandler, depending
100: * on how the connection is being used. This is probably excessive.
101: * Better would be for the Protocol object to support only a single
102: * response handler, which would be set before the connection is used
103: * and cleared when the connection is in the pool and can't be used. <p>
104: *
105: * A mechanism is provided for timing out idle connection pool IMAP
106: * protocol objects. Timed out connections are closed and removed (pruned)
107: * from the connection pool. The time out interval can be configured via
108: * the mail.imap.connectionpooltimeout property. <p>
109: *
110: * The connected IMAPStore object may or may not maintain a separate IMAP
111: * protocol object that provides the store a dedicated connection to the
112: * IMAP server. This is provided mainly for compatibility with previous
113: * implementations of Jakarta Mail and is determined by the value of the
114: * mail.imap.separatestoreconnection property. <p>
115: *
116: * An IMAPStore object provides closed IMAPFolder objects thru its list()
117: * and listSubscribed() methods. A closed IMAPFolder object acquires an
118: * IMAP protocol object from the store to communicate with the server. When
119: * the folder is opened, it gets its own protocol object and thus its own,
120: * separate connection to the server. The store maintains references to
121: * all 'open' folders. When a folder is/gets closed, the store removes
122: * it from its list. When the store is/gets closed, it closes all open
123: * folders in its list, thus cleaning up all open connections to the
124: * server. <p>
125: *
126: * A mutex is used to control access to the connection pool resources.
127: * Any time any of these resources need to be accessed, the following
128: * convention should be followed:
129: *
130: * synchronized (pool) { // ACQUIRE LOCK
131: * // access connection pool resources
132: * } // RELEASE LOCK <p>
133: *
134: * The locking relationship between the store and folders is that the
135: * store lock must be acquired before a folder lock. This is currently only
136: * applicable in the store's cleanup method. It's important that the
137: * connection pool lock is not held when calling into folder objects.
138: * The locking hierarchy is that a folder lock must be acquired before
139: * any connection pool operations are performed. You never need to hold
140: * all three locks, but if you hold more than one this is the order you
141: * have to acquire them in. <p>
142: *
143: * That is: Store > Folder, Folder > pool, Store > pool <p>
144: *
145: * The IMAPStore implements the ResponseHandler interface and listens to
146: * BYE or untagged OK-notification events from the server as a result of
147: * Store operations. IMAPFolder forwards notifications that result from
148: * Folder operations using the store connection; the IMAPStore ResponseHandler
149: * is not used directly in this case. <p>
150: */
151:
152: public class IMAPStore extends Store
153:          implements QuotaAwareStore, ResponseHandler {
154:
155: /**
156: * A special event type for a StoreEvent to indicate an IMAP
157: * response, if the mail.imap.enableimapevents property is set.
158: */
159: public static final int RESPONSE = 1000;
160:
161: public static final String ID_NAME = "name";
162: public static final String ID_VERSION = "version";
163: public static final String ID_OS = "os";
164: public static final String ID_OS_VERSION = "os-version";
165: public static final String ID_VENDOR = "vendor";
166: public static final String ID_SUPPORT_URL = "support-url";
167: public static final String ID_ADDRESS = "address";
168: public static final String ID_DATE = "date";
169: public static final String ID_COMMAND = "command";
170: public static final String ID_ARGUMENTS = "arguments";
171: public static final String ID_ENVIRONMENT = "environment";
172:
173: protected final String name;        // name of this protocol
174: protected final int defaultPort;        // default IMAP port
175: protected final boolean isSSL;        // use SSL?
176:
177: private final int blksize;                // Block size for data requested
178:                                         // in FETCH requests. Defaults to
179:                                         // 16K
180:
181: private boolean ignoreSize;                // ignore the size in BODYSTRUCTURE?
182:
183: private final int statusCacheTimeout;        // cache Status for 1 second
184:
185: private final int appendBufferSize;        // max size of msg buffered for append
186:
187: private final int minIdleTime;        // minimum idle time
188:
189: private volatile int port = -1;        // port to use
190:
191: // Auth info
192: protected String host;
193: protected String user;
194: protected String password;
195: protected String proxyAuthUser;
196: protected String authorizationID;
197: protected String saslRealm;
198:
199: private Namespaces namespaces;
200:
201: private boolean enableStartTLS = false;        // enable STARTTLS
202: private boolean requireStartTLS = false;        // require STARTTLS
203: private boolean usingSSL = false;                // using SSL?
204: private boolean enableSASL = false;                // enable SASL authentication
205: private String[] saslMechanisms;
206: private boolean forcePasswordRefresh = false;
207: // enable notification of IMAP responses
208: private boolean enableResponseEvents = false;
209: // enable notification of IMAP responses during IDLE
210: private boolean enableImapEvents = false;
211: private String guid;                        // for Yahoo! Mail IMAP
212: private boolean throwSearchException = false;
213: private boolean peek = false;
214: private boolean closeFoldersOnStoreFailure = true;
215: private boolean enableCompress = false;        // enable COMPRESS=DEFLATE
216: private boolean finalizeCleanClose = false;
217:
218: /*
219: * This field is set in the Store's response handler if we see
220: * a BYE response. The releaseStore method checks this field
221: * and if set it cleans up the Store. Field is volatile because
222: * there's no lock we consistently hold while manipulating it.
223: *
224: * Because volatile doesn't really work before JDK 1.5,
225: * use a lock to protect these two fields.
226: */
227: private volatile boolean connectionFailed = false;
228: private volatile boolean forceClose = false;
229: private final Object connectionFailedLock = new Object();
230:
231: private boolean debugusername;        // include username in debug output?
232: private boolean debugpassword;        // include password in debug output?
233: protected MailLogger logger;        // for debug output
234:
235: private boolean messageCacheDebug;
236:
237: // constructors for IMAPFolder class provided by user
238: private volatile Constructor<?> folderConstructor = null;
239: private volatile Constructor<?> folderConstructorLI = null;
240:
241: // Connection pool info
242:
243: static class ConnectionPool {
244:
245: // container for the pool's IMAP protocol objects
246: private Vector<IMAPProtocol> authenticatedConnections
247:                 = new Vector<>();
248:
249: // vectore of open folders
250: private Vector<IMAPFolder> folders;
251:
252: // is the store connection being used?
253: private boolean storeConnectionInUse = false;
254:
255: // the last time (in millis) the pool was checked for timed out
256: // connections
257: private long lastTimePruned;
258:
259: // flag to indicate whether there is a dedicated connection for
260: // store commands
261: private final boolean separateStoreConnection;
262:
263: // client timeout interval
264: private final long clientTimeoutInterval;
265:
266: // server timeout interval
267: private final long serverTimeoutInterval;
268:
269: // size of the connection pool
270: private final int poolSize;
271:
272: // interval for checking for timed out connections
273: private final long pruningInterval;
274:
275: // connection pool logger
276: private final MailLogger logger;
277:
278:         /*
279:          * The idleState field supports the IDLE command.
280:          * Normally when executing an IMAP command we hold the
281:          * store's lock.
282:          * While executing the IDLE command we can't hold the
283:          * lock or it would prevent other threads from
284:          * entering Store methods even far enough to check whether
285:          * an IDLE command is in progress. We need to check before
286:          * issuing another command so that we can abort the IDLE
287:          * command.
288:          *
289:          * The idleState field is protected by the store's lock.
290:          * The RUNNING state is the normal state and means no IDLE
291:          * command is in progress. The IDLE state means we've issued
292:          * an IDLE command and are reading responses. The ABORTING
293:          * state means we've sent the DONE continuation command and
294:          * are waiting for the thread running the IDLE command to
295:          * break out of its read loop.
296:          *
297:          * When an IDLE command is in progress, the thread calling
298:          * the idle method will be reading from the IMAP connection
299:          * while not holding the store's lock.
300:          * It's obviously critical that no other thread try to send a
301:          * command or read from the connection while in this state.
302:          * However, other threads can send the DONE continuation
303:          * command that will cause the server to break out of the IDLE
304:          * loop and send the ending tag response to the IDLE command.
305:          * The thread in the idle method that's reading the responses
306:          * from the IDLE command will see this ending response and
307:          * complete the idle method, setting the idleState field back
308:          * to RUNNING, and notifying any threads waiting to use the
309:          * connection.
310:          *
311:          * All uses of the IMAP connection (IMAPProtocol object) must
312:          * be preceeded by a check to make sure an IDLE command is not
313:          * running, and abort the IDLE command if necessary. This check
314:          * is made while holding the connection pool lock. While
315:          * waiting for the IDLE command to complete, these other threads
316:          * will give up the connection pool lock. This check is done by
317:          * the getStoreProtocol() method.
318:          */
319:         private static final int RUNNING = 0;        // not doing IDLE command
320:         private static final int IDLE = 1;        // IDLE command in effect
321:         private static final int ABORTING = 2;        // IDLE command aborting
322:         private int idleState = RUNNING;
323:         private IMAPProtocol idleProtocol;        // protocol object when IDLE
324:
325:         ConnectionPool(String name, MailLogger plogger, Session session) {
326:          lastTimePruned = System.currentTimeMillis();
327:          Properties props = session.getProperties();
328:
329:          boolean debug = PropUtil.getBooleanProperty(props,
330:                 "mail." + name + ".connectionpool.debug", false);
331:          logger = plogger.getSubLogger("connectionpool",
332:                                          "DEBUG IMAP CP", debug);
333:
334:          // check if the default connection pool size is overridden
335:          int size = PropUtil.getIntProperty(props,
336:                 "mail." + name + ".connectionpoolsize", -1);
337:          if (size > 0) {
338:                 poolSize = size;
339:                 if (logger.isLoggable(Level.CONFIG))
340:                  logger.config("mail.imap.connectionpoolsize: " + poolSize);
341:          } else
342:                 poolSize = 1;
343:
344:          // check if the default client-side timeout value is overridden
345:          int connectionPoolTimeout = PropUtil.getIntProperty(props,
346:                 "mail." + name + ".connectionpooltimeout", -1);
347:          if (connectionPoolTimeout > 0) {
348:                 clientTimeoutInterval = connectionPoolTimeout;
349:                 if (logger.isLoggable(Level.CONFIG))
350:                  logger.config("mail.imap.connectionpooltimeout: " +
351:                         clientTimeoutInterval);
352:          } else
353:                 clientTimeoutInterval = 45 * 1000;        // 45 seconds
354:
355:          // check if the default server-side timeout value is overridden
356:          int serverTimeout = PropUtil.getIntProperty(props,
357:                 "mail." + name + ".servertimeout", -1);
358:          if (serverTimeout > 0) {
359:                 serverTimeoutInterval = serverTimeout;
360:                 if (logger.isLoggable(Level.CONFIG))
361:                  logger.config("mail.imap.servertimeout: " +
362:                         serverTimeoutInterval);
363:          } else
364:                 serverTimeoutInterval = 30 * 60 * 1000;        // 30 minutes
365:
366:          // check if the default server-side timeout value is overridden
367:          int pruning = PropUtil.getIntProperty(props,
368:                 "mail." + name + ".pruninginterval", -1);
369:          if (pruning > 0) {
370:                 pruningInterval = pruning;
371:                 if (logger.isLoggable(Level.CONFIG))
372:                  logger.config("mail.imap.pruninginterval: " +
373:                         pruningInterval);
374:          } else
375:                 pruningInterval = 60 * 1000;                // 1 minute
376:
377:          // check to see if we should use a separate (i.e. dedicated)
378:          // store connection
379:          separateStoreConnection =
380:                 PropUtil.getBooleanProperty(props,
381:                  "mail." + name + ".separatestoreconnection", false);
382:          if (separateStoreConnection)
383:                 logger.config("dedicate a store connection");
384:
385:         }
386: }
387:
388: private final ConnectionPool pool;
389:
390: /**
391: * A special response handler for connections that are being used
392: * to perform operations on behalf of an object other than the Store.
393: * It DOESN'T cause the Store to be cleaned up if a BYE is seen.
394: * The BYE may be real or synthetic and in either case just indicates
395: * that the connection is dead.
396: */
397: private ResponseHandler nonStoreResponseHandler = new ResponseHandler() {
398:         @Override
399:         public void handleResponse(Response r) {
400:          // Any of these responses may have a response code.
401:•         if (r.isOK() || r.isNO() || r.isBAD() || r.isBYE())
402:                 handleResponseCode(r);
403:•         if (r.isBYE())
404:                 logger.fine("IMAPStore non-store connection dead");
405:         }
406: };
407:
408: /**
409: * Constructor that takes a Session object and a URLName that
410: * represents a specific IMAP server.
411: *
412: * @param        session        the Session
413: * @param        url        the URLName of this store
414: */
415: public IMAPStore(Session session, URLName url) {
416:         this(session, url, "imap", false);
417: }
418:
419: /**
420: * Constructor used by this class and by IMAPSSLStore subclass.
421: *
422: * @param        session        the Session
423: * @param        url        the URLName of this store
424: * @param        name        the protocol name for this store
425: * @param        isSSL        use SSL?
426: */
427: protected IMAPStore(Session session, URLName url,
428:                                 String name, boolean isSSL) {
429:         super(session, url); // call super constructor
430:         Properties props = session.getProperties();
431:
432:         if (url != null)
433:          name = url.getProtocol();
434:         this.name = name;
435:         if (!isSSL)
436:          isSSL = PropUtil.getBooleanProperty(props,
437:                                 "mail." + name + ".ssl.enable", false);
438:         if (isSSL)
439:          this.defaultPort = 993;
440:         else
441:          this.defaultPort = 143;
442:         this.isSSL = isSSL;
443:
444: debug = session.getDebug();
445:         debugusername = PropUtil.getBooleanProperty(props,
446:                         "mail.debug.auth.username", true);
447:         debugpassword = PropUtil.getBooleanProperty(props,
448:                         "mail.debug.auth.password", false);
449:         logger = new MailLogger(this.getClass(),
450:                         "DEBUG " + name.toUpperCase(Locale.ENGLISH),
451:                         session.getDebug(), session.getDebugOut());
452:
453:         boolean partialFetch = PropUtil.getBooleanProperty(props,
454:          "mail." + name + ".partialfetch", true);
455:         if (!partialFetch) {
456:          blksize = -1;
457:          logger.config("mail.imap.partialfetch: false");
458:         } else {
459:          blksize = PropUtil.getIntProperty(props,
460:                 "mail." + name +".fetchsize", 1024 * 16);
461:          if (logger.isLoggable(Level.CONFIG))
462:                 logger.config("mail.imap.fetchsize: " + blksize);
463:         }
464:
465:         ignoreSize = PropUtil.getBooleanProperty(props,
466:          "mail." + name +".ignorebodystructuresize", false);
467:         if (logger.isLoggable(Level.CONFIG))
468:          logger.config("mail.imap.ignorebodystructuresize: " + ignoreSize);
469:
470:         statusCacheTimeout = PropUtil.getIntProperty(props,
471:          "mail." + name + ".statuscachetimeout", 1000);
472:         if (logger.isLoggable(Level.CONFIG))
473:          logger.config("mail.imap.statuscachetimeout: " +
474:                                                 statusCacheTimeout);
475:
476:         appendBufferSize = PropUtil.getIntProperty(props,
477:          "mail." + name + ".appendbuffersize", -1);
478:         if (logger.isLoggable(Level.CONFIG))
479:          logger.config("mail.imap.appendbuffersize: " + appendBufferSize);
480:
481:         minIdleTime = PropUtil.getIntProperty(props,
482:          "mail." + name + ".minidletime", 10);
483:         if (logger.isLoggable(Level.CONFIG))
484:          logger.config("mail.imap.minidletime: " + minIdleTime);
485:
486:         // check if we should do a PROXYAUTH login
487:         String s = session.getProperty("mail." + name + ".proxyauth.user");
488:         if (s != null) {
489:          proxyAuthUser = s;
490:          if (logger.isLoggable(Level.CONFIG))
491:                 logger.config("mail.imap.proxyauth.user: " + proxyAuthUser);
492:         }
493:
494:         // check if STARTTLS is enabled
495:         enableStartTLS = PropUtil.getBooleanProperty(props,
496:          "mail." + name + ".starttls.enable", false);
497:         if (enableStartTLS)
498:          logger.config("enable STARTTLS");
499:
500:         // check if STARTTLS is required
501:         requireStartTLS = PropUtil.getBooleanProperty(props,
502:          "mail." + name + ".starttls.required", false);
503:         if (requireStartTLS)
504:          logger.config("require STARTTLS");
505:
506:         // check if SASL is enabled
507:         enableSASL = PropUtil.getBooleanProperty(props,
508:          "mail." + name + ".sasl.enable", false);
509:         if (enableSASL)
510:          logger.config("enable SASL");
511:
512:         // check if SASL mechanisms are specified
513:         if (enableSASL) {
514:          s = session.getProperty("mail." + name + ".sasl.mechanisms");
515:          if (s != null && s.length() > 0) {
516:                 if (logger.isLoggable(Level.CONFIG))
517:                  logger.config("SASL mechanisms allowed: " + s);
518:                 List<String> v = new ArrayList<>(5);
519:                 StringTokenizer st = new StringTokenizer(s, " ,");
520:                 while (st.hasMoreTokens()) {
521:                  String m = st.nextToken();
522:                  if (m.length() > 0)
523:                         v.add(m);
524:                 }
525:                 saslMechanisms = new String[v.size()];
526:                 v.toArray(saslMechanisms);
527:          }
528:         }
529:
530:         // check if an authorization ID has been specified
531:         s = session.getProperty("mail." + name + ".sasl.authorizationid");
532:         if (s != null) {
533:          authorizationID = s;
534:          logger.log(Level.CONFIG, "mail.imap.sasl.authorizationid: {0}",
535:                                                 authorizationID);
536:         }
537:
538:         // check if a SASL realm has been specified
539:         s = session.getProperty("mail." + name + ".sasl.realm");
540:         if (s != null) {
541:          saslRealm = s;
542:          logger.log(Level.CONFIG, "mail.imap.sasl.realm: {0}", saslRealm);
543:         }
544:
545:         // check if forcePasswordRefresh is enabled
546:         forcePasswordRefresh = PropUtil.getBooleanProperty(props,
547:          "mail." + name + ".forcepasswordrefresh", false);
548:         if (forcePasswordRefresh)
549:          logger.config("enable forcePasswordRefresh");
550:
551:         // check if enableimapevents is enabled
552:         enableResponseEvents = PropUtil.getBooleanProperty(props,
553:          "mail." + name + ".enableresponseevents", false);
554:         if (enableResponseEvents)
555:          logger.config("enable IMAP response events");
556:
557:         // check if enableresponseevents is enabled
558:         enableImapEvents = PropUtil.getBooleanProperty(props,
559:          "mail." + name + ".enableimapevents", false);
560:         if (enableImapEvents)
561:          logger.config("enable IMAP IDLE events");
562:
563:         // check if message cache debugging set
564:         messageCacheDebug = PropUtil.getBooleanProperty(props,
565:          "mail." + name + ".messagecache.debug", false);
566:
567:         guid = session.getProperty("mail." + name + ".yahoo.guid");
568:         if (guid != null)
569:          logger.log(Level.CONFIG, "mail.imap.yahoo.guid: {0}", guid);
570:
571:         // check if throwsearchexception is enabled
572:         throwSearchException = PropUtil.getBooleanProperty(props,
573:          "mail." + name + ".throwsearchexception", false);
574:         if (throwSearchException)
575:          logger.config("throw SearchException");
576:
577:         // check if peek is set
578:         peek = PropUtil.getBooleanProperty(props,
579:          "mail." + name + ".peek", false);
580:         if (peek)
581:          logger.config("peek");
582:
583:         // check if closeFoldersOnStoreFailure is set
584:         closeFoldersOnStoreFailure = PropUtil.getBooleanProperty(props,
585:          "mail." + name + ".closefoldersonstorefailure", true);
586:         if (closeFoldersOnStoreFailure)
587:          logger.config("closeFoldersOnStoreFailure");
588:
589:         // check if COMPRESS is enabled
590:         enableCompress = PropUtil.getBooleanProperty(props,
591:          "mail." + name + ".compress.enable", false);
592:         if (enableCompress)
593:          logger.config("enable COMPRESS");
594:
595:         // check if finalizeCleanClose is enabled
596:         finalizeCleanClose = PropUtil.getBooleanProperty(props,
597:          "mail." + name + ".finalizecleanclose", false);
598:         if (finalizeCleanClose)
599:          logger.config("close connection cleanly in finalize");
600:
601:         s = session.getProperty("mail." + name + ".folder.class");
602:         if (s != null) {
603:          logger.log(Level.CONFIG, "IMAP: folder class: {0}", s);
604:          try {
605:                 ClassLoader cl = this.getClass().getClassLoader();
606:
607:                 // now load the class
608:                 Class<?> folderClass = null;
609:                 try {
610:                  // First try the "application's" class loader.
611:                  // This should eventually be replaced by
612:                  // Thread.currentThread().getContextClassLoader().
613:                  folderClass = Class.forName(s, false, cl);
614:                 } catch (ClassNotFoundException ex1) {
615:                  // That didn't work, now try the "system" class loader.
616:                  // (Need both of these because JDK 1.1 class loaders
617:                  // may not delegate to their parent class loader.)
618:                  folderClass = Class.forName(s);
619:                 }
620:
621:                 Class<?>[] c = { String.class, char.class, IMAPStore.class,
622:                                 Boolean.class };
623:                 folderConstructor = folderClass.getConstructor(c);
624:                 Class<?>[] c2 = { ListInfo.class, IMAPStore.class };
625:                 folderConstructorLI = folderClass.getConstructor(c2);
626:          } catch (Exception ex) {
627:                 logger.log(Level.CONFIG,
628:                         "IMAP: failed to load folder class", ex);
629:          }
630:         }
631:
632:         pool = new ConnectionPool(name, logger, session);
633: }
634:
635: /**
636: * Implementation of protocolConnect(). Will create a connection
637: * to the server and authenticate the user using the mechanisms
638: * specified by various properties. <p>
639: *
640: * The <code>host</code>, <code>user</code>, and <code>password</code>
641: * parameters must all be non-null. If the authentication mechanism
642: * being used does not require a password, an empty string or other
643: * suitable dummy password should be used.
644: */
645: @Override
646: protected synchronized boolean
647: protocolConnect(String host, int pport, String user, String password)
648:                 throws MessagingException {
649:
650: IMAPProtocol protocol = null;
651:
652:         // check for non-null values of host, password, user
653:         if (host == null || password == null || user == null) {
654:          if (logger.isLoggable(Level.FINE))
655:                 logger.fine("protocolConnect returning false" +
656:                                 ", host=" + host +
657:                                 ", user=" + traceUser(user) +
658:                                 ", password=" + tracePassword(password));
659:          return false;
660:         }
661:
662:         // set the port correctly
663:         if (pport != -1) {
664:          port = pport;
665:         } else {
666:          port = PropUtil.getIntProperty(session.getProperties(),
667:                                         "mail." + name + ".port", port);
668:         }
669:         
670:         // use the default if needed
671:         if (port == -1) {
672:          port = defaultPort;
673:         }
674:         
675:         try {
676: boolean poolEmpty;
677: synchronized (pool) {
678: poolEmpty = pool.authenticatedConnections.isEmpty();
679: }
680:
681: if (poolEmpty) {
682:                 if (logger.isLoggable(Level.FINE))
683:                  logger.fine("trying to connect to host \"" + host +
684:                                 "\", port " + port + ", isSSL " + isSSL);
685: protocol = newIMAPProtocol(host, port);
686:                 if (logger.isLoggable(Level.FINE))
687:                  logger.fine("protocolConnect login" +
688:                                 ", host=" + host +
689:                                 ", user=" + traceUser(user) +
690:                                 ", password=" + tracePassword(password));
691:                 protocol.addResponseHandler(nonStoreResponseHandler);
692:          login(protocol, user, password);
693:                 protocol.removeResponseHandler(nonStoreResponseHandler);
694:          protocol.addResponseHandler(this);
695:
696:                 usingSSL = protocol.isSSL();        // in case anyone asks
697:
698:          this.host = host;
699:          this.user = user;
700:          this.password = password;
701:
702: synchronized (pool) {
703: pool.authenticatedConnections.addElement(protocol);
704: }
705: }
706:         } catch (IMAPReferralException ex) {
707:          // login failure due to IMAP REFERRAL, close connection to server
708:          if (protocol != null)
709:                 protocol.disconnect();
710:          protocol = null;
711:          throw new ReferralException(ex.getUrl(), ex.getMessage());
712:         } catch (CommandFailedException cex) {
713:          // login failure, close connection to server
714:          if (protocol != null)
715:                 protocol.disconnect();
716:          protocol = null;
717:          Response r = cex.getResponse();
718:          throw new AuthenticationFailedException(
719:                                  r != null ? r.getRest() : cex.getMessage());
720:         } catch (ProtocolException pex) { // any other exception
721:          // failure in login command, close connection to server
722:          if (protocol != null)
723:                 protocol.disconnect();
724:          protocol = null;
725:          throw new MessagingException(pex.getMessage(), pex);
726:         } catch (SocketConnectException scex) {
727:          throw new MailConnectException(scex);
728:         } catch (IOException ioex) {
729:          throw new MessagingException(ioex.getMessage(), ioex);
730:         }
731:
732: return true;
733: }
734:
735: /**
736: * Create an IMAPProtocol object connected to the host and port.
737: * Subclasses of IMAPStore may override this method to return a
738: * subclass of IMAPProtocol that supports product-specific extensions.
739: *
740: * @param        host        the host name
741: * @param        port        the port number
742: * @return                the new IMAPProtocol object
743: * @exception        IOException for I/O errors
744: * @exception        ProtocolException for protocol errors
745: * @since JavaMail 1.4.6
746: */
747: protected IMAPProtocol newIMAPProtocol(String host, int port)
748:                                 throws IOException, ProtocolException {
749:         return new IMAPProtocol(name, host, port,
750:                                          session.getProperties(),
751:                                          isSSL,
752:                                          logger
753:                                          );
754: }
755:
756: private void login(IMAPProtocol p, String u, String pw)
757:                 throws ProtocolException {
758:         // turn on TLS if it's been enabled or required and is supported
759:         // and we're not already using SSL
760:         if ((enableStartTLS || requireStartTLS) && !p.isSSL()) {
761:          if (p.hasCapability("STARTTLS")) {
762:                 p.startTLS();
763:                 // if startTLS succeeds, refresh capabilities
764:                 p.capability();
765:          } else if (requireStartTLS) {
766:                 logger.fine("STARTTLS required but not supported by server");
767:                 throw new ProtocolException(
768:                  "STARTTLS required but not supported by server");
769:          }
770:         }
771:         if (p.isAuthenticated())
772:          return;                // no need to login
773:
774:         // allow subclasses to issue commands before login
775:         preLogin(p);
776:
777:         // issue special ID command to Yahoo! Mail IMAP server
778:         // http://en.wikipedia.org/wiki/Yahoo%21_Mail#Free_IMAP_and_SMTPs_access
779:         if (guid != null) {
780:          Map<String,String> gmap = new HashMap<>();
781:          gmap.put("GUID", guid);
782:          p.id(gmap);
783:         }
784:
785:         /*
786:          * Put a special "marker" in the capabilities list so we can
787:          * detect if the server refreshed the capabilities in the OK
788:          * response.
789:          */
790:         p.getCapabilities().put("__PRELOGIN__", "");
791:         String authzid;
792:         if (authorizationID != null)
793:          authzid = authorizationID;
794:         else if (proxyAuthUser != null)
795:          authzid = proxyAuthUser;
796:         else
797:          authzid = null;
798:
799:         if (enableSASL) {
800:          try {
801:                 p.sasllogin(saslMechanisms, saslRealm, authzid, u, pw);
802:                 if (!p.isAuthenticated())
803:                  throw new CommandFailedException(
804:                                                 "SASL authentication failed");
805:          } catch (UnsupportedOperationException ex) {
806:                 // continue to try other authentication methods below
807:          }
808:         }
809:
810:         if (!p.isAuthenticated())
811:          authenticate(p, authzid, u, pw);
812:
813:         if (proxyAuthUser != null)
814:          p.proxyauth(proxyAuthUser);
815:
816:         /*
817:          * If marker is still there, capabilities haven't been refreshed,
818:          * refresh them now.
819:          */
820:         if (p.hasCapability("__PRELOGIN__")) {
821:          try {
822:                 p.capability();
823:          } catch (ConnectionException cex) {
824:                 throw cex;        // rethrow connection failures
825:                 // XXX - assume connection has been closed
826:          } catch (ProtocolException pex) {
827:                 // ignore other exceptions that "should never happen"
828:          }
829:         }
830:
831:         if (enableCompress) {
832:          if (p.hasCapability("COMPRESS=DEFLATE")) {
833:                 p.compress();
834:          }
835:         }
836:
837:         // if server supports UTF-8, enable it for client use
838:         // note that this is safe to enable even if mail.mime.allowutf8=false
839:         if (p.hasCapability("UTF8=ACCEPT") || p.hasCapability("UTF8=ONLY"))
840:          p.enable("UTF8=ACCEPT");
841: }
842:
843: /**
844: * Authenticate using one of the non-SASL mechanisms.
845: *
846: * @param        p        the IMAPProtocol object
847: * @param        authzid        the authorization ID
848: * @param        user        the user name
849: * @param        password the password
850: * @exception        ProtocolException        on failures
851: */
852: private void authenticate(IMAPProtocol p, String authzid,
853:                                 String user, String password)
854:                                 throws ProtocolException {
855:         // this list must match the "if" statements below
856:         String defaultAuthenticationMechanisms = "PLAIN LOGIN NTLM XOAUTH2";
857:
858:         // setting mail.imap.auth.mechanisms controls which mechanisms will
859:         // be used, and in what order they'll be considered. only the first
860:         // match is used.
861:         String mechs = session.getProperty("mail." + name + ".auth.mechanisms");
862:
863:         if (mechs == null)
864:          mechs = defaultAuthenticationMechanisms;
865:
866:         /*
867:          * Loop through the list of mechanisms supplied by the user
868:          * (or defaulted) and try each in turn. If the server supports
869:          * the mechanism and we have an authenticator for the mechanism,
870:          * and it hasn't been disabled, use it.
871:          */
872:         StringTokenizer st = new StringTokenizer(mechs);
873:         while (st.hasMoreTokens()) {
874:          String m = st.nextToken();
875:          m = m.toUpperCase(Locale.ENGLISH);
876:
877:          /*
878:          * If using the default mechanisms, check if this one is disabled.
879:          */
880:          if (mechs == defaultAuthenticationMechanisms) {
881:                 String dprop = "mail." + name + ".auth." +
882:                                  m.toLowerCase(Locale.ENGLISH) + ".disable";
883:                 boolean disabled = PropUtil.getBooleanProperty(
884:                                         session.getProperties(),
885:                                         dprop, m.equals("XOAUTH2"));
886:                 if (disabled) {
887:                  if (logger.isLoggable(Level.FINE))
888:                         logger.fine("mechanism " + m +
889:                                         " disabled by property: " + dprop);
890:                  continue;
891:                 }
892:          }
893:
894:          if (!(p.hasCapability("AUTH=" + m) ||
895:                  (m.equals("LOGIN") && p.hasCapability("AUTH-LOGIN")))) {
896:                 logger.log(Level.FINE, "mechanism {0} not supported by server",
897:                                         m);
898:                 continue;
899:          }
900:
901:          if (m.equals("PLAIN"))
902:                 p.authplain(authzid, user, password);
903:          else if (m.equals("LOGIN"))
904:                 p.authlogin(user, password);
905:          else if (m.equals("NTLM"))
906:                 p.authntlm(authzid, user, password);
907:          else if (m.equals("XOAUTH2"))
908:                 p.authoauth2(user, password);
909:          else {
910:                 logger.log(Level.FINE, "no authenticator for mechanism {0}", m);
911:                 continue;
912:          }
913:          return;
914:         }
915:
916:         if (!p.hasCapability("LOGINDISABLED")) {
917:          p.login(user, password);
918:          return;
919:         }
920:
921:         throw new ProtocolException("No login methods supported!");
922: }
923:
924: /**
925: * This method is called after the connection is made and
926: * TLS is started (if needed), but before any authentication
927: * is attempted. Subclasses can override this method to
928: * issue commands that are needed in the "not authenticated"
929: * state. Note that if the connection is pre-authenticated,
930: * this method won't be called. <p>
931: *
932: * The implementation of this method in this class does nothing.
933: *
934: * @param        p        the IMAPProtocol connection
935: * @exception        ProtocolException for protocol errors
936: * @since JavaMail 1.4.4
937: */
938: protected void preLogin(IMAPProtocol p) throws ProtocolException {
939: }
940:
941: /**
942: * Does this IMAPStore use SSL when connecting to the server?
943: *
944: * @return        true if using SSL
945: * @since        JavaMail 1.4.6
946: */
947: public synchronized boolean isSSL() {
948: return usingSSL;
949: }
950:
951: /**
952: * Set the user name that will be used for subsequent connections
953: * after this Store is first connected (for example, when creating
954: * a connection to open a Folder). This value is overridden
955: * by any call to the Store's connect method. <p>
956: *
957: * Some IMAP servers may provide an authentication ID that can
958: * be used for more efficient authentication for future connections.
959: * This authentication ID is provided in a server-specific manner
960: * not described here. <p>
961: *
962: * Most applications will never need to use this method.
963: *
964: * @param        user        the user name for the store
965: * @since        JavaMail 1.3.3
966: */
967: public synchronized void setUsername(String user) {
968:         this.user = user;
969: }
970:
971: /**
972: * Set the password that will be used for subsequent connections
973: * after this Store is first connected (for example, when creating
974: * a connection to open a Folder). This value is overridden
975: * by any call to the Store's connect method. <p>
976: *
977: * Most applications will never need to use this method.
978: *
979: * @param        password        the password for the store
980: * @since        JavaMail 1.3.3
981: */
982: public synchronized void setPassword(String password) {
983:         this.password = password;
984: }
985:
986: /*
987: * Get a new authenticated protocol object for this Folder.
988: * Also store a reference to this folder in our list of
989: * open folders.
990: */
991: IMAPProtocol getProtocol(IMAPFolder folder)
992:                 throws MessagingException {
993:         IMAPProtocol p = null;
994:
995:         // keep looking for a connection until we get a good one
996:         while (p == null) {
997:
998: // New authenticated protocol objects are either acquired
999: // from the connection pool, or created when the pool is
1000: // empty or no connections are available. None are available
1001: // if the current pool size is one and the separate store
1002: // property is set or the connection is in use.
1003:
1004: synchronized (pool) {
1005:
1006: // If there's none available in the pool,
1007: // create a new one.
1008: if (pool.authenticatedConnections.isEmpty() ||
1009: (pool.authenticatedConnections.size() == 1 &&
1010: (pool.separateStoreConnection || pool.storeConnectionInUse))) {
1011:
1012:                 logger.fine("no connections in the pool, creating a new one");
1013: try {
1014:                  if (forcePasswordRefresh)
1015:                         refreshPassword();
1016: // Use cached host, port and timeout values.
1017: p = newIMAPProtocol(host, port);
1018:                  p.addResponseHandler(nonStoreResponseHandler);
1019: // Use cached auth info
1020: login(p, user, password);
1021:                  p.removeResponseHandler(nonStoreResponseHandler);
1022: } catch(Exception ex1) {
1023: if (p != null)
1024: try {
1025: p.disconnect();
1026: } catch (Exception ex2) { }
1027: p = null;
1028: }
1029:
1030: if (p == null)
1031: throw new MessagingException("connection failure");
1032: } else {
1033:                 if (logger.isLoggable(Level.FINE))
1034: logger.fine("connection available -- size: " +
1035: pool.authenticatedConnections.size());
1036:
1037: // remove the available connection from the Authenticated queue
1038: p = pool.authenticatedConnections.lastElement();
1039: pool.authenticatedConnections.removeElement(p);
1040:
1041:                 // check if the connection is still live
1042:                 long lastUsed = System.currentTimeMillis() - p.getTimestamp();
1043:                 if (lastUsed > pool.serverTimeoutInterval) {
1044:                  try {
1045:                         /*
1046:                          * Swap in a special response handler that will handle
1047:                          * alerts, but won't cause the store to be closed and
1048:                          * cleaned up if the connection is dead.
1049:                          */
1050:                         p.removeResponseHandler(this);
1051:                         p.addResponseHandler(nonStoreResponseHandler);
1052:                         p.noop();
1053:                         p.removeResponseHandler(nonStoreResponseHandler);
1054:                         p.addResponseHandler(this);
1055:                  } catch (ProtocolException pex) {
1056:                         try {
1057:                          p.removeResponseHandler(nonStoreResponseHandler);
1058:                          p.disconnect();
1059:                         } catch (RuntimeException ignored) {
1060:                          // don't let any exception stop us
1061:                         }
1062:                         p = null;
1063:                         continue; // try again, from the top
1064:                  }
1065:                 }
1066:
1067:                 // if proxyAuthUser has changed, switch to new user
1068:                 if (proxyAuthUser != null &&
1069:                         !proxyAuthUser.equals(p.getProxyAuthUser()) &&
1070:                         p.hasCapability("X-UNAUTHENTICATE")) {
1071:                  try {
1072:                         /*
1073:                          * Swap in a special response handler that will handle
1074:                          * alerts, but won't cause the store to be closed and
1075:                          * cleaned up if the connection is dead.
1076:                          */
1077:                         p.removeResponseHandler(this);
1078:                         p.addResponseHandler(nonStoreResponseHandler);
1079:                         p.unauthenticate();
1080:                         login(p, user, password);
1081:                         p.removeResponseHandler(nonStoreResponseHandler);
1082:                         p.addResponseHandler(this);
1083:                  } catch (ProtocolException pex) {
1084:                         try {
1085:                          p.removeResponseHandler(nonStoreResponseHandler);
1086:                          p.disconnect();
1087:                         } catch (RuntimeException ignored) {
1088:                          // don't let any exception stop us
1089:                         }
1090:                         p = null;
1091:                         continue; // try again, from the top
1092:                  }
1093:                 }
1094:
1095: // remove the store as a response handler.
1096: p.removeResponseHandler(this);
1097:          }
1098:
1099: // check if we need to look for client-side timeouts
1100: timeoutConnections();
1101:
1102:          // Add folder to folder-list
1103:          if (folder != null) {
1104: if (pool.folders == null)
1105: pool.folders = new Vector<>();
1106:                 pool.folders.addElement(folder);
1107:          }
1108: }
1109:
1110:         }
1111:         
1112:         return p;
1113: }
1114:
1115: /**
1116: * Get this Store's protocol connection.
1117: *
1118: * When acquiring a store protocol object, it is important to
1119: * use the following steps:
1120: *
1121: * IMAPProtocol p = null;
1122: * try {
1123: * p = getStoreProtocol();
1124: * // perform the command
1125: * } catch (ConnectionException cex) {
1126: * throw new StoreClosedException(this, cex.getMessage());
1127: * } catch (WhateverException ex) {
1128: * // handle it
1129: * } finally {
1130: * releaseStoreProtocol(p);
1131: * }
1132: */
1133: private IMAPProtocol getStoreProtocol() throws ProtocolException {
1134: IMAPProtocol p = null;
1135:
1136:         while (p == null) {
1137: synchronized (pool) {
1138:          waitIfIdle();
1139:
1140: // If there's no authenticated connections available create a
1141: // new one and place it in the authenticated queue.
1142: if (pool.authenticatedConnections.isEmpty()) {
1143:                 pool.logger.fine("getStoreProtocol() - no connections " +
1144: "in the pool, creating a new one");
1145: try {
1146:                  if (forcePasswordRefresh)
1147:                         refreshPassword();
1148: // Use cached host, port and timeout values.
1149: p = newIMAPProtocol(host, port);
1150: // Use cached auth info
1151: login(p, user, password);
1152: } catch(Exception ex1) {
1153: if (p != null)
1154: try {
1155: p.logout();
1156: } catch (Exception ex2) { }
1157: p = null;
1158: }
1159:
1160: if (p == null)
1161: throw new ConnectionException(
1162:                                 "failed to create new store connection");
1163:
1164:          p.addResponseHandler(this);
1165: pool.authenticatedConnections.addElement(p);
1166:
1167: } else {
1168: // Always use the first element in the Authenticated queue.
1169:                 if (pool.logger.isLoggable(Level.FINE))
1170: pool.logger.fine("getStoreProtocol() - " +
1171: "connection available -- size: " +
1172: pool.authenticatedConnections.size());
1173: p = pool.authenticatedConnections.firstElement();
1174:
1175:                 // if proxyAuthUser has changed, switch to new user
1176:                 if (proxyAuthUser != null &&
1177:                         !proxyAuthUser.equals(p.getProxyAuthUser()) &&
1178:                         p.hasCapability("X-UNAUTHENTICATE")) {
1179:                  p.unauthenticate();
1180:                  login(p, user, password);
1181:                 }
1182: }
1183:
1184:          if (pool.storeConnectionInUse) {
1185:                 try {
1186:                  // someone else is using the connection, give up
1187:                  // and wait until they're done
1188:                  p = null;
1189:                  pool.wait();
1190:                 } catch (InterruptedException ex) {
1191:                  // restore the interrupted state, which callers might
1192:                  // depend on
1193:                  Thread.currentThread().interrupt();
1194:                  // don't keep looking for a connection if we've been
1195:                  // interrupted
1196:                  throw new ProtocolException(
1197:                                  "Interrupted getStoreProtocol", ex);
1198:                 }
1199:          } else {
1200:                 pool.storeConnectionInUse = true;
1201:
1202:                 pool.logger.fine("getStoreProtocol() -- storeConnectionInUse");
1203:          }
1204:
1205: timeoutConnections();
1206: }
1207:         }
1208:         return p;
1209: }
1210:
1211: /**
1212: * Get a store protocol object for use by a folder.
1213: */
1214: IMAPProtocol getFolderStoreProtocol() throws ProtocolException {
1215:         IMAPProtocol p = getStoreProtocol();
1216:         p.removeResponseHandler(this);
1217:         p.addResponseHandler(nonStoreResponseHandler);
1218:         return p;
1219: }
1220:
1221: /*
1222: * Some authentication systems use one time passwords
1223: * or tokens, so each authentication request requires
1224: * a new password. This "kludge" allows a callback
1225: * to application code to get a new password.
1226: *
1227: * XXX - remove this when SASL support is added
1228: */
1229: private void refreshPassword() {
1230:         if (logger.isLoggable(Level.FINE))
1231:          logger.fine("refresh password, user: " + traceUser(user));
1232:         InetAddress addr;
1233:         try {
1234:          addr = InetAddress.getByName(host);
1235:         } catch (UnknownHostException e) {
1236:          addr = null;
1237:         }
1238:         PasswordAuthentication pa =
1239:          session.requestPasswordAuthentication(addr, port,
1240:                                         name, null, user);
1241:         if (pa != null) {
1242:          user = pa.getUserName();
1243:          password = pa.getPassword();
1244:         }
1245: }
1246:
1247: /**
1248: * If a SELECT succeeds, but indicates that the folder is
1249: * READ-ONLY, and the user asked to open the folder READ_WRITE,
1250: * do we allow the open to succeed?
1251: */
1252: boolean allowReadOnlySelect() {
1253:         return PropUtil.getBooleanProperty(session.getProperties(),
1254:          "mail." + name + ".allowreadonlyselect", false);
1255: }
1256:
1257: /**
1258: * Report whether the separateStoreConnection is set.
1259: */
1260: boolean hasSeparateStoreConnection() {
1261: return pool.separateStoreConnection;
1262: }
1263:
1264: /**
1265: * Return the connection pool logger.
1266: */
1267: MailLogger getConnectionPoolLogger() {
1268: return pool.logger;
1269: }
1270:
1271: /**
1272: * Report whether message cache debugging is enabled.
1273: */
1274: boolean getMessageCacheDebug() {
1275: return messageCacheDebug;
1276: }
1277:
1278: /**
1279: * Report whether the connection pool is full.
1280: */
1281: boolean isConnectionPoolFull() {
1282:
1283: synchronized (pool) {
1284:          if (pool.logger.isLoggable(Level.FINE))
1285: pool.logger.fine("connection pool current size: " +
1286: pool.authenticatedConnections.size() +
1287: " pool size: " + pool.poolSize);
1288:
1289: return (pool.authenticatedConnections.size() >= pool.poolSize);
1290:
1291: }
1292: }
1293:
1294: /**
1295: * Release the protocol object back to the connection pool.
1296: */
1297: void releaseProtocol(IMAPFolder folder, IMAPProtocol protocol) {
1298:
1299: synchronized (pool) {
1300: if (protocol != null) {
1301: // If the pool is not full, add the store as a response handler
1302: // and return the protocol object to the connection pool.
1303: if (!isConnectionPoolFull()) {
1304: protocol.addResponseHandler(this);
1305: pool.authenticatedConnections.addElement(protocol);
1306:
1307:                  if (logger.isLoggable(Level.FINE))
1308: logger.fine(
1309:                          "added an Authenticated connection -- size: " +
1310: pool.authenticatedConnections.size());
1311: } else {
1312:                  logger.fine(
1313:                         "pool is full, not adding an Authenticated connection");
1314: try {
1315: protocol.logout();
1316: } catch (ProtocolException pex) {};
1317: }
1318: }
1319:
1320: if (pool.folders != null)
1321: pool.folders.removeElement(folder);
1322:
1323: timeoutConnections();
1324: }
1325: }
1326:
1327: /**
1328: * Release the store connection.
1329: */
1330: private void releaseStoreProtocol(IMAPProtocol protocol) {
1331:
1332:         // will be called from idle() without the Store lock held,
1333:         // but cleanup is synchronized and will acquire the Store lock
1334:
1335:         if (protocol == null) {
1336:          cleanup();                // failed to ever get the connection
1337:          return;                // nothing to release
1338:         }
1339:
1340:         /*
1341:          * Read out the flag that says whether this connection failed
1342:          * before releasing the protocol object for others to use.
1343:          */
1344:         boolean failed;
1345:         synchronized (connectionFailedLock) {
1346:          failed = connectionFailed;
1347:          connectionFailed = false;        // reset for next use
1348:         }
1349:
1350:         // now free the store connection
1351: synchronized (pool) {
1352:          pool.storeConnectionInUse = false;
1353:          pool.notifyAll();        // in case anyone waiting
1354:
1355:          pool.logger.fine("releaseStoreProtocol()");
1356:
1357: timeoutConnections();
1358: }
1359:
1360:         /*
1361:          * If the connection died while we were using it, clean up.
1362:          * It's critical that the store connection be freed and the
1363:          * connection pool not be locked while we do this.
1364:          */
1365:         assert !Thread.holdsLock(pool);
1366:         if (failed)
1367:          cleanup();
1368: }
1369:
1370: /**
1371: * Release a store protocol object that was being used by a folder.
1372: */
1373: void releaseFolderStoreProtocol(IMAPProtocol protocol) {
1374:         if (protocol == null)
1375:          return;                // should never happen
1376:         protocol.removeResponseHandler(nonStoreResponseHandler);
1377:         protocol.addResponseHandler(this);
1378: synchronized (pool) {
1379:          pool.storeConnectionInUse = false;
1380:          pool.notifyAll();        // in case anyone waiting
1381:
1382:          pool.logger.fine("releaseFolderStoreProtocol()");
1383:
1384: timeoutConnections();
1385: }
1386: }
1387:
1388: /**
1389: * Empty the connection pool.
1390: */
1391: private void emptyConnectionPool(boolean force) {
1392:
1393: synchronized (pool) {
1394: for (int index = pool.authenticatedConnections.size() - 1;
1395:                  index >= 0; --index) {
1396: try {
1397:                  IMAPProtocol p =
1398:                         pool.authenticatedConnections.elementAt(index);
1399:                  p.removeResponseHandler(this);
1400:                  if (force)
1401:                         p.disconnect();
1402:                  else
1403:                         p.logout();
1404: } catch (ProtocolException pex) {};
1405: }
1406:
1407: pool.authenticatedConnections.removeAllElements();
1408: }
1409:
1410:         pool.logger.fine("removed all authenticated connections from pool");
1411: }
1412:
1413: /**
1414: * Check to see if it's time to shrink the connection pool.
1415: */
1416: private void timeoutConnections() {
1417:
1418: synchronized (pool) {
1419:
1420: // If we've exceeded the pruning interval, look for stale
1421: // connections to logout.
1422: if (System.currentTimeMillis() - pool.lastTimePruned >
1423: pool.pruningInterval &&
1424: pool.authenticatedConnections.size() > 1) {
1425:
1426:                 if (pool.logger.isLoggable(Level.FINE)) {
1427: pool.logger.fine("checking for connections to prune: " +
1428: (System.currentTimeMillis() - pool.lastTimePruned));
1429: pool.logger.fine("clientTimeoutInterval: " +
1430: pool.clientTimeoutInterval);
1431: }
1432:
1433: IMAPProtocol p;
1434:
1435: // Check the timestamp of the protocol objects in the pool and
1436: // logout if the interval exceeds the client timeout value
1437: // (leave the first connection).
1438: for (int index = pool.authenticatedConnections.size() - 1;
1439: index > 0; index--) {
1440: p = pool.authenticatedConnections.
1441: elementAt(index);
1442:                  if (pool.logger.isLoggable(Level.FINE))
1443: pool.logger.fine("protocol last used: " +
1444: (System.currentTimeMillis() - p.getTimestamp()));
1445: if (System.currentTimeMillis() - p.getTimestamp() >
1446: pool.clientTimeoutInterval) {
1447:
1448:                         pool.logger.fine(
1449:                          "authenticated connection timed out, " +
1450:                          "logging out the connection");
1451:
1452: p.removeResponseHandler(this);
1453: pool.authenticatedConnections.removeElementAt(index);
1454:
1455: try {
1456: p.logout();
1457: } catch (ProtocolException pex) {}
1458: }
1459: }
1460: pool.lastTimePruned = System.currentTimeMillis();
1461: }
1462: }
1463: }
1464:
1465: /**
1466: * Get the block size to use for fetch requests on this Store.
1467: */
1468: int getFetchBlockSize() {
1469:         return blksize;
1470: }
1471:
1472: /**
1473: * Ignore the size reported in the BODYSTRUCTURE when fetching data?
1474: */
1475: boolean ignoreBodyStructureSize() {
1476:         return ignoreSize;
1477: }
1478:
1479: /**
1480: * Get a reference to the session.
1481: */
1482: Session getSession() {
1483: return session;
1484: }
1485:
1486: /**
1487: * Get the number of milliseconds to cache STATUS response.
1488: */
1489: int getStatusCacheTimeout() {
1490:         return statusCacheTimeout;
1491: }
1492:
1493: /**
1494: * Get the maximum size of a message to buffer for append.
1495: */
1496: int getAppendBufferSize() {
1497:         return appendBufferSize;
1498: }
1499:
1500: /**
1501: * Get the minimum amount of time to delay when returning from idle.
1502: */
1503: int getMinIdleTime() {
1504:         return minIdleTime;
1505: }
1506:
1507: /**
1508: * Throw a SearchException if the search expression is too complex?
1509: */
1510: boolean throwSearchException() {
1511:         return throwSearchException;
1512: }
1513:
1514: /**
1515: * Get the default "peek" value.
1516: */
1517: boolean getPeek() {
1518:         return peek;
1519: }
1520:
1521: /**
1522: * Return true if the specified capability string is in the list
1523: * of capabilities the server announced.
1524: *
1525: * @param        capability        the capability string
1526: * @return                        true if the server supports this capability
1527: * @exception        MessagingException for failures
1528: * @since        JavaMail 1.3.3
1529: */
1530: public synchronized boolean hasCapability(String capability)
1531:                                 throws MessagingException {
1532: IMAPProtocol p = null;
1533:         try {
1534:          p = getStoreProtocol();
1535: return p.hasCapability(capability);
1536:         } catch (ProtocolException pex) {
1537:          throw new MessagingException(pex.getMessage(), pex);
1538: } finally {
1539: releaseStoreProtocol(p);
1540: }
1541: }
1542:
1543: /**
1544: * Set the user name to be used with the PROXYAUTH command.
1545: * The PROXYAUTH user name can also be set using the
1546: * <code>mail.imap.proxyauth.user</code> property when this
1547: * Store is created.
1548: *
1549: * @param        user        the user name to set
1550: * @since        JavaMail 1.5.1
1551: */
1552: public void setProxyAuthUser(String user) {
1553:         proxyAuthUser = user;
1554: }
1555:
1556: /**
1557: * Get the user name to be used with the PROXYAUTH command.
1558: *
1559: * @return        the user name
1560: * @since        JavaMail 1.5.1
1561: */
1562: public String getProxyAuthUser() {
1563:         return proxyAuthUser;
1564: }
1565:
1566: /**
1567: * Check whether this store is connected. Override superclass
1568: * method, to actually ping our server connection.
1569: */
1570: @Override
1571: public synchronized boolean isConnected() {
1572:         if (!super.isConnected()) {
1573:          // if we haven't been connected at all, don't bother with
1574:          // the NOOP.
1575:          return false;
1576:         }
1577:
1578:         /*
1579:          * The below noop() request can:
1580:          * (1) succeed - in which case all is fine.
1581:          *
1582:          * (2) fail because the server returns NO or BAD, in which
1583:          *         case we ignore it since we can't really do anything.
1584:          * (2) fail because a BYE response is obtained from the
1585:          *        server
1586:          * (3) fail because the socket.write() to the server fails,
1587:          *        in which case the iap.protocol() code converts the
1588:          *        IOException into a BYE response.
1589:          *
1590:          * Thus, our BYE handler will take care of closing the Store
1591:          * in case our connection is really gone.
1592:          */
1593:
1594: IMAPProtocol p = null;
1595:         try {
1596:          p = getStoreProtocol();
1597: p.noop();
1598:         } catch (ProtocolException pex) {
1599:          // will return false below
1600: } finally {
1601: releaseStoreProtocol(p);
1602: }
1603:
1604:
1605:         return super.isConnected();
1606: }
1607:
1608: /**
1609: * Close this Store.
1610: */
1611: @Override
1612: public synchronized void close() throws MessagingException {
1613:         cleanup();
1614:         // do these again in case cleanup returned early
1615:         // because we were already closed due to a failure,
1616:         // in which case we force close everything
1617:         closeAllFolders(true);
1618:         emptyConnectionPool(true);
1619: }
1620:
1621: @Override
1622: protected void finalize() throws Throwable {
1623:         if (!finalizeCleanClose) {
1624:          // when finalizing, close connections abruptly
1625:          synchronized (connectionFailedLock) {
1626:                 connectionFailed = true;
1627:                 forceClose = true;
1628:          }
1629:          closeFoldersOnStoreFailure = true;        // make sure folders get closed
1630:         }
1631:         try {
1632:          close();
1633:         } finally {
1634:          super.finalize();
1635:         }
1636: }
1637:
1638: /**
1639: * Cleanup before dying.
1640: */
1641: private synchronized void cleanup() {
1642:         // if we're not connected, someone beat us to it
1643:         if (!super.isConnected()) {
1644:          logger.fine("IMAPStore cleanup, not connected");
1645:          return;
1646:         }
1647:
1648:         /*
1649:          * If forceClose is true, some thread ran into an error that suggests
1650:          * the server might be dead, so we force the folders to close
1651:          * abruptly without waiting for the server. Used when
1652:          * the store connection times out, for example.
1653:          */
1654:         boolean force;
1655:         synchronized (connectionFailedLock) {
1656:          force = forceClose;
1657:          forceClose = false;
1658:          connectionFailed = false;
1659:         }
1660:         if (logger.isLoggable(Level.FINE))
1661:          logger.fine("IMAPStore cleanup, force " + force);
1662:
1663:         if (!force || closeFoldersOnStoreFailure) {
1664:          closeAllFolders(force);
1665:         }
1666:
1667:         emptyConnectionPool(force);
1668:
1669:         // to set the state and send the closed connection event
1670:         try {
1671:          super.close();
1672:         } catch (MessagingException mex) {
1673:          // ignore it
1674:         }
1675:         logger.fine("IMAPStore cleanup done");
1676: }
1677:
1678: /**
1679: * Close all open Folders. If force is true, close them forcibly.
1680: */
1681: private void closeAllFolders(boolean force) {
1682: List<IMAPFolder> foldersCopy = null;
1683: boolean done = true;
1684:
1685:         // To avoid violating the locking hierarchy, there's no lock we
1686:         // can hold that prevents another thread from trying to open a
1687:         // folder at the same time we're trying to close all the folders.
1688:         // Thus, there's an inherent race condition here. We close all
1689:         // the folders we know about and then check whether any new folders
1690:         // have been opened in the mean time. We keep trying until we're
1691:         // successful in closing all the folders.
1692:         for (;;) {
1693:          // Make a copy of the folders list so we do not violate the
1694:          // folder-connection pool locking hierarchy.
1695:          synchronized (pool) {
1696:                 if (pool.folders != null) {
1697:                  done = false;
1698:                  foldersCopy = pool.folders;
1699:                  pool.folders = null;
1700:                 } else {
1701: done = true;
1702: }
1703:          }
1704:          if (done)
1705:                 break;
1706:
1707:          // Close and remove any open folders under this Store.
1708:          for (int i = 0, fsize = foldersCopy.size(); i < fsize; i++) {
1709:                 IMAPFolder f = foldersCopy.get(i);
1710:
1711:                 try {
1712:                  if (force) {
1713:                         logger.fine("force folder to close");
1714:                         // Don't want to wait for folder connection to timeout
1715:                         // (if, for example, the server is down) so we close
1716:                         // folders abruptly.
1717:                         f.forceClose();
1718:                  } else {
1719:                         logger.fine("close folder");
1720:                         f.close(false);
1721:                  }
1722:                 } catch (MessagingException mex) {
1723:                  // Who cares ?! Ignore 'em.
1724:                 } catch (IllegalStateException ex) {
1725:                  // Ditto
1726:                 }
1727:          }
1728:
1729:         }
1730: }
1731:
1732: /**
1733: * Get the default folder, representing the root of this user's
1734: * namespace. Returns a closed DefaultFolder object.
1735: */
1736: @Override
1737: public synchronized Folder getDefaultFolder() throws MessagingException {
1738:         checkConnected();
1739:         return new DefaultFolder(this);
1740: }
1741:
1742: /**
1743: * Get named folder. Returns a new, closed IMAPFolder.
1744: */
1745: @Override
1746: public synchronized Folder getFolder(String name)
1747:                                 throws MessagingException {
1748:         checkConnected();
1749:         return newIMAPFolder(name, IMAPFolder.UNKNOWN_SEPARATOR);
1750: }
1751:
1752: /**
1753: * Get named folder. Returns a new, closed IMAPFolder.
1754: */
1755: @Override
1756: public synchronized Folder getFolder(URLName url)
1757:                                 throws MessagingException {
1758:         checkConnected();
1759:         return newIMAPFolder(url.getFile(), IMAPFolder.UNKNOWN_SEPARATOR);
1760: }
1761:
1762: /**
1763: * Create an IMAPFolder object. If user supplied their own class,
1764: * use it. Otherwise, call the constructor.
1765: *
1766: * @param        fullName the full name of the folder
1767: * @param        separator the separator character for the folder hierarchy
1768: * @param        isNamespace does this name represent a namespace?
1769: * @return                the new IMAPFolder object
1770: */
1771: protected IMAPFolder newIMAPFolder(String fullName, char separator,
1772:                                 Boolean isNamespace) {
1773:         IMAPFolder f = null;
1774:         if (folderConstructor != null) {
1775:          try {
1776:                 Object[] o =
1777:                  { fullName, Character.valueOf(separator), this, isNamespace };
1778:                 f = (IMAPFolder)folderConstructor.newInstance(o);
1779:          } catch (Exception ex) {
1780:                 logger.log(Level.FINE,
1781:                          "exception creating IMAPFolder class", ex);
1782:          }
1783:         }
1784:         if (f == null)
1785:          f = new IMAPFolder(fullName, separator, this, isNamespace);
1786:         return f;
1787: }
1788:
1789: /**
1790: * Create an IMAPFolder object. Call the newIMAPFolder method
1791: * above with a null isNamespace.
1792: *
1793: * @param        fullName the full name of the folder
1794: * @param        separator the separator character for the folder hierarchy
1795: * @return                the new IMAPFolder object
1796: */
1797: protected IMAPFolder newIMAPFolder(String fullName, char separator) {
1798:         return newIMAPFolder(fullName, separator, null);
1799: }
1800:
1801: /**
1802: * Create an IMAPFolder object. If user supplied their own class,
1803: * use it. Otherwise, call the constructor.
1804: *
1805: * @param        li        the ListInfo for the folder
1806: * @return                the new IMAPFolder object
1807: */
1808: protected IMAPFolder newIMAPFolder(ListInfo li) {
1809:         IMAPFolder f = null;
1810:         if (folderConstructorLI != null) {
1811:          try {
1812:                 Object[] o = { li, this };
1813:                 f = (IMAPFolder)folderConstructorLI.newInstance(o);
1814:          } catch (Exception ex) {
1815:                 logger.log(Level.FINE,
1816:                         "exception creating IMAPFolder class LI", ex);
1817:          }
1818:         }
1819:         if (f == null)
1820:          f = new IMAPFolder(li, this);
1821:         return f;
1822: }
1823:
1824: /**
1825: * Using the IMAP NAMESPACE command (RFC 2342), return a set
1826: * of folders representing the Personal namespaces.
1827: */
1828: @Override
1829: public Folder[] getPersonalNamespaces() throws MessagingException {
1830:         Namespaces ns = getNamespaces();
1831:         if (ns == null || ns.personal == null)
1832:          return super.getPersonalNamespaces();
1833:         return namespaceToFolders(ns.personal, null);
1834: }
1835:
1836: /**
1837: * Using the IMAP NAMESPACE command (RFC 2342), return a set
1838: * of folders representing the User's namespaces.
1839: */
1840: @Override
1841: public Folder[] getUserNamespaces(String user)
1842:                                 throws MessagingException {
1843:         Namespaces ns = getNamespaces();
1844:         if (ns == null || ns.otherUsers == null)
1845:          return super.getUserNamespaces(user);
1846:         return namespaceToFolders(ns.otherUsers, user);
1847: }
1848:
1849: /**
1850: * Using the IMAP NAMESPACE command (RFC 2342), return a set
1851: * of folders representing the Shared namespaces.
1852: */
1853: @Override
1854: public Folder[] getSharedNamespaces() throws MessagingException {
1855:         Namespaces ns = getNamespaces();
1856:         if (ns == null || ns.shared == null)
1857:          return super.getSharedNamespaces();
1858:         return namespaceToFolders(ns.shared, null);
1859: }
1860:
1861: private synchronized Namespaces getNamespaces() throws MessagingException {
1862:         checkConnected();
1863:
1864: IMAPProtocol p = null;
1865:
1866:         if (namespaces == null) {
1867:          try {
1868: p = getStoreProtocol();
1869:                 namespaces = p.namespace();
1870:          } catch (BadCommandException bex) {
1871:                 // NAMESPACE not supported, ignore it
1872:          } catch (ConnectionException cex) {
1873:                 throw new StoreClosedException(this, cex.getMessage());
1874:          } catch (ProtocolException pex) {
1875:                 throw new MessagingException(pex.getMessage(), pex);
1876:          } finally {
1877:                 releaseStoreProtocol(p);
1878:          }
1879:         }
1880:         return namespaces;
1881: }
1882:
1883: private Folder[] namespaceToFolders(Namespaces.Namespace[] ns,
1884:                                         String user) {
1885:         Folder[] fa = new Folder[ns.length];
1886:         for (int i = 0; i < fa.length; i++) {
1887:          String name = ns[i].prefix;
1888:          if (user == null) {
1889:                 // strip trailing delimiter
1890:                 int len = name.length();
1891:                 if ( len > 0 && name.charAt(len - 1) == ns[i].delimiter)
1892:                  name = name.substring(0, len - 1);
1893:          } else {
1894:                 // add user
1895:                 name += user;
1896:          }
1897:          fa[i] = newIMAPFolder(name, ns[i].delimiter,
1898:                                         Boolean.valueOf(user == null));
1899:         }
1900:         return fa;
1901: }
1902:
1903: /**
1904: * Get the quotas for the named quota root.
1905: * Quotas are controlled on the basis of a quota root, not
1906: * (necessarily) a folder. The relationship between folders
1907: * and quota roots depends on the IMAP server. Some servers
1908: * might implement a single quota root for all folders owned by
1909: * a user. Other servers might implement a separate quota root
1910: * for each folder. A single folder can even have multiple
1911: * quota roots, perhaps controlling quotas for different
1912: * resources.
1913: *
1914: * @param        root        the name of the quota root
1915: * @return                array of Quota objects
1916: * @exception MessagingException        if the server doesn't support the
1917: *                                        QUOTA extension
1918: */
1919: @Override
1920: public synchronized Quota[] getQuota(String root)
1921:                                 throws MessagingException {
1922:         checkConnected();
1923:         Quota[] qa = null;
1924:
1925: IMAPProtocol p = null;
1926:         try {
1927:          p = getStoreProtocol();
1928:          qa = p.getQuotaRoot(root);
1929:         } catch (BadCommandException bex) {
1930:          throw new MessagingException("QUOTA not supported", bex);
1931:         } catch (ConnectionException cex) {
1932:          throw new StoreClosedException(this, cex.getMessage());
1933:         } catch (ProtocolException pex) {
1934:          throw new MessagingException(pex.getMessage(), pex);
1935:         } finally {
1936:          releaseStoreProtocol(p);
1937:         }
1938:         return qa;
1939: }
1940:
1941: /**
1942: * Set the quotas for the quota root specified in the quota argument.
1943: * Typically this will be one of the quota roots obtained from the
1944: * <code>getQuota</code> method, but it need not be.
1945: *
1946: * @param        quota        the quota to set
1947: * @exception MessagingException        if the server doesn't support the
1948: *                                        QUOTA extension
1949: */
1950: @Override
1951: public synchronized void setQuota(Quota quota) throws MessagingException {
1952:         checkConnected();
1953: IMAPProtocol p = null;
1954:         try {
1955:          p = getStoreProtocol();
1956:          p.setQuota(quota);
1957:         } catch (BadCommandException bex) {
1958:          throw new MessagingException("QUOTA not supported", bex);
1959:         } catch (ConnectionException cex) {
1960:          throw new StoreClosedException(this, cex.getMessage());
1961:         } catch (ProtocolException pex) {
1962:          throw new MessagingException(pex.getMessage(), pex);
1963:         } finally {
1964:          releaseStoreProtocol(p);
1965:         }
1966: }
1967:
1968: private void checkConnected() {
1969:         assert Thread.holdsLock(this);
1970:         if (!super.isConnected())
1971:          throw new IllegalStateException("Not connected");
1972: }
1973:
1974: /**
1975: * Response handler method.
1976: */
1977: @Override
1978: public void handleResponse(Response r) {
1979:         // Any of these responses may have a response code.
1980:         if (r.isOK() || r.isNO() || r.isBAD() || r.isBYE())
1981:          handleResponseCode(r);
1982:         if (r.isBYE()) {
1983:          logger.fine("IMAPStore connection dead");
1984:          // Store's IMAP connection is dead, save the response so that
1985:          // releaseStoreProtocol will cleanup later.
1986:          synchronized (connectionFailedLock) {
1987:                 connectionFailed = true;
1988:                 if (r.isSynthetic())
1989:                  forceClose = true;
1990:          }
1991:          return;
1992:         }
1993: }
1994:
1995: /**
1996: * Use the IMAP IDLE command (see
1997: * <A HREF="http://www.ietf.org/rfc/rfc2177.txt">RFC 2177</A>),
1998: * if supported by the server, to enter idle mode so that the server
1999: * can send unsolicited notifications
2000: * without the need for the client to constantly poll the server.
2001: * Use a <code>ConnectionListener</code> to be notified of
2002: * events. When another thread (e.g., the listener thread)
2003: * needs to issue an IMAP comand for this Store, the idle mode will
2004: * be terminated and this method will return. Typically the caller
2005: * will invoke this method in a loop. <p>
2006: *
2007: * If the mail.imap.enableimapevents property is set, notifications
2008: * received while the IDLE command is active will be delivered to
2009: * <code>ConnectionListener</code>s as events with a type of
2010: * <code>IMAPStore.RESPONSE</code>. The event's message will be
2011: * the raw IMAP response string.
2012: * Note that most IMAP servers will not deliver any events when
2013: * using the IDLE command on a connection with no mailbox selected
2014: * (i.e., this method). In most cases you'll want to use the
2015: * <code>idle</code> method on <code>IMAPFolder</code>. <p>
2016: *
2017: * NOTE: This capability is highly experimental and likely will change
2018: * in future releases. <p>
2019: *
2020: * The mail.imap.minidletime property enforces a minimum delay
2021: * before returning from this method, to ensure that other threads
2022: * have a chance to issue commands before the caller invokes this
2023: * method again. The default delay is 10 milliseconds.
2024: *
2025: * @exception MessagingException        if the server doesn't support the
2026: *                                        IDLE extension
2027: * @exception IllegalStateException        if the store isn't connected
2028: *
2029: * @since        JavaMail 1.4.1
2030: */
2031: public void idle() throws MessagingException {
2032:         IMAPProtocol p = null;
2033:         // ASSERT: Must NOT be called with the connection pool
2034:         // synchronization lock held.
2035:         assert !Thread.holdsLock(pool);
2036:         synchronized (this) {
2037:          checkConnected();
2038:         }
2039:         boolean needNotification = false;
2040:         try {
2041:          synchronized (pool) {
2042:                 p = getStoreProtocol();
2043:                 if (pool.idleState != ConnectionPool.RUNNING) {
2044:                  // some other thread must be running the IDLE
2045:                  // command, we'll just wait for it to finish
2046:                  // without aborting it ourselves
2047:                  try {
2048:                         // give up lock and wait to be not idle
2049:                         pool.wait();
2050:                  } catch (InterruptedException ex) {
2051:                         // restore the interrupted state, which callers might
2052:                         // depend on
2053:                         Thread.currentThread().interrupt();
2054:                         // stop waiting and return to caller
2055:                         throw new MessagingException("idle interrupted", ex);
2056:                  }
2057:                  return;
2058:                 }
2059:                 p.idleStart();
2060:                 needNotification = true;
2061:                 pool.idleState = ConnectionPool.IDLE;
2062:                 pool.idleProtocol = p;
2063:          }
2064:
2065:          /*
2066:          * We gave up the pool lock so that other threads
2067:          * can get into the pool far enough to see that we're
2068:          * in IDLE and abort the IDLE.
2069:          *
2070:          * Now we read responses from the IDLE command, especially
2071:          * including unsolicited notifications from the server.
2072:          * We don't hold the pool lock while reading because
2073:          * it protects the idleState and other threads need to be
2074:          * able to examine the state.
2075:          *
2076:          * We hold the pool lock while processing the responses.
2077:          */
2078:          for (;;) {
2079:                 Response r = p.readIdleResponse();
2080:                 synchronized (pool) {
2081:                  if (r == null || !p.processIdleResponse(r)) {
2082:                         pool.idleState = ConnectionPool.RUNNING;
2083:                         pool.idleProtocol = null;
2084:                         pool.notifyAll();
2085:                         needNotification = false;
2086:                         break;
2087:                  }
2088:                 }
2089:                 if (enableImapEvents && r.isUnTagged()) {
2090:                  notifyStoreListeners(IMAPStore.RESPONSE, r.toString());
2091:                 }
2092:          }
2093:
2094:          /*
2095:          * Enforce a minimum delay to give time to threads
2096:          * processing the responses that came in while we
2097:          * were idle.
2098:          */
2099:          int minidle = getMinIdleTime();
2100:          if (minidle > 0) {
2101:                 try {
2102:                  Thread.sleep(minidle);
2103:                 } catch (InterruptedException ex) {
2104:                  // restore the interrupted state, which callers might
2105:                  // depend on
2106:                  Thread.currentThread().interrupt();
2107:                 }
2108:          }
2109:
2110:         } catch (BadCommandException bex) {
2111:          throw new MessagingException("IDLE not supported", bex);
2112:         } catch (ConnectionException cex) {
2113:          throw new StoreClosedException(this, cex.getMessage());
2114:         } catch (ProtocolException pex) {
2115:          throw new MessagingException(pex.getMessage(), pex);
2116:         } finally {
2117:          if (needNotification) {
2118:                 synchronized (pool) {
2119:                  pool.idleState = ConnectionPool.RUNNING;
2120:                  pool.idleProtocol = null;
2121:                  pool.notifyAll();
2122:                 }
2123:          }
2124:          releaseStoreProtocol(p);
2125:         }
2126: }
2127:
2128: /*
2129: * If an IDLE command is in progress, abort it if necessary,
2130: * and wait until it completes.
2131: * ASSERT: Must be called with the pool's lock held.
2132: */
2133: private void waitIfIdle() throws ProtocolException {
2134:         assert Thread.holdsLock(pool);
2135:         while (pool.idleState != ConnectionPool.RUNNING) {
2136:          if (pool.idleState == ConnectionPool.IDLE) {
2137:                 pool.idleProtocol.idleAbort();
2138:                 pool.idleState = ConnectionPool.ABORTING;
2139:          }
2140:          try {
2141:                 // give up lock and wait to be not idle
2142:                 pool.wait();
2143:          } catch (InterruptedException ex) {
2144:                 // If someone is trying to interrupt us we can't keep going
2145:                 // around the loop waiting for IDLE to complete, but we can't
2146:                 // just return because callers expect the idleState to be
2147:                 // RUNNING when we return. Throwing this exception seems
2148:                 // like the best choice.
2149:                 throw new ProtocolException("Interrupted waitIfIdle", ex);
2150:          }
2151:         }
2152: }
2153:
2154: /**
2155: * Send the IMAP ID command (if supported by the server) and return
2156: * the result from the server. The ID command identfies the client
2157: * to the server and returns information about the server to the client.
2158: * See <A HREF="http://www.ietf.org/rfc/rfc2971.txt">RFC 2971</A>.
2159: * The returned Map is unmodifiable.
2160: *
2161: * @param        clientParams        a Map of keys and values identifying the client
2162: * @return                        a Map of keys and values identifying the server
2163: * @exception MessagingException        if the server doesn't support the
2164: *                                        ID extension
2165: * @since        JavaMail 1.5.1
2166: */
2167: public synchronized Map<String, String> id(Map<String, String> clientParams)
2168:                                 throws MessagingException {
2169:         checkConnected();
2170:         Map<String, String> serverParams = null;
2171:
2172: IMAPProtocol p = null;
2173:         try {
2174:          p = getStoreProtocol();
2175:          serverParams = p.id(clientParams);
2176:         } catch (BadCommandException bex) {
2177:          throw new MessagingException("ID not supported", bex);
2178:         } catch (ConnectionException cex) {
2179:          throw new StoreClosedException(this, cex.getMessage());
2180:         } catch (ProtocolException pex) {
2181:          throw new MessagingException(pex.getMessage(), pex);
2182:         } finally {
2183:          releaseStoreProtocol(p);
2184:         }
2185:         return serverParams;
2186: }
2187:
2188: /**
2189: * Handle notifications and alerts.
2190: * Response must be an OK, NO, BAD, or BYE response.
2191: */
2192: void handleResponseCode(Response r) {
2193:         if (enableResponseEvents)
2194:          notifyStoreListeners(IMAPStore.RESPONSE, r.toString());
2195:         String s = r.getRest();        // get the text after the response
2196:         boolean isAlert = false;
2197:         if (s.startsWith("[")) {        // a response code
2198:          int i = s.indexOf(']');
2199:          // remember if it's an alert
2200:          if (i > 0 && s.substring(0, i + 1).equalsIgnoreCase("[ALERT]"))
2201:                 isAlert = true;
2202:          // strip off the response code in any event
2203:          s = s.substring(i + 1).trim();
2204:         }
2205:         if (isAlert)
2206:          notifyStoreListeners(StoreEvent.ALERT, s);
2207:         else if (r.isUnTagged() && s.length() > 0)
2208:          // Only send notifications that come with untagged
2209:          // responses, and only if there is actually some
2210:          // text there.
2211:          notifyStoreListeners(StoreEvent.NOTICE, s);
2212: }
2213:
2214: private String traceUser(String user) {
2215:         return debugusername ? user : "<user name suppressed>";
2216: }
2217:
2218: private String tracePassword(String password) {
2219:         return debugpassword ? password :
2220:                                 (password == null ? "<null>" : "<non-null>");
2221: }
2222: }