KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sun > mail > imap > IMAPStore


1 /*
2  * The contents of this file are subject to the terms
3  * of the Common Development and Distribution License
4  * (the "License"). You may not use this file except
5  * in compliance with the License.
6  *
7  * You can obtain a copy of the license at
8  * glassfish/bootstrap/legal/CDDLv1.0.txt or
9  * https://glassfish.dev.java.net/public/CDDLv1.0.html.
10  * See the License for the specific language governing
11  * permissions and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL
14  * HEADER in each file and include the License file at
15  * glassfish/bootstrap/legal/CDDLv1.0.txt. If applicable,
16  * add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your
18  * own identifying information: Portions Copyright [yyyy]
19  * [name of copyright owner]
20  */

21
22 /*
23  * @(#)IMAPStore.java 1.66 05/08/29
24  *
25  * Copyright 1997-2005 Sun Microsystems, Inc. All Rights Reserved.
26  */

27
28 package com.sun.mail.imap;
29
30 import java.util.Vector JavaDoc;
31 import java.util.StringTokenizer JavaDoc;
32 import java.io.PrintStream JavaDoc;
33 import java.io.IOException JavaDoc;
34 import java.net.InetAddress JavaDoc;
35 import java.net.UnknownHostException JavaDoc;
36
37 import javax.mail.*;
38 import javax.mail.event.*;
39
40 import com.sun.mail.iap.*;
41 import com.sun.mail.imap.protocol.*;
42
43 /**
44  * This class provides access to an IMAP message store. <p>
45  *
46  * Applications that need to make use of IMAP-specific features may cast
47  * a <code>Store</code> object to an <code>IMAPStore</code> object and
48  * use the methods on this class. The {@link #getQuota getQuota} and
49  * {@link #setQuota setQuota} methods support the IMAP QUOTA extension.
50  * Refer to <A HREF="http://www.ietf.org/rfc/rfc2087.txt">RFC 2087</A>
51  * for more information. <p>
52  *
53  * See the <a HREF="package-summary.html">com.sun.mail.imap</a> package
54  * documentation for further information on the IMAP protocol provider. <p>
55  *
56  * <strong>WARNING:</strong> The APIs unique to this class should be
57  * considered <strong>EXPERIMENTAL</strong>. They may be changed in the
58  * future in ways that are incompatible with applications using the
59  * current APIs.
60  *
61  * @version 1.66, 05/08/29
62  * @author John Mani
63  * @author Bill Shannon
64  * @author Jim Glennon
65  */

66 /*
67  * This package is implemented over the "imap.protocol" package, which
68  * implements the protocol-level commands. <p>
69  *
70  * A connected IMAPStore maintains a pool of IMAP protocol objects for
71  * use in communicating with the IMAP server. The IMAPStore will create
72  * the initial AUTHENTICATED connection and seed the pool with this
73  * connection. As folders are opened and new IMAP protocol objects are
74  * needed, the IMAPStore will provide them from the connection pool,
75  * or create them if none are available. When a folder is closed,
76  * its IMAP protocol object is returned to the connection pool if the
77  * pool is not over capacity. The pool size can be configured by setting
78  * the mail.imap.connectionpoolsize property. <p>
79  *
80  * A mechanism is provided for timing out idle connection pool IMAP
81  * protocol objects. Timed out connections are closed and removed (pruned)
82  * from the connection pool. The time out interval can be configured via
83  * the mail.imap.connectionpooltimeout property. <p>
84  *
85  * The connected IMAPStore object may or may not maintain a separate IMAP
86  * protocol object that provides the store a dedicated connection to the
87  * IMAP server. This is provided mainly for compatibility with previous
88  * implementations of JavaMail and is determined by the value of the
89  * mail.imap.separatestoreconnection property. <p>
90  *
91  * An IMAPStore object provides closed IMAPFolder objects thru its list()
92  * and listSubscribed() methods. A closed IMAPFolder object acquires an
93  * IMAP protocol object from the store to communicate with the server. When
94  * the folder is opened, it gets its own protocol object and thus its own,
95  * separate connection to the server. The store maintains references to
96  * all 'open' folders. When a folder is/gets closed, the store removes
97  * it from its list. When the store is/gets closed, it closes all open
98  * folders in its list, thus cleaning up all open connections to the
99  * server. <p>
100  *
101  * A mutex is used to control access to the connection pool resources.
102  * Any time any of these resources need to be accessed, the following
103  * convention should be followed:
104  *
105  * synchronized (pool) { // ACQUIRE LOCK
106  * // access connection pool resources
107  * } // RELEASE LOCK <p>
108  *
109  * The locking relationship between the store and folders is that the
110  * store lock must be acquired before a folder lock. This is currently only
111  * applicable in the store's cleanup method. It's important that the
112  * connection pool lock is not not held when calling into folder objects.
113  * The locking hierarchy is that a folder lock must be acquired before
114  * any connection pool operations are performed. <p>
115  *
116  * The IMAPStore implements the ResponseHandler interface and listens to
117  * BYE or untagged OK-notification events from the server. <p>
118  */

