microsoft.exchange.webservices.data.autodiscover.AutodiscoverService.java Source code

Java tutorial

Introduction

Here is the source code for microsoft.exchange.webservices.data.autodiscover.AutodiscoverService.java

Source

/*
 * The MIT License
 * Copyright (c) 2012 Microsoft Corporation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package microsoft.exchange.webservices.data.autodiscover;

import microsoft.exchange.webservices.data.autodiscover.exception.AutodiscoverLocalException;
import microsoft.exchange.webservices.data.exception.ArgumentException;
import microsoft.exchange.webservices.data.autodiscover.enumeration.AutodiscoverEndpoints;
import microsoft.exchange.webservices.data.autodiscover.enumeration.AutodiscoverErrorCode;
import microsoft.exchange.webservices.data.autodiscover.exception.AutodiscoverRemoteException;
import microsoft.exchange.webservices.data.autodiscover.request.AutodiscoverRequest;
import microsoft.exchange.webservices.data.autodiscover.configuration.ConfigurationSettingsBase;
import microsoft.exchange.webservices.data.enumeration.DomainSettingName;
import microsoft.exchange.webservices.data.exception.EWSHttpException;
import microsoft.exchange.webservices.data.core.EwsUtilities;
import microsoft.exchange.webservices.data.core.EwsXmlReader;
import microsoft.exchange.webservices.data.core.ExchangeServiceBase;
import microsoft.exchange.webservices.data.enumeration.ExchangeVersion;
import microsoft.exchange.webservices.data.exception.FormatException;
import microsoft.exchange.webservices.data.autodiscover.request.GetDomainSettingsRequest;
import microsoft.exchange.webservices.data.autodiscover.response.GetDomainSettingsResponse;
import microsoft.exchange.webservices.data.autodiscover.response.GetDomainSettingsResponseCollection;
import microsoft.exchange.webservices.data.autodiscover.request.GetUserSettingsRequest;
import microsoft.exchange.webservices.data.autodiscover.response.GetUserSettingsResponse;
import microsoft.exchange.webservices.data.autodiscover.response.GetUserSettingsResponseCollection;
import microsoft.exchange.webservices.data.core.request.HttpClientWebRequest;
import microsoft.exchange.webservices.data.core.request.HttpWebRequest;
import microsoft.exchange.webservices.data.interfaces.IAutodiscoverRedirectionUrl;
import microsoft.exchange.webservices.data.interfaces.IFuncDelegate;
import microsoft.exchange.webservices.data.interfaces.IFunctionDelegate;
import microsoft.exchange.webservices.data.exception.MaximumRedirectionHopsExceededException;
import microsoft.exchange.webservices.data.misc.OutParam;
import microsoft.exchange.webservices.data.autodiscover.configuration.outlook.OutlookConfigurationSettings;
import microsoft.exchange.webservices.data.exception.ServiceLocalException;
import microsoft.exchange.webservices.data.exception.ServiceValidationException;
import microsoft.exchange.webservices.data.exception.ServiceVersionException;
import microsoft.exchange.webservices.data.enumeration.TraceFlags;
import microsoft.exchange.webservices.data.enumeration.UserSettingName;
import microsoft.exchange.webservices.data.credential.WSSecurityBasedCredentials;
import microsoft.exchange.webservices.data.security.XmlNodeType;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.xml.stream.XMLStreamException;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;

/**
 * Represents a binding to the Exchange Autodiscover Service.
 */
