001/*
002 * The MIT License
003 * Copyright (c) 2012 Microsoft Corporation
004 *
005 * Permission is hereby granted, free of charge, to any person obtaining a copy
006 * of this software and associated documentation files (the "Software"), to deal
007 * in the Software without restriction, including without limitation the rights
008 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
009 * copies of the Software, and to permit persons to whom the Software is
010 * furnished to do so, subject to the following conditions:
011 *
012 * The above copyright notice and this permission notice shall be included in
013 * all copies or substantial portions of the Software.
014 *
015 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
016 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
017 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
018 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
019 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
020 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
021 * THE SOFTWARE.
022 */
023
024package microsoft.exchange.webservices.data.core;
025
026import microsoft.exchange.webservices.data.*;
027import microsoft.exchange.webservices.data.core.enumeration.misc.*;
028import microsoft.exchange.webservices.data.core.exception.http.*;
029import microsoft.exchange.webservices.data.core.exception.service.local.*;
030import microsoft.exchange.webservices.data.core.exception.service.remote.*;
031import microsoft.exchange.webservices.data.core.request.*;
032import microsoft.exchange.webservices.data.credential.*;
033import microsoft.exchange.webservices.data.misc.*;
034import org.apache.commons.io.*;
035import org.apache.commons.logging.*;
036import org.apache.http.client.CookieStore;
037import org.apache.http.client.*;
038import org.apache.http.client.protocol.*;
039import org.apache.http.config.*;
040import org.apache.http.conn.*;
041import org.apache.http.conn.socket.*;
042import org.apache.http.impl.client.*;
043import org.apache.http.impl.conn.*;
044
045import javax.xml.stream.*;
046import java.io.*;
047import java.net.*;
048import java.security.*;
049import java.text.*;
050import java.util.*;
051
052/**
053 * Represents an abstract binding to an Exchange Service.
054 */
055public abstract class ExchangeServiceBase implements Closeable {
056  
057  private static final Log LOG = LogFactory.getLog(ExchangeService.class);
058
059  /**
060   * The credential.
061   */
062  private ExchangeCredentials credentials;
063
064  /**
065   * The use default credential.
066   */
067  private boolean useDefaultCredentials;
068
069  /**
070   * The binary secret.
071   */
072  private static byte[] binarySecret;
073
074  /**
075   * The timeout.
076   */
077  private int timeout = 100000;
078
079  /**
080   * The trace enabled.
081   */
082  private boolean traceEnabled;
083
084  /**
085   * The trace flags.
086   */
087  private EnumSet<TraceFlags> traceFlags = EnumSet.allOf(TraceFlags.class);
088
089  /**
090   * The trace listener.
091   */
092  private ITraceListener traceListener = new EwsTraceListener();
093
094  /**
095   * The pre authenticate.
096   */
097  private boolean preAuthenticate;
098
099  /**
100   * The user agent.
101   */
102  private String userAgent = ExchangeServiceBase.defaultUserAgent;
103
104  /**
105   * The accept gzip encoding.
106   */
107  private boolean acceptGzipEncoding = true;
108
109  /**
110   * The requested server version.
111   */
112  private ExchangeVersion requestedServerVersion = ExchangeVersion.Exchange2010_SP2;
113
114  /**
115   * The server info.
116   */
117  private ExchangeServerInfo serverInfo;
118
119  private Map<String, String> httpHeaders = new HashMap<String, String>();
120
121  private Map<String, String> httpResponseHeaders = new HashMap<String, String>();
122
123  private WebProxy webProxy;
124
125  protected CloseableHttpClient httpClient;
126
127  protected boolean externalHttpClient = false;
128
129  protected HttpClientContext httpContext;
130
131  protected CloseableHttpClient httpPoolingClient;
132
133  protected boolean externalHttpPoolingClient = false;
134  
135  private int maximumPoolingConnections = 10;
136
137  private boolean useGlobalCookieStore = false;
138
139//  protected HttpClientWebRequest request = null;
140
141  // protected static HttpStatusCode AccountIsLocked = (HttpStatusCode)456;
142
143  /**
144   * Default UserAgent.
145   */
146  private static String defaultUserAgent = "ExchangeServicesClient/" + EwsUtilities.getBuildVersion();
147
148  private ServiceRequestTraceListener serviceRequestTraceListener;
149
150  /**
151   * Initializes a new instance.
152   *
153   * This constructor performs the initialization of the HTTP connection manager, so it should be called by
154   * every other constructor.
155   */
156  protected ExchangeServiceBase() {
157    this((CloseableHttpClient) null, null);
158  }
159
160  protected ExchangeServiceBase(CloseableHttpClient httpClient, CloseableHttpClient httpPoolingClient) {
161    setUseDefaultCredentials(true);
162    if (httpClient != null) {
163      externalHttpClient = true;
164      this.httpClient = httpClient;
165    } else {
166      initializeHttpClient();
167    }
168    if (httpPoolingClient != null) {
169      externalHttpPoolingClient = true;
170      this.httpPoolingClient = httpPoolingClient;
171    }
172  }
173
174  protected ExchangeServiceBase(ExchangeVersion requestedServerVersion) {
175    this();
176    this.requestedServerVersion = requestedServerVersion;
177  }
178
179  protected ExchangeServiceBase(ExchangeVersion requestedServerVersion, CloseableHttpClient httpClient,
180      CloseableHttpClient httpPoolingClient) {
181    this(httpClient, httpPoolingClient);
182    this.requestedServerVersion = requestedServerVersion;
183  }
184
185  protected ExchangeServiceBase(ExchangeServiceBase service, ExchangeVersion requestedServerVersion) {
186    this(requestedServerVersion, service.httpClient, service.httpPoolingClient);
187    this.externalHttpClient = service.externalHttpClient;
188    this.externalHttpPoolingClient = service.externalHttpPoolingClient;
189    this.useDefaultCredentials = service.getUseDefaultCredentials();
190    this.credentials = service.getCredentials();
191    this.traceEnabled = service.isTraceEnabled();
192    this.traceListener = service.getTraceListener();
193    this.traceFlags = service.getTraceFlags();
194    this.timeout = service.getTimeout();
195    this.preAuthenticate = service.isPreAuthenticate();
196    this.userAgent = service.getUserAgent();
197    this.acceptGzipEncoding = service.getAcceptGzipEncoding();
198    this.httpHeaders = service.getHttpHeaders();
199  }
200
201  protected void initializeHttpClient() {
202    Registry<ConnectionSocketFactory> registry = createConnectionSocketFactoryRegistry();
203    HttpClientConnectionManager httpConnectionManager = new BasicHttpClientConnectionManager(registry);
204    AuthenticationStrategy authStrategy = new CookieProcessingTargetAuthenticationStrategy();
205
206    httpClient = HttpClients.custom()
207      .setConnectionManager(httpConnectionManager)
208      .setTargetAuthenticationStrategy(authStrategy)
209      .build();
210  }
211
212  protected void initializeHttpPoolingClient() {
213    Registry<ConnectionSocketFactory> registry = createConnectionSocketFactoryRegistry();
214    PoolingHttpClientConnectionManager httpConnectionManager = new PoolingHttpClientConnectionManager(registry);
215    httpConnectionManager.setMaxTotal(maximumPoolingConnections);
216    httpConnectionManager.setDefaultMaxPerRoute(maximumPoolingConnections);
217    AuthenticationStrategy authStrategy = new CookieProcessingTargetAuthenticationStrategy();
218
219    httpPoolingClient = HttpClients.custom()
220        .setConnectionManager(httpConnectionManager)
221        .setTargetAuthenticationStrategy(authStrategy)
222        .build();
223  }
224
225  /**
226   * Sets the maximum number of connections for the pooling connection manager which is used for
227   * subscriptions.
228   * <p>
229   * Default is 10.
230   * </p>
231   * 
232   * @param maximumPoolingConnections Maximum number of pooling connections
233   */
234  public void setMaximumPoolingConnections(int maximumPoolingConnections) {
235    if (maximumPoolingConnections < 1)
236      throw new IllegalArgumentException("maximumPoolingConnections must be 1 or greater");
237    this.maximumPoolingConnections = maximumPoolingConnections;
238  }
239
240  /**
241   * Create registry with configured {@link ConnectionSocketFactory} instances.
242   * Override this method to change how to work with different schemas.
243   *
244   * @return registry object
245   */
246  protected Registry<ConnectionSocketFactory> createConnectionSocketFactoryRegistry() {
247    try {
248      return RegistryBuilder.<ConnectionSocketFactory>create()
249        .register(EWSConstants.HTTP_SCHEME, new PlainConnectionSocketFactory())
250        .register(EWSConstants.HTTPS_SCHEME, EwsSSLProtocolSocketFactory.build(null))
251        .build();
252    } catch (GeneralSecurityException e) {
253      throw new RuntimeException(
254        "Could not initialize ConnectionSocketFactory instances for HttpClientConnectionManager", e
255      );
256    }
257  }
258
259  /**
260   * (Re)initializes the HttpContext object. This removes any existing state (mainly cookies). Use an own
261   * cookie store, instead of the httpClient's global store, so cookies get reset on reinitialization
262   */
263  private void initializeHttpContext() {
264    httpContext = HttpClientContext.create();
265    if (!useGlobalCookieStore) {
266      CookieStore cookieStore = new BasicCookieStore();
267      httpContext.setCookieStore(cookieStore);
268    }
269  }
270
271  @Override
272  public void close() {
273    if (!externalHttpClient) {
274      IOUtils.closeQuietly(httpClient);
275    }
276    if (!externalHttpPoolingClient) {
277      IOUtils.closeQuietly(httpPoolingClient);
278    }
279  }
280
281  // Event handlers
282
283  /**
284   * Calls the custom SOAP header serialisation event handlers, if defined.
285   *
286   * @param writer The XmlWriter to which to write the custom SOAP headers.
287   */
288  public void doOnSerializeCustomSoapHeaders(XMLStreamWriter writer) {
289    EwsUtilities
290        .ewsAssert(writer != null, "ExchangeService.DoOnSerializeCustomSoapHeaders", "writer is null");
291
292    if (null != getOnSerializeCustomSoapHeaders() &&
293        !getOnSerializeCustomSoapHeaders().isEmpty()) {
294      for (ICustomXmlSerialization customSerialization : getOnSerializeCustomSoapHeaders()) {
295        customSerialization.CustomXmlSerialization(writer);
296      }
297    }
298  }
299
300  // Utilities
301
302  /**
303   * Creates an HttpWebRequest instance and initialises it with the
304   * appropriate parameters, based on the configuration of this service
305   * object.
306   *
307   * @param url                The URL that the HttpWebRequest should target.
308   * @param acceptGzipEncoding If true, ask server for GZip compressed content.
309   * @param allowAutoRedirect  If true, redirection response will be automatically followed.
310   * @return An initialised instance of HttpWebRequest.
311   * @throws ServiceLocalException       the service local exception
312   * @throws java.net.URISyntaxException the uRI syntax exception
313   */
314  protected HttpWebRequest prepareHttpWebRequestForUrl(URI url, boolean acceptGzipEncoding,
315      boolean allowAutoRedirect) throws ServiceLocalException, URISyntaxException {
316    // Verify that the protocol is something that we can handle
317    String scheme = url.getScheme();
318    if (!scheme.equalsIgnoreCase(EWSConstants.HTTP_SCHEME)
319      && !scheme.equalsIgnoreCase(EWSConstants.HTTPS_SCHEME)) {
320      String strErr = String.format("Protocol %s isn't supported for service request.", scheme);
321      throw new ServiceLocalException(strErr);
322    }
323
324    if (httpContext == null) {
325      initializeHttpContext();
326    }
327
328    HttpClientWebRequest request = new HttpClientWebRequest(httpClient, httpContext);
329    prepareHttpWebRequestForUrl(url, acceptGzipEncoding, allowAutoRedirect, request);
330
331    return request;
332  }
333
334  /**
335   * Creates an HttpWebRequest instance from a pooling connection manager and initialises it with
336   * the appropriate parameters, based on the configuration of this service object.
337   * <p>
338   * This is used for subscriptions.
339   * </p>
340   *
341   * @param url The URL that the HttpWebRequest should target.
342   * @param acceptGzipEncoding If true, ask server for GZip compressed content.
343   * @param allowAutoRedirect If true, redirection response will be automatically followed.
344   * @return An initialised instance of HttpWebRequest.
345   * @throws ServiceLocalException the service local exception
346   * @throws java.net.URISyntaxException the uRI syntax exception
347   */
348  protected HttpWebRequest prepareHttpPoolingWebRequestForUrl(URI url, boolean acceptGzipEncoding,
349      boolean allowAutoRedirect) throws ServiceLocalException, URISyntaxException {
350    // Verify that the protocol is something that we can handle
351    String scheme = url.getScheme();
352    if (!scheme.equalsIgnoreCase(EWSConstants.HTTP_SCHEME)
353        && !scheme.equalsIgnoreCase(EWSConstants.HTTPS_SCHEME)) {
354      String strErr = String.format("Protocol %s isn't supported for service request.", scheme);
355      throw new ServiceLocalException(strErr);
356    }
357
358    if (httpPoolingClient == null) {
359      initializeHttpPoolingClient();
360    }
361
362    if (httpContext == null) {
363      initializeHttpContext();
364    }
365
366    HttpClientWebRequest request = new HttpClientWebRequest(httpPoolingClient, httpContext);
367    prepareHttpWebRequestForUrl(url, acceptGzipEncoding, allowAutoRedirect, request);
368
369    return request;
370  }
371
372  private void prepareHttpWebRequestForUrl(URI url, boolean acceptGzipEncoding, boolean allowAutoRedirect,
373      HttpClientWebRequest request) throws ServiceLocalException, URISyntaxException {
374    try {
375      request.setUrl(url.toURL());
376    } catch (MalformedURLException e) {
377      String strErr = String.format("Incorrect format : %s", url);
378      throw new ServiceLocalException(strErr);
379    }
380
381    request.setPreAuthenticate(preAuthenticate);
382    request.setTimeout(timeout);
383    request.setContentType("text/xml; charset=utf-8");
384    request.setAccept("text/xml");
385    request.setUserAgent(userAgent);
386    request.setAllowAutoRedirect(allowAutoRedirect);
387    request.setAcceptGzipEncoding(acceptGzipEncoding);
388    request.setHeaders(getHttpHeaders());
389    request.setProxy(getWebProxy());
390    prepareCredentials(request);
391
392    request.prepareConnection();
393
394    httpResponseHeaders.clear();
395  }
396
397  protected void prepareCredentials(HttpWebRequest request) throws ServiceLocalException, URISyntaxException {
398    request.setUseDefaultCredentials(useDefaultCredentials);
399    if (!useDefaultCredentials) {
400      if (credentials == null) {
401        throw new ServiceLocalException("Credentials are required to make a service request.");
402      }
403
404      // Make sure that credential have been authenticated if required
405      credentials.preAuthenticate();
406
407      // Apply credential to the request
408      credentials.prepareWebRequest(request);
409    }
410  }
411
412  /**
413   * This method doesn't handle 500 ISE errors. This is handled by the caller since
414   * 500 ISE typically indicates that a SOAP fault has occurred and the handling of
415   * a SOAP fault is currently service specific.
416   *
417   * @param httpWebResponse HTTP web response
418   * @param webException web exception
419   * @param responseHeadersTraceFlag trace flag for response headers
420   * @param responseTraceFlag trace flag for respone
421   * @throws Exception on error
422   */
423  protected void internalProcessHttpErrorResponse(HttpWebRequest httpWebResponse, Exception webException,
424      TraceFlags responseHeadersTraceFlag, TraceFlags responseTraceFlag) throws Exception {
425    EwsUtilities.ewsAssert(500 != httpWebResponse.getResponseCode(),
426        "ExchangeServiceBase.InternalProcessHttpErrorResponse",
427        "InternalProcessHttpErrorResponse does not handle 500 ISE errors, the caller is supposed to handle this.");
428
429    this.processHttpResponseHeaders(responseHeadersTraceFlag, httpWebResponse);
430
431    // E14:321785 -- Deal with new HTTP error code indicating that account is locked.
432    // The "unlock" URL is returned as the status description in the response.
433    if (httpWebResponse.getResponseCode() == 456) {
434      String location = httpWebResponse.getResponseContentType();
435
436      URI accountUnlockUrl = null;
437      if (checkURIPath(location)) {
438        accountUnlockUrl = new URI(location);
439      }
440
441      final String message = String.format("This account is locked. Visit %s to unlock it.", accountUnlockUrl);
442      this.traceMessage(responseTraceFlag, message);
443      throw new AccountIsLockedException(message, accountUnlockUrl, webException);
444    }
445  }
446
447  /**
448   * @param location file path
449   * @return false if location is null,true if this abstract pathname is absolute
450   */
451  public static boolean checkURIPath(String location) {
452    if (location == null) {
453      return false;
454    }
455    final File file = new File(location);
456    return file.isAbsolute();
457  }
458
459  /**
460   * @param httpWebResponse HTTP web response
461   * @param webException web exception
462   * @throws Exception on error
463   */
464  protected abstract void processHttpErrorResponse(HttpWebRequest httpWebResponse, Exception webException)
465      throws Exception;
466
467  /**
468   * Determines whether tracing is enabled for specified trace flag(s).
469   *
470   * @param traceFlags The trace flags.
471   * @return True if tracing is enabled for specified trace flag(s).
472   */
473  public boolean isTraceEnabledFor(TraceFlags traceFlags) {
474    return this.isTraceEnabled() && this.traceFlags.contains(traceFlags);
475  }
476
477  /**
478   * Logs the specified string to the TraceListener if tracing is enabled.
479   *
480   * @param traceType kind of trace entry
481   * @param logEntry the entry to log
482   * @throws XMLStreamException the XML stream exception
483   * @throws IOException signals that an I/O exception has occurred
484   */
485  public void traceMessage(TraceFlags traceType, String logEntry) throws XMLStreamException, IOException {
486    if (this.isTraceEnabledFor(traceType)) {
487      String traceTypeStr = traceType.toString();
488      String logMessage = EwsUtilities.formatLogMessage(traceTypeStr, logEntry);
489      this.traceListener.trace(traceTypeStr, logMessage);
490    }
491  }
492
493  /**
494   * Logs the specified XML to the TraceListener if tracing is enabled.
495   *
496   * @param traceType Kind of trace entry.
497   * @param stream    The stream containing XML.
498   */
499  public void traceXml(TraceFlags traceType, ByteArrayOutputStream stream) {
500    if (this.isTraceEnabledFor(traceType)) {
501      String traceTypeStr = traceType.toString();
502      String logMessage = EwsUtilities.formatLogMessageWithXmlContent(traceTypeStr, stream);
503      this.traceListener.trace(traceTypeStr, logMessage);
504    }
505  }
506
507  /**
508   * Traces the HTTP request headers.
509   *
510   * @param traceType Kind of trace entry.
511   * @param request   The request
512   * @throws EWSHttpException EWS http exception
513   * @throws URISyntaxException URI syntax error
514   * @throws IOException signals that an I/O exception has occurred
515   * @throws XMLStreamException the XML stream exception
516   */
517  public void traceHttpRequestHeaders(TraceFlags traceType, HttpWebRequest request)
518      throws URISyntaxException, EWSHttpException, XMLStreamException, IOException {
519    if (this.isTraceEnabledFor(traceType)) {
520      String traceTypeStr = traceType.toString();
521      String headersAsString = EwsUtilities.formatHttpRequestHeaders(request);
522      String logMessage = EwsUtilities.formatLogMessage(traceTypeStr, headersAsString);
523      this.traceListener.trace(traceTypeStr, logMessage);
524    }
525  }
526
527  /**
528   * Traces the HTTP response headers.
529   *
530   * @param traceType kind of trace entry
531   * @param request the HttpRequest object
532   * @throws XMLStreamException the XML stream exception
533   * @throws IOException signals that an I/O exception has occurred
534   * @throws EWSHttpException the EWS http exception
535   */
536  private void traceHttpResponseHeaders(TraceFlags traceType, HttpWebRequest request)
537      throws XMLStreamException, IOException, EWSHttpException {
538    if (this.isTraceEnabledFor(traceType)) {
539      String traceTypeStr = traceType.toString();
540      String headersAsString = EwsUtilities.formatHttpResponseHeaders(request);
541      String logMessage = EwsUtilities.formatLogMessage(traceTypeStr, headersAsString);
542      this.traceListener.trace(traceTypeStr, logMessage);
543    }
544  }
545
546  /**
547   * Converts the date time to universal date time string.
548   *
549   * @param dt the date
550   * @return String representation of DateTime in yyyy-MM-ddTHH:mm:ssZ format.
551   */
552  public String convertDateTimeToUniversalDateTimeString(Date dt) {
553    String utcPattern = "yyyy-MM-dd'T'HH:mm:ss'Z'";
554    DateFormat utcFormatter = new SimpleDateFormat(utcPattern);
555    utcFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
556    return utcFormatter.format(dt);
557  }
558
559  /**
560   * Sets the user agent to a custom value
561   *
562   * @param userAgent User agent string to set on the service
563   */
564  protected void setCustomUserAgent(String userAgent) {
565    this.userAgent = userAgent;
566  }
567
568  /**
569   * Validates this instance.
570   *
571   * @throws ServiceLocalException the service local exception
572   */
573  public void validate() throws ServiceLocalException {
574  }
575
576  /**
577   * Gets a value indicating whether tracing is enabled.
578   *
579   * @return True is tracing is enabled
580   */
581  public boolean isTraceEnabled() {
582    return this.traceEnabled;
583  }
584
585  /**
586   * Sets a value indicating whether tracing is enabled.
587   *
588   * @param traceEnabled true to enable tracing
589   */
590  public void setTraceEnabled(boolean traceEnabled) {
591    this.traceEnabled = traceEnabled;
592    if (this.traceEnabled && (this.traceListener == null)) {
593      this.traceListener = new EwsTraceListener();
594    }
595  }
596
597  /**
598   * Gets the trace flags.
599   *
600   * @return Set of trace flags.
601   */
602  public EnumSet<TraceFlags> getTraceFlags() {
603    return traceFlags;
604  }
605
606  /**
607   * Sets the trace flags.
608   *
609   * @param traceFlags A set of trace flags
610   */
611  public void setTraceFlags(EnumSet<TraceFlags> traceFlags) {
612    this.traceFlags = traceFlags;
613  }
614
615  /**
616   * Gets the trace listener.
617   *
618   * @return The trace listener.
619   */
620  public ITraceListener getTraceListener() {
621    return traceListener;
622  }
623
624  /**
625   * Sets the trace listener.
626   *
627   * @param traceListener the trace listener.
628   */
629  public void setTraceListener(ITraceListener traceListener) {
630    this.traceListener = traceListener;
631    this.traceEnabled = (traceListener != null);
632  }
633
634  public void setServiceRequestTracer(ServiceRequestTraceListener serviceRequestTraceListener) {
635    this.serviceRequestTraceListener = serviceRequestTraceListener;
636  }
637
638  /**
639   * Gets the credential used to authenticate with the Exchange Web Services.
640   *
641   * @return credential
642   */
643  public ExchangeCredentials getCredentials() {
644    return this.credentials;
645  }
646
647  /**
648   * Sets the credential used to authenticate with the Exchange Web Services.
649   * Setting the Credentials property automatically sets the
650   * UseDefaultCredentials to false.
651   *
652   * @param credentials Exchange credential.
653   */
654  public void setCredentials(ExchangeCredentials credentials) {
655    this.credentials = credentials;
656    this.useDefaultCredentials = false;
657
658    // Reset the httpContext, to remove any existing authentication cookies from subsequent request
659    httpContext = null;
660  }
661
662  /**
663   * Gets a value indicating whether the credential of the user currently
664   * logged into Windows should be used to authenticate with the Exchange Web
665   * Services.
666   *
667   * @return true if credential of the user currently logged in are used
668   */
669  public boolean getUseDefaultCredentials() {
670    return this.useDefaultCredentials;
671  }
672
673  /**
674   * Sets a value indicating whether the credential of the user currently
675   * logged into Windows should be used to authenticate with the Exchange Web
676   * Services. Setting UseDefaultCredentials to true automatically sets the
677   * Credentials property to null.
678   *
679   * @param value the new use default credential
680   */
681  public void setUseDefaultCredentials(boolean value) {
682    this.useDefaultCredentials = value;
683    if (value) {
684      this.credentials = null;
685    }
686
687    // Reset the httpContext, to remove any existing authentication cookies from subsequent request
688    httpContext = null;
689  }
690  
691  /**
692   * Gets the timeout used when sending HTTP request and when receiving HTTP
693   * response, in milliseconds.
694   *
695   * @return timeout in milliseconds
696   */
697  public int getTimeout() {
698    return timeout;
699  }
700
701  /**
702   * Sets the timeout used when sending HTTP request and when receiving HTTP
703   * respones, in milliseconds. Defaults to 100000.
704   *
705   * @param timeout timeout in milliseconds
706   */
707  public void setTimeout(int timeout) {
708    if (timeout < 1) {
709      throw new IllegalArgumentException("Timeout must be greater than zero.");
710    }
711    this.timeout = timeout;
712  }
713
714  /**
715   * Gets a value that indicates whether HTTP pre-authentication should be
716   * performed.
717   *
718   * @return true indicates pre-authentication is set
719   */
720  public boolean isPreAuthenticate() {
721    return preAuthenticate;
722  }
723
724  /**
725   * Sets a value that indicates whether HTTP pre-authentication should be
726   * performed.
727   *
728   * @param preAuthenticate true to enable pre-authentication
729   */
730  public void setPreAuthenticate(boolean preAuthenticate) {
731    this.preAuthenticate = preAuthenticate;
732  }
733
734  /**
735   * Gets a value indicating whether GZip compression encoding should be
736   * accepted. This value will tell the server that the client is able to
737   * handle GZip compression encoding. The server will only send Gzip
738   * compressed content if it has been configured to do so.
739   *
740   * @return true if compression is used
741   */
742  public boolean getAcceptGzipEncoding() {
743    return acceptGzipEncoding;
744  }
745
746  /**
747   * Gets a value indicating whether GZip compression encoding should
748   * be accepted. This value will tell the server that the client is able to
749   * handle GZip compression encoding. The server will only send Gzip
750   * compressed content if it has been configured to do so.
751   *
752   * @param acceptGzipEncoding true to enable compression
753   */
754  public void setAcceptGzipEncoding(boolean acceptGzipEncoding) {
755    this.acceptGzipEncoding = acceptGzipEncoding;
756  }
757
758  /**
759   * Gets the requested server version.
760   *
761   * @return The requested server version.
762   */
763  public ExchangeVersion getRequestedServerVersion() {
764    return this.requestedServerVersion;
765  }
766
767  /**
768   * Gets the user agent.
769   *
770   * @return The user agent.
771   */
772  public String getUserAgent() {
773    return this.userAgent;
774  }
775
776  /**
777   * Sets the user agent.
778   *
779   * @param userAgent The user agent
780   */
781  public void setUserAgent(String userAgent) {
782    this.userAgent = userAgent + " (" + ExchangeServiceBase.defaultUserAgent + ")";
783  }
784
785  /**
786   * Gets information associated with the server that processed the last
787   * request. Will be null if no request have been processed.
788   *
789   * @return the server info
790   */
791  public ExchangeServerInfo getServerInfo() {
792    return serverInfo;
793  }
794
795  /**
796   * Sets information associated with the server that processed the last
797   * request.
798   *
799   * @param serverInfo Server Information
800   */
801  public void setServerInfo(ExchangeServerInfo serverInfo) {
802    this.serverInfo = serverInfo;
803  }
804
805  /**
806   * Gets the web proxy that should be used when sending request to EWS.
807   *
808   * @return Proxy
809   * the Proxy Information
810   */
811  public WebProxy getWebProxy() {
812    return this.webProxy;
813  }
814
815  /**
816   * Sets the web proxy that should be used when sending request to EWS.
817   * Set this property to null to use the default web proxy.
818   *
819   * @param value the Proxy Information
820   */
821  public void setWebProxy(WebProxy value) {
822    this.webProxy = value;
823  }
824
825  public boolean isUseGlobalCookieStore() {
826    return useGlobalCookieStore;
827  }
828
829  public void setUseGlobalCookieStore(boolean useGlobalCookieStore) {
830    this.useGlobalCookieStore = useGlobalCookieStore;
831  }
832
833  /**
834   * Gets a collection of HTTP headers that will be sent with each request to
835   * EWS.
836   *
837   * @return httpHeaders
838   */
839  public Map<String, String> getHttpHeaders() {
840    return this.httpHeaders;
841  }
842
843  // Events
844
845  /**
846   * Provides an event that applications can implement to emit custom SOAP
847   * headers in request that are sent to Exchange.
848   */
849  private List<ICustomXmlSerialization> OnSerializeCustomSoapHeaders;
850
851  /**
852   * Gets the on serialize custom soap headers.
853   *
854   * @return the on serialize custom soap headers
855   */
856  public List<ICustomXmlSerialization> getOnSerializeCustomSoapHeaders() {
857    return OnSerializeCustomSoapHeaders;
858  }
859
860  /**
861   * Sets the on serialize custom soap headers.
862   *
863   * @param onSerializeCustomSoapHeaders the new on serialize custom soap headers
864   */
865  public void setOnSerializeCustomSoapHeaders(List<ICustomXmlSerialization> onSerializeCustomSoapHeaders) {
866    OnSerializeCustomSoapHeaders = onSerializeCustomSoapHeaders;
867  }
868
869  /**
870   * Traces the HTTP response headers.
871   *
872   * @param traceType kind of trace entry
873   * @param request   The request
874   * @throws EWSHttpException EWS http exception
875   * @throws IOException signals that an I/O exception has occurred
876   * @throws XMLStreamException the XML stream exception
877   */
878  public void processHttpResponseHeaders(TraceFlags traceType, HttpWebRequest request)
879      throws XMLStreamException, IOException, EWSHttpException {
880    this.traceHttpResponseHeaders(traceType, request);
881    this.saveHttpResponseHeaders(request.getResponseHeaders());
882  }
883
884  /**
885   * Save the HTTP response headers.
886   *
887   * @param headers The response headers
888   */
889  private void saveHttpResponseHeaders(Map<String, String> headers) {
890    this.httpResponseHeaders.clear();
891
892    for (String key : headers.keySet()) {
893      this.httpResponseHeaders.put(key, headers.get(key));
894    }
895  }
896
897  /**
898   * Gets a collection of HTTP headers from the last response.
899   * @return HTTP response headers
900   */
901  public Map<String, String> getHttpResponseHeaders() {
902    return this.httpResponseHeaders;
903  }
904
905  /**
906   * Gets the session key.
907   * @return session key
908   */
909  public static byte[] getSessionKey() {
910    // this has to be computed only once.
911    synchronized (ExchangeServiceBase.class) {
912      if (ExchangeServiceBase.binarySecret == null) {
913        Random randomNumberGenerator = new Random();
914        ExchangeServiceBase.binarySecret = new byte[256 / 8];
915        randomNumberGenerator.nextBytes(binarySecret);
916      }
917
918      return ExchangeServiceBase.binarySecret;
919    }
920  }
921
922  public int getMaximumPoolingConnections() {
923    return maximumPoolingConnections;
924  }
925
926  public <T> void traceServiceRequestStart(ServiceRequestBase<T> serviceRequest, HttpWebRequest request) {
927    if (serviceRequestTraceListener == null) {
928      return;
929    }
930    serviceRequestTraceListener.requestStart(serviceRequest, request);
931  }
932
933  public <T> void traceServiceRequestError(ServiceRequestBase<T> serviceRequest, HttpWebRequest request, Exception e) {
934    if (serviceRequestTraceListener == null) {
935      return;
936    }
937    serviceRequestTraceListener.requestError(serviceRequest, request, e);
938  }
939
940  public <T> void traceServiceRequestSuccess(ServiceRequestBase<T> serviceRequest, HttpWebRequest request) {
941    if (serviceRequestTraceListener == null) {
942      return;
943    }
944    serviceRequestTraceListener.requestFinish(serviceRequest, request);
945  }
946}