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    
024    package microsoft.exchange.webservices.data.autodiscover;
025    
026    import microsoft.exchange.webservices.data.autodiscover.configuration.ConfigurationSettingsBase;
027    import microsoft.exchange.webservices.data.autodiscover.configuration.outlook.OutlookConfigurationSettings;
028    import microsoft.exchange.webservices.data.autodiscover.enumeration.AutodiscoverEndpoints;
029    import microsoft.exchange.webservices.data.autodiscover.enumeration.AutodiscoverErrorCode;
030    import microsoft.exchange.webservices.data.autodiscover.exception.AutodiscoverLocalException;
031    import microsoft.exchange.webservices.data.autodiscover.exception.AutodiscoverRemoteException;
032    import microsoft.exchange.webservices.data.autodiscover.request.AutodiscoverRequest;
033    import microsoft.exchange.webservices.data.autodiscover.request.GetDomainSettingsRequest;
034    import microsoft.exchange.webservices.data.autodiscover.request.GetUserSettingsRequest;
035    import microsoft.exchange.webservices.data.autodiscover.response.GetDomainSettingsResponse;
036    import microsoft.exchange.webservices.data.autodiscover.response.GetDomainSettingsResponseCollection;
037    import microsoft.exchange.webservices.data.autodiscover.response.GetUserSettingsResponse;
038    import microsoft.exchange.webservices.data.autodiscover.response.GetUserSettingsResponseCollection;
039    import microsoft.exchange.webservices.data.core.EwsUtilities;
040    import microsoft.exchange.webservices.data.core.EwsXmlReader;
041    import microsoft.exchange.webservices.data.core.ExchangeServiceBase;
042    import microsoft.exchange.webservices.data.core.request.HttpClientWebRequest;
043    import microsoft.exchange.webservices.data.core.request.HttpWebRequest;
044    import microsoft.exchange.webservices.data.credential.WSSecurityBasedCredentials;
045    import microsoft.exchange.webservices.data.autodiscover.enumeration.DomainSettingName;
046    import microsoft.exchange.webservices.data.core.enumeration.misc.ExchangeVersion;
047    import microsoft.exchange.webservices.data.core.enumeration.misc.TraceFlags;
048    import microsoft.exchange.webservices.data.autodiscover.enumeration.UserSettingName;
049    import microsoft.exchange.webservices.data.core.exception.misc.ArgumentException;
050    import microsoft.exchange.webservices.data.core.exception.http.EWSHttpException;
051    import microsoft.exchange.webservices.data.core.exception.misc.FormatException;
052    import microsoft.exchange.webservices.data.autodiscover.exception.MaximumRedirectionHopsExceededException;
053    import microsoft.exchange.webservices.data.core.exception.service.local.ServiceLocalException;
054    import microsoft.exchange.webservices.data.core.exception.service.local.ServiceValidationException;
055    import microsoft.exchange.webservices.data.core.exception.service.local.ServiceVersionException;
056    import microsoft.exchange.webservices.data.misc.OutParam;
057    import microsoft.exchange.webservices.data.security.XmlNodeType;
058    
059    import javax.xml.stream.XMLStreamException;
060    
061    import java.io.ByteArrayInputStream;
062    import java.io.ByteArrayOutputStream;
063    import java.io.IOException;
064    import java.io.InputStream;
065    import java.io.OutputStream;
066    import java.io.PrintWriter;
067    import java.net.MalformedURLException;
068    import java.net.URI;
069    import java.net.URISyntaxException;
070    import java.util.ArrayList;
071    import java.util.Arrays;
072    import java.util.Collection;
073    import java.util.EnumSet;
074    import java.util.List;
075    
076    /**
077     * Represents a binding to the Exchange Autodiscover Service.
078     */
079    public class AutodiscoverService extends ExchangeServiceBase
080        implements IAutodiscoverRedirectionUrl, IFunctionDelegate {
081    
082      // region Private members
083      /**
084       * The domain.
085       */
086      private String domain;
087    
088      /**
089       * The is external.
090       */
091      private Boolean isExternal = true;
092    
093      /**
094       * The url.
095       */
096      private URI url;
097    
098      /**
099       * The redirection url validation callback.
100       */
101      private IAutodiscoverRedirectionUrl
102          redirectionUrlValidationCallback;
103    
104      /**
105       * The dns client.
106       */
107      private AutodiscoverDnsClient dnsClient;
108    
109      /**
110       * The dns server address.
111       */
112      private String dnsServerAddress;
113    
114      /**
115       * The enable scp lookup.
116       */
117      private boolean enableScpLookup = true;
118    
119      // Autodiscover legacy path
120      /**
121       * The Constant AutodiscoverLegacyPath.
122       */
123      private static final String AutodiscoverLegacyPath =
124          "/autodiscover/autodiscover.xml";
125    
126      // Autodiscover legacy HTTPS Url
127      /**
128       * The Constant AutodiscoverLegacyHttpsUrl.
129       */
130      private static final String AutodiscoverLegacyHttpsUrl = "https://%s" +
131          AutodiscoverLegacyPath;
132      // Autodiscover legacy HTTP Url
133      /**
134       * The Constant AutodiscoverLegacyHttpUrl.
135       */
136      private static final String AutodiscoverLegacyHttpUrl = "http://%s" +
137          AutodiscoverLegacyPath;
138      // Autodiscover SOAP HTTPS Url
139      /**
140       * The Constant AutodiscoverSoapHttpsUrl.
141       */
142      private static final String AutodiscoverSoapHttpsUrl =
143          "https://%s/autodiscover/autodiscover.svc";
144      // Autodiscover SOAP WS-Security HTTPS Url
145      /**
146       * The Constant AutodiscoverSoapWsSecurityHttpsUrl.
147       */
148      private static final String AutodiscoverSoapWsSecurityHttpsUrl =
149          AutodiscoverSoapHttpsUrl +
150              "/wssecurity";
151    
152      /**
153       * Autodiscover SOAP WS-Security symmetrickey HTTPS Url
154       */
155      private static final String AutodiscoverSoapWsSecuritySymmetricKeyHttpsUrl =
156          AutodiscoverSoapHttpsUrl + "/wssecurity/symmetrickey";
157    
158      /**
159       * Autodiscover SOAP WS-Security x509cert HTTPS Url
160       */
161      private static final String AutodiscoverSoapWsSecurityX509CertHttpsUrl =
162          AutodiscoverSoapHttpsUrl + "/wssecurity/x509cert";
163    
164    
165      // Autodiscover request namespace
166      /**
167       * The Constant AutodiscoverRequestNamespace.
168       */
169      private static final String AutodiscoverRequestNamespace =
170          "http://schemas.microsoft.com/exchange/autodiscover/" +
171              "outlook/requestschema/2006";
172      // Maximum number of Url (or address) redirections that will be followed by
173      // an Autodiscover call
174      /**
175       * The Constant AutodiscoverMaxRedirections.
176       */
177      protected static final int AutodiscoverMaxRedirections = 10;
178      // HTTP header indicating that SOAP Autodiscover service is enabled.
179      /**
180       * The Constant AutodiscoverSoapEnabledHeaderName.
181       */
182      private static final String AutodiscoverSoapEnabledHeaderName =
183          "X-SOAP-Enabled";
184      // HTTP header indicating that WS-Security Autodiscover service is enabled.
185      /**
186       * The Constant AutodiscoverWsSecurityEnabledHeaderName.
187       */
188      private static final String AutodiscoverWsSecurityEnabledHeaderName =
189          "X-WSSecurity-Enabled";
190    
191    
192      /**
193       * HTTP header indicating that WS-Security/SymmetricKey Autodiscover service is enabled.
194       */
195    
196      private static final String AutodiscoverWsSecuritySymmetricKeyEnabledHeaderName =
197          "X-WSSecurity-SymmetricKey-Enabled";
198    
199    
200      /**
201       * HTTP header indicating that WS-Security/X509Cert Autodiscover service is enabled.
202       */
203    
204      private static final String AutodiscoverWsSecurityX509CertEnabledHeaderName =
205          "X-WSSecurity-X509Cert-Enabled";
206    
207    
208      // Minimum request version for Autodiscover SOAP service.
209      /**
210       * The Constant MinimumRequestVersionForAutoDiscoverSoapService.
211       */
212      private static final ExchangeVersion
213          MinimumRequestVersionForAutoDiscoverSoapService =
214          ExchangeVersion.Exchange2010;
215    
216      /**
217       * Default implementation of AutodiscoverRedirectionUrlValidationCallback.
218       * Always returns true indicating that the URL can be used.
219       *
220       * @param redirectionUrl the redirection url
221       * @return Returns true.
222       * @throws AutodiscoverLocalException the autodiscover local exception
223       */
224      private boolean defaultAutodiscoverRedirectionUrlValidationCallback(
225          String redirectionUrl) throws AutodiscoverLocalException {
226        throw new AutodiscoverLocalException(String.format(
227            "Autodiscover blocked a potentially insecure redirection to %s. To allow Autodiscover to follow the "
228            + "redirection, use the AutodiscoverUrl(string, AutodiscoverRedirectionUrlValidationCallback) "
229            + "overload.", redirectionUrl));
230      }
231    
232      // Legacy Autodiscover
233    
234      /**
235       * Calls the Autodiscover service to get configuration settings at the
236       * specified URL.
237       *
238       * @param <TSettings>  the generic type
239       * @param cls          the cls
240       * @param emailAddress the email address
241       * @param url          the url
242       * @return The requested configuration settings. (TSettings The type of the
243       * settings to retrieve)
244       * @throws Exception the exception
245       */
246      private <TSettings extends ConfigurationSettingsBase>
247      TSettings getLegacyUserSettingsAtUrl(
248          Class<TSettings> cls, String emailAddress, URI url)
249          throws Exception {
250        this
251            .traceMessage(TraceFlags.AutodiscoverConfiguration,
252                          String.format("Trying to call Autodiscover for %s on %s.", emailAddress, url));
253    
254        TSettings settings = cls.newInstance();
255    
256        HttpWebRequest request = null;
257        try {
258          request = this.prepareHttpWebRequestForUrl(url);
259    
260          this.traceHttpRequestHeaders(
261              TraceFlags.AutodiscoverRequestHttpHeaders,
262              request);
263          // OutputStreamWriter out = new
264          // OutputStreamWriter(request.getOutputStream());
265          OutputStream urlOutStream = request.getOutputStream();
266    
267          // If tracing is enabled, we generate the request in-memory so that we
268          // can pass it along to the ITraceListener. Then we copy the stream to
269          // the request stream.
270          if (this.isTraceEnabledFor(TraceFlags.AutodiscoverRequest)) {
271            ByteArrayOutputStream memoryStream = new ByteArrayOutputStream();
272    
273            PrintWriter writer = new PrintWriter(memoryStream);
274            this.writeLegacyAutodiscoverRequest(emailAddress, settings, writer);
275            writer.flush();
276    
277            this.traceXml(TraceFlags.AutodiscoverRequest, memoryStream);
278            // out.write(memoryStream.toString());
279            // out.close();
280            memoryStream.writeTo(urlOutStream);
281            urlOutStream.flush();
282            urlOutStream.close();
283            memoryStream.close();
284          } else {
285            PrintWriter writer = new PrintWriter(urlOutStream);
286            this.writeLegacyAutodiscoverRequest(emailAddress, settings, writer);
287    
288          /*  Flush Start */
289            writer.flush();
290            urlOutStream.flush();
291            urlOutStream.close();
292          /* Flush End */
293          }
294          request.executeRequest();
295          request.getResponseCode();
296          URI redirectUrl;
297          OutParam<URI> outParam = new OutParam<URI>();
298          if (this.tryGetRedirectionResponse(request, outParam)) {
299            redirectUrl = outParam.getParam();
300            settings.makeRedirectionResponse(redirectUrl);
301            return settings;
302          }
303          InputStream serviceResponseStream = request.getInputStream();
304          // If tracing is enabled, we read the entire response into a
305          // MemoryStream so that we
306          // can pass it along to the ITraceListener. Then we parse the response
307          // from the
308          // MemoryStream.
309          if (this.isTraceEnabledFor(TraceFlags.AutodiscoverResponse)) {
310            ByteArrayOutputStream memoryStream = new ByteArrayOutputStream();
311    
312            while (true) {
313              int data = serviceResponseStream.read();
314              if (-1 == data) {
315                break;
316              } else {
317                memoryStream.write(data);
318              }
319            }
320            memoryStream.flush();
321    
322            this.traceResponse(request, memoryStream);
323            ByteArrayInputStream memoryStreamIn = new ByteArrayInputStream(
324                memoryStream.toByteArray());
325            EwsXmlReader reader = new EwsXmlReader(memoryStreamIn);
326            reader.read(new XmlNodeType(XmlNodeType.START_DOCUMENT));
327            settings.loadFromXml(reader);
328    
329          } else {
330            EwsXmlReader reader = new EwsXmlReader(serviceResponseStream);
331            reader.read(new XmlNodeType(XmlNodeType.START_DOCUMENT));
332            settings.loadFromXml(reader);
333          }
334    
335          serviceResponseStream.close();
336        } finally {
337          if (request != null) {
338            try {
339              request.close();
340            } catch (Exception e2) {
341              // Ignore exception while closing the request.
342            }
343          }
344        }
345    
346        return settings;
347      }
348    
349      /**
350       * Writes the autodiscover request.
351       *
352       * @param emailAddress the email address
353       * @param settings     the settings
354       * @param writer       the writer
355       * @throws java.io.IOException Signals that an I/O exception has occurred.
356       */
357      private void writeLegacyAutodiscoverRequest(String emailAddress,
358          ConfigurationSettingsBase settings, PrintWriter writer)
359          throws IOException {
360        writer.write(String.format("<Autodiscover xmlns=\"%s\">", AutodiscoverRequestNamespace));
361        writer.write("<Request>");
362        writer.write(String.format("<EMailAddress>%s</EMailAddress>",
363            emailAddress));
364        writer.write(
365            String.format("<AcceptableResponseSchema>%s</AcceptableResponseSchema>", settings.getNamespace()));
366        writer.write("</Request>");
367        writer.write("</Autodiscover>");
368      }
369    
370      /**
371       * Gets a redirection URL to an SSL-enabled Autodiscover service from the
372       * standard non-SSL Autodiscover URL.
373       *
374       * @param domainName the domain name
375       * @return A valid SSL-enabled redirection URL. (May be null)
376       * @throws EWSHttpException the EWS http exception
377       * @throws XMLStreamException the XML stream exception
378       * @throws IOException Signals that an I/O exception has occurred.
379       * @throws ServiceLocalException the service local exception
380       * @throws URISyntaxException the uRI syntax exception
381       */
382      private URI getRedirectUrl(String domainName)
383          throws EWSHttpException, XMLStreamException, IOException, ServiceLocalException, URISyntaxException {
384        String url = String.format(AutodiscoverLegacyHttpUrl, "autodiscover." + domainName);
385    
386        traceMessage(TraceFlags.AutodiscoverConfiguration,
387                     String.format("Trying to get Autodiscover redirection URL from %s.", url));
388    
389        HttpWebRequest request = null;
390    
391        try {
392          request = new HttpClientWebRequest(httpClient, httpContext);
393          request.setProxy(getWebProxy());
394    
395          try {
396            request.setUrl(URI.create(url).toURL());
397          } catch (MalformedURLException e) {
398            String strErr = String.format("Incorrect format : %s", url);
399            throw new ServiceLocalException(strErr);
400          }
401    
402          request.setRequestMethod("GET");
403          request.setAllowAutoRedirect(false);
404    
405          // Do NOT allow authentication as this single request will be made over plain HTTP.
406          request.setAllowAuthentication(false);
407    
408          prepareCredentials(request);
409    
410          request.prepareConnection();
411          try {
412            request.executeRequest();
413          } catch (IOException e) {
414            traceMessage(TraceFlags.AutodiscoverConfiguration, "No Autodiscover redirection URL was returned.");
415            return null;
416          }
417    
418          OutParam<URI> outParam = new OutParam<URI>();
419          if (tryGetRedirectionResponse(request, outParam)) {
420            return outParam.getParam();
421          }
422        } finally {
423          if (request != null) {
424            try {
425              request.close();
426            } catch (Exception e) {
427              // Ignore exception when closing the request
428            }
429          }
430        }
431    
432        traceMessage(TraceFlags.AutodiscoverConfiguration, "No Autodiscover redirection URL was returned.");
433        return null;
434      }
435    
436      /**
437       * Tries the get redirection response.
438       *
439       * @param request     the request
440       * @param redirectUrl the redirect URL
441       * @return true if a valid redirection URL was found
442       * @throws XMLStreamException the XML stream exception
443       * @throws IOException signals that an I/O exception has occurred.
444       * @throws EWSHttpException the EWS http exception
445       */
446      private boolean tryGetRedirectionResponse(HttpWebRequest request,
447          OutParam<URI> redirectUrl) throws XMLStreamException, IOException,
448          EWSHttpException {
449        // redirectUrl = null;
450        if (AutodiscoverRequest.isRedirectionResponse(request)) {
451          // Get the redirect location and verify that it's valid.
452          String location = request.getResponseHeaderField("Location");
453    
454          if (!(location == null || location.isEmpty())) {
455            try {
456              redirectUrl.setParam(new URI(location));
457    
458              // Check if URL is SSL and that the path matches.
459              if ((redirectUrl.getParam().getScheme().toLowerCase()
460                  .equals("https")) &&
461                  (redirectUrl.getParam().getPath()
462                      .equalsIgnoreCase(
463                          AutodiscoverLegacyPath))) {
464                this.traceMessage(TraceFlags.AutodiscoverConfiguration,
465                    String.format("Redirection URL found: '%s'",
466                        redirectUrl.getParam().toString()));
467    
468                return true;
469              }
470            } catch (URISyntaxException ex) {
471              this
472                  .traceMessage(
473                      TraceFlags.AutodiscoverConfiguration,
474                      String
475                          .format(
476                              "Invalid redirection URL " +
477                                  "was returned: '%s'",
478                              location));
479              return false;
480            }
481          }
482        }
483        return false;
484      }
485    
486      /**
487       * Calls the legacy Autodiscover service to retrieve configuration settings.
488       *
489       * @param <TSettings>  the generic type
490       * @param cls          the cls
491       * @param emailAddress The email address to retrieve configuration settings for.
492       * @return The requested configuration settings.
493       * @throws Exception the exception
494       */
495      protected <TSettings extends ConfigurationSettingsBase>
496      TSettings getLegacyUserSettings(
497          Class<TSettings> cls, String emailAddress) throws Exception {
498                    /*int currentHop = 1;
499                    return this.internalGetConfigurationSettings(cls, emailAddress,
500                                    currentHop);*/
501    
502        // If Url is specified, call service directly.
503        if (this.url != null) {
504          // this.Uri is intended for Autodiscover SOAP service, convert to Legacy endpoint URL.
505          URI autodiscoverUrl = new URI(this.url.toString() + AutodiscoverLegacyPath);
506          return this.getLegacyUserSettingsAtUrl(cls, emailAddress, autodiscoverUrl);
507        }
508    
509        // If Domain is specified, figure out the endpoint Url and call service.
510        else if (!(this.domain == null || this.domain.isEmpty())) {
511          URI autodiscoverUrl = new URI(String.format(AutodiscoverLegacyHttpsUrl, this.domain));
512          return this.getLegacyUserSettingsAtUrl(cls,
513              emailAddress, autodiscoverUrl);
514        } else {
515          // No Url or Domain specified, need to
516          //figure out which endpoint to use.
517          int currentHop = 1;
518          OutParam<Integer> outParam = new OutParam<Integer>();
519          outParam.setParam(currentHop);
520          List<String> redirectionEmailAddresses = new ArrayList<String>();
521          return this.internalGetLegacyUserSettings(
522              cls,
523              emailAddress,
524              redirectionEmailAddresses,
525              outParam);
526        }
527      }
528    
529      /**
530       * Calls the Autodiscover service to retrieve configuration settings.
531       *
532       * @param <TSettings>  the generic type
533       * @param cls          the cls
534       * @param emailAddress The email address to retrieve configuration settings for.
535       * @param currentHop   Current number of redirection urls/addresses attempted so far.
536       * @return The requested configuration settings.
537       * @throws Exception the exception
538       */
539      private <TSettings extends ConfigurationSettingsBase>
540      TSettings internalGetLegacyUserSettings(
541          Class<TSettings> cls,
542          String emailAddress,
543          List<String> redirectionEmailAddresses,
544          OutParam<Integer> currentHop)
545          throws Exception {
546        String domainName = EwsUtilities.domainFromEmailAddress(emailAddress);
547    
548        int scpUrlCount;
549        OutParam<Integer> outParamInt = new OutParam<Integer>();
550        List<URI> urls = this.getAutodiscoverServiceUrls(domainName, outParamInt);
551        scpUrlCount = outParamInt.getParam();
552        if (urls.size() == 0) {
553          throw new ServiceValidationException(
554              "This Autodiscover request requires that either the Domain or Url be specified.");
555        }
556    
557        // Assume caller is not inside the Intranet, regardless of whether SCP
558        // Urls
559        // were returned or not. SCP Urls are only relevent if one of them
560        // returns
561        // valid Autodiscover settings.
562        this.isExternal = true;
563    
564        int currentUrlIndex = 0;
565    
566        // Used to save exception for later reporting.
567        Exception delayedException = null;
568        TSettings settings;
569    
570        do {
571          URI autodiscoverUrl = urls.get(currentUrlIndex);
572          boolean isScpUrl = currentUrlIndex < scpUrlCount;
573    
574          try {
575            settings = this.getLegacyUserSettingsAtUrl(cls,
576                emailAddress, autodiscoverUrl);
577    
578            switch (settings.getResponseType()) {
579              case Success:
580                // Not external if Autodiscover endpoint found via SCP
581                // returned the settings.
582                if (isScpUrl) {
583                  this.isExternal = false;
584                }
585                this.url = autodiscoverUrl;
586                return settings;
587              case RedirectUrl:
588                if (currentHop.getParam() < AutodiscoverMaxRedirections) {
589                  currentHop.setParam(currentHop.getParam() + 1);
590    
591                  this
592                      .traceMessage(
593                          TraceFlags.AutodiscoverResponse,
594                          String
595                              .format(
596                                  "Autodiscover " +
597                                      "service " +
598                                      "returned " +
599                                      "redirection URL '%s'.",
600                                  settings
601                                      .getRedirectTarget()));
602    
603                  urls.add(currentUrlIndex, new URI(
604                      settings.getRedirectTarget()));
605    
606                  break;
607                } else {
608                  throw new MaximumRedirectionHopsExceededException();
609                }
610              case RedirectAddress:
611                if (currentHop.getParam() < AutodiscoverMaxRedirections) {
612                  currentHop.setParam(currentHop.getParam() + 1);
613    
614                  this
615                      .traceMessage(
616                          TraceFlags.AutodiscoverResponse,
617                          String
618                              .format(
619                                  "Autodiscover " +
620                                      "service " +
621                                      "returned " +
622                                      "redirection email " +
623                                      "address '%s'.",
624                                  settings
625                                      .getRedirectTarget()));
626                  // Bug E14:255576 If this email address was already tried, we may have a loop
627                  // in SCP lookups. Disable consideration of SCP records.
628                  this.disableScpLookupIfDuplicateRedirection(
629                      settings.getRedirectTarget(),
630                      redirectionEmailAddresses);
631    
632                  return this.internalGetLegacyUserSettings(cls,
633                      settings.getRedirectTarget(),
634                      redirectionEmailAddresses,
635                      currentHop);
636                } else {
637                  throw new MaximumRedirectionHopsExceededException();
638                }
639              case Error:
640                // Don't treat errors from an SCP-based Autodiscover service
641                // to be conclusive.
642                // We'll try the next one and record the error for later.
643                if (isScpUrl) {
644                  this
645                      .traceMessage(
646                          TraceFlags.AutodiscoverConfiguration,
647                          "Error returned by " +
648                              "Autodiscover service " +
649                              "found via SCP, treating " +
650                              "as inconclusive.");
651    
652                  delayedException = new AutodiscoverRemoteException(
653                      "The Autodiscover service returned an error.", settings.getError());
654                  currentUrlIndex++;
655                } else {
656                  throw new AutodiscoverRemoteException("The Autodiscover service returned an error.", settings.getError());
657                }
658                break;
659              default:
660                EwsUtilities
661                    .ewsAssert(false, "Autodiscover.GetConfigurationSettings",
662                               "An unexpected error has occured. This code path should never be reached.");
663                break;
664            }
665          } catch (XMLStreamException ex) {
666            this.traceMessage(TraceFlags.AutodiscoverConfiguration, String
667                .format("%s failed: XML parsing error: %s", url, ex
668                    .getMessage()));
669    
670            // The content at the URL wasn't a valid response, let's try the
671            // next.
672            currentUrlIndex++;
673          } catch (IOException ex) {
674            this.traceMessage(
675                TraceFlags.AutodiscoverConfiguration,
676                String.format("%s failed: I/O error: %s",
677                    url, ex.getMessage()));
678    
679            // The content at the URL wasn't a valid response, let's try the next.
680            currentUrlIndex++;
681          } catch (Exception ex) {
682            HttpWebRequest response = null;
683            URI redirectUrl;
684            OutParam<URI> outParam1 = new OutParam<URI>();
685            if ((response != null) &&
686                this.tryGetRedirectionResponse(response, outParam1)) {
687              redirectUrl = outParam1.getParam();
688              this.traceMessage(TraceFlags.AutodiscoverConfiguration,
689                  String.format(
690                      "Host returned a redirection to url %s",
691                      redirectUrl.toString()));
692    
693              currentHop.setParam(currentHop.getParam() + 1);
694              urls.add(currentUrlIndex, redirectUrl);
695            } else {
696              if (response != null) {
697                this.processHttpErrorResponse(response, ex);
698    
699              }
700    
701              this.traceMessage(TraceFlags.AutodiscoverConfiguration,
702                  String.format("%s failed: %s (%s)", url, ex
703                      .getClass().getName(), ex.getMessage()));
704    
705              // The url did not work, let's try the next.
706              currentUrlIndex++;
707            }
708          }
709        } while (currentUrlIndex < urls.size());
710    
711        // If we got this far it's because none of the URLs we tried have
712        // worked. As a next-to-last chance, use GetRedirectUrl to
713        // try to get a redirection URL using an HTTP GET on a non-SSL
714        // Autodiscover endpoint. If successful, use this
715        // redirection URL to get the configuration settings for this email
716        // address. (This will be a common scenario for
717        // DataCenter deployments).
718        URI redirectionUrl = this.getRedirectUrl(domainName);
719        OutParam<TSettings> outParam = new OutParam<TSettings>();
720        if ((redirectionUrl != null)
721            && this.tryLastChanceHostRedirection(cls, emailAddress,
722            redirectionUrl, outParam)) {
723          settings = outParam.getParam();
724          return settings;
725        } else {
726          // Getting a redirection URL from an HTTP GET failed too. As a last
727          // chance, try to get an appropriate SRV Record
728          // using DnsQuery. If successful, use this redirection URL to get
729          // the configuration settings for this email address.
730          redirectionUrl = this.getRedirectionUrlFromDnsSrvRecord(domainName);
731          if ((redirectionUrl != null)
732              && this.tryLastChanceHostRedirection(cls, emailAddress,
733              redirectionUrl, outParam)) {
734            return outParam.getParam();
735          }
736    
737          // If there was an earlier exception, throw it.
738          if (delayedException != null) {
739            throw delayedException;
740          }
741    
742          throw new AutodiscoverLocalException("The Autodiscover service couldn't be located.");
743        }
744      }
745    
746      /**
747       * Get an autodiscover SRV record in DNS and construct autodiscover URL.
748       *
749       * @param domainName Name of the domain.
750       * @return Autodiscover URL (may be null if lookup failed)
751       * @throws Exception the exception
752       */
753      protected URI getRedirectionUrlFromDnsSrvRecord(String domainName)
754          throws Exception {
755    
756        this
757            .traceMessage(
758                TraceFlags.AutodiscoverConfiguration,
759                String
760                    .format(
761                        "Trying to get Autodiscover host " +
762                            "from DNS SRV record for %s.",
763                        domainName));
764    
765        String hostname = this.dnsClient
766            .findAutodiscoverHostFromSrv(domainName);
767        if (!(hostname == null || hostname.isEmpty())) {
768          this
769              .traceMessage(TraceFlags.AutodiscoverConfiguration,
770                  String.format(
771                      "Autodiscover host %s was returned.",
772                      hostname));
773    
774          return new URI(String.format(AutodiscoverLegacyHttpsUrl,
775              hostname));
776        } else {
777          this.traceMessage(TraceFlags.AutodiscoverConfiguration,
778              "No matching Autodiscover DNS SRV records were found.");
779    
780          return null;
781        }
782      }
783    
784      /**
785       * Tries to get Autodiscover settings using redirection Url.
786       *
787       * @param <TSettings>    the generic type
788       * @param cls            the cls
789       * @param emailAddress   The email address.
790       * @param redirectionUrl Redirection Url.
791       * @param settings       The settings.
792       * @return boolean The boolean.
793       * @throws AutodiscoverLocalException  the autodiscover local exception
794       * @throws AutodiscoverRemoteException the autodiscover remote exception
795       * @throws Exception                   the exception
796       */
797      private <TSettings extends ConfigurationSettingsBase> boolean
798      tryLastChanceHostRedirection(
799          Class<TSettings> cls, String emailAddress, URI redirectionUrl,
800          OutParam<TSettings> settings) throws AutodiscoverLocalException,
801          AutodiscoverRemoteException, Exception {
802        List<String> redirectionEmailAddresses = new ArrayList<String>();
803    
804        // Bug 60274: Performing a non-SSL HTTP GET to retrieve a redirection
805        // URL is potentially unsafe. We allow the caller
806        // to specify delegate to be called to determine whether we are allowed
807        // to use the redirection URL.
808        if (this
809            .callRedirectionUrlValidationCallback(redirectionUrl.toString())) {
810          for (int currentHop = 0; currentHop < AutodiscoverService.AutodiscoverMaxRedirections; currentHop++) {
811            try {
812              settings.setParam(this.getLegacyUserSettingsAtUrl(cls,
813                  emailAddress, redirectionUrl));
814    
815              switch (settings.getParam().getResponseType()) {
816                case Success:
817                  return true;
818                case Error:
819                  throw new AutodiscoverRemoteException("The Autodiscover service returned an error.", settings.getParam()
820                      .getError());
821                case RedirectAddress:
822                  // If this email address was already tried,
823                  //we may have a loop
824                  // in SCP lookups. Disable consideration of SCP records.
825                  this.disableScpLookupIfDuplicateRedirection(settings.getParam().getRedirectTarget(),
826                      redirectionEmailAddresses);
827                  OutParam<Integer> outParam = new OutParam<Integer>();
828                  outParam.setParam(currentHop);
829                  settings.setParam(
830                      this.internalGetLegacyUserSettings(cls,
831                          emailAddress,
832                          redirectionEmailAddresses,
833                          outParam));
834                  currentHop = outParam.getParam();
835                  return true;
836                case RedirectUrl:
837                  try {
838                    redirectionUrl = new URI(settings.getParam()
839                        .getRedirectTarget());
840                  } catch (URISyntaxException ex) {
841                    this
842                        .traceMessage(
843                            TraceFlags.
844                                AutodiscoverConfiguration,
845                            String
846                                .format(
847                                    "Service " +
848                                        "returned " +
849                                        "invalid " +
850                                        "redirection " +
851                                        "URL %s",
852                                    settings
853                                        .getParam()
854                                        .getRedirectTarget()));
855                    return false;
856                  }
857                  break;
858                default:
859                  String failureMessage = String.format(
860                      "Autodiscover call at %s failed with error %s, target %s",
861                      redirectionUrl,
862                      settings.getParam().getResponseType(),
863                      settings.getParam().getRedirectTarget());
864                  this.traceMessage(
865                      TraceFlags.AutodiscoverConfiguration, failureMessage);
866    
867                  return false;
868              }
869            } catch (XMLStreamException ex) {
870              // If the response is malformed, it wasn't a valid
871              // Autodiscover endpoint.
872              this
873                  .traceMessage(TraceFlags.AutodiscoverConfiguration,
874                      String.format(
875                          "%s failed: XML parsing error: %s",
876                          redirectionUrl.toString(), ex
877                              .getMessage()));
878              return false;
879            } catch (IOException ex) {
880              this.traceMessage(
881                  TraceFlags.AutodiscoverConfiguration,
882                  String.format("%s failed: I/O error: %s",
883                      redirectionUrl, ex.getMessage()));
884              return false;
885            } catch (Exception ex) {
886              // TODO: BUG response is always null
887              HttpWebRequest response = null;
888              OutParam<URI> outParam = new OutParam<URI>();
889              if ((response != null)
890                  && this.tryGetRedirectionResponse(response,
891                  outParam)) {
892                redirectionUrl = outParam.getParam();
893                this
894                    .traceMessage(
895                        TraceFlags.AutodiscoverConfiguration,
896                        String
897                            .format(
898                                "Host returned a " +
899                                    "redirection" +
900                                    " to url %s",
901                                redirectionUrl));
902    
903              } else {
904                if (response != null) {
905                  this.processHttpErrorResponse(response, ex);
906                }
907    
908                this
909                    .traceMessage(
910                        TraceFlags.AutodiscoverConfiguration,
911                        String.format("%s failed: %s (%s)",
912                            url, ex.getClass().getName(),
913                            ex.getMessage()));
914                return false;
915              }
916            }
917          }
918        }
919    
920        return false;
921      }
922    
923      /**
924       * Disables SCP lookup if duplicate email address redirection.
925       *
926       * @param emailAddress              The email address to use.
927       * @param redirectionEmailAddresses The list of prior redirection email addresses.
928       */
929      private void disableScpLookupIfDuplicateRedirection(
930          String emailAddress,
931          List<String> redirectionEmailAddresses) {
932        // SMTP addresses are case-insensitive so entries are converted to lower-case.
933        emailAddress = emailAddress.toLowerCase();
934    
935        if (redirectionEmailAddresses.contains(emailAddress)) {
936          this.enableScpLookup = false;
937        } else {
938          redirectionEmailAddresses.add(emailAddress);
939        }
940      }
941    
942      /**
943       * Gets user settings from Autodiscover legacy endpoint.
944       *
945       * @param emailAddress      The email address to use.
946       * @param requestedSettings The requested settings.
947       * @return GetUserSettingsResponse
948       * @throws Exception on error
949       */
950      protected GetUserSettingsResponse internalGetLegacyUserSettings(
951          String emailAddress,
952          List<UserSettingName> requestedSettings) throws Exception {
953        // Cannot call legacy Autodiscover service with WindowsLive and other WSSecurity-based credential
954        if ((this.getCredentials() != null) && (this.getCredentials() instanceof WSSecurityBasedCredentials)) {
955          throw new AutodiscoverLocalException(
956              "WindowsLiveCredentials can't be used with this Autodiscover endpoint.");
957        }
958    
959        OutlookConfigurationSettings settings = this.getLegacyUserSettings(
960            OutlookConfigurationSettings.class,
961            emailAddress);
962    
963    
964    
965        return settings.convertSettings(emailAddress, requestedSettings);
966      }
967    
968      /**
969       * Calls the SOAP Autodiscover service
970       * for user settings for a single SMTP address.
971       *
972       * @param smtpAddress       SMTP address.
973       * @param requestedSettings The requested settings.
974       * @return GetUserSettingsResponse
975       * @throws Exception on error
976       */
977      protected GetUserSettingsResponse internalGetSoapUserSettings(
978          String smtpAddress,
979          List<UserSettingName> requestedSettings) throws Exception {
980        List<String> smtpAddresses = new ArrayList<String>();
981        smtpAddresses.add(smtpAddress);
982    
983        List<String> redirectionEmailAddresses = new ArrayList<String>();
984        redirectionEmailAddresses.add(smtpAddress.toLowerCase());
985    
986        for (int currentHop = 0; currentHop < AutodiscoverService.AutodiscoverMaxRedirections; currentHop++) {
987          GetUserSettingsResponse response = this.getUserSettings(smtpAddresses,
988              requestedSettings).getTResponseAtIndex(0);
989    
990          switch (response.getErrorCode()) {
991            case RedirectAddress:
992              this.traceMessage(
993                  TraceFlags.AutodiscoverResponse,
994                  String.format("Autodiscover service returned redirection email address '%s'.",
995                      response.getRedirectTarget()));
996    
997              smtpAddresses.clear();
998              smtpAddresses.add(response.getRedirectTarget().
999                  toLowerCase());
1000              this.url = null;
1001              this.domain = null;
1002    
1003              // If this email address was already tried,
1004              //we may have a loop
1005              // in SCP lookups. Disable consideration of SCP records.
1006              this.disableScpLookupIfDuplicateRedirection(response.getRedirectTarget(),
1007                  redirectionEmailAddresses);
1008              break;
1009    
1010            case RedirectUrl:
1011              this.traceMessage(
1012                  TraceFlags.AutodiscoverResponse,
1013                  String.format("Autodiscover service returned redirection URL '%s'.",
1014                      response.getRedirectTarget()));
1015    
1016              //this.url = new URI(response.getRedirectTarget());
1017              this.url = this.getCredentials().adjustUrl(new URI(response.getRedirectTarget()));
1018              break;
1019    
1020            case NoError:
1021            default:
1022              return response;
1023          }
1024        }
1025    
1026        throw new AutodiscoverLocalException("The Autodiscover service couldn't be located.");
1027      }
1028    
1029      /**
1030       * Gets the user settings using Autodiscover SOAP service.
1031       *
1032       * @param smtpAddresses The SMTP addresses of the users.
1033       * @param settings      The settings.
1034       * @return GetUserSettingsResponseCollection Object.
1035       * @throws Exception the exception
1036       */
1037      protected GetUserSettingsResponseCollection getUserSettings(
1038          final List<String> smtpAddresses, List<UserSettingName> settings)
1039          throws Exception {
1040        EwsUtilities.validateParam(smtpAddresses, "smtpAddresses");
1041        EwsUtilities.validateParam(settings, "settings");
1042    
1043        return this.getSettings(
1044            GetUserSettingsResponseCollection.class, UserSettingName.class,
1045            smtpAddresses, settings, null, this,
1046            new IFuncDelegate<String>() {
1047              public String func() throws FormatException {
1048                return EwsUtilities
1049                    .domainFromEmailAddress(smtpAddresses.get(0));
1050              }
1051            });
1052      }
1053    
1054      /**
1055       * Gets user or domain settings using Autodiscover SOAP service.
1056       *
1057       * @param <TGetSettingsResponseCollection> the generic type
1058       * @param <TSettingName>                   the generic type
1059       * @param cls                              the cls
1060       * @param cls1                             the cls1
1061       * @param identities                       Either the domains or the SMTP addresses of the users.
1062       * @param settings                         The settings.
1063       * @param requestedVersion                 Requested version of the Exchange service.
1064       * @param getSettingsMethod                The method to use.
1065       * @param getDomainMethod                  The method to calculate the domain value.
1066       * @return TGetSettingsResponse Collection.
1067       * @throws Exception the exception
1068       */
1069      private <TGetSettingsResponseCollection, TSettingName>
1070      TGetSettingsResponseCollection getSettings(
1071          Class<TGetSettingsResponseCollection> cls,
1072          Class<TSettingName> cls1,
1073          List<String> identities,
1074          List<TSettingName> settings,
1075          ExchangeVersion requestedVersion,
1076          IFunctionDelegate<List<String>, List<TSettingName>,
1077              TGetSettingsResponseCollection> getSettingsMethod,
1078          IFuncDelegate<String> getDomainMethod) throws Exception {
1079        TGetSettingsResponseCollection response;
1080    
1081        // Autodiscover service only exists in E14 or later.
1082        if (this.getRequestedServerVersion().compareTo(
1083            MinimumRequestVersionForAutoDiscoverSoapService) < 0) {
1084          throw new ServiceVersionException(String.format(
1085              "The Autodiscover service only supports %s or a later version.",
1086              MinimumRequestVersionForAutoDiscoverSoapService));
1087        }
1088    
1089        // If Url is specified, call service directly.
1090        if (this.url != null) {
1091          URI autodiscoverUrl = this.url;
1092          response = getSettingsMethod.func(identities, settings,
1093              requestedVersion, this.url);
1094          this.url = autodiscoverUrl;
1095          return response;
1096        }
1097        // If Domain is specified, determine endpoint Url and call service.
1098        else if (!(this.domain == null || this.domain.isEmpty())) {
1099          URI autodiscoverUrl = this.getAutodiscoverEndpointUrl(this.domain);
1100          response = getSettingsMethod.func(identities, settings,
1101              requestedVersion,
1102              autodiscoverUrl);
1103    
1104          // If we got this far, response was successful, set Url.
1105          this.url = autodiscoverUrl;
1106          return response;
1107        }
1108        // No Url or Domain specified, need to figure out which endpoint(s) to
1109        // try.
1110        else {
1111          // Assume caller is not inside the Intranet, regardless of whether
1112          // SCP Urls
1113          // were returned or not. SCP Urls are only relevent if one of them
1114          // returns
1115          // valid Autodiscover settings.
1116          this.isExternal = true;
1117    
1118          URI autodiscoverUrl;
1119    
1120          String domainName = getDomainMethod.func();
1121          int scpHostCount;
1122          OutParam<Integer> outParam = new OutParam<Integer>();
1123          List<String> hosts = this.getAutodiscoverServiceHosts(domainName,
1124              outParam);
1125          scpHostCount = outParam.getParam();
1126          if (hosts.size() == 0) {
1127            throw new ServiceValidationException(
1128                "This Autodiscover request requires that either the Domain or Url be specified.");
1129          }
1130    
1131          for (int currentHostIndex = 0; currentHostIndex < hosts.size(); currentHostIndex++) {
1132            String host = hosts.get(currentHostIndex);
1133            boolean isScpHost = currentHostIndex < scpHostCount;
1134            OutParam<URI> outParams = new OutParam<URI>();
1135            if (this.tryGetAutodiscoverEndpointUrl(host, outParams)) {
1136              autodiscoverUrl = outParams.getParam();
1137              response = getSettingsMethod.func(identities, settings,
1138                  requestedVersion,
1139                  autodiscoverUrl);
1140    
1141              // If we got this far, the response was successful, set Url.
1142              this.url = autodiscoverUrl;
1143    
1144              // Not external if Autodiscover endpoint found via SCP
1145              // returned the settings.
1146              if (isScpHost) {
1147                this.isExternal = false;
1148              }
1149    
1150              return response;
1151            }
1152          }
1153    
1154          // Next-to-last chance: try unauthenticated GET over HTTP to be
1155          // redirected to appropriate service endpoint.
1156          autodiscoverUrl = this.getRedirectUrl(domainName);
1157          OutParam<URI> outParamUrl = new OutParam<URI>();
1158          if ((autodiscoverUrl != null) &&
1159              this
1160                  .callRedirectionUrlValidationCallback(
1161                      autodiscoverUrl.toString()) &&
1162              this.tryGetAutodiscoverEndpointUrl(autodiscoverUrl
1163                  .getHost(), outParamUrl)) {
1164            autodiscoverUrl = outParamUrl.getParam();
1165            response = getSettingsMethod.func(identities, settings,
1166                requestedVersion,
1167                autodiscoverUrl);
1168    
1169            // If we got this far, the response was successful, set Url.
1170            this.url = autodiscoverUrl;
1171    
1172            return response;
1173          }
1174    
1175          // Last Chance: try to read autodiscover SRV Record from DNS. If we
1176          // find one, use
1177          // the hostname returned to construct an Autodiscover endpoint URL.
1178          autodiscoverUrl = this
1179              .getRedirectionUrlFromDnsSrvRecord(domainName);
1180          if ((autodiscoverUrl != null) &&
1181              this
1182                  .callRedirectionUrlValidationCallback(
1183                      autodiscoverUrl.toString()) &&
1184              this.tryGetAutodiscoverEndpointUrl(autodiscoverUrl
1185                  .getHost(), outParamUrl)) {
1186            autodiscoverUrl = outParamUrl.getParam();
1187            response = getSettingsMethod.func(identities, settings,
1188                requestedVersion,
1189                autodiscoverUrl);
1190    
1191            // If we got this far, the response was successful, set Url.
1192            this.url = autodiscoverUrl;
1193    
1194            return response;
1195          } else {
1196            throw new AutodiscoverLocalException("The Autodiscover service couldn't be located.");
1197          }
1198        }
1199      }
1200    
1201      /**
1202       * Gets settings for one or more users.
1203       *
1204       * @param smtpAddresses    The SMTP addresses of the users.
1205       * @param settings         The settings.
1206       * @param requestedVersion Requested version of the Exchange service.
1207       * @param autodiscoverUrl  The autodiscover URL.
1208       * @return GetUserSettingsResponse collection.
1209       * @throws ServiceLocalException the service local exception
1210       * @throws Exception             the exception
1211       */
1212      private GetUserSettingsResponseCollection internalGetUserSettings(
1213          List<String> smtpAddresses, List<UserSettingName> settings,
1214          ExchangeVersion requestedVersion,
1215          URI autodiscoverUrl) throws ServiceLocalException, Exception {
1216        // The response to GetUserSettings can be a redirection. Execute
1217        // GetUserSettings until we get back
1218        // a valid response or we've followed too many redirections.
1219        for (int currentHop = 0; currentHop < AutodiscoverService.AutodiscoverMaxRedirections; currentHop++) {
1220          GetUserSettingsRequest request = new GetUserSettingsRequest(this,
1221              autodiscoverUrl);
1222          request.setSmtpAddresses(smtpAddresses);
1223          request.setSettings(settings);
1224          GetUserSettingsResponseCollection response = request.execute();
1225    
1226          // Did we get redirected?
1227          if (response.getErrorCode() == AutodiscoverErrorCode.RedirectUrl
1228              && response.getRedirectionUrl() != null) {
1229            this.traceMessage(
1230                TraceFlags.AutodiscoverConfiguration,
1231                String.format("Request to %s returned redirection to %s",
1232                    autodiscoverUrl.toString(), response.getRedirectionUrl()));
1233    
1234            autodiscoverUrl = response.getRedirectionUrl();
1235          } else {
1236            return response;
1237          }
1238        }
1239    
1240        this.traceMessage(TraceFlags.AutodiscoverConfiguration, String.format(
1241            "Maximum number of redirection hops %d exceeded",
1242            AutodiscoverMaxRedirections));
1243    
1244        throw new MaximumRedirectionHopsExceededException();
1245      }
1246    
1247      /**
1248       * Gets the domain settings using Autodiscover SOAP service.
1249       *
1250       * @param domains          The domains.
1251       * @param settings         The settings.
1252       * @param requestedVersion Requested version of the Exchange service.
1253       * @return GetDomainSettingsResponse collection.
1254       * @throws Exception the exception
1255       */
1256      protected GetDomainSettingsResponseCollection getDomainSettings(
1257          final List<String> domains, List<DomainSettingName> settings,
1258          ExchangeVersion requestedVersion)
1259          throws Exception {
1260        EwsUtilities.validateParam(domains, "domains");
1261        EwsUtilities.validateParam(settings, "settings");
1262    
1263        return this.getSettings(
1264            GetDomainSettingsResponseCollection.class,
1265            DomainSettingName.class, domains, settings,
1266            requestedVersion, this,
1267            new IFuncDelegate<String>() {
1268              public String func() {
1269                return domains.get(0);
1270              }
1271            });
1272      }
1273    
1274      /**
1275       * Gets settings for one or more domains.
1276       *
1277       * @param domains          The domains.
1278       * @param settings         The settings.
1279       * @param requestedVersion Requested version of the Exchange service.
1280       * @param autodiscoverUrl  The autodiscover URL.
1281       * @return GetDomainSettingsResponse Collection.
1282       * @throws ServiceLocalException the service local exception
1283       * @throws Exception             the exception
1284       */
1285      private GetDomainSettingsResponseCollection internalGetDomainSettings(
1286          List<String> domains, List<DomainSettingName> settings,
1287          ExchangeVersion requestedVersion,
1288          URI autodiscoverUrl) throws ServiceLocalException, Exception {
1289        // The response to GetDomainSettings can be a redirection. Execute
1290        // GetDomainSettings until we get back
1291        // a valid response or we've followed too many redirections.
1292        for (int currentHop = 0; currentHop < AutodiscoverService.AutodiscoverMaxRedirections; currentHop++) {
1293          GetDomainSettingsRequest request = new GetDomainSettingsRequest(
1294              this, autodiscoverUrl);
1295          request.setDomains(domains);
1296          request.setSettings(settings);
1297          request.setRequestedVersion(requestedVersion);
1298          GetDomainSettingsResponseCollection response = request.execute();
1299    
1300          // Did we get redirected?
1301          if (response.getErrorCode() == AutodiscoverErrorCode.RedirectUrl
1302              && response.getRedirectionUrl() != null) {
1303            autodiscoverUrl = response.getRedirectionUrl();
1304          } else {
1305            return response;
1306          }
1307        }
1308    
1309        this.traceMessage(TraceFlags.AutodiscoverConfiguration, String.format(
1310            "Maximum number of redirection hops %d exceeded",
1311            AutodiscoverMaxRedirections));
1312    
1313        throw new MaximumRedirectionHopsExceededException();
1314      }
1315    
1316      /**
1317       * Gets the autodiscover endpoint URL.
1318       *
1319       * @param host The host.
1320       * @return URI The URI.
1321       * @throws Exception the exception
1322       */
1323      private URI getAutodiscoverEndpointUrl(String host) throws Exception {
1324        URI autodiscoverUrl = null;
1325        OutParam<URI> outParam = new OutParam<URI>();
1326        if (this.tryGetAutodiscoverEndpointUrl(host, outParam)) {
1327          return autodiscoverUrl;
1328        } else {
1329          throw new AutodiscoverLocalException(
1330              "No appropriate Autodiscover SOAP or WS-Security endpoint is available.");
1331        }
1332      }
1333    
1334      /**
1335       * Tries the get Autodiscover Service endpoint URL.
1336       *
1337       * @param host The host.
1338       * @param url  the url
1339       * @return boolean The boolean.
1340       * @throws Exception the exception
1341       */
1342      private boolean tryGetAutodiscoverEndpointUrl(String host,
1343          OutParam<URI> url)
1344          throws Exception {
1345        EnumSet<AutodiscoverEndpoints> endpoints;
1346        OutParam<EnumSet<AutodiscoverEndpoints>> outParam =
1347            new OutParam<EnumSet<AutodiscoverEndpoints>>();
1348        if (this.tryGetEnabledEndpointsForHost(host, outParam)) {
1349          endpoints = outParam.getParam();
1350          url
1351              .setParam(new URI(String.format(AutodiscoverSoapHttpsUrl,
1352                  host)));
1353    
1354          // Make sure that at least one of the non-legacy endpoints is
1355          // available.
1356          if ((!endpoints.contains(AutodiscoverEndpoints.Soap)) &&
1357              (!endpoints.contains(
1358                  AutodiscoverEndpoints.WsSecurity))
1359            // (endpoints .contains( AutodiscoverEndpoints.WSSecuritySymmetricKey) ) &&
1360            //(endpoints .contains( AutodiscoverEndpoints.WSSecurityX509Cert))
1361              ) {
1362            this
1363                .traceMessage(
1364                    TraceFlags.AutodiscoverConfiguration,
1365                    String
1366                        .format(
1367                            "No Autodiscover endpoints " +
1368                                "are available  for host %s",
1369                            host));
1370    
1371            return false;
1372          }
1373    
1374          // If we have WLID credential, make sure that we have a WS-Security
1375          // endpoint
1376                            /*
1377                            if (this.getCredentials() instanceof WindowsLiveCredentials) {
1378                                    if (endpoints.contains(AutodiscoverEndpoints.WsSecurity)) {
1379                                            this
1380                                                            .traceMessage(
1381                                                                            TraceFlags.AutodiscoverConfiguration,
1382                                                                            String
1383                                                                                            .format(
1384                                                                                                            "No Autodiscover " +
1385                                                                                                            "WS-Security " +
1386                                                                                                            "endpoint is available" +
1387                                                                                                            " for host %s",
1388                                                                                                            host));
1389    
1390                                            return false;
1391                                    } else {
1392                                            url.setParam(new URI(String.format(
1393                                                            AutodiscoverSoapWsSecurityHttpsUrl, host)));
1394                                    }
1395                            }
1396                               else if (this.getCredentials() instanceof PartnerTokenCredentials)
1397                    {
1398                        if (endpoints.contains( AutodiscoverEndpoints.WSSecuritySymmetricKey))
1399                        {
1400                            this.traceMessage(
1401                                TraceFlags.AutodiscoverConfiguration,
1402                                String.format("No Autodiscover WS-Security/SymmetricKey endpoint is available for host {0}", host));
1403    
1404                            return false;
1405                        }
1406                        else
1407                        {
1408                            url.setParam( new URI(String.format(AutodiscoverSoapWsSecuritySymmetricKeyHttpsUrl, host)));
1409                        }
1410                    }
1411                    else if (this.getCredentials()instanceof X509CertificateCredentials)
1412                    {
1413                        if ((endpoints.contains(AutodiscoverEndpoints.WSSecurityX509Cert))
1414                        {
1415                            this.traceMessage(
1416                                TraceFlags.AutodiscoverConfiguration,
1417                                String.format("No Autodiscover WS-Security/X509Cert endpoint is available for host {0}", host));
1418    
1419                            return false;
1420                        }
1421                        else
1422                        {
1423                            url.setParam( new URI(String.format(AutodiscoverSoapWsSecurityX509CertHttpsUrl, host)));
1424                        }
1425                    }
1426                                      */
1427          return true;
1428    
1429    
1430        } else {
1431          this
1432              .traceMessage(
1433                  TraceFlags.AutodiscoverConfiguration,
1434                  String
1435                      .format(
1436                          "No Autodiscover endpoints " +
1437                              "are available for host %s",
1438                          host));
1439    
1440          return false;
1441        }
1442      }
1443    
1444      /**
1445       * Gets the list of autodiscover service URLs.
1446       *
1447       * @param domainName   Domain name.
1448       * @param scpHostCount Count of hosts found via SCP lookup.
1449       * @return List of Autodiscover URLs.
1450       * @throws java.net.URISyntaxException the URI Syntax exception
1451       */
1452      protected List<URI> getAutodiscoverServiceUrls(String domainName,
1453          OutParam<Integer> scpHostCount) throws URISyntaxException {
1454        List<URI> urls;
1455    
1456        urls = new ArrayList<URI>();
1457    
1458        scpHostCount.setParam(urls.size());
1459    
1460        // As a fallback, add autodiscover URLs base on the domain name.
1461        urls.add(new URI(String.format(AutodiscoverLegacyHttpsUrl,
1462            domainName)));
1463        urls.add(new URI(String.format(AutodiscoverLegacyHttpsUrl,
1464            "autodiscover." + domainName)));
1465    
1466        return urls;
1467      }
1468    
1469      /**
1470       * Gets the list of autodiscover service hosts.
1471       *
1472       * @param domainName Domain name.
1473       * @param outParam   the out param
1474       * @return List of hosts.
1475       * @throws java.net.URISyntaxException the uRI syntax exception
1476       * @throws ClassNotFoundException      the class not found exception
1477       */
1478      protected List<String> getAutodiscoverServiceHosts(String domainName,
1479          OutParam<Integer> outParam) throws URISyntaxException,
1480          ClassNotFoundException {
1481    
1482        List<URI> urls = this.getAutodiscoverServiceUrls(domainName, outParam);
1483        List<String> lst = new ArrayList<String>();
1484        for (URI url : urls) {
1485          lst.add(url.getHost());
1486        }
1487        return lst;
1488      }
1489    
1490      /**
1491       * Gets the enabled autodiscover endpoints on a specific host.
1492       *
1493       * @param host      The host.
1494       * @param endpoints Endpoints found for host.
1495       * @return Flags indicating which endpoints are enabled.
1496       * @throws Exception the exception
1497       */
1498      private boolean tryGetEnabledEndpointsForHost(String host,
1499          OutParam<EnumSet<AutodiscoverEndpoints>> endpoints) throws Exception {
1500        this.traceMessage(TraceFlags.AutodiscoverConfiguration, String.format(
1501            "Determining which endpoints are enabled for host %s", host));
1502    
1503        // We may get redirected to another host. And therefore need to limit the number of redirections we'll
1504        // tolerate.
1505        for (int currentHop = 0; currentHop < AutodiscoverMaxRedirections; currentHop++) {
1506          URI autoDiscoverUrl = new URI(String.format(AutodiscoverLegacyHttpsUrl, host));
1507    
1508          endpoints.setParam(EnumSet.of(AutodiscoverEndpoints.None));
1509    
1510          HttpWebRequest request = null;
1511          try {
1512            request = new HttpClientWebRequest(httpClient, httpContext);
1513            request.setProxy(getWebProxy());
1514    
1515            try {
1516              request.setUrl(autoDiscoverUrl.toURL());
1517            } catch (MalformedURLException e) {
1518              String strErr = String.format("Incorrect format : %s", url);
1519              throw new ServiceLocalException(strErr);
1520            }
1521    
1522            request.setRequestMethod("GET");
1523            request.setAllowAutoRedirect(false);
1524            request.setPreAuthenticate(false);
1525            request.setUseDefaultCredentials(this.getUseDefaultCredentials());
1526    
1527            prepareCredentials(request);
1528    
1529            request.prepareConnection();
1530            try {
1531              request.executeRequest();
1532            } catch (IOException e) {
1533              return false;
1534            }
1535    
1536            OutParam<URI> outParam = new OutParam<URI>();
1537            if (this.tryGetRedirectionResponse(request, outParam)) {
1538              URI redirectUrl = outParam.getParam();
1539              this.traceMessage(TraceFlags.AutodiscoverConfiguration,
1540                  String.format("Host returned redirection to host '%s'", redirectUrl.getHost()));
1541    
1542              host = redirectUrl.getHost();
1543            } else {
1544              endpoints.setParam(this.getEndpointsFromHttpWebResponse(request));
1545    
1546              this.traceMessage(TraceFlags.AutodiscoverConfiguration,
1547                  String.format("Host returned enabled endpoint flags: %s", endpoints.getParam().toString()));
1548    
1549              return true;
1550            }
1551          } finally {
1552            if (request != null) {
1553              try {
1554                request.close();
1555              } catch (Exception e) {
1556                // Connection can't be closed. We'll ignore this...
1557              }
1558            }
1559          }
1560        }
1561    
1562        this.traceMessage(TraceFlags.AutodiscoverConfiguration,
1563            String.format("Maximum number of redirection hops %d exceeded", AutodiscoverMaxRedirections));
1564    
1565        throw new MaximumRedirectionHopsExceededException();
1566      }
1567    
1568      /**
1569       * Gets the endpoints from HTTP web response.
1570       *
1571       * @param request the request
1572       * @return Endpoints enabled.
1573       * @throws EWSHttpException the EWS http exception
1574       */
1575      private EnumSet<AutodiscoverEndpoints> getEndpointsFromHttpWebResponse(
1576          HttpWebRequest request) throws EWSHttpException {
1577        EnumSet<AutodiscoverEndpoints> endpoints = EnumSet
1578            .noneOf(AutodiscoverEndpoints.class);
1579        endpoints.add(AutodiscoverEndpoints.Legacy);
1580    
1581        if (!(request.getResponseHeaders().get(
1582            AutodiscoverSoapEnabledHeaderName) == null || request
1583            .getResponseHeaders().get(AutodiscoverSoapEnabledHeaderName)
1584            .isEmpty())) {
1585          endpoints.add(AutodiscoverEndpoints.Soap);
1586        }
1587        if (!(request.getResponseHeaders().get(
1588            AutodiscoverWsSecurityEnabledHeaderName) == null || request
1589            .getResponseHeaders().get(
1590                AutodiscoverWsSecurityEnabledHeaderName).isEmpty())) {
1591          endpoints.add(AutodiscoverEndpoints.WsSecurity);
1592        }
1593                    
1594                    /* if (! (request.getResponseHeaders().get(
1595                                     AutodiscoverWsSecuritySymmetricKeyEnabledHeaderName) !=null || request
1596                                     .getResponseHeaders().get(
1597                                     AutodiscoverWsSecuritySymmetricKeyEnabledHeaderName).isEmpty()))
1598             {
1599                 endpoints .add( AutodiscoverEndpoints.WSSecuritySymmetricKey);
1600             }
1601             if (!(request.getResponseHeaders().get(
1602                             AutodiscoverWsSecurityX509CertEnabledHeaderName)!=null ||
1603                             request.getResponseHeaders().get(
1604                                     AutodiscoverWsSecurityX509CertEnabledHeaderName).isEmpty()))
1605                             
1606             {
1607                 endpoints .add(AutodiscoverEndpoints.WSSecurityX509Cert);
1608             }*/
1609    
1610        return endpoints;
1611      }
1612    
1613      /**
1614       * Traces the response.
1615       *
1616       * @param request      the request
1617       * @param memoryStream the memory stream
1618       * @throws XMLStreamException the XML stream exception
1619       * @throws IOException signals that an I/O exception has occurred.
1620       * @throws EWSHttpException the EWS http exception
1621       */
1622      public void traceResponse(HttpWebRequest request, ByteArrayOutputStream memoryStream) throws XMLStreamException,
1623          IOException, EWSHttpException {
1624        this.processHttpResponseHeaders(
1625            TraceFlags.AutodiscoverResponseHttpHeaders, request);
1626        String contentType = request.getResponseContentType();
1627        if (!(contentType == null || contentType.isEmpty())) {
1628          contentType = contentType.toLowerCase();
1629          if (contentType.toLowerCase().startsWith("text/") ||
1630              contentType.toLowerCase().
1631                  startsWith("application/soap")) {
1632            this.traceXml(TraceFlags.AutodiscoverResponse, memoryStream);
1633          } else {
1634            this.traceMessage(TraceFlags.AutodiscoverResponse,
1635                "Non-textual response");
1636          }
1637        }
1638      }
1639    
1640      /**
1641       * Creates an HttpWebRequest instance and initializes it with the
1642       * appropriate parameters, based on the configuration of this service
1643       * object.
1644       *
1645       * @param url The URL that the HttpWebRequest should target
1646       * @return HttpWebRequest The HttpWebRequest
1647       * @throws ServiceLocalException       the service local exception
1648       * @throws java.net.URISyntaxException the uRI syntax exception
1649       */
1650      public HttpWebRequest prepareHttpWebRequestForUrl(URI url)
1651          throws ServiceLocalException, URISyntaxException {
1652        return this.prepareHttpWebRequestForUrl(url, false,
1653            // acceptGzipEncoding
1654            false); // allowAutoRedirect
1655      }
1656    
1657      /**
1658       * Calls the redirection URL validation callback. If the redirection URL
1659       * validation callback is null, use the default callback which does not
1660       * allow following any redirections.
1661       *
1662       * @param redirectionUrl The redirection URL.
1663       * @return True if redirection should be followed.
1664       * @throws AutodiscoverLocalException the autodiscover local exception
1665       */
1666      private boolean callRedirectionUrlValidationCallback(String redirectionUrl)
1667          throws AutodiscoverLocalException {
1668        IAutodiscoverRedirectionUrl callback =
1669            (this.redirectionUrlValidationCallback == null) ? this
1670                : this.redirectionUrlValidationCallback;
1671        return callback
1672            .autodiscoverRedirectionUrlValidationCallback(redirectionUrl);
1673      }
1674    
1675      /**
1676       * Processes an HTTP error response.
1677       *
1678       * @param httpWebResponse The HTTP web response.
1679       * @throws Exception the exception
1680       */
1681      @Override public void processHttpErrorResponse(HttpWebRequest httpWebResponse, Exception webException) throws Exception {
1682        this.internalProcessHttpErrorResponse(
1683            httpWebResponse,
1684            webException,
1685            TraceFlags.AutodiscoverResponseHttpHeaders,
1686            TraceFlags.AutodiscoverResponse);
1687      }
1688    
1689      /*
1690       * (non-Javadoc)
1691       *
1692       * @see microsoft.exchange.webservices.AutodiscoverRedirectionUrlInterface#
1693       * autodiscoverRedirectionUrlValidationCallback(java.lang.String)
1694       */
1695      public boolean autodiscoverRedirectionUrlValidationCallback(
1696          String redirectionUrl) throws AutodiscoverLocalException {
1697        return defaultAutodiscoverRedirectionUrlValidationCallback(
1698            redirectionUrl);
1699      }
1700    
1701      /**
1702       * Initializes a new instance of the "AutodiscoverService" class.
1703       *
1704       * @throws ArgumentException on validation error
1705       */
1706      public AutodiscoverService() throws ArgumentException {
1707        this(ExchangeVersion.Exchange2010);
1708      }
1709    
1710      /**
1711       * Initializes a new instance of the "AutodiscoverService" class.
1712       *
1713       * @param requestedServerVersion The requested server version
1714       * @throws ArgumentException on validation error
1715       */
1716      public AutodiscoverService(ExchangeVersion requestedServerVersion)
1717          throws ArgumentException {
1718        this(null, null, requestedServerVersion);
1719      }
1720    
1721      /**
1722       * Initializes a new instance of the "AutodiscoverService" class.
1723       *
1724       * @param domain The domain that will be used to determine the URL of the service
1725       * @throws ArgumentException on validation error
1726       */
1727      public AutodiscoverService(String domain) throws ArgumentException {
1728        this(null, domain);
1729      }
1730    
1731      /**
1732       * Initializes a new instance of the "AutodiscoverService" class.
1733       *
1734       * @param domain                 The domain that will be used to determine the URL of the service
1735       * @param requestedServerVersion The requested server version
1736       * @throws ArgumentException on validation error
1737       */
1738      public AutodiscoverService(String domain,
1739          ExchangeVersion requestedServerVersion) throws ArgumentException {
1740        this(null, domain, requestedServerVersion);
1741      }
1742    
1743      /**
1744       * Initializes a new instance of the "AutodiscoverService" class.
1745       *
1746       * @param url The URL of the service
1747       * @throws ArgumentException on validation error
1748       */
1749      public AutodiscoverService(URI url) throws ArgumentException {
1750        this(url, url.getHost());
1751      }
1752    
1753      /**
1754       * Initializes a new instance of the "AutodiscoverService" class.
1755       *
1756       * @param url                    The URL of the service
1757       * @param requestedServerVersion The requested server version
1758       * @throws ArgumentException on validation error
1759       */
1760      public AutodiscoverService(URI url,
1761          ExchangeVersion requestedServerVersion) throws ArgumentException {
1762        this(url, url.getHost(), requestedServerVersion);
1763      }
1764    
1765      /**
1766       * Initializes a new instance of the "AutodiscoverService" class.
1767       *
1768       * @param url    The URL of the service
1769       * @param domain The domain that will be used to determine the URL of the service
1770       * @throws ArgumentException on validation error
1771       */
1772      public AutodiscoverService(URI url, String domain)
1773          throws ArgumentException {
1774        super();
1775        EwsUtilities.validateDomainNameAllowNull(domain, "domain");
1776        this.url = url;
1777        this.domain = domain;
1778        this.dnsClient = new AutodiscoverDnsClient(this);
1779      }
1780    
1781      /**
1782       * Initializes a new instance of the "AutodiscoverService" class.
1783       *
1784       * @param url                    The URL of the service.
1785       * @param domain                 The domain that will be used to determine the URL of the
1786       *                               service.
1787       * @param requestedServerVersion The requested server version.
1788       * @throws ArgumentException on validation error
1789       */
1790      public AutodiscoverService(URI url, String domain,
1791          ExchangeVersion requestedServerVersion) throws ArgumentException {
1792        super(requestedServerVersion);
1793        EwsUtilities.validateDomainNameAllowNull(domain, "domain");
1794    
1795        this.url = url;
1796        this.domain = domain;
1797        this.dnsClient = new AutodiscoverDnsClient(this);
1798      }
1799    
1800      /**
1801       * Initializes a new instance of the AutodiscoverService class.
1802       *
1803       * @param service                The other service.
1804       * @param requestedServerVersion The requested server version.
1805       */
1806      public AutodiscoverService(ExchangeServiceBase service,
1807          ExchangeVersion requestedServerVersion) {
1808        super(service, requestedServerVersion);
1809        this.dnsClient = new AutodiscoverDnsClient(this);
1810      }
1811    
1812      /**
1813       * Initializes a new instance of the "AutodiscoverService" class.
1814       *
1815       * @param service The service.
1816       */
1817      public AutodiscoverService(ExchangeServiceBase service) {
1818        super(service, service.getRequestedServerVersion());
1819      }
1820    
1821      /**
1822       * Retrieves the specified settings for single SMTP address.
1823       * <p>This method will run the entire Autodiscover "discovery"
1824       * algorithm and will follow address and URL redirections.</p>
1825    
1826       * @param userSmtpAddress  The SMTP addresses of the user.
1827       * @param userSettingNames The user setting names.
1828       * @return A UserResponse object containing the requested settings for the
1829       * specified user.
1830       * @throws Exception on error
1831       */
1832      public GetUserSettingsResponse getUserSettings(String userSmtpAddress,
1833          UserSettingName... userSettingNames) throws Exception {
1834        List<UserSettingName> requestedSettings = new ArrayList<UserSettingName>();
1835        requestedSettings.addAll(Arrays.asList(userSettingNames));
1836    
1837        if (userSmtpAddress == null || userSmtpAddress.isEmpty()) {
1838          throw new ServiceValidationException("A valid SMTP address must be specified.");
1839        }
1840    
1841        if (requestedSettings.size() == 0) {
1842          throw new ServiceValidationException("At least one setting must be requested.");
1843        }
1844    
1845        if (this.getRequestedServerVersion().compareTo(MinimumRequestVersionForAutoDiscoverSoapService) < 0) {
1846          return this.internalGetLegacyUserSettings(userSmtpAddress,
1847              requestedSettings);
1848        } else {
1849          return this.internalGetSoapUserSettings(userSmtpAddress,
1850              requestedSettings);
1851        }
1852    
1853      }
1854    
1855      /**
1856       * Retrieves the specified settings for a set of users.
1857       *
1858       * @param userSmtpAddresses the user smtp addresses
1859       * @param userSettingNames  The user setting names.
1860       * @return A GetUserSettingsResponseCollection object containing the
1861       * response for each individual user.
1862       * @throws Exception the exception
1863       */
1864      public GetUserSettingsResponseCollection getUsersSettings(
1865          Iterable<String> userSmtpAddresses,
1866          UserSettingName... userSettingNames) throws Exception {
1867        if (this.getRequestedServerVersion().compareTo(MinimumRequestVersionForAutoDiscoverSoapService) < 0) {
1868          throw new ServiceVersionException(
1869              String.format("The Autodiscover service only supports %s or a later version.",
1870                  MinimumRequestVersionForAutoDiscoverSoapService));
1871        }
1872        List<String> smtpAddresses = new ArrayList<String>();
1873        smtpAddresses.addAll((Collection<? extends String>) userSmtpAddresses);
1874        List<UserSettingName> settings = new ArrayList<UserSettingName>();
1875        settings.addAll(Arrays.asList(userSettingNames));
1876        return this.getUserSettings(smtpAddresses, settings);
1877      }
1878    
1879      /**
1880       * Retrieves the specified settings for a domain.
1881       *
1882       * @param domain             The domain.
1883       * @param requestedVersion   Requested version of the Exchange service.
1884       * @param domainSettingNames The domain setting names.
1885       * @return A DomainResponse object containing the requested settings for the
1886       * specified domain.
1887       * @throws Exception the exception
1888       */
1889      public GetDomainSettingsResponse getDomainSettings(String domain,
1890          ExchangeVersion requestedVersion,
1891          DomainSettingName... domainSettingNames) throws Exception {
1892        List<String> domains = new ArrayList<String>(1);
1893        domains.add(domain);
1894    
1895        List<DomainSettingName> settings = new ArrayList<DomainSettingName>();
1896        settings.addAll(Arrays.asList(domainSettingNames));
1897    
1898        return this.getDomainSettings(domains, settings, requestedVersion).
1899            getTResponseAtIndex(0);
1900      }
1901    
1902      /**
1903       * Retrieves the specified settings for a set of domains.
1904       *
1905       * @param domains            the domains
1906       * @param requestedVersion   Requested version of the Exchange service.
1907       * @param domainSettingNames The domain setting names.
1908       * @return A GetDomainSettingsResponseCollection object containing the
1909       * response for each individual domain.
1910       * @throws Exception the exception
1911       */
1912      public GetDomainSettingsResponseCollection getDomainSettings(
1913          Iterable<String> domains, ExchangeVersion requestedVersion,
1914          DomainSettingName... domainSettingNames)
1915          throws Exception {
1916        List<DomainSettingName> settings = new ArrayList<DomainSettingName>();
1917        settings.addAll(Arrays.asList(domainSettingNames));
1918    
1919        List<String> domainslst = new ArrayList<String>();
1920        domainslst.addAll((Collection<? extends String>) domains);
1921    
1922        return this.getDomainSettings(domainslst, settings, requestedVersion);
1923      }
1924    
1925      /**
1926       * Gets the domain this service is bound to. When this property is
1927       * set, the domain name is used to automatically determine the Autodiscover service URL.
1928       *
1929       * @return the domain
1930       */
1931      public String getDomain() {
1932        return this.domain;
1933      }
1934    
1935      /**
1936       * Sets the domain this service is bound to. When this property is
1937       * set, the domain
1938       * name is used to automatically determine the Autodiscover service URL.
1939       *
1940       * @param value the new domain
1941       * @throws ArgumentException on validation error
1942       */
1943      public void setDomain(String value) throws ArgumentException {
1944        EwsUtilities.validateDomainNameAllowNull(value, "Domain");
1945    
1946        // If Domain property is set to non-null value, Url property is nulled.
1947        if (value != null) {
1948          this.url = null;
1949        }
1950        this.domain = value;
1951      }
1952    
1953      /**
1954       * Gets the url this service is bound to.
1955       *
1956       * @return the url
1957       */
1958      public URI getUrl() {
1959        return this.url;
1960      }
1961    
1962      /**
1963       * Sets the url this service is bound to.
1964       *
1965       * @param value the new url
1966       */
1967      public void setUrl(URI value) {
1968        // If Url property is set to non-null value, Domain property is set to
1969        // host portion of Url.
1970        if (value != null) {
1971          this.domain = value.getHost();
1972        }
1973        this.url = value;
1974      }
1975    
1976      public Boolean isExternal() {
1977        return this.isExternal;
1978      }
1979    
1980      protected void setIsExternal(Boolean value) {
1981        this.isExternal = value;
1982      }
1983    
1984    
1985      /**
1986       * Gets the redirection url validation callback.
1987       *
1988       * @return the redirection url validation callback
1989       */
1990      public IAutodiscoverRedirectionUrl
1991      getRedirectionUrlValidationCallback() {
1992        return this.redirectionUrlValidationCallback;
1993      }
1994    
1995      /**
1996       * Sets the redirection url validation callback.
1997       *
1998       * @param value the new redirection url validation callback
1999       */
2000      public void setRedirectionUrlValidationCallback(
2001          IAutodiscoverRedirectionUrl value) {
2002        this.redirectionUrlValidationCallback = value;
2003      }
2004    
2005      /**
2006       * Gets the dns server address.
2007       *
2008       * @return the dns server address
2009       */
2010      protected String getDnsServerAddress() {
2011        return this.dnsServerAddress;
2012      }
2013    
2014      /**
2015       * Sets the dns server address.
2016       *
2017       * @param value the new dns server address
2018       */
2019      protected void setDnsServerAddress(String value) {
2020        this.dnsServerAddress = value;
2021      }
2022    
2023      /**
2024       * Gets a value indicating whether the AutodiscoverService should
2025       * perform SCP (ServiceConnectionPoint) record lookup when determining
2026       * the Autodiscover service URL.
2027       *
2028       * @return the enable scp lookup
2029       */
2030      public boolean getEnableScpLookup() {
2031        return this.enableScpLookup;
2032      }
2033    
2034      /**
2035       * Sets the enable scp lookup.
2036       *
2037       * @param value the new enable scp lookup
2038       */
2039      public void setEnableScpLookup(boolean value) {
2040        this.enableScpLookup = value;
2041      }
2042    
2043      /*
2044       * (non-Javadoc)
2045       *
2046       * @see
2047       * microsoft.exchange.webservices.FuncDelegateInterface#func(java.util.List,
2048       * java.util.List, java.net.URI)
2049       */
2050      @Override
2051      public Object func(List arg1, List arg2, ExchangeVersion arg3, URI arg4)
2052          throws ServiceLocalException, Exception {
2053        if (arg2.get(0).getClass().equals(DomainSettingName.class)) {
2054          return internalGetDomainSettings(arg1, arg2, arg3, arg4);
2055        } else if (arg2.get(0).getClass().equals(UserSettingName.class)) {
2056          return internalGetUserSettings(arg1, arg2, arg3, arg4);
2057        } else {
2058          return null;
2059        }
2060      }
2061    
2062    }