diff --git a/src/main/java/microsoft/exchange/webservices/data/autodiscover/AutodiscoverService.java b/src/main/java/microsoft/exchange/webservices/data/autodiscover/AutodiscoverService.java index bbb6a67de..69791f4b6 100644 --- a/src/main/java/microsoft/exchange/webservices/data/autodiscover/AutodiscoverService.java +++ b/src/main/java/microsoft/exchange/webservices/data/autodiscover/AutodiscoverService.java @@ -389,7 +389,7 @@ private URI getRedirectUrl(String domainName) HttpWebRequest request = null; try { - request = new HttpClientWebRequest(httpClient, httpContext); + request = new HttpClientWebRequest(getHttpClient(), createHttpClientContext()); request.setProxy(getWebProxy()); try { @@ -1509,7 +1509,7 @@ private boolean tryGetEnabledEndpointsForHost(String host, HttpWebRequest request = null; try { - request = new HttpClientWebRequest(httpClient, httpContext); + request = new HttpClientWebRequest(getHttpClient(), createHttpClientContext()); request.setProxy(getWebProxy()); try { diff --git a/src/main/java/microsoft/exchange/webservices/data/core/ExchangeService.java b/src/main/java/microsoft/exchange/webservices/data/core/ExchangeService.java index 51ce1f990..b15ad40af 100644 --- a/src/main/java/microsoft/exchange/webservices/data/core/ExchangeService.java +++ b/src/main/java/microsoft/exchange/webservices/data/core/ExchangeService.java @@ -3573,7 +3573,7 @@ public void autodiscoverUrl(String emailAddress, ExchangeVersion.Exchange2007_SP1, validateRedirectionUrlCallback); - this.setUrl(this.adjustServiceUriFromCredentials(exchangeServiceUrl)); + setUrl(this.adjustServiceUriFromCredentials(exchangeServiceUrl)); } /** @@ -3741,11 +3741,11 @@ public ExchangeService(ExchangeVersion requestedServerVersion) { public HttpWebRequest prepareHttpWebRequest() throws ServiceLocalException, URISyntaxException { try { - this.url = this.adjustServiceUriFromCredentials(this.getUrl()); + setUrl(this.adjustServiceUriFromCredentials(this.getUrl())); } catch (Exception e) { LOG.error(e); } - return this.prepareHttpWebRequestForUrl(url, this + return this.prepareHttpWebRequestForUrl(getUrl(), this .getAcceptGzipEncoding(), true); } @@ -3759,11 +3759,11 @@ public HttpWebRequest prepareHttpWebRequest() public HttpWebRequest prepareHttpPoolingWebRequest() throws ServiceLocalException, URISyntaxException { try { - this.url = this.adjustServiceUriFromCredentials(this.getUrl()); + setUrl(this.adjustServiceUriFromCredentials(this.getUrl())); } catch (Exception e) { LOG.error(e); } - return this.prepareHttpPoolingWebRequestForUrl(url, this + return this.prepareHttpPoolingWebRequestForUrl(getUrl(), this .getAcceptGzipEncoding(), true); } diff --git a/src/main/java/microsoft/exchange/webservices/data/core/ExchangeServiceBase.java b/src/main/java/microsoft/exchange/webservices/data/core/ExchangeServiceBase.java index 302bdfc39..175e29b17 100644 --- a/src/main/java/microsoft/exchange/webservices/data/core/ExchangeServiceBase.java +++ b/src/main/java/microsoft/exchange/webservices/data/core/ExchangeServiceBase.java @@ -31,6 +31,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.security.GeneralSecurityException; +import java.security.SecureRandom; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; @@ -40,6 +41,7 @@ import java.util.Map; import java.util.Random; import java.util.TimeZone; +import java.util.concurrent.ConcurrentHashMap; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; @@ -92,7 +94,7 @@ public abstract class ExchangeServiceBase implements Closeable { /** * The binary secret. */ - private static byte[] binarySecret; + private static byte[] binarySecret = generateSessionKey(); /** * The timeout. @@ -132,24 +134,26 @@ public abstract class ExchangeServiceBase implements Closeable { /** * The requested server version. */ - private ExchangeVersion requestedServerVersion = ExchangeVersion.Exchange2010_SP2; + private final ExchangeVersion requestedServerVersion; /** * The server info. */ private ExchangeServerInfo serverInfo; - private Map httpHeaders = new HashMap(); - - private Map httpResponseHeaders = new HashMap(); + private Map httpHeaders = new ConcurrentHashMap(); private WebProxy webProxy; - protected CloseableHttpClient httpClient; + private volatile CloseableHttpClient httpClient; + + private final Object httpClientLock = new Object(); + + private final CookieStore cookieStore = new BasicCookieStore(); - protected HttpClientContext httpContext; + private volatile CloseableHttpClient httpPoolingClient; - protected CloseableHttpClient httpPoolingClient; + private final Object httpPoolingClientLock = new Object(); private int maximumPoolingConnections = 10; @@ -161,7 +165,7 @@ public abstract class ExchangeServiceBase implements Closeable { /** * Default UserAgent. */ - private static String defaultUserAgent = "ExchangeServicesClient/" + EwsUtilities.getBuildVersion(); + private static final String defaultUserAgent = "ExchangeServicesClient/" + EwsUtilities.getBuildVersion(); /** * Initializes a new instance. @@ -170,13 +174,11 @@ public abstract class ExchangeServiceBase implements Closeable { * every other constructor. */ protected ExchangeServiceBase() { - setUseDefaultCredentials(true); - initializeHttpClient(); - initializeHttpContext(); + this(ExchangeVersion.Exchange2010_SP2); } protected ExchangeServiceBase(ExchangeVersion requestedServerVersion) { - this(); + setUseDefaultCredentials(true); this.requestedServerVersion = requestedServerVersion; } @@ -194,30 +196,6 @@ protected ExchangeServiceBase(ExchangeServiceBase service, ExchangeVersion reque this.httpHeaders = service.getHttpHeaders(); } - private void initializeHttpClient() { - Registry registry = createConnectionSocketFactoryRegistry(); - HttpClientConnectionManager httpConnectionManager = new BasicHttpClientConnectionManager(registry); - AuthenticationStrategy authStrategy = new CookieProcessingTargetAuthenticationStrategy(); - - httpClient = HttpClients.custom() - .setConnectionManager(httpConnectionManager) - .setTargetAuthenticationStrategy(authStrategy) - .build(); - } - - private void initializeHttpPoolingClient() { - Registry registry = createConnectionSocketFactoryRegistry(); - PoolingHttpClientConnectionManager httpConnectionManager = new PoolingHttpClientConnectionManager(registry); - httpConnectionManager.setMaxTotal(maximumPoolingConnections); - httpConnectionManager.setDefaultMaxPerRoute(maximumPoolingConnections); - AuthenticationStrategy authStrategy = new CookieProcessingTargetAuthenticationStrategy(); - - httpPoolingClient = HttpClients.custom() - .setConnectionManager(httpConnectionManager) - .setTargetAuthenticationStrategy(authStrategy) - .build(); - } - /** * Sets the maximum number of connections for the pooling connection manager which is used for * subscriptions. @@ -230,6 +208,9 @@ private void initializeHttpPoolingClient() { public void setMaximumPoolingConnections(int maximumPoolingConnections) { if (maximumPoolingConnections < 1) throw new IllegalArgumentException("maximumPoolingConnections must be 1 or greater"); + if (httpPoolingClient != null) { + throw new IllegalStateException("Cannot change the maximumPoolingConnections setting after a request has been made"); + } this.maximumPoolingConnections = maximumPoolingConnections; } @@ -252,29 +233,37 @@ protected Registry createConnectionSocketFactoryRegistr } } - /** - * (Re)initializes the HttpContext object. This removes any existing state (mainly cookies). Use an own - * cookie store, instead of the httpClient's global store, so cookies get reset on reinitialization - */ - private void initializeHttpContext() { - CookieStore cookieStore = new BasicCookieStore(); - httpContext = HttpClientContext.create(); + protected HttpClientContext createHttpClientContext() { + HttpClientContext httpContext = HttpClientContext.create(); httpContext.setCookieStore(cookieStore); + return httpContext; } @Override public void close() { - try { - httpClient.close(); - } catch (IOException e) { - LOG.debug(e); + if(httpClient!=null) { + synchronized(httpClientLock){ + if(httpClient!=null) { + try { + httpClient.close(); + } catch (IOException e) { + LOG.debug(e); + } + httpClient = null; + } + } } if (httpPoolingClient != null) { - try { - httpPoolingClient.close(); - } catch (IOException e) { - LOG.debug(e); + synchronized(httpPoolingClientLock){ + if (httpPoolingClient != null) { + try { + httpPoolingClient.close(); + } catch (IOException e) { + LOG.debug(e); + } + httpPoolingClient = null; + } } } } @@ -322,7 +311,8 @@ protected HttpWebRequest prepareHttpWebRequestForUrl(URI url, boolean acceptGzip throw new ServiceLocalException(strErr); } - HttpClientWebRequest request = new HttpClientWebRequest(httpClient, httpContext); + HttpClientWebRequest request = new HttpClientWebRequest(getHttpClient(), createHttpClientContext()); + request.setProxy(getWebProxy()); prepareHttpWebRequestForUrl(url, acceptGzipEncoding, allowAutoRedirect, request); return request; @@ -352,11 +342,8 @@ protected HttpWebRequest prepareHttpPoolingWebRequestForUrl(URI url, boolean acc throw new ServiceLocalException(strErr); } - if (httpPoolingClient == null) { - initializeHttpPoolingClient(); - } - - HttpClientWebRequest request = new HttpClientWebRequest(httpPoolingClient, httpContext); + HttpClientWebRequest request = new HttpClientWebRequest(getHttpPoolingClient(), createHttpClientContext()); + request.setProxy(getWebProxy()); prepareHttpWebRequestForUrl(url, acceptGzipEncoding, allowAutoRedirect, request); return request; @@ -383,8 +370,6 @@ private void prepareHttpWebRequestForUrl(URI url, boolean acceptGzipEncoding, bo prepareCredentials(request); request.prepareConnection(); - - httpResponseHeaders.clear(); } protected void prepareCredentials(HttpWebRequest request) throws ServiceLocalException, URISyntaxException { @@ -644,8 +629,8 @@ public void setCredentials(ExchangeCredentials credentials) { this.credentials = credentials; this.useDefaultCredentials = false; - // Reset the httpContext, to remove any existing authentication cookies from subsequent request - initializeHttpContext(); + // Reset the cookies, to remove any existing authentication cookies from subsequent request + cookieStore.clear(); } /** @@ -673,8 +658,8 @@ public void setUseDefaultCredentials(boolean value) { this.credentials = null; } - // Reset the httpContext, to remove any existing authentication cookies from subsequent request - initializeHttpContext(); + // Reset the cookies, to remove any existing authentication cookies from subsequent request + cookieStore.clear(); } /** @@ -820,6 +805,48 @@ public void setWebProxy(WebProxy value) { public Map getHttpHeaders() { return this.httpHeaders; } + + protected final CloseableHttpClient getHttpClient(){ + CloseableHttpClient ret = httpClient; + if(ret == null){ + synchronized(httpClientLock){ + ret = httpClient; + if(ret == null){ + Registry registry = createConnectionSocketFactoryRegistry(); + HttpClientConnectionManager httpConnectionManager = new BasicHttpClientConnectionManager(registry); + AuthenticationStrategy authStrategy = new CookieProcessingTargetAuthenticationStrategy(); + + httpClient = ret = HttpClients.custom() + .setConnectionManager(httpConnectionManager) + .setTargetAuthenticationStrategy(authStrategy) + .build(); + } + } + } + return ret; + } + + protected final CloseableHttpClient getHttpPoolingClient(){ + CloseableHttpClient ret = httpClient; + if(ret == null){ + synchronized(httpPoolingClientLock){ + ret = httpPoolingClient; + if(ret == null){ + Registry registry = createConnectionSocketFactoryRegistry(); + PoolingHttpClientConnectionManager poolingHttpConnectionManager = new PoolingHttpClientConnectionManager(registry); + poolingHttpConnectionManager.setMaxTotal(maximumPoolingConnections); + poolingHttpConnectionManager.setDefaultMaxPerRoute(maximumPoolingConnections); + AuthenticationStrategy authStrategy = new CookieProcessingTargetAuthenticationStrategy(); + + httpPoolingClient = ret = HttpClients.custom() + .setConnectionManager(poolingHttpConnectionManager) + .setTargetAuthenticationStrategy(authStrategy) + .build(); + } + } + } + return ret; + } // Events @@ -859,28 +886,6 @@ public void setOnSerializeCustomSoapHeaders(List onSeri public void processHttpResponseHeaders(TraceFlags traceType, HttpWebRequest request) throws XMLStreamException, IOException, EWSHttpException { this.traceHttpResponseHeaders(traceType, request); - this.saveHttpResponseHeaders(request.getResponseHeaders()); - } - - /** - * Save the HTTP response headers. - * - * @param headers The response headers - */ - private void saveHttpResponseHeaders(Map headers) { - this.httpResponseHeaders.clear(); - - for (String key : headers.keySet()) { - this.httpResponseHeaders.put(key, headers.get(key)); - } - } - - /** - * Gets a collection of HTTP headers from the last response. - * @return HTTP response headers - */ - public Map getHttpResponseHeaders() { - return this.httpResponseHeaders; } /** @@ -888,15 +893,14 @@ public Map getHttpResponseHeaders() { * @return session key */ public static byte[] getSessionKey() { + return ExchangeServiceBase.binarySecret; + } + + private static byte[] generateSessionKey(){ // this has to be computed only once. - synchronized (ExchangeServiceBase.class) { - if (ExchangeServiceBase.binarySecret == null) { - Random randomNumberGenerator = new Random(); - ExchangeServiceBase.binarySecret = new byte[256 / 8]; - randomNumberGenerator.nextBytes(binarySecret); - } - - return ExchangeServiceBase.binarySecret; - } + Random randomNumberGenerator = new SecureRandom(); + final byte[] bytes = new byte[256 / 8]; + randomNumberGenerator.nextBytes(bytes); + return bytes; } } diff --git a/src/main/java/microsoft/exchange/webservices/data/core/WebProxy.java b/src/main/java/microsoft/exchange/webservices/data/core/WebProxy.java index 5f814fa13..b85cd8f8d 100644 --- a/src/main/java/microsoft/exchange/webservices/data/core/WebProxy.java +++ b/src/main/java/microsoft/exchange/webservices/data/core/WebProxy.java @@ -31,11 +31,11 @@ */ public class WebProxy { - private String host; + private final String host; - private int port; + private final int port; - private WebProxyCredentials credentials; + private final WebProxyCredentials credentials; /** @@ -45,8 +45,7 @@ public class WebProxy { * @param port proxy port. */ public WebProxy(String host, int port) { - this.host = host; - this.port = port; + this(host, port, null); } /** @@ -55,8 +54,7 @@ public WebProxy(String host, int port) { * @param host proxy host. */ public WebProxy(String host) { - this.host = host; - this.port = 80; + this(host, 80, null); } /** @@ -66,8 +64,7 @@ public WebProxy(String host) { * @param credentials the credential to use for the proxy. */ public WebProxy(String host, WebProxyCredentials credentials) { - this.host = host; - this.credentials = credentials; + this(host, 80, credentials); } /** diff --git a/src/main/java/microsoft/exchange/webservices/data/credential/WebProxyCredentials.java b/src/main/java/microsoft/exchange/webservices/data/credential/WebProxyCredentials.java index 64fd45082..d0d2c301a 100644 --- a/src/main/java/microsoft/exchange/webservices/data/credential/WebProxyCredentials.java +++ b/src/main/java/microsoft/exchange/webservices/data/credential/WebProxyCredentials.java @@ -23,13 +23,13 @@ package microsoft.exchange.webservices.data.credential; -public class WebProxyCredentials { +public final class WebProxyCredentials { - private String username; + private final String username; - private String password; + private final String password; - private String domain; + private final String domain; public WebProxyCredentials(String username, String password, String domain) { this.username = username;