mirror of
https://github.com/moparisthebest/davmail
synced 2024-12-13 11:12:22 -05:00
Implement an ExchangeSession pool (needed with LdapServer)
git-svn-id: http://svn.code.sf.net/p/davmail/code/trunk@196 3d1905a2-6b24-0410-a738-b14d5a86fcbd
This commit is contained in:
parent
9b5feac91f
commit
24362b8632
@ -1,6 +1,7 @@
|
||||
package davmail;
|
||||
|
||||
import davmail.exchange.ExchangeSession;
|
||||
import davmail.exchange.ExchangeSessionFactory;
|
||||
import davmail.smtp.SmtpConnection;
|
||||
import davmail.tray.DavGatewayTray;
|
||||
import org.apache.commons.httpclient.util.Base64;
|
||||
@ -142,13 +143,7 @@ public class AbstractConnection extends Thread {
|
||||
} catch (IOException e2) {
|
||||
DavGatewayTray.warn("Exception closing client socket", e2);
|
||||
}
|
||||
try {
|
||||
if (session != null) {
|
||||
session.close();
|
||||
}
|
||||
} catch (IOException e3) {
|
||||
DavGatewayTray.warn("Exception closing gateway", e3);
|
||||
}
|
||||
ExchangeSessionFactory.close(session);
|
||||
}
|
||||
|
||||
protected String base64Encode(String value) {
|
||||
|
@ -64,11 +64,20 @@ public class ExchangeSession {
|
||||
private String currentFolderUrl;
|
||||
private WebdavResource wdr = null;
|
||||
|
||||
private ExchangeSessionFactory.PoolKey poolKey;
|
||||
|
||||
ExchangeSessionFactory.PoolKey getPoolKey() {
|
||||
return poolKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an exchange session for the given URL.
|
||||
* The session is not actually established until a call to login()
|
||||
*
|
||||
* @param poolKey session pool key
|
||||
*/
|
||||
ExchangeSession() {
|
||||
ExchangeSession(ExchangeSessionFactory.PoolKey poolKey) {
|
||||
this.poolKey = poolKey;
|
||||
// SimpleDateFormat are not thread safe, need to create one instance for
|
||||
// each session
|
||||
dateParser = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
|
||||
@ -76,6 +85,31 @@ public class ExchangeSession {
|
||||
LOGGER.debug("Session " + this + " created");
|
||||
}
|
||||
|
||||
public boolean isExpired() {
|
||||
boolean isExpired = false;
|
||||
HttpMethod testMethod = null;
|
||||
try {
|
||||
testMethod = DavGatewayHttpClientFacade.executeFollowRedirects(wdr.retrieveSessionInstance(),
|
||||
URIUtil.encodePath(currentFolderUrl));
|
||||
String queryString = testMethod.getQueryString();
|
||||
|
||||
if (testMethod.getStatusCode() != HttpStatus.SC_OK) {
|
||||
isExpired = true;
|
||||
} else if (queryString != null && queryString.contains("reason=")) {
|
||||
isExpired = true;
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
isExpired = true;
|
||||
} finally {
|
||||
if (testMethod != null) {
|
||||
testMethod.releaseConnection();
|
||||
}
|
||||
}
|
||||
|
||||
return isExpired;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test authentication mode : form based or basic.
|
||||
*
|
||||
@ -197,25 +231,23 @@ public class ExchangeSession {
|
||||
return result;
|
||||
}
|
||||
|
||||
void login(String userName, String password) throws IOException {
|
||||
void login() throws IOException {
|
||||
LOGGER.debug("Session " + this + " login");
|
||||
try {
|
||||
String baseUrl = Settings.getProperty("davmail.url");
|
||||
|
||||
boolean isBasicAuthentication = isBasicAuthentication(baseUrl);
|
||||
boolean isBasicAuthentication = isBasicAuthentication(poolKey.url);
|
||||
|
||||
// get proxy configuration from setttings properties
|
||||
URL urlObject = new URL(baseUrl);
|
||||
URL urlObject = new URL(poolKey.url);
|
||||
// webdavresource is unable to create the correct url type
|
||||
HttpURL httpURL;
|
||||
if (baseUrl.startsWith("http://")) {
|
||||
httpURL = new HttpURL(userName, password,
|
||||
if (poolKey.url.startsWith("http://")) {
|
||||
httpURL = new HttpURL(poolKey.userName, poolKey.password,
|
||||
urlObject.getHost(), urlObject.getPort());
|
||||
} else if (baseUrl.startsWith("https://")) {
|
||||
httpURL = new HttpsURL(userName, password,
|
||||
} else if (poolKey.url.startsWith("https://")) {
|
||||
httpURL = new HttpsURL(poolKey.userName, poolKey.password,
|
||||
urlObject.getHost(), urlObject.getPort());
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid URL: " + baseUrl);
|
||||
throw new IllegalArgumentException("Invalid URL: " + poolKey.url);
|
||||
}
|
||||
wdr = new WebdavResource(httpURL, WebdavResource.NOACTION, 0);
|
||||
|
||||
@ -230,10 +262,10 @@ public class ExchangeSession {
|
||||
// get webmail root url
|
||||
// providing credentials
|
||||
// manually follow redirect
|
||||
HttpMethod method = DavGatewayHttpClientFacade.executeFollowRedirects(httpClient, baseUrl);
|
||||
HttpMethod method = DavGatewayHttpClientFacade.executeFollowRedirects(httpClient, poolKey.url);
|
||||
|
||||
if (!isBasicAuthentication) {
|
||||
method = formLogin(httpClient, method, userName, password);
|
||||
method = formLogin(httpClient, method, poolKey.userName, poolKey.password);
|
||||
// reexecute method with new base URL
|
||||
// method = DavGatewayHttpClientFacade.executeFollowRedirects(httpClient, baseUrl);
|
||||
}
|
||||
@ -250,7 +282,7 @@ public class ExchangeSession {
|
||||
String queryString = method.getQueryString();
|
||||
if (queryString != null && queryString.contains("reason=2")) {
|
||||
method.releaseConnection();
|
||||
if (userName != null && userName.contains("\\")) {
|
||||
if (poolKey.userName != null && poolKey.userName.contains("\\")) {
|
||||
throw new HttpException("Authentication failed: invalid user or password");
|
||||
} else {
|
||||
throw new HttpException("Authentication failed: invalid user or password, " +
|
||||
@ -261,7 +293,7 @@ public class ExchangeSession {
|
||||
mailPath = getMailPath(method);
|
||||
|
||||
if (mailPath == null) {
|
||||
throw new HttpException(baseUrl + " not found in body, authentication failed: password expired ?");
|
||||
throw new HttpException(poolKey.url + " not found in body, authentication failed: password expired ?");
|
||||
}
|
||||
|
||||
// got base http mailbox http url
|
||||
@ -343,12 +375,14 @@ public class ExchangeSession {
|
||||
/**
|
||||
* Close session.
|
||||
* This will only close http client, not the actual Exchange session
|
||||
*
|
||||
* @throws IOException if unable to close Webdav context
|
||||
*/
|
||||
public void close() throws IOException {
|
||||
LOGGER.debug("Session " + this + " closed");
|
||||
wdr.close();
|
||||
void close() {
|
||||
try {
|
||||
wdr.close();
|
||||
LOGGER.debug("Session " + this + " closed");
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Exception closing session", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -422,14 +456,9 @@ public class ExchangeSession {
|
||||
|
||||
public Message getMessage(String messageUrl) throws IOException {
|
||||
|
||||
// TODO switch according to Log4J log level
|
||||
//wdr.setDebug(4);
|
||||
//wdr.propfindMethod(messageUrl, 0);
|
||||
Enumeration messageEnum = wdr.propfindMethod(messageUrl, 0, MESSAGE_REQUEST_PROPERTIES);
|
||||
//wdr.setDebug(0);
|
||||
|
||||
// 201 created in some cases ?!?
|
||||
if ((wdr.getStatusCode() != HttpURLConnection.HTTP_OK && wdr.getStatusCode() != HttpURLConnection.HTTP_CREATED)
|
||||
if ((wdr.getStatusCode() != HttpURLConnection.HTTP_OK)
|
||||
|| !messageEnum.hasMoreElements()) {
|
||||
throw new IOException("Unable to get message: " + wdr.getStatusCode()
|
||||
+ " " + wdr.getStatusMessage());
|
||||
@ -771,7 +800,7 @@ public class ExchangeSession {
|
||||
" FROM Scope('SHALLOW TRAVERSAL OF \"" + calendarUrl + "\"')\n" +
|
||||
" WHERE NOT \"urn:schemas:calendar:instancetype\" = 1\n" +
|
||||
" AND \"DAV:contentclass\" = 'urn:content-classes:appointment'\n" +
|
||||
" AND \"urn:schemas:calendar:dtstart\" > '2008/11/01 00:00:00'\n" +
|
||||
// " AND \"urn:schemas:calendar:dtstart\" > '2008/11/01 00:00:00'\n" +
|
||||
" ORDER BY \"urn:schemas:calendar:dtstart\" ASC\n" +
|
||||
" </d:sql>\n" +
|
||||
"</d:searchrequest>";
|
||||
@ -965,10 +994,10 @@ public class ExchangeSession {
|
||||
}
|
||||
results = XMLStreamUtil.getElementContentsAsMap(getMethod.getResponseBodyAsStream(), "item", "AN");
|
||||
// add detailed information, only if few results
|
||||
if (results.size() <=10) {
|
||||
for (Map<String, String> person : results.values()) {
|
||||
galLookup(person);
|
||||
}
|
||||
if (results.size() <= 10) {
|
||||
for (Map<String, String> person : results.values()) {
|
||||
galLookup(person);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
getMethod.releaseConnection();
|
||||
|
@ -11,12 +11,69 @@ import java.io.IOException;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Create ExchangeSession instances.
|
||||
*/
|
||||
public class ExchangeSessionFactory {
|
||||
private static final Object LOCK = new Object();
|
||||
private static final Map<PoolKey, ExchangeSessionStack> poolMap = new HashMap<PoolKey, ExchangeSessionStack>();
|
||||
|
||||
static class PoolKey {
|
||||
public String url;
|
||||
public String userName;
|
||||
public String password;
|
||||
|
||||
public PoolKey(String url, String userName, String password) {
|
||||
this.url = url;
|
||||
this.userName = userName;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
return object == this ||
|
||||
object instanceof PoolKey &&
|
||||
((PoolKey) object).url.equals(this.url) &&
|
||||
((PoolKey) object).userName.equals(this.userName) &&
|
||||
((PoolKey) object).password.equals(this.password);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return url.hashCode() + userName.hashCode() + password.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
static class ExchangeSessionStack extends Stack<ExchangeSession> {
|
||||
// 15 minutes expire delay
|
||||
protected static final long EXPIRE_DELAY = 1000 * 60 * 15;
|
||||
protected long timestamp = System.currentTimeMillis();
|
||||
|
||||
@Override
|
||||
public ExchangeSession pop() throws EmptyStackException {
|
||||
timestamp = System.currentTimeMillis();
|
||||
return super.pop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExchangeSession push(ExchangeSession session) {
|
||||
timestamp = System.currentTimeMillis();
|
||||
return super.push(session);
|
||||
}
|
||||
|
||||
public boolean isExpired() {
|
||||
return (System.currentTimeMillis() - timestamp) > EXPIRE_DELAY;
|
||||
}
|
||||
|
||||
public void clean() {
|
||||
while (!isEmpty()) {
|
||||
pop().close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create authenticated Exchange session
|
||||
*
|
||||
@ -27,8 +84,29 @@ public class ExchangeSessionFactory {
|
||||
*/
|
||||
public static ExchangeSession getInstance(String userName, String password) throws IOException {
|
||||
try {
|
||||
ExchangeSession session = new ExchangeSession();
|
||||
session.login(userName, password);
|
||||
String baseUrl = Settings.getProperty("davmail.url");
|
||||
PoolKey poolKey = new PoolKey(baseUrl, userName, password);
|
||||
|
||||
ExchangeSession session = null;
|
||||
synchronized (LOCK) {
|
||||
Stack<ExchangeSession> sessionStack = poolMap.get(poolKey);
|
||||
if (sessionStack != null && !sessionStack.isEmpty()) {
|
||||
session = sessionStack.pop();
|
||||
ExchangeSession.LOGGER.debug("Got session " + session + " from pool");
|
||||
}
|
||||
}
|
||||
|
||||
if (session != null && session.isExpired()) {
|
||||
ExchangeSession.LOGGER.debug("Session " + session + " expired");
|
||||
session.close();
|
||||
session = null;
|
||||
}
|
||||
|
||||
if (session == null) {
|
||||
session = new ExchangeSession(poolKey);
|
||||
session.login();
|
||||
ExchangeSession.LOGGER.debug("Created new session: " + session);
|
||||
}
|
||||
return session;
|
||||
} catch (IOException e) {
|
||||
if (checkNetwork()) {
|
||||
@ -39,6 +117,41 @@ public class ExchangeSessionFactory {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close (or pool) session.
|
||||
*
|
||||
* @param session exchange session
|
||||
*/
|
||||
public static void close(ExchangeSession session) {
|
||||
synchronized (LOCK) {
|
||||
if (session != null) {
|
||||
PoolKey poolKey = session.getPoolKey();
|
||||
ExchangeSessionStack sessionStack = poolMap.get(poolKey);
|
||||
if (sessionStack == null) {
|
||||
sessionStack = new ExchangeSessionStack();
|
||||
poolMap.put(poolKey, sessionStack);
|
||||
}
|
||||
// keep httpClient, but close HTTP connection
|
||||
session.close();
|
||||
sessionStack.push(session);
|
||||
ExchangeSession.LOGGER.debug("Pooled session: " + session);
|
||||
}
|
||||
|
||||
// clean pool
|
||||
List<PoolKey> toDeleteKeys = new ArrayList<PoolKey>();
|
||||
for (Map.Entry<PoolKey, ExchangeSessionStack> entry : poolMap.entrySet()) {
|
||||
if (entry.getValue().isExpired()) {
|
||||
ExchangeSession.LOGGER.debug("Session pool for " + entry.getKey().userName + " expired");
|
||||
entry.getValue().clean();
|
||||
toDeleteKeys.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
for (PoolKey toDeleteKey : toDeleteKeys) {
|
||||
poolMap.remove(toDeleteKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void checkConfig() throws IOException {
|
||||
String url = Settings.getProperty("davmail.url");
|
||||
@ -49,7 +162,6 @@ public class ExchangeSessionFactory {
|
||||
testMethod.setFollowRedirects(false);
|
||||
testMethod.setDoAuthentication(false);
|
||||
int status = httpClient.executeMethod(testMethod);
|
||||
testMethod.releaseConnection();
|
||||
ExchangeSession.LOGGER.debug("Test configuration status: " + status);
|
||||
if (status != HttpStatus.SC_OK && status != HttpStatus.SC_UNAUTHORIZED
|
||||
&& status != HttpStatus.SC_MOVED_TEMPORARILY) {
|
||||
|
@ -178,7 +178,7 @@ public class LdapConnection extends AbstractConnection {
|
||||
|
||||
} else if (operation == LDAP_REQ_UNBIND) {
|
||||
if (session != null) {
|
||||
session.close();
|
||||
ExchangeSessionFactory.close(session);
|
||||
session = null;
|
||||
}
|
||||
} else if (operation == LDAP_REQ_SEARCH) {
|
||||
|
@ -16,11 +16,11 @@ public class TestExchangeSession {
|
||||
Settings.setConfigFilePath(argv[currentArg++]);
|
||||
Settings.load();
|
||||
|
||||
ExchangeSession session = new ExchangeSession();
|
||||
ExchangeSession session;
|
||||
// test auth
|
||||
try {
|
||||
ExchangeSessionFactory.checkConfig();
|
||||
session.login(argv[currentArg++], argv[currentArg++]);
|
||||
session = ExchangeSessionFactory.getInstance(argv[currentArg++], argv[currentArg++]);
|
||||
|
||||
ExchangeSession.Folder folder = session.selectFolder(argv[currentArg++]);
|
||||
String messageName;
|
||||
|
Loading…
Reference in New Issue
Block a user