119
120 public class IMAPStore extends Store
121          implements QuotaAwareStore, ResponseHandler {
122     
123     private String JavaDoc name = "imap"; // name of this protocol
124
private int defaultPort = 143; // default IMAP port
125
private boolean isSSL = false; // use SSL?
126

127     private int port = -1; // port to use
128
private int blksize = 1024 * 16; // Block size for data requested
129
// in FETCH requests. Defaults to
130
// 16K
131

132     private int statusCacheTimeout = 1000; // cache Status for 1 second
133

134     private int appendBufferSize = -1; // max size of msg buffered for append
135

136     // Auth info
137
private String JavaDoc host;
138     private String JavaDoc user;
139     private String JavaDoc password;
140     private String JavaDoc proxyAuthUser;
141     private String JavaDoc authorizationID;
142     private String JavaDoc saslRealm;
143
144     private Namespaces namespaces;
145
146     private boolean disableAuthLogin = false; // disable AUTH=LOGIN
147
private boolean disableAuthPlain = false; // disable AUTH=PLAIN
148
private boolean enableStartTLS = false; // enable STARTTLS
149
private boolean enableSASL = false; // enable SASL authentication
150
private String JavaDoc[] saslMechanisms;
151     private boolean forcePasswordRefresh = false;
152
153     private boolean debug;
154     private PrintStream JavaDoc out; // debug output stream
155

156     // Connection pool info
157

158     static class ConnectionPool {
159
160         // container for the pool's IMAP protocol objects
161
private Vector JavaDoc authenticatedConnections = new Vector JavaDoc();
162
163         // vectore of open folders
164
private Vector JavaDoc folders;
165
166         // flag to indicate whether there is a dedicated connection for
167
// store commands
168
private boolean separateStoreConnection = false;
169
170         // counter of users of a borrowed connection (for store commands)
171
private long borrowedStoreConnections = 0;
172
173         //default client timeout interval
174
private long clientTimeoutInterval = 45 * 1000; // 45 seconds
175

176         //default server timeout interval
177
private long serverTimeoutInterval = 30 *60 * 1000; // 30 minutes
178

179         // the last time (in millis) the pool was checked for timed out
180
// connections
181
private long lastTimePruned;
182
183         // default size of the connection pool
184
private int poolSize = 1;
185
186         // default interval for checking for timed out connections
187
private long pruningInterval = 60000;
188     
189         // connection pool debug flag
190
private boolean debug = false;
191     }
192  
193     private ConnectionPool pool = new ConnectionPool();
194
195  
196     /**
197      * Constructor that takes a Session object and a URLName that
198      * represents a specific IMAP server.
199      */

200     public IMAPStore(Session session, URLName url) {
201     this(session, url, "imap", 143, false);
202     }
203
204     /**
205      * Constructor used by this class and by IMAPSSLStore subclass.
206      */

207     protected IMAPStore(Session session, URLName url,
208                 String JavaDoc name, int defaultPort, boolean isSSL) {
209     super(session, url); // call super constructor
210
if (url != null)
211         name = url.getProtocol();
212     this.name = name;
213     this.defaultPort = defaultPort;
214     this.isSSL = isSSL;
215
216         pool.lastTimePruned = System.currentTimeMillis();
217
218         debug = session.getDebug();
219     out = session.getDebugOut();
220     if (out == null) // should never happen
221
out = System.out;
222
223         String JavaDoc s = session.getProperty(
224         "mail." + name + ".connectionpool.debug");
225
226         if (s != null && s.equalsIgnoreCase("true"))
227             pool.debug = true;
228
229     s = session.getProperty("mail." + name + ".partialfetch");
230
231     if (s != null && s.equalsIgnoreCase("false")) {
232         // property exits and is set to false
233
blksize = -1; // turn off partial-fetch
234
if (debug)
235         out.println("DEBUG: mail.imap.partialfetch: false");
236     } else { // either property doesn't exist, or its set to true
237
if ((s = session.getProperty("mail." + name +".fetchsize"))
238              != null)
239         // Set the block size to be used in FETCH requests
240
blksize = Integer.parseInt(s);
241                 if (debug)
242                     out.println("DEBUG: mail.imap.fetchsize: " + blksize);
243     }
244
245     s = session.getProperty("mail." + name + ".statuscachetimeout");
246     if (s != null) {
247         statusCacheTimeout = Integer.parseInt(s);
248         if (debug)
249         out.println("DEBUG: mail.imap.statuscachetimeout: " +
250                         statusCacheTimeout);
251     }
252     s = session.getProperty("mail." + name + ".appendbuffersize");
253     if (s != null) {
254         appendBufferSize = Integer.parseInt(s);
255         if (debug)
256         out.println("DEBUG: mail.imap.appendbuffersize: " +
257                         appendBufferSize);
258     }
259
260         // check if the default connection pool size is overridden
261
s = session.getProperty("mail." + name + ".connectionpoolsize");
262         if (s != null) {
263             try {
264                 int size = Integer.parseInt(s);
265                 if (size > 0)
266                     pool.poolSize = size;
267             } catch (NumberFormatException JavaDoc nfe) {
268             }
269             if (pool.debug)
270                 out.println("DEBUG: mail.imap.connectionpoolsize: " +
271                     pool.poolSize);
272         }
273
274
275         // check if the default client-side timeout value is overridden
276
s = session.getProperty("mail." + name + ".connectionpooltimeout");
277         if (s != null) {
278             try {
279                 int connectionPoolTimeout = Integer.parseInt(s);
280                 if (connectionPoolTimeout > 0)
281                     pool.clientTimeoutInterval = connectionPoolTimeout;
282             } catch (NumberFormatException JavaDoc nfe) {
283             }
284             if (pool.debug)
285                 out.println("DEBUG: mail.imap.connectionpooltimeout: " +
286                     pool.clientTimeoutInterval);
287         }
288
289         // check if the default server-side timeout value is overridden
290
s = session.getProperty("mail." + name + ".servertimeout");
291         if (s != null) {
292             try {
293                 int serverTimeout = Integer.parseInt(s);
294                 if (serverTimeout > 0)
295                     pool.serverTimeoutInterval = serverTimeout;
296             } catch (NumberFormatException JavaDoc nfe) {
297             }
298             if (pool.debug)
299                 out.println("DEBUG: mail.imap.servertimeout: " +
300                     pool.serverTimeoutInterval);
301         }
302  
303         // check to see if we should use a separate (i.e. dedicated)
304
// store connection
305
s = session.getProperty("mail." + name + ".separatestoreconnection");
306         if (s != null && s.equalsIgnoreCase("true")) {
307             if (pool.debug)
308                 out.println("DEBUG: dedicate a store connection");
309             pool.separateStoreConnection = true;
310         }
311
312     // check if we should do a PROXYAUTH login
313
s = session.getProperty("mail." + name + ".proxyauth.user");
314     if (s != null) {
315         proxyAuthUser = s;
316         if (debug)
317         out.println("DEBUG: mail.imap.proxyauth.user: " +
318                         proxyAuthUser);
319     }
320
321     // check if AUTH=LOGIN is disabled
322
s = session.getProperty("mail." + name + ".auth.login.disable");
323     if (s != null && s.equalsIgnoreCase("true")) {
324         if (debug)
325         out.println("DEBUG: disable AUTH=LOGIN");
326         disableAuthLogin = true;
327     }
328
329     // check if AUTH=PLAIN is disabled
330
s = session.getProperty("mail." + name + ".auth.plain.disable");
331     if (s != null && s.equalsIgnoreCase("true")) {
332         if (debug)
333         out.println("DEBUG: disable AUTH=PLAIN");
334         disableAuthPlain = true;
335     }
336
337     // check if STARTTLS is enabled
338
s = session.getProperty("mail." + name + ".starttls.enable");
339     if (s != null && s.equalsIgnoreCase("true")) {
340         if (debug)
341         out.println("DEBUG: enable STARTTLS");
342         enableStartTLS = true;
343     }
344
345     // check if SASL is enabled
346
s = session.getProperty("mail." + name + ".sasl.enable");
347     if (s != null && s.equalsIgnoreCase("true")) {
348         if (debug)
349         out.println("DEBUG: enable SASL");
350         enableSASL = true;
351     }
352
353     // check if SASL mechanisms are specified
354
if (enableSASL) {
355         s = session.getProperty("mail." + name + ".sasl.mechanisms");
356         if (s != null && s.length() > 0) {
357         if (debug)
358             out.println("DEBUG: SASL mechanisms allowed: " + s);
359         Vector JavaDoc v = new Vector JavaDoc(5);
360         StringTokenizer JavaDoc st = new StringTokenizer JavaDoc(s, " ,");
361         while (st.hasMoreTokens()) {
362             String JavaDoc m = st.nextToken();
363             if (m.length() > 0)
364             v.addElement(m);
365         }
366         saslMechanisms = new String JavaDoc[v.size()];
367         v.copyInto(saslMechanisms);
368         }
369     }
370
371     // check if an authorization ID has been specified
372
s = session.getProperty("mail." + name + ".sasl.authorizationid");
373     if (s != null) {
374         authorizationID = s;
375         if (debug)
376         out.println("DEBUG: mail.imap.sasl.authorizationid: " +
377                         authorizationID);
378     }
379
380     // check if a SASL realm has been specified
381
s = session.getProperty("mail." + name + ".sasl.realm");
382     if (s != null) {
383         saslRealm = s;
384         if (debug)
385         out.println("DEBUG: mail.imap.sasl.realm: " + saslRealm);
386     }
387
388     // check if forcePasswordRefresh is enabled
389
s = session.getProperty("mail." + name + ".forcepasswordrefresh");
390     if (s != null && s.equalsIgnoreCase("true")) {
391         if (debug)
392         out.println("DEBUG: enable forcePasswordRefresh");
393         forcePasswordRefresh = true;
394     }
395     }
396
397     /**
398      * Implementation of protocolConnect(). Will create a connection
399      * to the server and authenticate the user using the mechanisms
400      * specified by various properties. <p>
401      *
402      * The <code>host</code>, <code>user</code>, and <code>password</code>
403      * parameters must all be non-null. If the authentication mechanism
404      * being used does not require a password, an empty string or other
405      * suitable dummy password should be used.
406      */

407     protected synchronized boolean
408     protocolConnect(String JavaDoc host, int pport, String JavaDoc user, String JavaDoc password)
409         throws MessagingException {
410         
411         IMAPProtocol protocol = null;
412
413     // check for non-null values of host, password, user
414
if (host == null || password == null || user == null) {
415         if (debug)
416         out.println("DEBUG: protocolConnect returning false" +
417                 ", host=" + host +
418                 ", user=" + user +
419                 ", password=" + (password != null ?
420                     "<non-null>" : "<null>"));
421         return false;
422     }
423
424     // set the port correctly
425
if (pport != -1) {
426         port = pport;
427     } else {
428         String JavaDoc portstring = session.getProperty("mail."+name+".port");
429         if (portstring != null) {
430         port = Integer.parseInt(portstring);
431         }
432     }
433     
434     // use the default if needed
435
if (port == -1) {
436         port = defaultPort;
437     }
438     
439     try {
440             boolean poolEmpty;
441             synchronized (pool) {
442                 poolEmpty = pool.authenticatedConnections.isEmpty();
443             }
444
445             if (poolEmpty) {
446                 protocol = new IMAPProtocol(name, host, port,
447                             session.getDebug(),
448                             session.getDebugOut(),
449                         session.getProperties(),
450                         isSSL
451                        );
452         if (debug)
453             out.println("DEBUG: protocolConnect login" +
454                 ", host=" + host +
455                 ", user=" + user +
456                 ", password=" + (password != null ?
457                     "<non-null>" : "<null>"));
458             login(protocol, user, password);
459
460             protocol.addResponseHandler(this);
461
462             this.host = host;
463             this.user = user;
464             this.password = password;
465
466                 synchronized (pool) {
467                     pool.authenticatedConnections.addElement(protocol);
468                 }
469             }
470     } catch (CommandFailedException cex) {
471         // login failure, close connection to server
472
protocol.disconnect();
473         protocol = null;
474         throw new AuthenticationFailedException(
475                     cex.getResponse().getRest());
476     } catch (ProtocolException pex) { // any other exception
477
throw new MessagingException(pex.getMessage(), pex);
478     } catch (IOException JavaDoc ioex) {
479         throw new MessagingException(ioex.getMessage(), ioex);
480     }
481
482         return true;
483
484     }
485
486     private void login(IMAPProtocol p, String JavaDoc u, String JavaDoc pw)
487         throws ProtocolException {
488     // turn on TLS if it's been enabled and is supported
489
if (enableStartTLS && p.hasCapability("STARTTLS")) {
490         p.startTLS();
491         // if startTLS succeeds, refresh capabilities
492
p.capability();
493     }
494     if (p.isAuthenticated())
495         return; // no need to login
496
String JavaDoc authzid;
497     if (authorizationID != null)
498         authzid = authorizationID;
499     else if (proxyAuthUser != null)
500         authzid = proxyAuthUser;
501     else
502         authzid = u;
503     if (enableSASL) {
504         p.sasllogin(saslMechanisms, saslRealm, authzid, u, pw);
505         if (p.isAuthenticated()) {
506         if (proxyAuthUser != null)
507             p.proxyauth(proxyAuthUser);
508         return;
509         }
510     }
511     if (p.hasCapability("AUTH=PLAIN") && !disableAuthPlain)
512         p.authplain(authzid, u, pw);
513     else if ((p.hasCapability("AUTH-LOGIN") ||
514         p.hasCapability("AUTH=LOGIN")) && !disableAuthLogin)
515         p.authlogin(u, pw);
516     else if (!p.hasCapability("LOGINDISABLED"))
517         p.login(u, pw);
518     else
519         throw new ProtocolException("No login methods supported!");
520     if (proxyAuthUser != null)
521         p.proxyauth(proxyAuthUser);
522     }
523
524     /**
525      * Set the user name that will be used for subsequent connections
526      * after this Store is first connected (for example, when creating
527      * a connection to open a Folder). This value is overridden
528      * by any call to the Store's connect method. <p>
529      *
530      * Some IMAP servers may provide an authentication ID that can
531      * be used for more efficient authentication for future connections.
532      * This authentication ID is provided in a server-specific manner
533      * not described here. <p>
534      *
535      * Most applications will never need to use this method.
536      *
537      * @since JavaMail 1.3.3
538      */

539     public void setUsername(String JavaDoc user) {
540     this.user = user;
541     }
542
543     /**
544      * Set the password that will be used for subsequent connections
545      * after this Store is first connected (for example, when creating
546      * a connection to open a Folder). This value is overridden
547      * by any call to the Store's connect method. <p>
548      *
549      * Most applications will never need to use this method.
550      *
551      * @since JavaMail 1.3.3
552      */

553     public void setPassword(String JavaDoc password) {
554     this.password = password;
555     }
556
557     /*
558      * Get a new authenticated protocol object for this Folder.
559      * Also store a reference to this folder in our list of
560      * open folders.
561      */

562     IMAPProtocol getProtocol(IMAPFolder folder)
563         throws MessagingException {
564     IMAPProtocol p = null;
565
566     // keep looking for a connection until we get a good one
567
while (p == null) {
568  
569         // New authenticated protocol objects are either acquired
570
// from the connection pool, or created when the pool is
571
// empty or no connections are available. None are available
572
// if the current pool size is one and the separate store
573
// property is set, or if the current pool size is one and
574
// that connection is being "borrowed" for store commands.
575

576         synchronized (pool) {
577
578             // If there's none available in the pool,
579
// create a new one.
580
if (pool.authenticatedConnections.isEmpty() ||
581                 (pool.separateStoreConnection &&
582                  pool.authenticatedConnections.size() == 1) ||
583                 (pool.borrowedStoreConnections > 0 &&
584                  pool.authenticatedConnections.size() == 1)) {
585
586                 if (debug)
587                     out.println("DEBUG: no connections in the pool, " +
588                                        "creating a new one");
589                 try {
590             /*
591              * Some authentication systems use one time passwords
592              * or tokens, so each authentication request requires
593              * a new password. This "kludge" allows a callback
594              * to application code to get a new password.
595              *
596              * XXX - remove this when SASL support is added
597              */

598             if (forcePasswordRefresh) {
599             InetAddress JavaDoc addr;
600             try {
601                 addr = InetAddress.getByName(host);
602             } catch (UnknownHostException JavaDoc e) {
603                 addr = null;
604             }
605             PasswordAuthentication pa =
606                 session.requestPasswordAuthentication(addr, port,
607                             name, null, user);
608             if (pa != null) {
609                 user = pa.getUserName();
610                 password = pa.getPassword();
611             }
612             }
613                     // Use cached host, port and timeout values.
614
p = new IMAPProtocol(name, host, port,
615                                          session.getDebug(),
616                                          session.getDebugOut(),
617                                          session.getProperties(),
618                      isSSL
619                                         );
620                     // Use cached auth info
621
login(p, user, password);
622                 } catch(Exception JavaDoc ex1) {
623                     if (p != null)
624                         try {
625                             p.disconnect();
626                         } catch (Exception JavaDoc ex2) { }
627                     p = null;
628                 }
629                  
630                 if (p == null)
631                     throw new MessagingException("connection failure");
632             } else {
633                 if (debug)
634                     out.println("DEBUG: connection available -- size: " +
635                         pool.authenticatedConnections.size());
636
637                 // remove the available connection from the Authenticated queue
638
p = (IMAPProtocol)pool.authenticatedConnections.lastElement();
639                 pool.authenticatedConnections.removeElement(p);
640
641         // check if the connection is still live
642
long lastUsed = System.currentTimeMillis() - p.getTimestamp();
643         if (lastUsed > pool.serverTimeoutInterval) {
644             try {
645             // note that store is still the response handler,
646
// in case we get any alerts
647
p.noop();
648             } catch (ProtocolException pex) {
649             try {
650                 p.removeResponseHandler(this);
651                 p.disconnect();
652             } catch (Exception JavaDoc ex) { }
653             p = null;
654             continue; // try again, from the top
655
}
656         }
657
658                 // remove the store as a response handler.
659
p.removeResponseHandler(this);
660         }
661
662             // check if we need to look for client-side timeouts
663
timeoutConnections();
664
665         // Add folder to folder-list
666
if (folder != null) {
667                 if (pool.folders == null)
668                     pool.folders = new Vector JavaDoc();
669         pool.folders.addElement(folder);
670         }
671         }
672
673     }
674     
675     return p;
676     }
677
678     /**
679      * Get this Store's protocol connection.
680      *
681      * When acquiring a store protocol object, it is important to
682      * use the following steps:
683      *
684      * IMAPProtocol p = null;
685      * try {
686      * p = getStoreProtocol();
687      * // perform the command
688      * } catch (ConnectionException cex) {
689      * throw new StoreClosedException(this, cex.getMessage());
690      * } catch (WhateverException ex) {
691      * // handle it
692      * } finally {
693      * releaseStoreProtocol(p);
694      * if (p == null) { // failed to get a Store connection
695      * // have to force Store to be closed
696      * cleanup();
697      * }
698      * }
699      */

700     IMAPProtocol getStoreProtocol() throws ProtocolException {
701         IMAPProtocol p = null;
702
703         synchronized (pool) {
704
705             // If there's no authenticated connections available create a
706
// new one and place it in the authenticated queue.
707
if (pool.authenticatedConnections.isEmpty()) {
708                 if (pool.debug)
709                     out.println("DEBUG: getStoreProtocol() - no connections " +
710                         "in the pool, creating a new one");
711                 try {
712                     // Use cached host, port and timeout values.
713
p = new IMAPProtocol(name, host, port,
714                                          session.getDebug(),
715                                          session.getDebugOut(),
716                                          session.getProperties(),
717                      isSSL
718                                         );
719                     // Use cached auth info
720
login(p, user, password);
721                 } catch(Exception JavaDoc ex1) {
722                     if (p != null)
723                         try {
724                             p.logout();
725                         } catch (Exception JavaDoc ex2) { }
726                     p = null;
727                 }
728  
729                 if (p == null)
730                     throw new ConnectionException(
731                 "failed to create new store connection");
732              
733             p.addResponseHandler(this);
734                 pool.authenticatedConnections.addElement(p);
735  
736             } else {
737                 // Always use the first element in the Authenticated queue.
738
if (pool.debug)
739                     out.println("DEBUG: getStoreProtocol() - " +
740                         "connection available -- size: " +
741                         pool.authenticatedConnections.size());
742                 p = (IMAPProtocol)pool.authenticatedConnections.firstElement();
743             }
744  
745             // If we're not using a 'dedicated' separate store connection
746
// increment the borrowed store connection counter.
747
if (!pool.separateStoreConnection) {
748                 pool.borrowedStoreConnections++;
749
750                 if (pool.debug)
751                     out.println("DEBUG: getStoreProtocol() -- " +
752                         "borrowedStoreConnections: " +
753                         pool.borrowedStoreConnections);
754             }
755  
756             timeoutConnections();
757
758             return p;
759  
760         }
761     }
762
763     /**
764      * If a SELECT succeeds, but indicates that the folder is
765      * READ-ONLY, and the user asked to open the folder READ_WRITE,
766      * do we allow the open to succeed?
767      */

768     boolean allowReadOnlySelect() {
769     String JavaDoc s = session.getProperty("mail." + name +
770         ".allowreadonlyselect");
771     return s != null && s.equalsIgnoreCase("true");
772     }
773
774     /**
775      * Report whether the separateStoreConnection is set.
776      */

777     boolean hasSeparateStoreConnection() {
778         return pool.separateStoreConnection;
779     }
780
781     /**
782      * Report whether connection pool debugging is enabled.
783      */

784     boolean getConnectionPoolDebug() {
785         return pool.debug;
786     }
787  
788     /**
789      * Report whether the connection pool is full.
790      */

791     boolean isConnectionPoolFull() {
792
793         synchronized (pool) {
794             if (pool.debug)
795                 out.println("DEBUG: current size: " +
796                     pool.authenticatedConnections.size() +
797                     " pool size: " + pool.poolSize);
798
799             return (pool.authenticatedConnections.size() >= pool.poolSize);
800
801         }
802     }
803
804     /**
805      * Release the protocol object back to the connection pool.
806      */

807     void releaseProtocol(IMAPFolder folder, IMAPProtocol protocol) {
808
809         synchronized (pool) {
810             if (protocol != null) {
811                 // If the pool is not full, add the store as a response handler
812
// and return the protocol object to the connection pool.
813
if (!isConnectionPoolFull()) {
814                     protocol.addResponseHandler(this);
815                     pool.authenticatedConnections.addElement(protocol);
816
817                     if (debug)
818                         out.println("DEBUG: added an " +
819                             "Authenticated connection -- size: " +
820                             pool.authenticatedConnections.size());
821                 } else {
822                     if (debug)
823                         out.println("DEBUG: pool is full, not adding " +
824                             "an Authenticated connection");
825                     try {
826                         protocol.logout();
827                     } catch (ProtocolException pex) {};
828                 }
829             }
830
831             if (pool.folders != null)
832                 pool.folders.removeElement(folder);
833
834             timeoutConnections();
835         }
836     }
837
838     /**
839      * Release the store connection.
840      */

841     void releaseStoreProtocol(IMAPProtocol protocol) {
842
843     if (protocol == null)
844         return; // nothing to release
845
synchronized (pool) {
846             // Decrement the borrowed store connection counter if
847
// not using a separate store connection.
848
if (!pool.separateStoreConnection) {
849                 pool.borrowedStoreConnections--;
850
851                 if (pool.debug)
852                     out.println("DEBUG: releaseStoreProtocol() -- " +
853                         "borrowedStoreConnections: " +
854                         pool.borrowedStoreConnections);
855             }
856
857             timeoutConnections();
858         }
859     }
860
861     /**
862      * Empty the connection pool.
863      */

864     private void emptyConnectionPool(boolean force) {
865
866         synchronized (pool) {
867             for (int index = pool.authenticatedConnections.size() - 1;
868             index >= 0; --index) {
869                 try {
870             IMAPProtocol p = (IMAPProtocol)
871             pool.authenticatedConnections.elementAt(index);
872             p.removeResponseHandler(this);
873             if (force)
874             p.disconnect();
875             else
876             p.logout();
877                 } catch (ProtocolException pex) {};
878             }
879
880             pool.authenticatedConnections.removeAllElements();
881         }
882         
883         if (pool.debug)
884             out.println("DEBUG: removed all authenticated connections");
885     }
886
887     /**
888      * Check to see if it's time to shrink the connection pool.
889      */

890     private void timeoutConnections() {
891
892         synchronized (pool) {
893
894             // If we've exceeded the pruning interval, look for stale
895
// connections to logout.
896
if (System.currentTimeMillis() - pool.lastTimePruned >
897                 pool.pruningInterval &&
898                 pool.authenticatedConnections.size() > 1) {
899
900                 if (pool.debug) {
901                     out.println("DEBUG: checking for connections " +
902                         "to prune: " +
903                         (System.currentTimeMillis() - pool.lastTimePruned));
904                     out.println("DEBUG: clientTimeoutInterval: " +
905                         pool.clientTimeoutInterval);
906                 }
907  
908                 IMAPProtocol p;
909  
910                 // Check the timestamp of the protocol objects in the pool and
911
// logout if the interval exceeds the client timeout value
912
// (leave the first connection).
913
for (int index = pool.authenticatedConnections.size() - 1;
914                      index > 0; index--) {
915                     p = (IMAPProtocol)pool.authenticatedConnections.
916                         elementAt(index);
917                     if (pool.debug) {
918                         out.println("DEBUG: protocol last used: " +
919                             (System.currentTimeMillis() - p.getTimestamp()));
920                     }
921                     if (System.currentTimeMillis() - p.getTimestamp() >
922                         pool.clientTimeoutInterval) {
923  
924                         if (pool.debug) {
925                             out.println("DEBUG: authenticated " +
926                                 "connection timed out");
927                             out.println("DEBUG: logging out " +
928                                 "the connection");
929                         }
930  
931                         p.removeResponseHandler(this);
932                         pool.authenticatedConnections.removeElementAt(index);
933
934                         try {
935                             p.logout();
936                         } catch (ProtocolException pex) {}
937                     }
938                 }
939                 pool.lastTimePruned = System.currentTimeMillis();
940             }
941         }
942     }
943
944     /**
945      * Get the block size to use for fetch requests on this Store.
946      */

947     int getFetchBlockSize() {
948     return blksize;
949     }
950
951     /**
952      * Get a reference to the session.
953      */

954     Session getSession() {
955         return session;
956     }
957
958     /**
959      * Get the number of milliseconds to cache STATUS response.
960      */

961     int getStatusCacheTimeout() {
962     return statusCacheTimeout;
963     }
964
965     /**
966      * Get the maximum size of a message to buffer for append.
967      */

968     int getAppendBufferSize() {
969     return appendBufferSize;
970     }
971
972     /**
973      * Return true if the specified capability string is in the list
974      * of capabilities the server announced.
975      *
976      * @since JavaMail 1.3.3
977      */

978     public boolean hasCapability(String JavaDoc capability) throws MessagingException {
979         IMAPProtocol p = null;
980     try {
981         p = getStoreProtocol();
982             return p.hasCapability(capability);
983     } catch (ProtocolException pex) {
984         if (p == null) { // failed to get a Store connection
985
// have to force Store to be closed
986
cleanup();
987         }
988         throw new MessagingException(pex.getMessage(), pex);
989         } finally {
990             releaseStoreProtocol(p);
991         }
992     }
993
994     /**
995      * Check whether this store is connected. Override superclass
996      * method, to actually ping our server connection.
997      */

998     public synchronized boolean isConnected() {
999     if (!super.isConnected())
1000        // if we haven't been connected at all, don't bother with
1001
// the NOOP.
1002
return false;
1003
1004    /*
1005     * The below noop() request can:
1006     * (1) succeed - in which case all is fine.
1007     *
1008     * (2) fail because the server returns NO or BAD, in which
1009     * case we ignore it since we can't really do anything.
1010     * (2) fail because a BYE response is obtained from the
1011     * server
1012     * (3) fail because the socket.write() to the server fails,
1013     * in which case the iap.protocol() code converts the
1014     * IOException into a BYE response.
1015     *
1016     * Thus, our BYE handler will take care of closing the Store
1017     * in case our connection is really gone.
1018     */

1019   
1020        IMAPProtocol p = null;
1021    try {
1022        p = getStoreProtocol();
1023            p.noop();
1024    } catch (ProtocolException pex) {
1025        if (p == null) { // failed to get a Store connection
1026
// have to force Store to be closed
1027
cleanup();
1028        }
1029        // will return false below
1030
} finally {
1031            releaseStoreProtocol(p);
1032        }
1033
1034
1035    return super.isConnected();
1036    }
1037
1038    /**
1039     * Close this Store.
1040     */

1041    public void close() throws MessagingException {
1042    if (!super.isConnected()) // Already closed.
1043
return;
1044
1045        IMAPProtocol protocol = null;
1046    try {
1047        boolean isEmpty;
1048        synchronized (pool) {
1049        // If there's no authenticated connections available
1050
// don't create a new one
1051
isEmpty = pool.authenticatedConnections.isEmpty();
1052        }
1053        /*
1054         * Have to drop the lock before calling cleanup.
1055         * Yes, there's a potential race here. The pool could
1056         * become empty after we check, in which case we'll just
1057         * waste time getting a new connection and closing it.
1058         * Or, the pool could be empty now and not empty by the
1059         * time we get into cleanup, but that's ok because cleanup
1060         * will just close the connection.
1061         */

1062        if (isEmpty) {
1063        if (pool.debug)
1064            out.println("DEBUG: close() - no connections ");
1065        cleanup();
1066        return;
1067        }
1068
1069            protocol = getStoreProtocol();
1070        /*
1071         * We have to remove the protocol from the pool so that,
1072         * when our response handler processes the BYE response
1073         * and calls cleanup, which calls emptyConnection, that
1074         * we don't try to log out this connection twice.
1075         */

1076        synchronized (pool) {
1077                pool.authenticatedConnections.removeElement(protocol);
1078        }
1079
1080        /*
1081         * LOGOUT.
1082         *
1083         * Note that protocol.logout() closes the server socket
1084         * connection, regardless of what happens ..
1085         *
1086         * Also note that protocol.logout() results in a BYE
1087         * response (As per rfc 2060, BYE is a *required* response
1088         * to LOGOUT). In fact, even if protocol.logout() fails
1089         * with an IOException (if the server connection is dead),
1090         * iap.Protocol.command() converts that exception into a
1091         * BYE response. So, I depend on my BYE handler to do the
1092         * Store cleanup.
1093         */

1094        protocol.logout();
1095    } catch (ProtocolException pex) {
1096        // Hmm .. will this ever happen ?
1097
cleanup();
1098        throw new MessagingException(pex.getMessage(), pex);
1099        } finally {
1100            releaseStoreProtocol(protocol);
1101        }
1102    }
1103
1104    protected void finalize() throws Throwable JavaDoc {
1105    super.finalize();
1106    close();
1107    }
1108
1109    // Cleanup before dying.
1110
private synchronized void cleanup() {
1111    cleanup(false);
1112    }
1113
1114    /**
1115     * Cleanup before dying.
1116     * If force is true, we force the folders to close
1117     * abruptly without waiting for the server. Used when
1118     * the store connection times out.
1119     *
1120     */

1121    private synchronized void cleanup(boolean force) {
1122    if (debug)
1123        out.println("DEBUG: IMAPStore cleanup, force " + force);
1124        
1125        Vector JavaDoc foldersCopy = null;
1126        boolean done = true;
1127
1128    // To avoid violating the locking hierarchy, there's no lock we
1129
// can hold that prevents another thread from trying to open a
1130
// folder at the same time we're trying to close all the folders.
1131
// Thus, there's an inherent race condition here. We close all
1132
// the folders we know about and then check whether any new folders
1133
// have been opened in the mean time. We keep trying until we're
1134
// successful in closing all the folders.
1135
for (;;) {
1136        // Make a copy of the folders list so we do not violate the
1137
// folder-connection pool locking hierarchy.
1138
synchronized (pool) {
1139        if (pool.folders != null) {
1140            done = false;
1141            foldersCopy = pool.folders;
1142            pool.folders = null;
1143        } else {
1144                    done = true;
1145                }
1146        }
1147        if (done)
1148        break;
1149
1150        // Close and remove any open folders under this Store.
1151
for (int i = 0, fsize = foldersCopy.size(); i < fsize; i++) {
1152        IMAPFolder f = (IMAPFolder)foldersCopy.elementAt(i);
1153
1154        try {
1155            if (force) {
1156            if (debug)
1157                out.println("DEBUG: force folder to close");
1158            // Don't want to wait for folder connection to timeout
1159
// (if, for example, the server is down) so we close
1160
// folders abruptly.
1161
f.forceClose();
1162            } else {
1163            if (debug)
1164                out.println("DEBUG: close folder");
1165            f.close(false);
1166            }
1167        } catch (MessagingException mex) {
1168            // Who cares ?! Ignore 'em.
1169
} catch (IllegalStateException JavaDoc ex) {
1170            // Ditto
1171
}
1172        }
1173
1174    }
1175
1176        synchronized (pool) {
1177        emptyConnectionPool(force);
1178    }
1179
1180    // to set the state and send the closed connection event
1181
try {
1182        super.close();
1183    } catch (MessagingException mex) { }
1184    if (debug)
1185        out.println("DEBUG: IMAPStore cleanup done");
1186    }
1187
1188    /**
1189     * Get the default folder, representing the root of this user's
1190     * namespace. Returns a closed DefaultFolder object.
1191     */

1192    public Folder getDefaultFolder() throws MessagingException {
1193    checkConnected();
1194    return new DefaultFolder(this);
1195    }
1196
1197    /**
1198     * Get named folder. Returns a new, closed IMAPFolder.
1199     */

1200    public Folder getFolder(String JavaDoc name) throws MessagingException {
1201    checkConnected();
1202    return new IMAPFolder(name, IMAPFolder.UNKNOWN_SEPARATOR, this);
1203    }
1204
1205    /**
1206     * Get named folder. Returns a new, closed IMAPFolder.
1207     */

1208    public Folder getFolder(URLName url) throws MessagingException {
1209    checkConnected();
1210    return new IMAPFolder(url.getFile(),
1211                  IMAPFolder.UNKNOWN_SEPARATOR,
1212                  this);
1213    }
1214
1215    /**
1216     * Using the IMAP NAMESPACE command (RFC 2342), return a set
1217     * of folders representing the Personal namespaces.
1218     */

1219    public Folder[] getPersonalNamespaces() throws MessagingException {
1220    Namespaces ns = getNamespaces();
1221    if (ns == null || ns.personal == null)
1222        return super.getPersonalNamespaces();
1223    return namespaceToFolders(ns.personal, null);
1224    }
1225
1226    /**
1227     * Using the IMAP NAMESPACE command (RFC 2342), return a set
1228     * of folders representing the User's namespaces.
1229     */

1230    public Folder[] getUserNamespaces(String JavaDoc user)
1231                throws MessagingException {
1232    Namespaces ns = getNamespaces();
1233    if (ns == null || ns.otherUsers == null)
1234        return super.getUserNamespaces(user);
1235    return namespaceToFolders(ns.otherUsers, user);
1236    }
1237
1238    /**
1239     * Using the IMAP NAMESPACE command (RFC 2342), return a set
1240     * of folders representing the Shared namespaces.
1241     */

1242    public Folder[] getSharedNamespaces() throws MessagingException {
1243    Namespaces ns = getNamespaces();
1244    if (ns == null || ns.shared == null)
1245        return super.getSharedNamespaces();
1246    return namespaceToFolders(ns.shared, null);
1247    }
1248
1249    private synchronized Namespaces getNamespaces() throws MessagingException {
1250    checkConnected();
1251
1252        IMAPProtocol p = null;
1253
1254    if (namespaces == null) {
1255        try {
1256                p = getStoreProtocol();
1257        namespaces = p.namespace();
1258        } catch (BadCommandException bex) {
1259        // NAMESPACE not supported, ignore it
1260
} catch (ConnectionException cex) {
1261        throw new StoreClosedException(this, cex.getMessage());
1262        } catch (ProtocolException pex) {
1263        throw new MessagingException(pex.getMessage(), pex);
1264        } finally {
1265        releaseStoreProtocol(p);
1266        if (p == null) { // failed to get a Store connection
1267
// have to force Store to be closed
1268
cleanup();
1269        }
1270        }
1271    }
1272    return namespaces;
1273    }
1274
1275    private Folder[] namespaceToFolders(Namespaces.Namespace[] ns,
1276                    String JavaDoc user) {
1277    Folder[] fa = new Folder[ns.length];
1278    for (int i = 0; i < fa.length; i++) {
1279        String JavaDoc name = ns[i].prefix;
1280        if (user == null) {
1281        // strip trailing delimiter
1282
int len = name.length();
1283        if ( len > 0 && name.charAt(len - 1) == ns[i].delimiter)
1284            name = name.substring(0, len - 1);
1285        } else {
1286        // add user
1287
name += user;
1288        }
1289        fa[i] = new IMAPFolder(name, ns[i].delimiter, this, user == null);
1290    }
1291    return fa;
1292    }
1293
1294    /**
1295     * Get the quotas for the named quota root.
1296     * Quotas are controlled on the basis of a quota root, not
1297     * (necessarily) a folder. The relationship between folders
1298     * and quota roots depends on the IMAP server. Some servers
1299     * might implement a single quota root for all folders owned by
1300     * a user. Other servers might implement a separate quota root
1301     * for each folder. A single folder can even have multiple
1302     * quota roots, perhaps controlling quotas for different
1303     * resources.
1304     *
1305     * @param root the name of the quota root
1306     * @return array of Quota objects
1307     * @exception MessagingException if the server doesn't support the
1308     * QUOTA extension
1309     */

1310    public Quota[] getQuota(String JavaDoc root) throws MessagingException {
1311    Quota[] qa = null;
1312
1313        IMAPProtocol p = null;
1314    try {
1315        p = getStoreProtocol();
1316        qa = p.getQuotaRoot(root);
1317    } catch (BadCommandException bex) {
1318        throw new MessagingException("QUOTA not supported", bex);
1319    } catch (ConnectionException cex) {
1320        throw new StoreClosedException(this, cex.getMessage());
1321    } catch (ProtocolException pex) {
1322        throw new MessagingException(pex.getMessage(), pex);
1323    } finally {
1324        releaseStoreProtocol(p);
1325        if (p == null) { // failed to get a Store connection
1326
// have to force Store to be closed
1327
cleanup();
1328        }
1329    }
1330    return qa;
1331    }
1332
1333    /**
1334     * Set the quotas for the quota root specified in the quota argument.
1335     * Typically this will be one of the quota roots obtained from the
1336     * <code>getQuota</code> method, but it need not be.
1337     *
1338     * @param quota the quota to set
1339     * @exception MessagingException if the server doesn't support the
1340     * QUOTA extension
1341     */

1342    public void setQuota(Quota quota) throws MessagingException {
1343        IMAPProtocol p = null;
1344    try {
1345        p = getStoreProtocol();
1346        p.setQuota(quota);
1347    } catch (BadCommandException bex) {
1348        throw new MessagingException("QUOTA not supported", bex);
1349    } catch (ConnectionException cex) {
1350        throw new StoreClosedException(this, cex.getMessage());
1351    } catch (ProtocolException pex) {
1352        throw new MessagingException(pex.getMessage(), pex);
1353    } finally {
1354        releaseStoreProtocol(p);
1355        if (p == null) { // failed to get a Store connection
1356
// have to force Store to be closed
1357
cleanup();
1358        }
1359    }
1360    }
1361
1362    private void checkConnected() {
1363    if (!super.isConnected())
1364        throw new IllegalStateException JavaDoc("Not connected");
1365    }
1366
1367    /**
1368     * Response handler method.
1369     */

1370    public void handleResponse(Response r) {
1371    // Any of these responses may have a response code.
1372
if (r.isOK() || r.isNO() || r.isBAD() || r.isBYE())
1373        handleResponseCode(r);
1374    if (r.isBYE()) {
1375        if (debug)
1376        out.println("DEBUG: IMAPStore connection dead");
1377        // Store's IMAP connection is dead, cleanup.
1378
if (super.isConnected()) // Check if its already closed
1379
cleanup(r.isSynthetic());
1380        return;
1381    }
1382    }
1383
1384    /**
1385     * Handle notifications and alerts.
1386     * Response must be an OK, NO, BAD, or BYE response.
1387     */

1388    void handleResponseCode(Response r) {
1389    String JavaDoc s = r.getRest(); // get the text after the response
1390
boolean isAlert = false;
1391    if (s.startsWith("[")) { // a response code
1392
int i = s.indexOf(']');
1393        // remember if it's an alert
1394
if (i > 0 && s.substring(0, i + 1).equalsIgnoreCase("[ALERT]"))
1395        isAlert = true;
1396        // strip off the response code in any event
1397
s = s.substring(i + 1).trim();
1398    }
1399    if (isAlert)
1400        notifyStoreListeners(StoreEvent.ALERT, s);
1401    else if (r.isUnTagged() && s.length() > 0)
1402        // Only send notifications that come with untagged
1403
// responses, and only if there is actually some
1404
// text there.
1405
notifyStoreListeners(StoreEvent.NOTICE, s);
1406    }
1407}
1408
Popular Tags