Inheritance: ExtensibleJsonObject
        /// <inheritdoc/>
        /// <remarks>
        /// This method updates the <paramref name="serviceType"/> and/or <paramref name="serviceName"/> values for
        /// cases where "vanilla" OpenStack values were specified but Rackspace exposes the compatible services under
        /// different names in the service catalog.
        /// </remarks>
        protected override Uri GetBaseAddressImpl(Access access, string serviceType, string serviceName, string region, bool internalAddress)
        {
            if (string.IsNullOrEmpty(serviceName))
            {
                switch (serviceType)
                {
                    case "cdn":
                        serviceName = "rackCDN";
                        serviceType = "rax:cdn";
                        break;

                    case "compute":
                        serviceName = "cloudServersOpenStack";
                        break;

                    case "volume":
                        serviceName = "cloudBlockStorage";
                        break;

                    case "object-store":
                        serviceName = "cloudFiles";
                        break;

                    case "database":
                        serviceType = "rax:database";
                        serviceName = "cloudDatabases";
                        break;

                    case "queuing":
                        serviceType = "rax:queues";
                        serviceName = "cloudQueues";
                        break;
                }
            }

            return base.GetBaseAddressImpl(access, serviceType, serviceName, region, internalAddress);
        }
        /// <inheritdoc/>
        /// <remarks>
        /// This implementation returns <paramref name="region"/> if it is non-<see langword="null"/>. If
        /// <paramref name="region"/> is <see langword="null"/>, the default region for the specified
        /// <see cref="Access"/> is returned if available; otherwise, this method returns <see langword="null"/>.
        /// </remarks>
        protected override string GetEffectiveRegion(Access access, string region)
        {
            string effectiveRegion = base.GetEffectiveRegion(access, region);
            if (effectiveRegion == null)
                effectiveRegion = access.User.GetDefaultRegion();

            return effectiveRegion;
        }
        /// <summary>
        /// This method provides the core implementation of <see cref="GetBaseAddressAsync"/> after the
        /// <see cref="OpenStack.Services.Identity.V2.Access"/> details are obtained from the Identity Service.
        /// </summary>
        /// <param name="access">An <see cref="OpenStack.Services.Identity.V2.Access"/> object containing the details
        /// for the authenticated user.</param>
        /// <param name="serviceType">The service type to locate.</param>
        /// <param name="serviceName">The preferred name of the service.</param>
        /// <param name="region">The preferred region for the service. This method calls
        /// <see cref="GetEffectiveRegion"/> with this value to obtain the actual region to consider for this
        /// algorithm.</param>
        /// <param name="internalAddress">
        /// <para><see langword="true"/> to return a base address for accessing the service over a local network.</para>
        /// <para>-or-</para>
        /// <para><see langword="false"/> to return a base address for accessing the service over a public network (the
        /// Internet).</para></param>
        /// <returns>A <see cref="Uri"/> containing the absolute base address for accessing the service.</returns>
        /// <exception cref="ArgumentNullException">
        /// <para>If <paramref name="access"/> is <see langword="null"/>.</para>
        /// <para>-or-</para>
        /// <para>If <paramref name="serviceType"/> is <see langword="null"/>.</para>
        /// </exception>
        /// <exception cref="ArgumentException">If <paramref name="serviceType"/> is empty.</exception>
        protected virtual Uri GetBaseAddressImpl(Access access, string serviceType, string serviceName, string region, bool internalAddress)
        {
            if (access == null)
                throw new ArgumentNullException("access");
            if (serviceType == null)
                throw new ArgumentNullException("serviceType");
            if (string.IsNullOrEmpty(serviceType))
                throw new ArgumentException("serviceType cannot be empty", "serviceType");

            if (access.ServiceCatalog == null)
                throw new InvalidOperationException("The authentication information provided by the Identity Service did not include a service catalog.");

            List<ServiceCatalogEntry> services = access.ServiceCatalog.Where(sc => string.Equals(sc.Type, serviceType, StringComparison.OrdinalIgnoreCase)).ToList();
            if (services.Count == 0)
                throw new InvalidOperationException(string.Format("The service catalog provided by the Identity Service did not include any service with the type '{0}'.", serviceType));

            if (serviceName != null)
            {
                // If any service matches the preferred name, filter the entire list. Otherwise, simply ignore the
                // preferred name and continue.
                List<ServiceCatalogEntry> namedServices = services.Where(sc => string.Equals(sc.Name, serviceName, StringComparison.OrdinalIgnoreCase)).ToList();
                if (namedServices.Count > 0)
                    services = namedServices;
            }

            // Treat each endpoint individually for the purpose of endpoint selection.
            List<Tuple<ServiceCatalogEntry, Endpoint>> endpoints =
                services.SelectMany(service => service.Endpoints.Select(endpoint => Tuple.Create(service, endpoint))).ToList();

            string effectiveRegion = GetEffectiveRegion(access, region);

            // Locate all endpoints in the effective region. (Note that null and empty are equivalent for regions.)
            List<Tuple<ServiceCatalogEntry, Endpoint>> regionEndpoints =
                endpoints.Where(i => string.Equals(i.Item2.Region ?? string.Empty, effectiveRegion ?? string.Empty, StringComparison.OrdinalIgnoreCase)).ToList();

            // Use filtered results if possible, otherwise only consider "global" endpoints.
            if (regionEndpoints.Count > 0)
                endpoints = regionEndpoints;
            else
                endpoints.RemoveAll(i => !string.IsNullOrEmpty(i.Item2.Region));

            if (effectiveRegion == null && !endpoints.Any())
                throw new InvalidOperationException("No region was provided, no default region is available for the current credentials, and the service does not provide a region-independent endpoint.");

            Tuple<ServiceCatalogEntry, Endpoint> serviceEndpoint = endpoints.FirstOrDefault();
            if (internalAddress)
                serviceEndpoint = endpoints.FirstOrDefault(i => i.Item2.InternalUrl != null);
            else
                serviceEndpoint = endpoints.FirstOrDefault(i => i.Item2.PublicUrl != null);

            if (serviceEndpoint == null)
                throw new InvalidOperationException("No endpoint matching the specified parameters was located in the service catalog.");

            Uri baseAddress;
            if (internalAddress)
                baseAddress = serviceEndpoint.Item2.InternalUrl;
            else
                baseAddress = serviceEndpoint.Item2.PublicUrl;

            Uri adjustedBaseAddress = FilterBaseAddress(baseAddress);
            return adjustedBaseAddress;
        }
        /// <summary>
        /// Gets the effective region to use for locating services in the service catalog, for the specified
        /// <see cref="OpenStack.Services.Identity.V2.Access"/> information and preferred <paramref name="region"/>.
        /// </summary>
        /// <remarks>
        /// The default implementation simply returns <paramref name="region"/>. Specific vendors may extend this
        /// functionality to provide a default value or other region selections as appropriate for their users and
        /// service offerings.
        /// </remarks>
        /// <param name="access">The <see cref="OpenStack.Services.Identity.V2.Access"/> object providing details for
        /// the authenticated user.</param>
        /// <param name="region">The preferred region, as specified in the call to
        /// <see cref="GetBaseAddressAsync"/>.</param>
        /// <returns>The effective region to use for service location in <see cref="GetBaseAddressImpl"/>.</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="access"/> is <see langword="null"/>.</exception>
        protected virtual string GetEffectiveRegion(Access access, string region)
        {
            if (access == null)
                throw new ArgumentNullException("access");

            return region;
        }
        /// <summary>
        /// Authenticates the credentials provided in <see cref="AuthenticationRequest"/> with the OpenStack Identity
        /// Service V2.
        /// </summary>
        /// <remarks>
        /// <para>This method caches the authentication result, and returns the cached result of a previous
        /// authentication request when possible to avoid unnecessary calls to the Identity API. If a cached
        /// authentication result is available but has either expired or will expire soon (see
        /// <see cref="ExpirationOverlap"/>), the cached result is discarded and the credentials are re-authenticated
        /// with the Identity Service.</para>
        /// </remarks>
        /// <param name="cancellationToken">The <see cref="CancellationToken"/> that the task will observe.</param>
        /// <returns>
        /// A <see cref="Task"/> representing the asynchronous operation. When the task completes successfully, the
        /// <see cref="Task{TResult}.Result"/> property will contain an <see cref="Access"/> instance providing the
        /// authentication result.
        /// </returns>
        /// <exception cref="HttpWebException">
        /// If an error occurs during an HTTP request as part of authenticating with the Identity API.
        /// </exception>
        protected virtual Task<Access> AuthenticateAsync(CancellationToken cancellationToken)
        {
            Access access = Access;
            if (access != null && access.Token != null)
            {
                Token token = access.Token;
                // Note: this code uses lifting to null to cover the case where token.ExpiresAt is null
                DateTimeOffset? effectiveExpiration = token.ExpiresAt - ExpirationOverlap;
                if (effectiveExpiration > DateTimeOffset.Now)
                    return CompletedTask.FromResult(access);
            }

            return
                IdentityService.AuthenticateAsync(AuthenticationRequest, cancellationToken)
                .Select(
                    task =>
                    {
                        _access = task.Result;
                        return task.Result;
                    });
        }