/// <summary>
        /// Gets <see cref="PartitionKeyRange"/> instance which corresponds to <paramref name="rangeFromContinuationToken"/>
        /// </summary>
        /// <param name="providedPartitionKeyRanges"></param>
        /// <param name="routingMapProvider"></param>
        /// <param name="collectionRid"></param>
        /// <param name="rangeFromContinuationToken"></param>
        /// <param name="suppliedTokens"></param>
        /// <param name="direction"></param>
        /// <returns>null if collection with specified <paramref name="collectionRid"/> doesn't exist, which potentially means
        /// that collection was resolved to outdated Rid by name. Also null can be returned if <paramref name="rangeFromContinuationToken"/>
        /// is not found - this means it was split.
        /// </returns>
        public virtual async Task <ResolvedRangeInfo> TryGetTargetRangeFromContinuationTokenRangeAsync(
            IReadOnlyList <Range <string> > providedPartitionKeyRanges,
            IRoutingMapProvider routingMapProvider,
            string collectionRid,
            Range <string> rangeFromContinuationToken,
            List <CompositeContinuationToken> suppliedTokens,
            RntdbEnumerationDirection direction = RntdbEnumerationDirection.Forward)
        {
            // For queries such as "SELECT * FROM root WHERE false",
            // we will have empty ranges and just forward the request to the first partition
            if (providedPartitionKeyRanges.Count == 0)
            {
                return(new ResolvedRangeInfo(
                           await routingMapProvider.TryGetRangeByEffectivePartitionKeyAsync(
                               collectionRid,
                               PartitionKeyInternal.MinimumInclusiveEffectivePartitionKey),
                           suppliedTokens));
            }

            // Initially currentRange will be empty
            if (rangeFromContinuationToken.IsEmpty)
            {
                if (direction == RntdbEnumerationDirection.Reverse)
                {
                    PartitionKeyRange lastPartitionKeyRange = (await routingMapProvider.TryGetOverlappingRangesAsync(collectionRid, providedPartitionKeyRanges.Single())).Last();
                    return(new ResolvedRangeInfo(
                               lastPartitionKeyRange,
                               suppliedTokens));
                }

                Range <string> minimumRange = PartitionRoutingHelper.Min(
                    providedPartitionKeyRanges,
                    Range <string> .MinComparer.Instance);

                return(new ResolvedRangeInfo(
                           await routingMapProvider.TryGetRangeByEffectivePartitionKeyAsync(collectionRid, minimumRange.Min),
                           suppliedTokens));
            }

            PartitionKeyRange targetPartitionKeyRange = await routingMapProvider.TryGetRangeByEffectivePartitionKeyAsync(collectionRid, rangeFromContinuationToken.Min);

            if (targetPartitionKeyRange == null)
            {
                return(new ResolvedRangeInfo(null, suppliedTokens));
            }

            if (!rangeFromContinuationToken.Equals(targetPartitionKeyRange.ToRange()))
            {
                // Cannot find target range. Either collection was resolved incorrectly or the range was split
                List <PartitionKeyRange> replacedRanges = (await routingMapProvider.TryGetOverlappingRangesAsync(collectionRid, rangeFromContinuationToken, true)).ToList();

                if (replacedRanges == null || replacedRanges.Count < 1)
                {
                    return(new ResolvedRangeInfo(null, null));
                }
                else
                {
                    if (!(replacedRanges[0].MinInclusive.Equals(rangeFromContinuationToken.Min) && replacedRanges[replacedRanges.Count - 1].MaxExclusive.Equals(rangeFromContinuationToken.Max)))
                    {
                        return(new ResolvedRangeInfo(null, null));
                    }
                }

                if (direction == RntdbEnumerationDirection.Reverse)
                {
                    replacedRanges.Reverse();
                }

                List <CompositeContinuationToken> continuationTokensToBePersisted = null;

                if (suppliedTokens != null && suppliedTokens.Count > 0)
                {
                    continuationTokensToBePersisted = new List <CompositeContinuationToken>(replacedRanges.Count + suppliedTokens.Count - 1);

                    foreach (PartitionKeyRange partitionKeyRange in replacedRanges)
                    {
                        CompositeContinuationToken token = (CompositeContinuationToken)suppliedTokens[0].ShallowCopy();
                        token.Range = partitionKeyRange.ToRange();
                        continuationTokensToBePersisted.Add(token);
                    }

                    continuationTokensToBePersisted.AddRange(suppliedTokens.Skip(1));
                }

                return(new ResolvedRangeInfo(replacedRanges[0], continuationTokensToBePersisted));
            }

            return(new ResolvedRangeInfo(targetPartitionKeyRange, suppliedTokens));
        }
        public virtual Range <string> ExtractPartitionKeyRangeFromContinuationToken(INameValueCollection headers, out List <CompositeContinuationToken> compositeContinuationTokens)
        {
            if (headers == null)
            {
                throw new ArgumentNullException("headers");
            }

            compositeContinuationTokens = null;

            Range <string> range = Range <string> .GetEmptyRange(PartitionKeyInternal.MinimumInclusiveEffectivePartitionKey);

            if (string.IsNullOrEmpty(headers[HttpConstants.HttpHeaders.Continuation]))
            {
                return(range);
            }

            string providedContinuation = headers[HttpConstants.HttpHeaders.Continuation];
            CompositeContinuationToken initialContinuationToken = null;

            if (!string.IsNullOrEmpty(providedContinuation))
            {
                try
                {
                    if (providedContinuation.Trim().StartsWith("[", StringComparison.Ordinal))
                    {
                        compositeContinuationTokens = JsonConvert.DeserializeObject <List <CompositeContinuationToken> >(providedContinuation);

                        if (compositeContinuationTokens != null && compositeContinuationTokens.Count > 0)
                        {
                            headers[HttpConstants.HttpHeaders.Continuation] = compositeContinuationTokens[0].Token;
                            initialContinuationToken = compositeContinuationTokens[0];
                        }
                        else
                        {
                            headers.Remove(HttpConstants.HttpHeaders.Continuation);
                        }
                    }
                    else
                    {
                        // TODO: Remove the else logic after the gateway deployment is complete
                        initialContinuationToken = JsonConvert.DeserializeObject <CompositeContinuationToken>(providedContinuation);
                        if (initialContinuationToken != null)
                        {
                            compositeContinuationTokens = new List <CompositeContinuationToken> {
                                initialContinuationToken
                            };
                        }
                        else
                        {
                            throw new BadRequestException(RMResources.InvalidContinuationToken);
                        }
                    }

                    if (initialContinuationToken != null && initialContinuationToken.Range != null)
                    {
                        range = initialContinuationToken.Range;
                    }

                    if (initialContinuationToken != null && !string.IsNullOrEmpty(initialContinuationToken.Token))
                    {
                        headers[HttpConstants.HttpHeaders.Continuation] = initialContinuationToken.Token;
                    }
                    else
                    {
                        headers.Remove(HttpConstants.HttpHeaders.Continuation);
                    }
                }
                catch (JsonException ex)
                {
                    DefaultTrace.TraceWarning(
                        string.Format(
                            CultureInfo.InvariantCulture,
                            "{0} Invalid JSON in the continuation token {1}",
                            DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture),
                            providedContinuation));
                    throw new BadRequestException(RMResources.InvalidContinuationToken, ex);
                }
            }
            else
            {
                headers.Remove(HttpConstants.HttpHeaders.Continuation);
            }

            return(range);
        }
        private void AddFormattedContinuationHeaderHelper(AddFormattedContinuationToHeaderTestUnit positiveTestData, out INameValueCollection headers, out List <PartitionKeyRange> resolvedRanges, out List <CompositeContinuationToken> resolvedContinuationTokens)
        {
            Func <string, INameValueCollection> getHeadersWithContinuation = (string continuationToken) =>
            {
                INameValueCollection localHeaders = new DictionaryNameValueCollection();
                if (continuationToken != null)
                {
                    localHeaders[HttpConstants.HttpHeaders.Continuation] = continuationToken;
                }
                return(localHeaders);
            };

            resolvedRanges = positiveTestData.ResolvedRanges.Select(x => new PartitionKeyRange()
            {
                MinInclusive = x.Min, MaxExclusive = x.Max
            }).ToList();
            resolvedContinuationTokens = new List <CompositeContinuationToken>();

            CompositeContinuationToken[] initialContinuationTokens = null;
            if (!string.IsNullOrEmpty(positiveTestData.InputCompositeContinuationToken))
            {
                if (positiveTestData.InputCompositeContinuationToken.Trim().StartsWith("[", StringComparison.Ordinal))
                {
                    initialContinuationTokens = JsonConvert.DeserializeObject <CompositeContinuationToken[]>(positiveTestData.InputCompositeContinuationToken);
                }
                else
                {
                    initialContinuationTokens = new CompositeContinuationToken[] { JsonConvert.DeserializeObject <CompositeContinuationToken>(positiveTestData.InputCompositeContinuationToken) };
                }
            }

            if (resolvedRanges.Count > 1)
            {
                CompositeContinuationToken continuationToBeCopied;
                if (initialContinuationTokens != null && initialContinuationTokens.Length > 0)
                {
                    continuationToBeCopied = (CompositeContinuationToken)initialContinuationTokens[0].ShallowCopy();
                }
                else
                {
                    continuationToBeCopied       = new CompositeContinuationToken();
                    continuationToBeCopied.Token = string.Empty;
                }

                headers = getHeadersWithContinuation(continuationToBeCopied.Token);

                foreach (PartitionKeyRange pkrange in resolvedRanges)
                {
                    CompositeContinuationToken token = (CompositeContinuationToken)continuationToBeCopied.ShallowCopy();
                    token.Range = pkrange.ToRange();
                    resolvedContinuationTokens.Add(token);
                }

                if (initialContinuationTokens != null)
                {
                    resolvedContinuationTokens.AddRange(initialContinuationTokens.Skip(1));
                }
            }
            else
            {
                headers = getHeadersWithContinuation(null);
            }
        }