/// <summary>
        /// Verifies the extension rule.
        /// </summary>
        /// <param name="context">The Interop service context</param>
        /// <param name="info">out parameter to return violation information when rule does not pass</param>
        /// <returns>true if rule passes; false otherwise</returns>
        public override bool?Verify(ServiceContext context, out ExtensionRuleViolationInfo info)
        {
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }

            info = null;
            bool?passed = null;

            Uri uriEmptyFeed = FeedCore2000.ConstructProbeUri(context);
            var resp         = WebHelper.Get(uriEmptyFeed, Constants.AcceptHeaderJson, RuleEngineSetting.Instance().DefaultMaximumPayloadSize, context.RequestHeaders);

            JObject response = JsonParserHelper.GetResponseObject(resp.ResponsePayload);

            if (response != null)
            {
                JArray entries = JsonParserHelper.GetEntries(response);
                if (entries.Count == 0)
                {
                    passed = true;
                }
                else
                {
                    passed = false;
                    info   = new ExtensionRuleViolationInfo(Resource.NotEmptyFeed, uriEmptyFeed, resp.ResponsePayload);
                }
            }

            return(passed);
        }
        /// <summary>
        /// Checks whether __next name-value pair is included in the (json) response payload
        /// </summary>
        /// <param name="context">The service context object</param>
        /// <returns>True if it is found in the response; false otherwise</returns>
        private static bool IsNextLinkPresent(ServiceContext context)
        {
            bool hasNextLink = false;

            JObject feedV2 = JsonParserHelper.GetResponseObject(context);

            if (feedV2 != null && feedV2.Count == 1)
            {
                var d = (JProperty)feedV2.First;
                if (d.Name.Equals("d", StringComparison.Ordinal))
                {
                    var sub = d.Value;
                    if (sub.Type == JTokenType.Object)
                    {
                        // looking for property of name-value pair of "__next:..."
                        JObject resultObject = (JObject)sub;

                        var nextLinks = from p in resultObject.Children <JProperty>()
                                        where p.Name.Equals("__next", StringComparison.Ordinal)
                                        select p;

                        hasNextLink = nextLinks.Any();
                    }
                }
            }

            return(hasNextLink);
        }
        /// <summary>
        /// Checks whether the context payload is a partial collection of entrities
        /// </summary>
        /// <param name="context">The service context</param>
        /// <returns>True if it is partial collection; false otherwise</returns>
        private static bool IsPartialColleclection(ServiceContext context)
        {
            bool anyNewEntry = false;

            JObject contextResponse = JsonParserHelper.GetResponseObject(context);

            if (contextResponse != null)
            {
                JArray contextEntries = JsonParserHelper.GetEntries(contextResponse);
                if (contextEntries.Count > 0)
                {
                    // if any more entries return, the context response payload was a partial collection
                    Uri prober = FeedCore2001.ConstructProbeUri(context);
                    var resp   = WebHelper.Get(prober, Constants.AcceptHeaderJson, RuleEngineSetting.Instance().DefaultMaximumPayloadSize, context.RequestHeaders);

                    if (resp == null || string.IsNullOrEmpty(resp.ResponsePayload) || !JsonHelper.IsJsonVerboseFeed(resp.ResponsePayload))
                    {
                        return(false);
                    }

                    JObject response = JsonParserHelper.GetResponseObject(resp.ResponsePayload);
                    if (response != null)
                    {
                        JArray entries = JsonParserHelper.GetEntries(response);

                        if (entries.Count > 0)
                        {
                            // some producers do not respect $skipton;
                            // need to look at each entry to see whether there is any new entry.
                            HashSet <string> contextEntryKeys = new HashSet <string>(EqualityComparer <string> .Default);
                            foreach (var e in contextEntries)
                            {
                                if (e.Type == JTokenType.Object)
                                {
                                    var i = JsonParserHelper.GetTokenOfEntry((JObject)e);
                                    contextEntryKeys.Add(i);
                                }
                            }

                            foreach (var e in entries)
                            {
                                if (e.Type == JTokenType.Object)
                                {
                                    var i = JsonParserHelper.GetTokenOfEntry((JObject)e);
                                    if (!contextEntryKeys.Contains(i))
                                    {
                                        anyNewEntry = true;
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }
            }

            return(anyNewEntry);
        }
        /// <summary>
        /// Verify the rule
        /// </summary>
        /// <param name="context">Service context</param>
        /// <param name="info">out parameter to return violation information when rule fail</param>
        /// <returns>true if rule passes; false otherwise</returns>
        public override bool?Verify(ServiceContext context, out ExtensionRuleViolationInfo info)
        {
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }

            bool?passed = null;

            info = null;

            // if __metadata.etag property is there, ensure its value equals to ETag header value
            var  entry       = JsonParserHelper.GetResponseObject(context);
            var  etag        = entry.GetPropertyOfChild("__metadata", "etag");
            bool etagInPlace = etag != null;

            if (etagInPlace)
            {
                var etagInHeader             = context.ResponseHttpHeaders.GetHeaderValue("ETag").Trim();
                var etagLiteral              = StringHelper.ToLiteral(etagInHeader);
                RuleEngine.TestResult result = null;

                ODataVersion version = JsonParserHelper.GetPayloadODataVersion(entry);
                switch (version)
                {
                case ODataVersion.V1:
                    string schemaV1 = string.Format(EntryCore2009.schemaFormat_v1, etagLiteral);
                    passed = JsonParserHelper.ValidateJson(schemaV1, context.ResponsePayload, out result);
                    break;

                case ODataVersion.V2:
                    string schemaV2 = string.Format(EntryCore2009.schemaFormat_v2, etagLiteral);
                    passed = JsonParserHelper.ValidateJson(schemaV2, context.ResponsePayload, out result);
                    break;

                default:
                    passed = false;
                    break;
                }

                if (!passed.Value)
                {
                    info = new ExtensionRuleViolationInfo(this.ErrorMessage, context.Destination, context.ResponsePayload, result != null ? result.LineNumberInError : -1);
                }
            }

            return(passed);
        }
        /// <summary>
        /// Builds the uri for purpose of probing any remaining entries satisfying the request of service context
        /// use the last entry's token in the feed as the marker of skiptoken;
        /// and reduce the $top count if top was in context request URI.
        /// </summary>
        /// <param name="context">The service context object</param>
        /// <returns>Uri object if a meaningful prober can be constructed; null otherwise </returns>
        private static Uri ConstructProbeUri(ServiceContext context)
        {
            Uri result = null;

            JObject response = JsonParserHelper.GetResponseObject(context);

            if (response != null)
            {
                JArray entries = JsonParserHelper.GetEntries(response);
                if (entries != null)
                {
                    JObject lastEntry = JsonParserHelper.GetLastEntry(entries);
                    if (lastEntry != null)
                    {
                        string lastToken         = JsonParserHelper.GetTokenOfEntry(lastEntry);
                        string lastTokenOfValues = ResourcePathHelper.GetValuesOfKey(lastToken);

                        var uri = context.Destination;
                        ResourcePathHelper pathHelper = new ResourcePathHelper(uri);

                        // replace top value with the reduced value, if $top was in context request
                        string topValue = pathHelper.GetQueryValue("$top");
                        if (!string.IsNullOrEmpty(topValue))
                        {
                            pathHelper.RemoveQueryOption("$top");

                            int entriesGot = entries.Count;
                            int entriesToGet;
                            if (Int32.TryParse(topValue, out entriesToGet) && entriesToGet > entriesGot)
                            {
                                int entriesLeft = entriesToGet - entriesGot;
                                pathHelper.AddQueryOption("$top", entriesLeft.ToString(CultureInfo.InvariantCulture));
                            }
                        }

                        // set up new skiptoken query
                        pathHelper.RemoveQueryOption("$skiptoken");
                        pathHelper.AddQueryOption("$skiptoken", lastTokenOfValues);

                        result = new Uri(pathHelper.Product);
                    }
                }
            }

            return(result);
        }