public final class AutodiscoverService extends ExchangeServiceBase
        implements IAutodiscoverRedirectionUrl, IFunctionDelegate {

    private static final Log log = LogFactory.getLog(AutodiscoverService.class);

    // region Private members
    /**
     * The domain.
     */
    private String domain;

    /**
     * The is external.
     */
    private Boolean isExternal = true;

    /**
     * The url.
     */
    private URI url;

    /**
     * The redirection url validation callback.
     */
    private IAutodiscoverRedirectionUrl redirectionUrlValidationCallback;

    /**
     * The dns client.
     */
    private AutodiscoverDnsClient dnsClient;

    /**
     * The dns server address.
     */
    private String dnsServerAddress;

    /**
     * The enable scp lookup.
     */
    private boolean enableScpLookup = true;

    // Autodiscover legacy path
    /**
     * The Constant AutodiscoverLegacyPath.
     */
    private static final String AutodiscoverLegacyPath = "/autodiscover/autodiscover.xml";

    // Autodiscover legacy HTTPS Url
    /**
     * The Constant AutodiscoverLegacyHttpsUrl.
     */
    private static final String AutodiscoverLegacyHttpsUrl = "https://%s" + AutodiscoverLegacyPath;
    // Autodiscover legacy HTTP Url
    /**
     * The Constant AutodiscoverLegacyHttpUrl.
     */
    private static final String AutodiscoverLegacyHttpUrl = "http://%s" + AutodiscoverLegacyPath;
    // Autodiscover SOAP HTTPS Url
    /**
     * The Constant AutodiscoverSoapHttpsUrl.
     */
    private static final String AutodiscoverSoapHttpsUrl = "https://%s/autodiscover/autodiscover.svc";
    // Autodiscover SOAP WS-Security HTTPS Url
    /**
     * The Constant AutodiscoverSoapWsSecurityHttpsUrl.
     */
    private static final String AutodiscoverSoapWsSecurityHttpsUrl = AutodiscoverSoapHttpsUrl + "/wssecurity";

    /**
     * Autodiscover SOAP WS-Security symmetrickey HTTPS Url
     */
    private static final String AutodiscoverSoapWsSecuritySymmetricKeyHttpsUrl = AutodiscoverSoapHttpsUrl
            + "/wssecurity/symmetrickey";

    /**
     * Autodiscover SOAP WS-Security x509cert HTTPS Url
     */
    private static final String AutodiscoverSoapWsSecurityX509CertHttpsUrl = AutodiscoverSoapHttpsUrl
            + "/wssecurity/x509cert";

    // Autodiscover request namespace
    /**
     * The Constant AutodiscoverRequestNamespace.
     */
    private static final String AutodiscoverRequestNamespace = "http://schemas.microsoft.com/exchange/autodiscover/"
            + "outlook/requestschema/2006";
    // Maximum number of Url (or address) redirections that will be followed by
    // an Autodiscover call
    /**
     * The Constant AutodiscoverMaxRedirections.
     */
    protected static final int AutodiscoverMaxRedirections = 10;
    // HTTP header indicating that SOAP Autodiscover service is enabled.
    /**
     * The Constant AutodiscoverSoapEnabledHeaderName.
     */
    private static final String AutodiscoverSoapEnabledHeaderName = "X-SOAP-Enabled";
    // HTTP header indicating that WS-Security Autodiscover service is enabled.
    /**
     * The Constant AutodiscoverWsSecurityEnabledHeaderName.
     */
    private static final String AutodiscoverWsSecurityEnabledHeaderName = "X-WSSecurity-Enabled";

    /**
     * HTTP header indicating that WS-Security/SymmetricKey Autodiscover service is enabled.
     */

    private static final String AutodiscoverWsSecuritySymmetricKeyEnabledHeaderName = "X-WSSecurity-SymmetricKey-Enabled";

    /**
     * HTTP header indicating that WS-Security/X509Cert Autodiscover service is enabled.
     */

    private static final String AutodiscoverWsSecurityX509CertEnabledHeaderName = "X-WSSecurity-X509Cert-Enabled";

    // Minimum request version for Autodiscover SOAP service.
    /**
     * The Constant MinimumRequestVersionForAutoDiscoverSoapService.
     */
    private static final ExchangeVersion MinimumRequestVersionForAutoDiscoverSoapService = ExchangeVersion.Exchange2010;

    /**
     * Default implementation of AutodiscoverRedirectionUrlValidationCallback.
     * Always returns true indicating that the URL can be used.
     *
     * @param redirectionUrl the redirection url
     * @return Returns true.
     * @throws AutodiscoverLocalException the autodiscover local exception
     */
    private boolean defaultAutodiscoverRedirectionUrlValidationCallback(String redirectionUrl)
            throws AutodiscoverLocalException {
        throw new AutodiscoverLocalException(String.format(
                "Autodiscover blocked a potentially insecure redirection to %s. To allow Autodiscover to follow the "
                        + "redirection, use the AutodiscoverUrl(string, AutodiscoverRedirectionUrlValidationCallback) "
                        + "overload.",
                redirectionUrl));
    }

    // Legacy Autodiscover

    /**
     * Calls the Autodiscover service to get configuration settings at the
     * specified URL.
     *
     * @param <TSettings>  the generic type
     * @param cls          the cls
     * @param emailAddress the email address
     * @param url          the url
     * @return The requested configuration settings. (TSettings The type of the
     * settings to retrieve)
     * @throws Exception the exception
     */
    private <TSettings extends ConfigurationSettingsBase> TSettings getLegacyUserSettingsAtUrl(Class<TSettings> cls,
            String emailAddress, URI url) throws Exception {
        this.traceMessage(TraceFlags.AutodiscoverConfiguration,
                String.format("Trying to call Autodiscover for %s on %s.", emailAddress, url));

        TSettings settings = cls.newInstance();

        HttpWebRequest request = null;
        try {
            request = this.prepareHttpWebRequestForUrl(url);

            this.traceHttpRequestHeaders(TraceFlags.AutodiscoverRequestHttpHeaders, request);
            // OutputStreamWriter out = new
            // OutputStreamWriter(request.getOutputStream());
            OutputStream urlOutStream = request.getOutputStream();

            // If tracing is enabled, we generate the request in-memory so that we
            // can pass it along to the ITraceListener. Then we copy the stream to
            // the request stream.
            if (this.isTraceEnabledFor(TraceFlags.AutodiscoverRequest)) {
                ByteArrayOutputStream memoryStream = new ByteArrayOutputStream();

                PrintWriter writer = new PrintWriter(memoryStream);
                this.writeLegacyAutodiscoverRequest(emailAddress, settings, writer);
                writer.flush();

                this.traceXml(TraceFlags.AutodiscoverRequest, memoryStream);
                // out.write(memoryStream.toString());
                // out.close();
                memoryStream.writeTo(urlOutStream);
                urlOutStream.flush();
                urlOutStream.close();
                memoryStream.close();
            } else {
                PrintWriter writer = new PrintWriter(urlOutStream);
                this.writeLegacyAutodiscoverRequest(emailAddress, settings, writer);

                /*  Flush Start */
                writer.flush();
                urlOutStream.flush();
                urlOutStream.close();
                /* Flush End */
            }
            request.executeRequest();
            request.getResponseCode();
            URI redirectUrl;
            OutParam<URI> outParam = new OutParam<URI>();
            if (this.tryGetRedirectionResponse(request, outParam)) {
                redirectUrl = outParam.getParam();
                settings.makeRedirectionResponse(redirectUrl);
                return settings;
            }
            InputStream serviceResponseStream = request.getInputStream();
            // If tracing is enabled, we read the entire response into a
            // MemoryStream so that we
            // can pass it along to the ITraceListener. Then we parse the response
            // from the
            // MemoryStream.
            if (this.isTraceEnabledFor(TraceFlags.AutodiscoverResponse)) {
                ByteArrayOutputStream memoryStream = new ByteArrayOutputStream();

                while (true) {
                    int data = serviceResponseStream.read();
                    if (-1 == data) {
                        break;
                    } else {
                        memoryStream.write(data);
                    }
                }
                memoryStream.flush();

                this.traceResponse(request, memoryStream);
                ByteArrayInputStream memoryStreamIn = new ByteArrayInputStream(memoryStream.toByteArray());
                EwsXmlReader reader = new EwsXmlReader(memoryStreamIn);
                reader.read(new XmlNodeType(XmlNodeType.START_DOCUMENT));
                settings.loadFromXml(reader);

            } else {
                EwsXmlReader reader = new EwsXmlReader(serviceResponseStream);
                reader.read(new XmlNodeType(XmlNodeType.START_DOCUMENT));
                settings.loadFromXml(reader);
            }

            serviceResponseStream.close();
        } finally {
            if (request != null) {
                try {
                    request.close();
                } catch (Exception e2) {
                    // Ignore exception while closing the request.
                }
            }
        }

        return settings;
    }

    /**
     * Writes the autodiscover request.
     *
     * @param emailAddress the email address
     * @param settings     the settings
     * @param writer       the writer
     * @throws java.io.IOException Signals that an I/O exception has occurred.
     */
    private void writeLegacyAutodiscoverRequest(String emailAddress, ConfigurationSettingsBase settings,
            PrintWriter writer) throws IOException {
        writer.write(String.format("<Autodiscover xmlns=\"%s\">", AutodiscoverRequestNamespace));
        writer.write("<Request>");
        writer.write(String.format("<EMailAddress>%s</EMailAddress>", emailAddress));
        writer.write(
                String.format("<AcceptableResponseSchema>%s</AcceptableResponseSchema>", settings.getNamespace()));
        writer.write("</Request>");
        writer.write("</Autodiscover>");
    }

    /**
     * Gets a redirection URL to an SSL-enabled Autodiscover service from the
     * standard non-SSL Autodiscover URL.
     *
     * @param domainName the domain name
     * @return A valid SSL-enabled redirection URL. (May be null).
     * @throws microsoft.exchange.webservices.data.exception.EWSHttpException the eWS http exception
     * @throws javax.xml.stream.XMLStreamException                  the xML stream exception
     * @throws java.io.IOException                                  Signals that an I/O exception has occurred.
     * @throws microsoft.exchange.webservices.data.exception.ServiceLocalException                                the service local exception
     * @throws java.net.URISyntaxException                          the uRI syntax exception
     */
    private URI getRedirectUrl(String domainName)
            throws EWSHttpException, XMLStreamException, IOException, ServiceLocalException, URISyntaxException {
        String url = String.format(AutodiscoverLegacyHttpUrl, "autodiscover." + domainName);

        traceMessage(TraceFlags.AutodiscoverConfiguration,
                String.format("Trying to get Autodiscover redirection URL from %s.", url));

        HttpWebRequest request = null;

        try {
            request = new HttpClientWebRequest(httpClient, httpContext);

            try {
                request.setUrl(URI.create(url).toURL());
            } catch (MalformedURLException e) {
                String strErr = String.format("Incorrect format : %s", url);
                throw new ServiceLocalException(strErr);
            }

            request.setRequestMethod("GET");
            request.setAllowAutoRedirect(false);

            // Do NOT allow authentication as this single request will be made over plain HTTP.
            request.setAllowAuthentication(false);

            prepareCredentials(request);

            request.prepareConnection();
            try {
                request.executeRequest();
            } catch (IOException e) {
                traceMessage(TraceFlags.AutodiscoverConfiguration, "No Autodiscover redirection URL was returned.");
                return null;
            }

            OutParam<URI> outParam = new OutParam<URI>();
            if (tryGetRedirectionResponse(request, outParam)) {
                return outParam.getParam();
            }
        } finally {
            if (request != null) {
                try {
                    request.close();
                } catch (Exception e) {
                    // Ignore exception when closing the request
                }
            }
        }

        traceMessage(TraceFlags.AutodiscoverConfiguration, "No Autodiscover redirection URL was returned.");
        return null;
    }

    /**
     * Tries the get redirection response.
     *
     * @param request     the request
     * @param redirectUrl The redirect URL.
     * @return True if a valid redirection URL was found.
     * @throws javax.xml.stream.XMLStreamException                  the xML stream exception
     * @throws java.io.IOException                                  Signals that an I/O exception has occurred.
     * @throws microsoft.exchange.webservices.data.exception.EWSHttpException the eWS http exception
     */
    private boolean tryGetRedirectionResponse(HttpWebRequest request, OutParam<URI> redirectUrl)
            throws XMLStreamException, IOException, EWSHttpException {
        // redirectUrl = null;
        if (AutodiscoverRequest.isRedirectionResponse(request)) {
            // Get the redirect location and verify that it's valid.
            String location = request.getResponseHeaderField("Location");

            if (!(location == null || location.isEmpty())) {
                try {
                    redirectUrl.setParam(new URI(location));

                    // Check if URL is SSL and that the path matches.
                    if ((redirectUrl.getParam().getScheme().toLowerCase().equals("https"))
                            && (redirectUrl.getParam().getPath().equalsIgnoreCase(AutodiscoverLegacyPath))) {
                        this.traceMessage(TraceFlags.AutodiscoverConfiguration,
                                String.format("Redirection URL found: '%s'", redirectUrl.getParam().toString()));

                        return true;
                    }
                } catch (URISyntaxException ex) {
                    this.traceMessage(TraceFlags.AutodiscoverConfiguration,
                            String.format("Invalid redirection URL " + "was returned: '%s'", location));
                    return false;
                }
            }
        }
        return false;
    }

    /**
     * Calls the legacy Autodiscover service to retrieve configuration settings.
     *
     * @param <TSettings>  the generic type
     * @param cls          the cls
     * @param emailAddress The email address to retrieve configuration settings for.
     * @return The requested configuration settings.
     * @throws Exception the exception
     */
    protected <TSettings extends ConfigurationSettingsBase> TSettings getLegacyUserSettings(Class<TSettings> cls,
            String emailAddress) throws Exception {
        /*int currentHop = 1;
        return this.internalGetConfigurationSettings(cls, emailAddress,
        currentHop);*/

        // If Url is specified, call service directly.
        if (this.url != null) {
            // this.Uri is intended for Autodiscover SOAP service, convert to Legacy endpoint URL.
            URI autodiscoverUrl = new URI(this.url.toString() + AutodiscoverLegacyPath);
            return this.getLegacyUserSettingsAtUrl(cls, emailAddress, autodiscoverUrl);
        }

        // If Domain is specified, figure out the endpoint Url and call service.
        else if (!(this.domain == null || this.domain.isEmpty())) {
            URI autodiscoverUrl = new URI(String.format(AutodiscoverLegacyHttpsUrl, this.domain));
            return this.getLegacyUserSettingsAtUrl(cls, emailAddress, autodiscoverUrl);
        } else {
            // No Url or Domain specified, need to
            //figure out which endpoint to use.
            int currentHop = 1;
            OutParam<Integer> outParam = new OutParam<Integer>();
            outParam.setParam(currentHop);
            List<String> redirectionEmailAddresses = new ArrayList<String>();
            return this.internalGetLegacyUserSettings(cls, emailAddress, redirectionEmailAddresses, outParam);
        }
    }

    /**
     * Calls the Autodiscover service to retrieve configuration settings.
     *
     * @param <TSettings>  the generic type
     * @param cls          the cls
     * @param emailAddress The email address to retrieve configuration settings for.
     * @param currentHop   Current number of redirection urls/addresses attempted so far.
     * @return The requested configuration settings.
     * @throws Exception the exception
     */
    private <TSettings extends ConfigurationSettingsBase> TSettings internalGetLegacyUserSettings(
            Class<TSettings> cls, String emailAddress, List<String> redirectionEmailAddresses,
            OutParam<Integer> currentHop) throws Exception {
        String domainName = EwsUtilities.domainFromEmailAddress(emailAddress);

        int scpUrlCount;
        OutParam<Integer> outParamInt = new OutParam<Integer>();
        List<URI> urls = this.getAutodiscoverServiceUrls(domainName, outParamInt);
        scpUrlCount = outParamInt.getParam();
        if (urls.size() == 0) {
            throw new ServiceValidationException(
                    "This Autodiscover request requires that either the Domain or Url be specified.");
        }

        // Assume caller is not inside the Intranet, regardless of whether SCP
        // Urls
        // were returned or not. SCP Urls are only relevent if one of them
        // returns
        // valid Autodiscover settings.
        this.isExternal = true;

        int currentUrlIndex = 0;

        // Used to save exception for later reporting.
        Exception delayedException = null;
        TSettings settings;

        do {
            URI autodiscoverUrl = urls.get(currentUrlIndex);
            boolean isScpUrl = currentUrlIndex < scpUrlCount;

            try {
                settings = this.getLegacyUserSettingsAtUrl(cls, emailAddress, autodiscoverUrl);

                switch (settings.getResponseType()) {
                case Success:
                    // Not external if Autodiscover endpoint found via SCP
                    // returned the settings.
                    if (isScpUrl) {
                        this.isExternal = false;
                    }
                    this.url = autodiscoverUrl;
                    return settings;
                case RedirectUrl:
                    if (currentHop.getParam() < AutodiscoverMaxRedirections) {
                        currentHop.setParam(currentHop.getParam() + 1);

                        this.traceMessage(TraceFlags.AutodiscoverResponse,
                                String.format("Autodiscover " + "service " + "returned " + "redirection URL '%s'.",
                                        settings.getRedirectTarget()));

                        urls.add(currentUrlIndex, new URI(settings.getRedirectTarget()));

                        break;
                    } else {
                        throw new MaximumRedirectionHopsExceededException();
                    }
                case RedirectAddress:
                    if (currentHop.getParam() < AutodiscoverMaxRedirections) {
                        currentHop.setParam(currentHop.getParam() + 1);

                        this.traceMessage(TraceFlags.AutodiscoverResponse, String.format(
                                "Autodiscover " + "service " + "returned " + "redirection email " + "address '%s'.",
                                settings.getRedirectTarget()));
                        // Bug E14:255576 If this email address was already tried, we may have a loop
                        // in SCP lookups. Disable consideration of SCP records.
                        this.disableScpLookupIfDuplicateRedirection(settings.getRedirectTarget(),
                                redirectionEmailAddresses);

                        return this.internalGetLegacyUserSettings(cls, settings.getRedirectTarget(),
                                redirectionEmailAddresses, currentHop);
                    } else {
                        throw new MaximumRedirectionHopsExceededException();
                    }
                case Error:
                    // Don't treat errors from an SCP-based Autodiscover service
                    // to be conclusive.
                    // We'll try the next one and record the error for later.
                    if (isScpUrl) {
                        this.traceMessage(TraceFlags.AutodiscoverConfiguration, "Error returned by "
                                + "Autodiscover service " + "found via SCP, treating " + "as inconclusive.");

                        delayedException = new AutodiscoverRemoteException(
                                "The Autodiscover service returned an error.", settings.getError());
                        currentUrlIndex++;
                    } else {
                        throw new AutodiscoverRemoteException("The Autodiscover service returned an error.",
                                settings.getError());
                    }
                    break;
                default:
                    EwsUtilities.EwsAssert(false, "Autodiscover.GetConfigurationSettings",
                            "An unexpected error has occured. " + "This code path should never be reached.");
                    break;
                }
            } catch (XMLStreamException ex) {
                this.traceMessage(TraceFlags.AutodiscoverConfiguration,
                        String.format("%s failed: XML parsing error: %s", url, ex.getMessage()));

                // The content at the URL wasn't a valid response, let's try the
                // next.
                currentUrlIndex++;
            } catch (IOException ex) {
                this.traceMessage(TraceFlags.AutodiscoverConfiguration,
                        String.format("%s failed: I/O error: %s", url, ex.getMessage()));

                // The content at the URL wasn't a valid response, let's try the next.
                currentUrlIndex++;
            } catch (Exception ex) {
                HttpWebRequest response = null;
                URI redirectUrl;
                OutParam<URI> outParam1 = new OutParam<URI>();
                if ((response != null) && this.tryGetRedirectionResponse(response, outParam1)) {
                    redirectUrl = outParam1.getParam();
                    this.traceMessage(TraceFlags.AutodiscoverConfiguration,
                            String.format("Host returned a redirection to url %s", redirectUrl.toString()));

                    currentHop.setParam(currentHop.getParam() + 1);
                    urls.add(currentUrlIndex, redirectUrl);
                } else {
                    if (response != null) {
                        this.processHttpErrorResponse(response, ex);

                    }

                    this.traceMessage(TraceFlags.AutodiscoverConfiguration,
                            String.format("%s failed: %s (%s)", url, ex.getClass().getName(), ex.getMessage()));

                    // The url did not work, let's try the next.
                    currentUrlIndex++;
                }
            }
        } while (currentUrlIndex < urls.size());

        // If we got this far it's because none of the URLs we tried have
        // worked. As a next-to-last chance, use GetRedirectUrl to
        // try to get a redirection URL using an HTTP GET on a non-SSL
        // Autodiscover endpoint. If successful, use this
        // redirection URL to get the configuration settings for this email
        // address. (This will be a common scenario for
        // DataCenter deployments).
        URI redirectionUrl = this.getRedirectUrl(domainName);
        OutParam<TSettings> outParam = new OutParam<TSettings>();
        if ((redirectionUrl != null)
                && this.tryLastChanceHostRedirection(cls, emailAddress, redirectionUrl, outParam)) {
            settings = outParam.getParam();
            return settings;
        } else {
            // Getting a redirection URL from an HTTP GET failed too. As a last
            // chance, try to get an appropriate SRV Record
            // using DnsQuery. If successful, use this redirection URL to get
            // the configuration settings for this email address.
            redirectionUrl = this.getRedirectionUrlFromDnsSrvRecord(domainName);
            if ((redirectionUrl != null)
                    && this.tryLastChanceHostRedirection(cls, emailAddress, redirectionUrl, outParam)) {
                return outParam.getParam();
            }

            // If there was an earlier exception, throw it.
            if (delayedException != null) {
                throw delayedException;
            }

            throw new AutodiscoverLocalException("The Autodiscover service couldn't be located.");
        }
    }

    /**
     * Get an autodiscover SRV record in DNS and construct autodiscover URL.
     *
     * @param domainName Name of the domain.
     * @return Autodiscover URL (may be null if lookup failed)
     * @throws Exception the exception
     */
    protected URI getRedirectionUrlFromDnsSrvRecord(String domainName) throws Exception {

        this.traceMessage(TraceFlags.AutodiscoverConfiguration,
                String.format("Trying to get Autodiscover host " + "from DNS SRV record for %s.", domainName));

        String hostname = this.dnsClient.findAutodiscoverHostFromSrv(domainName);
        if (!(hostname == null || hostname.isEmpty())) {
            this.traceMessage(TraceFlags.AutodiscoverConfiguration,
                    String.format("Autodiscover host %s was returned.", hostname));

            return new URI(String.format(AutodiscoverLegacyHttpsUrl, hostname));
        } else {
            this.traceMessage(TraceFlags.AutodiscoverConfiguration,
                    "No matching Autodiscover DNS SRV records were found.");

            return null;
        }
    }

    /**
     * Tries to get Autodiscover settings using redirection Url.
     *
     * @param <TSettings>    the generic type
     * @param cls            the cls
     * @param emailAddress   The email address.
     * @param redirectionUrl Redirection Url.
     * @param settings       The settings.
     * @return boolean The boolean.
     * @throws AutodiscoverLocalException  the autodiscover local exception
     * @throws AutodiscoverRemoteException the autodiscover remote exception
     * @throws Exception                   the exception
     */
    private <TSettings extends ConfigurationSettingsBase> boolean tryLastChanceHostRedirection(Class<TSettings> cls,
            String emailAddress, URI redirectionUrl, OutParam<TSettings> settings)
            throws AutodiscoverLocalException, AutodiscoverRemoteException, Exception {
        List<String> redirectionEmailAddresses = new ArrayList<String>();

        // Bug 60274: Performing a non-SSL HTTP GET to retrieve a redirection
        // URL is potentially unsafe. We allow the caller
        // to specify delegate to be called to determine whether we are allowed
        // to use the redirection URL.
        if (this.callRedirectionUrlValidationCallback(redirectionUrl.toString())) {
            for (int currentHop = 0; currentHop < AutodiscoverService.AutodiscoverMaxRedirections; currentHop++) {
                try {
                    settings.setParam(this.getLegacyUserSettingsAtUrl(cls, emailAddress, redirectionUrl));

                    switch (settings.getParam().getResponseType()) {
                    case Success:
                        return true;
                    case Error:
                        throw new AutodiscoverRemoteException("The Autodiscover service returned an error.",
                                settings.getParam().getError());
                    case RedirectAddress:
                        // If this email address was already tried,
                        //we may have a loop
                        // in SCP lookups. Disable consideration of SCP records.
                        this.disableScpLookupIfDuplicateRedirection(settings.getParam().getRedirectTarget(),
                                redirectionEmailAddresses);
                        OutParam<Integer> outParam = new OutParam<Integer>();
                        outParam.setParam(currentHop);
                        settings.setParam(this.internalGetLegacyUserSettings(cls, emailAddress,
                                redirectionEmailAddresses, outParam));
                        currentHop = outParam.getParam();
                        return true;
                    case RedirectUrl:
                        try {
                            redirectionUrl = new URI(settings.getParam().getRedirectTarget());
                        } catch (URISyntaxException ex) {
                            this.traceMessage(TraceFlags.AutodiscoverConfiguration,
                                    String.format("Service " + "returned " + "invalid " + "redirection " + "URL %s",
                                            settings.getParam().getRedirectTarget()));
                            return false;
                        }
                        break;
                    default:
                        String failureMessage = String.format(
                                "Autodiscover call at %s failed with error %s, target %s", redirectionUrl,
                                settings.getParam().getResponseType(), settings.getParam().getRedirectTarget());
                        this.traceMessage(TraceFlags.AutodiscoverConfiguration, failureMessage);

                        return false;
                    }
                } catch (XMLStreamException ex) {
                    // If the response is malformed, it wasn't a valid
                    // Autodiscover endpoint.
                    this.traceMessage(TraceFlags.AutodiscoverConfiguration, String.format(
                            "%s failed: XML parsing error: %s", redirectionUrl.toString(), ex.getMessage()));
                    return false;
                } catch (IOException ex) {
                    this.traceMessage(TraceFlags.AutodiscoverConfiguration,
                            String.format("%s failed: I/O error: %s", redirectionUrl, ex.getMessage()));
                    return false;
                } catch (Exception ex) {
                    // TODO: BUG response is always null
                    HttpWebRequest response = null;
                    OutParam<URI> outParam = new OutParam<URI>();
                    if ((response != null) && this.tryGetRedirectionResponse(response, outParam)) {
                        redirectionUrl = outParam.getParam();
                        this.traceMessage(TraceFlags.AutodiscoverConfiguration,
                                String.format("Host returned a " + "redirection" + " to url %s", redirectionUrl));

                    } else {
                        if (response != null) {
                            this.processHttpErrorResponse(response, ex);
                        }

                        this.traceMessage(TraceFlags.AutodiscoverConfiguration,
                                String.format("%s failed: %s (%s)", url, ex.getClass().getName(), ex.getMessage()));
                        return false;
                    }
                }
            }
        }

        return false;
    }

    /**
     * Disables SCP lookup if duplicate email address redirection.
     *
     * @param emailAddress              The email address to use.
     * @param redirectionEmailAddresses The list of prior redirection email addresses.
     */
    private void disableScpLookupIfDuplicateRedirection(String emailAddress,
            List<String> redirectionEmailAddresses) {
        // SMTP addresses are case-insensitive so entries are converted to lower-case.
        emailAddress = emailAddress.toLowerCase();

        if (redirectionEmailAddresses.contains(emailAddress)) {
            this.enableScpLookup = false;
        } else {
            redirectionEmailAddresses.add(emailAddress);
        }
    }

    /**
     * Gets user settings from Autodiscover legacy endpoint.
     *
     * @param emailAddress      The email address to use.
     * @param requestedSettings The requested settings.
     * @return GetUserSettingsResponse
     */
    protected GetUserSettingsResponse internalGetLegacyUserSettings(String emailAddress,
            List<UserSettingName> requestedSettings) throws Exception {
        // Cannot call legacy Autodiscover service with WindowsLive credential

        /*if ((this.getCredentials() != null) && (this.getCredentials() instanceof WindowsLiveCredentials)) {
        throw new AutodiscoverLocalException(
           Strings.WLIDCredentialsCannotBeUsedWithLegacyAutodiscover);
        }*/

        // Cannot call legacy Autodiscover service with WindowsLive and other WSSecurity-based credential
        if ((this.getCredentials() != null) && (this.getCredentials() instanceof WSSecurityBasedCredentials)) {
            throw new AutodiscoverLocalException(
                    "WindowsLiveCredentials can't be used with this Autodiscover endpoint.");
        }

        OutlookConfigurationSettings settings = this.getLegacyUserSettings(OutlookConfigurationSettings.class,
                emailAddress);

        return settings.convertSettings(emailAddress, requestedSettings);
    }

    /**
     * Calls the SOAP Autodiscover service
     * for user settings for a single SMTP address.
     *
     * @param smtpAddress       SMTP address.
     * @param requestedSettings The requested settings.
     * @return GetUserSettingsResponse
     */
    protected GetUserSettingsResponse internalGetSoapUserSettings(String smtpAddress,
            List<UserSettingName> requestedSettings) throws Exception {
        List<String> smtpAddresses = new ArrayList<String>();
        smtpAddresses.add(smtpAddress);

        List<String> redirectionEmailAddresses = new ArrayList<String>();
        redirectionEmailAddresses.add(smtpAddress.toLowerCase());

        for (int currentHop = 0; currentHop < AutodiscoverService.AutodiscoverMaxRedirections; currentHop++) {
            GetUserSettingsResponse response = this.getUserSettings(smtpAddresses, requestedSettings)
                    .getTResponseAtIndex(0);

            switch (response.getErrorCode()) {
            case RedirectAddress:
                this.traceMessage(TraceFlags.AutodiscoverResponse,
                        String.format("Autodiscover service returned redirection email address '%s'.",
                                response.getRedirectTarget()));

                smtpAddresses.clear();
                smtpAddresses.add(response.getRedirectTarget().toLowerCase());
                this.url = null;
                this.domain = null;

                // If this email address was already tried,
                //we may have a loop
                // in SCP lookups. Disable consideration of SCP records.
                this.disableScpLookupIfDuplicateRedirection(response.getRedirectTarget(),
                        redirectionEmailAddresses);
                break;

            case RedirectUrl:
                this.traceMessage(TraceFlags.AutodiscoverResponse, String.format(
                        "Autodiscover service returned redirection URL '%s'.", response.getRedirectTarget()));

                //this.url = new URI(response.getRedirectTarget());
                this.url = this.getCredentials().adjustUrl(new URI(response.getRedirectTarget()));
                break;

            case NoError:
            default:
                return response;
            }
        }

        throw new AutodiscoverLocalException("The Autodiscover service couldn't be located.");
    }

    /**
     * Gets the user settings using Autodiscover SOAP service.
     *
     * @param smtpAddresses The SMTP addresses of the users.
     * @param settings      The settings.
     * @return GetUserSettingsResponseCollection Object.
     * @throws Exception the exception
     */
    protected GetUserSettingsResponseCollection getUserSettings(final List<String> smtpAddresses,
            List<UserSettingName> settings) throws Exception {
        EwsUtilities.validateParam(smtpAddresses, "smtpAddresses");
        EwsUtilities.validateParam(settings, "settings");

        return this.getSettings(GetUserSettingsResponseCollection.class, UserSettingName.class, smtpAddresses,
                settings, null, this, new IFuncDelegate<String>() {
                    public String func() throws FormatException {
                        return EwsUtilities.domainFromEmailAddress(smtpAddresses.get(0));
                    }
                });
    }

    /**
     * Gets user or domain settings using Autodiscover SOAP service.
     *
     * @param <TGetSettingsResponseCollection> the generic type
     * @param <TSettingName>                   the generic type
     * @param cls                              the cls
     * @param cls1                             the cls1
     * @param identities                       Either the domains or the SMTP addresses of the users.
     * @param settings                         The settings.
     * @param requestedVersion                 Requested version of the Exchange service.
     * @param getSettingsMethod                The method to use.
     * @param getDomainMethod                  The method to calculate the domain value.
     * @return TGetSettingsResponse Collection.
     * @throws Exception the exception
     */
    private <TGetSettingsResponseCollection, TSettingName> TGetSettingsResponseCollection getSettings(
            Class<TGetSettingsResponseCollection> cls, Class<TSettingName> cls1, List<String> identities,
            List<TSettingName> settings, ExchangeVersion requestedVersion,
            IFunctionDelegate<List<String>, List<TSettingName>, TGetSettingsResponseCollection> getSettingsMethod,
            IFuncDelegate<String> getDomainMethod) throws Exception {
        TGetSettingsResponseCollection response;

        // Autodiscover service only exists in E14 or later.
        if (this.getRequestedServerVersion().compareTo(MinimumRequestVersionForAutoDiscoverSoapService) < 0) {
            throw new ServiceVersionException(
                    String.format("The Autodiscover service only supports %s or a later version.",
                            MinimumRequestVersionForAutoDiscoverSoapService));
        }

        // If Url is specified, call service directly.
        if (this.url != null) {
            URI autodiscoverUrl = this.url;
            response = getSettingsMethod.func(identities, settings, requestedVersion, this.url);
            this.url = autodiscoverUrl;
            return response;
        }
        // If Domain is specified, determine endpoint Url and call service.
        else if (!(this.domain == null || this.domain.isEmpty())) {
            URI autodiscoverUrl = this.getAutodiscoverEndpointUrl(this.domain);
            response = getSettingsMethod.func(identities, settings, requestedVersion, autodiscoverUrl);

            // If we got this far, response was successful, set Url.
            this.url = autodiscoverUrl;
            return response;
        }
        // No Url or Domain specified, need to figure out which endpoint(s) to
        // try.
        else {
            // Assume caller is not inside the Intranet, regardless of whether
            // SCP Urls
            // were returned or not. SCP Urls are only relevent if one of them
            // returns
            // valid Autodiscover settings.
            this.isExternal = true;

            URI autodiscoverUrl;

            String domainName = getDomainMethod.func();
            int scpHostCount;
            OutParam<Integer> outParam = new OutParam<Integer>();
            List<String> hosts = this.getAutodiscoverServiceHosts(domainName, outParam);
            scpHostCount = outParam.getParam();
            if (hosts.size() == 0) {
                throw new ServiceValidationException(
                        "This Autodiscover request requires that either the Domain or Url be specified.");
            }

            for (int currentHostIndex = 0; currentHostIndex < hosts.size(); currentHostIndex++) {
                String host = hosts.get(currentHostIndex);
                boolean isScpHost = currentHostIndex < scpHostCount;
                OutParam<URI> outParams = new OutParam<URI>();
                if (this.tryGetAutodiscoverEndpointUrl(host, outParams)) {
                    autodiscoverUrl = outParams.getParam();
                    response = getSettingsMethod.func(identities, settings, requestedVersion, autodiscoverUrl);

                    // If we got this far, the response was successful, set Url.
                    this.url = autodiscoverUrl;

                    // Not external if Autodiscover endpoint found via SCP
                    // returned the settings.
                    if (isScpHost) {
                        this.isExternal = false;
                    }

                    return response;
                }
            }

            // Next-to-last chance: try unauthenticated GET over HTTP to be
            // redirected to appropriate service endpoint.
            autodiscoverUrl = this.getRedirectUrl(domainName);
            OutParam<URI> outParamUrl = new OutParam<URI>();
            if ((autodiscoverUrl != null) && this.callRedirectionUrlValidationCallback(autodiscoverUrl.toString())
                    && this.tryGetAutodiscoverEndpointUrl(autodiscoverUrl.getHost(), outParamUrl)) {
                autodiscoverUrl = outParamUrl.getParam();
                response = getSettingsMethod.func(identities, settings, requestedVersion, autodiscoverUrl);

                // If we got this far, the response was successful, set Url.
                this.url = autodiscoverUrl;

                return response;
            }

            // Last Chance: try to read autodiscover SRV Record from DNS. If we
            // find one, use
            // the hostname returned to construct an Autodiscover endpoint URL.
            autodiscoverUrl = this.getRedirectionUrlFromDnsSrvRecord(domainName);
            if ((autodiscoverUrl != null) && this.callRedirectionUrlValidationCallback(autodiscoverUrl.toString())
                    && this.tryGetAutodiscoverEndpointUrl(autodiscoverUrl.getHost(), outParamUrl)) {
                autodiscoverUrl = outParamUrl.getParam();
                response = getSettingsMethod.func(identities, settings, requestedVersion, autodiscoverUrl);

                // If we got this far, the response was successful, set Url.
                this.url = autodiscoverUrl;

                return response;
            } else {
                throw new AutodiscoverLocalException("The Autodiscover service couldn't be located.");
            }
        }
    }

    /**
     * Gets settings for one or more users.
     *
     * @param smtpAddresses    The SMTP addresses of the users.
     * @param settings         The settings.
     * @param requestedVersion Requested version of the Exchange service.
     * @param autodiscoverUrl  The autodiscover URL.
     * @return GetUserSettingsResponse collection.
     * @throws ServiceLocalException the service local exception
     * @throws Exception             the exception
     */
    private GetUserSettingsResponseCollection internalGetUserSettings(List<String> smtpAddresses,
            List<UserSettingName> settings, ExchangeVersion requestedVersion, URI autodiscoverUrl)
            throws ServiceLocalException, Exception {
        // The response to GetUserSettings can be a redirection. Execute
        // GetUserSettings until we get back
        // a valid response or we've followed too many redirections.
        for (int currentHop = 0; currentHop < AutodiscoverService.AutodiscoverMaxRedirections; currentHop++) {
            GetUserSettingsRequest request = new GetUserSettingsRequest(this, autodiscoverUrl);
            request.setSmtpAddresses(smtpAddresses);
            request.setSettings(settings);
            GetUserSettingsResponseCollection response = request.execute();

            // Did we get redirected?
            if (response.getErrorCode() == AutodiscoverErrorCode.RedirectUrl
                    && response.getRedirectionUrl() != null) {
                this.traceMessage(TraceFlags.AutodiscoverConfiguration,
                        String.format("Request to %s returned redirection to %s", autodiscoverUrl.toString(),
                                response.getRedirectionUrl()));

                autodiscoverUrl = response.getRedirectionUrl();
            } else {
                return response;
            }
        }

        this.traceMessage(TraceFlags.AutodiscoverConfiguration,
                String.format("Maximum number of redirection hops %d exceeded", AutodiscoverMaxRedirections));

        throw new MaximumRedirectionHopsExceededException();
    }

    /**
     * Gets the domain settings using Autodiscover SOAP service.
     *
     * @param domains          The domains.
     * @param settings         The settings.
     * @param requestedVersion Requested version of the Exchange service.
     * @return GetDomainSettingsResponse collection.
     * @throws Exception the exception
     */
    protected GetDomainSettingsResponseCollection getDomainSettings(final List<String> domains,
            List<DomainSettingName> settings, ExchangeVersion requestedVersion) throws Exception {
        EwsUtilities.validateParam(domains, "domains");
        EwsUtilities.validateParam(settings, "settings");

        return this.getSettings(GetDomainSettingsResponseCollection.class, DomainSettingName.class, domains,
                settings, requestedVersion, this, new IFuncDelegate<String>() {
                    public String func() {
                        return domains.get(0);
                    }
                });
    }

    /**
     * Gets settings for one or more domains.
     *
     * @param domains          The domains.
     * @param settings         The settings.
     * @param requestedVersion Requested version of the Exchange service.
     * @param autodiscoverUrl  The autodiscover URL.
     * @return GetDomainSettingsResponse Collection.
     * @throws ServiceLocalException the service local exception
     * @throws Exception             the exception
     */
    private GetDomainSettingsResponseCollection internalGetDomainSettings(List<String> domains,
            List<DomainSettingName> settings, ExchangeVersion requestedVersion, URI autodiscoverUrl)
            throws ServiceLocalException, Exception {
        // The response to GetDomainSettings can be a redirection. Execute
        // GetDomainSettings until we get back
        // a valid response or we've followed too many redirections.
        for (int currentHop = 0; currentHop < AutodiscoverService.AutodiscoverMaxRedirections; currentHop++) {
            GetDomainSettingsRequest request = new GetDomainSettingsRequest(this, autodiscoverUrl);
            request.setDomains(domains);
            request.setSettings(settings);
            request.setRequestedVersion(requestedVersion);
            GetDomainSettingsResponseCollection response = request.execute();

            // Did we get redirected?
            if (response.getErrorCode() == AutodiscoverErrorCode.RedirectUrl
                    && response.getRedirectionUrl() != null) {
                autodiscoverUrl = response.getRedirectionUrl();
            } else {
                return response;
            }
        }

        this.traceMessage(TraceFlags.AutodiscoverConfiguration,
                String.format("Maximum number of redirection hops %d exceeded", AutodiscoverMaxRedirections));

        throw new MaximumRedirectionHopsExceededException();
    }

    /**
     * Gets the autodiscover endpoint URL.
     *
     * @param host The host.
     * @return URI The URI.
     * @throws Exception the exception
     */
    private URI getAutodiscoverEndpointUrl(String host) throws Exception {
        URI autodiscoverUrl = null;
        OutParam<URI> outParam = new OutParam<URI>();
        if (this.tryGetAutodiscoverEndpointUrl(host, outParam)) {
            return autodiscoverUrl;
        } else {
            throw new AutodiscoverLocalException(
                    "No appropriate Autodiscover SOAP or WS-Security endpoint is available.");
        }
    }

    /**
     * Tries the get Autodiscover Service endpoint URL.
     *
     * @param host The host.
     * @param url  the url
     * @return boolean The boolean.
     * @throws Exception the exception
     */
    private boolean tryGetAutodiscoverEndpointUrl(String host, OutParam<URI> url) throws Exception {
        EnumSet<AutodiscoverEndpoints> endpoints;
        OutParam<EnumSet<AutodiscoverEndpoints>> outParam = new OutParam<EnumSet<AutodiscoverEndpoints>>();
        if (this.tryGetEnabledEndpointsForHost(host, outParam)) {
            endpoints = outParam.getParam();
            url.setParam(new URI(String.format(AutodiscoverSoapHttpsUrl, host)));

            // Make sure that at least one of the non-legacy endpoints is
            // available.
            if ((!endpoints.contains(AutodiscoverEndpoints.Soap))
                    && (!endpoints.contains(AutodiscoverEndpoints.WsSecurity))
            // (endpoints .contains( AutodiscoverEndpoints.WSSecuritySymmetricKey) ) &&
            //(endpoints .contains( AutodiscoverEndpoints.WSSecurityX509Cert))
            ) {
                this.traceMessage(TraceFlags.AutodiscoverConfiguration,
                        String.format("No Autodiscover endpoints " + "are available  for host %s", host));

                return false;
            }

            // If we have WLID credential, make sure that we have a WS-Security
            // endpoint
            /*
            if (this.getCredentials() instanceof WindowsLiveCredentials) {
               if (endpoints.contains(AutodiscoverEndpoints.WsSecurity)) {
                  this
                 .traceMessage(
                       TraceFlags.AutodiscoverConfiguration,
                       String
                             .format(
                                   "No Autodiscover " +
                                   "WS-Security " +
                                   "endpoint is available" +
                                   " for host %s",
                                   host));
                
                  return false;
               } else {
                  url.setParam(new URI(String.format(
                 AutodiscoverSoapWsSecurityHttpsUrl, host)));
               }
            }
               else if (this.getCredentials() instanceof PartnerTokenCredentials)
            {
                if (endpoints.contains( AutodiscoverEndpoints.WSSecuritySymmetricKey))
                {
                    this.traceMessage(
                        TraceFlags.AutodiscoverConfiguration,
                        String.format("No Autodiscover WS-Security/SymmetricKey endpoint is available for host {0}", host));
                
                    return false;
                }
                else
                {
                    url.setParam( new URI(String.format(AutodiscoverSoapWsSecuritySymmetricKeyHttpsUrl, host)));
                }
            }
            else if (this.getCredentials()instanceof X509CertificateCredentials)
            {
                if ((endpoints.contains(AutodiscoverEndpoints.WSSecurityX509Cert))
                {
                    this.traceMessage(
                        TraceFlags.AutodiscoverConfiguration,
                        String.format("No Autodiscover WS-Security/X509Cert endpoint is available for host {0}", host));
                
                    return false;
                }
                else
                {
                    url.setParam( new URI(String.format(AutodiscoverSoapWsSecurityX509CertHttpsUrl, host)));
                }
            }
                 */
            return true;

        } else {
            this.traceMessage(TraceFlags.AutodiscoverConfiguration,
                    String.format("No Autodiscover endpoints " + "are available for host %s", host));

            return false;
        }
    }

    /**
     * Gets the list of autodiscover service URLs.
     *
     * @param domainName   Domain name.
     * @param scpHostCount Count of hosts found via SCP lookup.
     * @return List of Autodiscover URLs.
     * @throws java.net.URISyntaxException the URI Syntax exception
     */
    protected List<URI> getAutodiscoverServiceUrls(String domainName, OutParam<Integer> scpHostCount)
            throws URISyntaxException {
        List<URI> urls;

        urls = new ArrayList<URI>();

        scpHostCount.setParam(urls.size());

        // As a fallback, add autodiscover URLs base on the domain name.
        urls.add(new URI(String.format(AutodiscoverLegacyHttpsUrl, domainName)));
        urls.add(new URI(String.format(AutodiscoverLegacyHttpsUrl, "autodiscover." + domainName)));

        return urls;
    }

    /**
     * Gets the list of autodiscover service hosts.
     *
     * @param domainName Domain name.
     * @param outParam   the out param
     * @return List of hosts.
     * @throws java.net.URISyntaxException the uRI syntax exception
     * @throws ClassNotFoundException      the class not found exception
     */
    protected List<String> getAutodiscoverServiceHosts(String domainName, OutParam<Integer> outParam)
            throws URISyntaxException, ClassNotFoundException {

        List<URI> urls = this.getAutodiscoverServiceUrls(domainName, outParam);
        List<String> lst = new ArrayList<String>();
        for (URI url : urls) {
            lst.add(url.getHost());
        }
        return lst;
    }

    /**
     * Gets the enabled autodiscover endpoints on a specific host.
     *
     * @param host      The host.
     * @param endpoints Endpoints found for host.
     * @return Flags indicating which endpoints are enabled.
     * @throws Exception the exception
     */
    private boolean tryGetEnabledEndpointsForHost(String host, OutParam<EnumSet<AutodiscoverEndpoints>> endpoints)
            throws Exception {
        this.traceMessage(TraceFlags.AutodiscoverConfiguration,
                String.format("Determining which endpoints are enabled for host %s", host));

        // We may get redirected to another host. And therefore need to limit the number of redirections we'll
        // tolerate.
        for (int currentHop = 0; currentHop < AutodiscoverMaxRedirections; currentHop++) {
            URI autoDiscoverUrl = new URI(String.format(AutodiscoverLegacyHttpsUrl, host));

            endpoints.setParam(EnumSet.of(AutodiscoverEndpoints.None));

            HttpWebRequest request = null;
            try {
                request = new HttpClientWebRequest(httpClient, httpContext);

                try {
                    request.setUrl(autoDiscoverUrl.toURL());
                } catch (MalformedURLException e) {
                    String strErr = String.format("Incorrect format : %s", url);
                    throw new ServiceLocalException(strErr);
                }

                request.setRequestMethod("GET");
                request.setAllowAutoRedirect(false);
                request.setPreAuthenticate(false);
                request.setUseDefaultCredentials(this.getUseDefaultCredentials());

                prepareCredentials(request);

                request.prepareConnection();
                try {
                    request.executeRequest();
                } catch (IOException e) {
                    return false;
                }

                OutParam<URI> outParam = new OutParam<URI>();
                if (this.tryGetRedirectionResponse(request, outParam)) {
                    URI redirectUrl = outParam.getParam();
                    this.traceMessage(TraceFlags.AutodiscoverConfiguration,
                            String.format("Host returned redirection to host '%s'", redirectUrl.getHost()));

                    host = redirectUrl.getHost();
                } else {
                    endpoints.setParam(this.getEndpointsFromHttpWebResponse(request));

                    this.traceMessage(TraceFlags.AutodiscoverConfiguration, String
                            .format("Host returned enabled endpoint flags: %s", endpoints.getParam().toString()));

                    return true;
                }
            } finally {
                if (request != null) {
                    try {
                        request.close();
                    } catch (Exception e) {
                        // Connection can't be closed. We'll ignore this...
                    }
                }
            }
        }

        this.traceMessage(TraceFlags.AutodiscoverConfiguration,
                String.format("Maximum number of redirection hops %d exceeded", AutodiscoverMaxRedirections));

        throw new MaximumRedirectionHopsExceededException();
    }

    /**
     * Gets the endpoints from HTTP web response.
     *
     * @param request the request
     * @return Endpoints enabled.
     * @throws microsoft.exchange.webservices.data.exception.EWSHttpException the eWS http exception
     */
    private EnumSet<AutodiscoverEndpoints> getEndpointsFromHttpWebResponse(HttpWebRequest request)
            throws EWSHttpException {
        EnumSet<AutodiscoverEndpoints> endpoints = EnumSet.noneOf(AutodiscoverEndpoints.class);
        endpoints.add(AutodiscoverEndpoints.Legacy);

        if (!(request.getResponseHeaders().get(AutodiscoverSoapEnabledHeaderName) == null
                || request.getResponseHeaders().get(AutodiscoverSoapEnabledHeaderName).isEmpty())) {
            endpoints.add(AutodiscoverEndpoints.Soap);
        }
        if (!(request.getResponseHeaders().get(AutodiscoverWsSecurityEnabledHeaderName) == null
                || request.getResponseHeaders().get(AutodiscoverWsSecurityEnabledHeaderName).isEmpty())) {
            endpoints.add(AutodiscoverEndpoints.WsSecurity);
        }

        /* if (! (request.getResponseHeaders().get(
         AutodiscoverWsSecuritySymmetricKeyEnabledHeaderName) !=null || request
         .getResponseHeaders().get(
         AutodiscoverWsSecuritySymmetricKeyEnabledHeaderName).isEmpty()))
           {
         endpoints .add( AutodiscoverEndpoints.WSSecuritySymmetricKey);
           }
           if (!(request.getResponseHeaders().get(
           AutodiscoverWsSecurityX509CertEnabledHeaderName)!=null ||
           request.getResponseHeaders().get(
                   AutodiscoverWsSecurityX509CertEnabledHeaderName).isEmpty()))
               
           {
         endpoints .add(AutodiscoverEndpoints.WSSecurityX509Cert);
           }*/

        return endpoints;
    }

    /**
     * Traces the response.
     *
     * @param request      the request
     * @param memoryStream the memory stream
     * @throws javax.xml.stream.XMLStreamException                  the xML stream exception
     * @throws java.io.IOException                                  Signals that an I/O exception has occurred.
     * @throws microsoft.exchange.webservices.data.exception.EWSHttpException the eWS http exception
     */
    public void traceResponse(HttpWebRequest request, ByteArrayOutputStream memoryStream)
            throws XMLStreamException, IOException, EWSHttpException {
        this.processHttpResponseHeaders(TraceFlags.AutodiscoverResponseHttpHeaders, request);
        String contentType = request.getResponseContentType();
        if (!(contentType == null || contentType.isEmpty())) {
            contentType = contentType.toLowerCase();
            if (contentType.toLowerCase().startsWith("text/")
                    || contentType.toLowerCase().startsWith("application/soap")) {
                this.traceXml(TraceFlags.AutodiscoverResponse, memoryStream);
            } else {
                this.traceMessage(TraceFlags.AutodiscoverResponse, "Non-textual response");
            }
        }
    }

    /**
     * Creates an HttpWebRequest instance and initializes it with the
     * appropriate parameters, based on the configuration of this service
     * object.
     *
     * @param url The URL that the HttpWebRequest should target.
     * @return HttpWebRequest The HttpWebRequest.
     * @throws ServiceLocalException       the service local exception
     * @throws java.net.URISyntaxException the uRI syntax exception
     */
    public HttpWebRequest prepareHttpWebRequestForUrl(URI url) throws ServiceLocalException, URISyntaxException {
        return this.prepareHttpWebRequestForUrl(url, false,
                // acceptGzipEncoding
                false); // allowAutoRedirect
    }

    /**
     * Calls the redirection URL validation callback. If the redirection URL
     * validation callback is null, use the default callback which does not
     * allow following any redirections.
     *
     * @param redirectionUrl The redirection URL.
     * @return True if redirection should be followed.
     * @throws AutodiscoverLocalException the autodiscover local exception
     */
    private boolean callRedirectionUrlValidationCallback(String redirectionUrl) throws AutodiscoverLocalException {
        IAutodiscoverRedirectionUrl callback = (this.redirectionUrlValidationCallback == null) ? this
                : this.redirectionUrlValidationCallback;
        return callback.autodiscoverRedirectionUrlValidationCallback(redirectionUrl);
    }

    /**
     * Processes an HTTP error response.
     *
     * @param httpWebResponse The HTTP web response.
     * @throws Exception the exception
     */
    @Override
    public void processHttpErrorResponse(HttpWebRequest httpWebResponse, Exception webException) throws Exception {
        this.internalProcessHttpErrorResponse(httpWebResponse, webException,
                TraceFlags.AutodiscoverResponseHttpHeaders, TraceFlags.AutodiscoverResponse);
    }

    /*
     * (non-Javadoc)
     *
     * @see microsoft.exchange.webservices.AutodiscoverRedirectionUrlInterface#
     * autodiscoverRedirectionUrlValidationCallback(java.lang.String)
     */
    public boolean autodiscoverRedirectionUrlValidationCallback(String redirectionUrl)
            throws AutodiscoverLocalException {
        return defaultAutodiscoverRedirectionUrlValidationCallback(redirectionUrl);
    }

    /**
     * Initializes a new instance of the "AutodiscoverService" class.
     *
     * @throws microsoft.exchange.webservices.data.exception.ArgumentException
     */
    public AutodiscoverService() throws ArgumentException {
        this(ExchangeVersion.Exchange2010);
    }

    /**
     * Initializes a new instance of the "AutodiscoverService" class.
     *
     * @param requestedServerVersion The requested server version.
     * @throws microsoft.exchange.webservices.data.exception.ArgumentException
     */
    public AutodiscoverService(ExchangeVersion requestedServerVersion) throws ArgumentException {
        this(null, null, requestedServerVersion);
    }

    /**
     * Initializes a new instance of the "AutodiscoverService" class.
     *
     * @param domain The domain that will be used to determine the URL of the
     *               service.
     * @throws microsoft.exchange.webservices.data.exception.ArgumentException
     */
    public AutodiscoverService(String domain) throws ArgumentException {
        this(null, domain);
    }

    /**
     * Initializes a new instance of the "AutodiscoverService" class.
     *
     * @param domain                 The domain that will be used to determine the URL of the
     *                               service.
     * @param requestedServerVersion The requested server version.
     * @throws microsoft.exchange.webservices.data.exception.ArgumentException
     */
    public AutodiscoverService(String domain, ExchangeVersion requestedServerVersion) throws ArgumentException {
        this(null, domain, requestedServerVersion);
    }

    /**
     * Initializes a new instance of the "AutodiscoverService" class.
     *
     * @param url The URL of the service.
     * @throws microsoft.exchange.webservices.data.exception.ArgumentException
     */
    public AutodiscoverService(URI url) throws ArgumentException {
        this(url, url.getHost());
    }

    /**
     * Initializes a new instance of the "AutodiscoverService" class.
     *
     * @param url                    The URL of the service.
     * @param requestedServerVersion The requested server version.
     * @throws microsoft.exchange.webservices.data.exception.ArgumentException
     */
    public AutodiscoverService(URI url, ExchangeVersion requestedServerVersion) throws ArgumentException {
        this(url, url.getHost(), requestedServerVersion);
    }

    /**
     * Initializes a new instance of the "AutodiscoverService" class.
     *
     * @param url    The URL of the service.
     * @param domain The domain that will be used to determine the URL of the
     *               service.
     * @throws microsoft.exchange.webservices.data.exception.ArgumentException
     */
    public AutodiscoverService(URI url, String domain) throws ArgumentException {
        super();
        EwsUtilities.validateDomainNameAllowNull(domain, "domain");
        this.url = url;
        this.domain = domain;
        this.dnsClient = new AutodiscoverDnsClient(this);
    }

    /**
     * Initializes a new instance of the "AutodiscoverService" class.
     *
     * @param url                    The URL of the service.
     * @param domain                 The domain that will be used to determine the URL of the
     *                               service.
     * @param requestedServerVersion The requested server version.
     * @throws microsoft.exchange.webservices.data.exception.ArgumentException
     */
    public AutodiscoverService(URI url, String domain, ExchangeVersion requestedServerVersion)
            throws ArgumentException {
        super(requestedServerVersion);
        EwsUtilities.validateDomainNameAllowNull(domain, "domain");

        this.url = url;
        this.domain = domain;
        this.dnsClient = new AutodiscoverDnsClient(this);
    }

    /**
     * Initializes a new instance of the AutodiscoverService class.
     *
     * @param service                The other service.
     * @param requestedServerVersion The requested server version.
     */
    public AutodiscoverService(ExchangeServiceBase service, ExchangeVersion requestedServerVersion) {
        super(service, requestedServerVersion);
        this.dnsClient = new AutodiscoverDnsClient(this);
    }

    /**
     * Initializes a new instance of the "AutodiscoverService" class.
     *
     * @param service The service.
     */
    public AutodiscoverService(ExchangeServiceBase service) {
        super(service, service.getRequestedServerVersion());
    }

    /**
     * Retrieves the specified settings for single SMTP address.
     *
     * @param userSmtpAddress  The SMTP addresses of the user.
     * @param userSettingNames The user setting names.
     * @return A UserResponse object containing the requested settings for the
     * specified user.
     * @throws Exception the exception
     *                   <p/>
     *                   This method handles will run the entire Autodiscover "discovery"
     *                   algorithm and will follow address and URL redirections.
     */
    public GetUserSettingsResponse getUserSettings(String userSmtpAddress, UserSettingName... userSettingNames)
            throws Exception {
        List<UserSettingName> requestedSettings = new ArrayList<UserSettingName>();
        requestedSettings.addAll(Arrays.asList(userSettingNames));

        if (userSmtpAddress == null || userSmtpAddress.isEmpty()) {
            throw new ServiceValidationException("A valid SMTP address must be specified.");
        }

        if (requestedSettings.size() == 0) {
            throw new ServiceValidationException("At least one setting must be requested.");
        }

        if (this.getRequestedServerVersion().compareTo(MinimumRequestVersionForAutoDiscoverSoapService) < 0) {
            return this.internalGetLegacyUserSettings(userSmtpAddress, requestedSettings);
        } else {
            return this.internalGetSoapUserSettings(userSmtpAddress, requestedSettings);
        }

    }

    /**
     * Retrieves the specified settings for a set of users.
     *
     * @param userSmtpAddresses the user smtp addresses
     * @param userSettingNames  The user setting names.
     * @return A GetUserSettingsResponseCollection object containing the
     * response for each individual user.
     * @throws Exception the exception
     */
    public GetUserSettingsResponseCollection getUsersSettings(Iterable<String> userSmtpAddresses,
            UserSettingName... userSettingNames) throws Exception {
        if (this.getRequestedServerVersion().compareTo(MinimumRequestVersionForAutoDiscoverSoapService) < 0) {
            throw new ServiceVersionException(
                    String.format("The Autodiscover service only supports %s or a later version.",
                            MinimumRequestVersionForAutoDiscoverSoapService));
        }
        List<String> smtpAddresses = new ArrayList<String>();
        smtpAddresses.addAll((Collection<? extends String>) userSmtpAddresses);
        List<UserSettingName> settings = new ArrayList<UserSettingName>();
        settings.addAll(Arrays.asList(userSettingNames));
        return this.getUserSettings(smtpAddresses, settings);
    }

    /**
     * Retrieves the specified settings for a domain.
     *
     * @param domain             The domain.
     * @param requestedVersion   Requested version of the Exchange service.
     * @param domainSettingNames The domain setting names.
     * @return A DomainResponse object containing the requested settings for the
     * specified domain.
     * @throws Exception the exception
     */
    public GetDomainSettingsResponse getDomainSettings(String domain, ExchangeVersion requestedVersion,
            DomainSettingName... domainSettingNames) throws Exception {
        List<String> domains = new ArrayList<String>(1);
        domains.add(domain);

        List<DomainSettingName> settings = new ArrayList<DomainSettingName>();
        settings.addAll(Arrays.asList(domainSettingNames));

        return this.getDomainSettings(domains, settings, requestedVersion).getTResponseAtIndex(0);
    }

    /**
     * Retrieves the specified settings for a set of domains.
     *
     * @param domains            the domains
     * @param requestedVersion   Requested version of the Exchange service.
     * @param domainSettingNames The domain setting names.
     * @return A GetDomainSettingsResponseCollection object containing the
     * response for each individual domain.
     * @throws Exception the exception
     */
    public GetDomainSettingsResponseCollection getDomainSettings(Iterable<String> domains,
            ExchangeVersion requestedVersion, DomainSettingName... domainSettingNames) throws Exception {
        List<DomainSettingName> settings = new ArrayList<DomainSettingName>();
        settings.addAll(Arrays.asList(domainSettingNames));

        List<String> domainslst = new ArrayList<String>();
        domainslst.addAll((Collection<? extends String>) domains);

        return this.getDomainSettings(domainslst, settings, requestedVersion);
    }

    /**
     * Try to get the partner access information for the given target tenant.
     *
     * @param targetTenantDomain The target domain or user email address.
     * @param partnerAccessCredentials The partner access credential.
     * @param targetTenantAutodiscoverUrl The autodiscover url for the given tenant.
     * @return True if the partner access information was retrieved, false otherwise.
     */

    /** commented as the code belongs to Partener Token credential. */

    /*  public boolean tryGetPartnerAccess(
    String targetTenantDomain,
    OutParam<ExchangeCredentials> partnerAccessCredentials,
    OutParam<URI> targetTenantAutodiscoverUrl)
      {
    EwsUtilities.validateNonBlankStringParam(targetTenantDomain, "targetTenantDomain");
        
    // the user should set the url to its own tenant's autodiscover url.
    // 
    if (this.url == null)
    {
        throw new ServiceValidationException(Strings.PartnerTokenRequestRequiresUrl);
    }
        
    if (this.getRequestedServerVersion().ordinal() < ExchangeVersion.Exchange2010_SP1.ordinal())
    {
        throw new ServiceVersionException(
            String.format(
                Strings.PartnerTokenIncompatibleWithRequestVersion,
                ExchangeVersion.Exchange2010_SP1));
    }
        
    partnerAccessCredentials = null;
    targetTenantAutodiscoverUrl = null;
        
    String smtpAddress = targetTenantDomain;
    if (!smtpAddress.contains("@"))
    {
        smtpAddress = "SystemMailbox{e0dc1c29-89c3-4034-b678-e6c29d823ed9}@" + targetTenantDomain;
    }
        
    List<String> smtpAddresses = new ArrayList<String>();
    smtpAddresses.add(smtpAddress);
    List<UserSettingName> settings = new ArrayList<UserSettingName>();
    settings.add(UserSettingName.ExternalEwsUrl);
        
    GetUserSettingsRequest request = new GetUserSettingsRequest(this, this.url, true  expectPartnerToken );
    request.setSmtpAddresses(smtpAddresses);
    request.setSettings(settings);
    GetUserSettingsResponseCollection response = request.execute();
        
    if (request.getPartnerToken()!=null && !request.getPartnerToken().isEmpty())
        || request.getPartnerTokenReference()!=null && !request.getPartnerTokenReference().isEmpty() ))
    {
        return false;
    }
        
    if (request.getErrorCode() == AutodiscoverErrorCode.NoError)
    {
        GetUserSettingsResponse firstResponse = request.getResponse(0);
        if (firstResponse.getErrorCode() == AutodiscoverErrorCode.NoError)
        {
            targetTenantAutodiscoverUrl = this.url;
        }
        else if (firstResponse.getErrorCode() == AutodiscoverErrorCode.RedirectUrl)
        {
            targetTenantAutodiscoverUrl = new URI(firstResponse.getRedirectTarget());
        }
        else
        {
            return false;
        }
    }
    else
    {
        return false;
    }
        
    partnerAccessCredentials = new PartnerTokenCredentials(
        request.getPartnerToken(),
        request.getPartnerTokenReference());
        
    targetTenantAutodiscoverUrl = partnerAccessCredentials.adjustUrl(
        targetTenantAutodiscoverUrl);
        
    return true;
      }
    */

    /**
     * Gets the domain this service is bound to. When this property is
     * set, the domain
     * <p/>
     * name is used to automatically determine the Autodiscover service URL.
     *
     * @return the domain
     */
    public String getDomain() {
        return this.domain;
    }

    /**
     * Sets the domain this service is bound to. When this property is
     * set, the domain
     * name is used to automatically determine the Autodiscover service URL.
     *
     * @param value the new domain
     * @throws microsoft.exchange.webservices.data.exception.ArgumentException
     */
    public void setDomain(String value) throws ArgumentException {
        EwsUtilities.validateDomainNameAllowNull(value, "Domain");

        // If Domain property is set to non-null value, Url property is nulled.
        if (value != null) {
            this.url = null;
        }
        this.domain = value;
    }

    /**
     * Gets the url this service is bound to.
     *
     * @return the url
     */
    public URI getUrl() {
        return this.url;
    }

    /**
     * Sets the url this service is bound to.
     *
     * @param value the new url
     */
    public void setUrl(URI value) {
        // If Url property is set to non-null value, Domain property is set to
        // host portion of Url.
        if (value != null) {
            this.domain = value.getHost();
        }
        this.url = value;
    }

    public Boolean isExternal() {
        return this.isExternal;
    }

    protected void setIsExternal(Boolean value) {
        this.isExternal = value;
    }

    /**
     * Gets the redirection url validation callback.
     *
     * @return the redirection url validation callback
     */
    public IAutodiscoverRedirectionUrl getRedirectionUrlValidationCallback() {
        return this.redirectionUrlValidationCallback;
    }

    /**
     * Sets the redirection url validation callback.
     *
     * @param value the new redirection url validation callback
     */
    public void setRedirectionUrlValidationCallback(IAutodiscoverRedirectionUrl value) {
        this.redirectionUrlValidationCallback = value;
    }

    /**
     * Gets the dns server address.
     *
     * @return the dns server address
     */
    protected String getDnsServerAddress() {
        return this.dnsServerAddress;
    }

    /**
     * Sets the dns server address.
     *
     * @param value the new dns server address
     */
    protected void setDnsServerAddress(String value) {
        this.dnsServerAddress = value;
    }

    /**
     * Gets a value indicating whether the AutodiscoverService should
     * perform SCP (ServiceConnectionPoint) record lookup when determining
     * the Autodiscover service URL.
     *
     * @return the enable scp lookup
     */
    public boolean getEnableScpLookup() {
        return this.enableScpLookup;
    }

    /**
     * Sets the enable scp lookup.
     *
     * @param value the new enable scp lookup
     */
    public void setEnableScpLookup(boolean value) {
        this.enableScpLookup = value;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * microsoft.exchange.webservices.FuncDelegateInterface#func(java.util.List,
     * java.util.List, java.net.URI)
     */
    @Override
    public Object func(List arg1, List arg2, ExchangeVersion arg3, URI arg4)
            throws ServiceLocalException, Exception {
        if (arg2.get(0).getClass().equals(DomainSettingName.class)) {
            return internalGetDomainSettings(arg1, arg2, arg3, arg4);
        } else if (arg2.get(0).getClass().equals(UserSettingName.class)) {
            return internalGetUserSettings(arg1, arg2, arg3, arg4);
        } else {
            return null;
        }
    }

}