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:
mguessan 2008-12-04 11:50:31 +00:00
parent 9b5feac91f
commit 24362b8632
5 changed files with 181 additions and 45 deletions

View File

@ -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) {

View File

@ -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();

View File

@ -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) {

View File

@ -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) {

View File

@ -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;