Skip to content

Package: IMAPStore

IMAPStore

nameinstructionbranchcomplexitylinemethod
IMAPStore(Session, URLName)
M: 7 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
IMAPStore(Session, URLName, String, boolean)
M: 859 C: 0
0%
M: 64 C: 0
0%
M: 33 C: 0
0%
M: 137 C: 0
0%
M: 1 C: 0
0%
allowReadOnlySelect()
M: 17 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
authenticate(IMAPProtocol, String, String, String)
M: 175 C: 0
0%
M: 26 C: 0
0%
M: 14 C: 0
0%
M: 36 C: 0
0%
M: 1 C: 0
0%
checkConnected()
M: 17 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
cleanup()
M: 61 C: 0
0%
M: 8 C: 0
0%
M: 5 C: 0
0%
M: 18 C: 0
0%
M: 1 C: 0
0%
close()
M: 9 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
closeAllFolders(boolean)
M: 70 C: 0
0%
M: 8 C: 0
0%
M: 5 C: 0
0%
M: 22 C: 0
0%
M: 1 C: 0
0%
emptyConnectionPool(boolean)
M: 48 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 15 C: 0
0%
M: 1 C: 0
0%
finalize()
M: 25 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 9 C: 0
0%
M: 1 C: 0
0%
getAppendBufferSize()
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
getConnectionPoolLogger()
M: 4 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
getDefaultFolder()
M: 7 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
getFetchBlockSize()
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
getFolder(String)
M: 7 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
getFolder(URLName)
M: 8 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
getFolderStoreProtocol()
M: 12 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
getMessageCacheDebug()
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
getMinIdleTime()
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
getNamespaces()
M: 37 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 12 C: 0
0%
M: 1 C: 0
0%
getPeek()
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
getPersonalNamespaces()
M: 17 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
getProtocol(IMAPFolder)
M: 229 C: 0
0%
M: 30 C: 0
0%
M: 16 C: 0
0%
M: 67 C: 0
0%
M: 1 C: 0
0%
getProxyAuthUser()
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
getQuota(String)
M: 41 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 13 C: 0
0%
M: 1 C: 0
0%
getSession()
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
getSharedNamespaces()
M: 17 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
getStatusCacheTimeout()
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
getStoreProtocol()
M: 152 C: 0
0%
M: 20 C: 0
0%
M: 11 C: 0
0%
M: 42 C: 0
0%
M: 1 C: 0
0%
getUserNamespaces(String)
M: 18 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
handleResponse(Response)
M: 41 C: 0
0%
M: 12 C: 0
0%
M: 7 C: 0
0%
M: 11 C: 0
0%
M: 1 C: 0
0%
handleResponseCode(Response)
M: 59 C: 0
0%
M: 14 C: 0
0%
M: 8 C: 0
0%
M: 14 C: 0
0%
M: 1 C: 0
0%
hasCapability(String)
M: 22 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 6 C: 0
0%
M: 1 C: 0
0%
hasSeparateStoreConnection()
M: 4 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
id(Map)
M: 41 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 13 C: 0
0%
M: 1 C: 0
0%
idle()
M: 176 C: 0
0%
M: 16 C: 0
0%
M: 9 C: 0
0%
M: 52 C: 0
0%
M: 1 C: 0
0%
ignoreBodyStructureSize()
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
isConnected()
M: 19 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 8 C: 0
0%
M: 1 C: 0
0%
isConnectionPoolFull()
M: 46 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 6 C: 0
0%
M: 1 C: 0
0%
isSSL()
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
login(IMAPProtocol, String, String)
M: 146 C: 0
0%
M: 36 C: 0
0%
M: 19 C: 0
0%
M: 42 C: 0
0%
M: 1 C: 0
0%
namespaceToFolders(Namespaces.Namespace[], String)
M: 69 C: 0
0%
M: 10 C: 0
0%
M: 6 C: 0
0%
M: 12 C: 0
0%
M: 1 C: 0
0%
newIMAPFolder(ListInfo)
M: 40 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 10 C: 0
0%
M: 1 C: 0
0%
newIMAPFolder(String, char)
M: 6 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
newIMAPFolder(String, char, Boolean)
M: 51 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 11 C: 0
0%
M: 1 C: 0
0%
newIMAPProtocol(String, int)
M: 15 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
preLogin(IMAPProtocol)
M: 1 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
protocolConnect(String, int, String, String)
M: 256 C: 0
0%
M: 26 C: 0
0%
M: 14 C: 0
0%
M: 57 C: 0
0%
M: 1 C: 0
0%
refreshPassword()
M: 50 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 12 C: 0
0%
M: 1 C: 0
0%
releaseFolderStoreProtocol(IMAPProtocol)
M: 34 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 11 C: 0
0%
M: 1 C: 0
0%
releaseProtocol(IMAPFolder, IMAPProtocol)
M: 62 C: 0
0%
M: 8 C: 0
0%
M: 5 C: 0
0%
M: 17 C: 0
0%
M: 1 C: 0
0%
releaseStoreProtocol(IMAPProtocol)
M: 56 C: 0
0%
M: 6 C: 0
0%
M: 4 C: 0
0%
M: 17 C: 0
0%
M: 1 C: 0
0%
setPassword(String)
M: 4 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
setProxyAuthUser(String)
M: 4 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
setQuota(Quota)
M: 37 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 12 C: 0
0%
M: 1 C: 0
0%
setUsername(String)
M: 4 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
static {...}
M: 1 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
throwSearchException()
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
timeoutConnections()
M: 131 C: 0
0%
M: 12 C: 0
0%
M: 7 C: 0
0%
M: 27 C: 0
0%
M: 1 C: 0
0%
tracePassword(String)
M: 11 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
traceUser(String)
M: 7 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
waitIfIdle()
M: 39 C: 0
0%
M: 6 C: 0
0%
M: 4 C: 0
0%
M: 10 C: 0
0%
M: 1 C: 0
0%

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