Example #1
0
        /// <summary>
        /// Adds language restriction to search query, if possible
        /// </summary>
        /// <param name="query">Entity query</param>
        /// <param name="booleanQuery">Search query</param>
        private void AddLanguageRestrictionToQuery(ICrmEntityQuery query, BooleanQuery booleanQuery)
        {
            if (string.IsNullOrEmpty(this.Index.LanguageLocaleCodeFieldName))
            {
                return;
            }

            // Multilanguage enabled, language code provided in query
            if (query.MultiLanguageEnabled && !string.IsNullOrEmpty(query.ContextLanguage.Code))
            {
                var languageQuery = new BooleanQuery();
                languageQuery.Add(new TermQuery(new Term(this.Index.LanguageLocaleCodeFieldName, query.ContextLanguage.Code.ToLowerInvariant())), Occur.SHOULD);
                languageQuery.Add(new TermQuery(new Term(this.Index.LanguageLocaleCodeFieldName, this.Index.LanguageLocaleCodeDefaultValue)), Occur.SHOULD);

                booleanQuery.Add(languageQuery, Occur.MUST);
            }
            //// Multilanguage disabled, KB language present in settings
            else if (!query.MultiLanguageEnabled && !string.IsNullOrWhiteSpace(this.Index.LanguageLocaleCode))
            {
                var languageQuery = new BooleanQuery();
                languageQuery.Add(new TermQuery(new Term(this.Index.LanguageLocaleCodeFieldName, this.Index.LanguageLocaleCode)), Occur.SHOULD);
                languageQuery.Add(new TermQuery(new Term(this.Index.LanguageLocaleCodeFieldName, this.Index.LanguageLocaleCodeDefaultValue)), Occur.SHOULD);

                booleanQuery.Add(languageQuery, Occur.MUST);
            }
            //// Otherwise, do not restrict the language
        }
        protected override Query CreateQuery(ICrmEntityQuery query)
        {
            var scopedQuery = query as IScopedEntityQuery;

            if (scopedQuery == null)
            {
                return(base.CreateQuery(query));
            }

            var scopeQuery = new BooleanQuery();

            foreach (var scope in scopedQuery.Scopes)
            {
                if (scope == null)
                {
                    continue;
                }

                scopeQuery.Add(new TermQuery(new Term(Index.ScopeFieldName, scope)), Occur.SHOULD);
            }

            scopeQuery.Add(new TermQuery(new Term(Index.ScopeFieldName, Index.ScopeDefaultValue)), Occur.SHOULD);

            var baseQuery = base.CreateQuery(scopedQuery);

            var compositeQuery = new BooleanQuery
            {
                { baseQuery, Occur.MUST },
                { scopeQuery, Occur.MUST }
            };

            return(compositeQuery);
        }
Example #3
0
        /// <summary>
        /// Gets the unprocessed results for a search query.
        /// </summary>
        /// <param name="query">The query</param>
        /// <param name="searchLimit">The max number of results we want</param>
        /// <param name="offset">The number of already processed results to skip.</param>
        /// <returns>The results of the search unfiltered for the user.</returns>
        protected virtual RawSearchResultSet GetRawSearchResults(ICrmEntityQuery query, int searchLimit, int rawOffset)
        {
            var stopwatch = new Stopwatch();

            stopwatch.Start();

            var     luceneQuery = CreateQuery(query);
            TopDocs topDocs;

            try
            {
                topDocs = _searcher.Search(luceneQuery, searchLimit);
            }
            catch (Exception e)
            {
                SearchEventSource.Log.QueryError(e);
                throw;
            }

            stopwatch.Stop();

            ADXTrace.Instance.TraceInfo(TraceCategory.Application, string.Format("Lucene: {0} total hits ({1}ms)", topDocs.TotalHits, stopwatch.ElapsedMilliseconds));

            PortalFeatureTrace.TraceInstance.LogSearch(FeatureTraceCategory.Search, topDocs.TotalHits, stopwatch.ElapsedMilliseconds, string.Format("Lucene: {0} total hits ({1}ms)", topDocs.TotalHits, stopwatch.ElapsedMilliseconds));

            return(ConvertTopDocsToRawSearchResultSet(topDocs, rawOffset));
        }
Example #4
0
        protected virtual Query CreateQuery(ICrmEntityQuery query)
        {
            var booleanQuery = new BooleanQuery();
            var queryParser  = new QueryParser(Index.Version, Index.ContentFieldName, Index.GetQuerySpecificAnalyzer(query.MultiLanguageEnabled, query.ContextLanguage));

            // The QueryText field is intended to be a user-submitted query, so we want to be forgiving in how
            // we parse it. If the query parser fails to parse the value, escape it and parse it again.
            if (!string.IsNullOrWhiteSpace(query.QueryText))
            {
                Query textQuery;

                try
                {
                    textQuery = queryParser.Parse(query.QueryText);
                }
                catch (ParseException)
                {
                    textQuery = queryParser.Parse(QueryParser.Escape(query.QueryText));
                }

                booleanQuery.Add(textQuery, Occur.MUST);
            }

            // The Filter field, on the other hand, is intended to be a provided by a developer/admin, and will
            // therefore be parsed strictly. If this query is invalid, a ParseException will be thrown.
            if (!string.IsNullOrWhiteSpace(query.Filter))
            {
                booleanQuery.Add(queryParser.Parse(query.Filter), Occur.MUST);
            }

            // If there is no user query text or filter, return no results. (A BooleanQuery with no clauses does this.)
            if (!booleanQuery.Any())
            {
                return(new BooleanQuery());
            }

            if (query.LogicalNames.Any())
            {
                var logicalNameQuery = new BooleanQuery();

                foreach (var logicalName in query.LogicalNames)
                {
                    if (string.IsNullOrEmpty(logicalName))
                    {
                        continue;
                    }

                    logicalNameQuery.Add(new TermQuery(new Term(Index.LogicalNameFieldName, logicalName)), Occur.SHOULD);
                }

                booleanQuery.Add(logicalNameQuery, Occur.MUST);
            }

            this.AddLanguageRestrictionToQuery(query, booleanQuery);

            return(booleanQuery);
        }
Example #5
0
        /// <summary>
        /// Gets the sort field.
        /// </summary>
        /// <param name="query">The query.</param>
        /// <returns>Sort Field</returns>
        private SortField[] GetSortField(ICrmEntityQuery query)
        {
            var sortingOption = query.SortingOption;

            if (sortingOption == SortingFields.Rating || sortingOption == SortingFields.ViewCount)
            {
                return(new SortField[] { new SortField(sortingOption, SortField.DOUBLE, true) });
            }

            return(new SortField[] { SortField.FIELD_SCORE });
        }
Example #6
0
        public virtual ICrmEntitySearchResultPage Search(ICrmEntityQuery query)
        {
            ADXTrace.Instance.TraceInfo(TraceCategory.Application, string.Format(@"query=(PageNumber={0},PageSize={1},LogicalNames=({2}))", query.PageNumber, query.PageSize, string.Join(",", query.LogicalNames.ToArray())));

            var pageNumber = query.PageNumber;

            if (pageNumber < 1)
            {
                ADXTrace.Instance.TraceInfo(TraceCategory.Application, "Page number cannot be less than 1. Forcing PageNumber to 1.");

                pageNumber = 1;
            }

            var pageSize = query.PageSize;

            if (pageSize < 1)
            {
                ADXTrace.Instance.TraceInfo(TraceCategory.Application, "Page size cannot be less than 1. Forcing PageSize to 1.");

                pageSize = 1;
            }

            var luceneQuery = CreateQuery(query);

            var resultFactory = Index.GetSearchResultFactory(luceneQuery);

            var results = new List <ICrmEntitySearchResult>();

            // We add a +1 to the searchLimit and resultLimit so as to try and go one result beyond the requested result page, so that
            // approximateTotalHits will reflect whether there is at least one further valid/readable result beyond the current page.
            // This eliminates the edge case where a user gets a full page of results, the total hits indicates there are more results,
            // but there actually aren't any, leading to a blank final page of results.
            var userResults = GetUserSearchResults(
                query,
                ((pageSize * pageNumber) * InitialSearchLimitMultiple) + 1,
                0,
                (pageSize * pageNumber) + 1,
                resultFactory,
                pageNumber,
                pageSize,
                results);


            // sprinkle these calls in for whichever events we want to trace
            if (FeatureCheckHelper.IsFeatureEnabled(FeatureNames.CustomerJourneyTracking))
            {
                var    queryStringArray = query.QueryText.Split('(', ')');
                string queryString      = queryStringArray.Length > 1 ? queryStringArray[1] : query.QueryText;
                PortalTrackingTrace.TraceInstance.Log(Constants.Search, queryString, string.Empty);
            }

            return(userResults);
        }
Example #7
0
        public SearchDataSourceSelectingEventArgs(SearchProvider provider, ICrmEntityQuery query)
        {
            if (provider == null)
            {
                throw new ArgumentNullException("provider");
            }

            if (query == null)
            {
                throw new ArgumentNullException("query");
            }

            Provider = provider;
            Query    = query;
        }
        public SearchIndexQueryDrop(IPortalLiquidContext portalLiquidContext, SearchProvider searchProvider, ICrmEntityQuery query) : base(portalLiquidContext)
        {
            if (searchProvider == null)
            {
                throw new ArgumentNullException("searchProvider");
            }
            if (query == null)
            {
                throw new ArgumentNullException("query");
            }

            SearchProvider = searchProvider;
            Query          = query;

            _resultPage = new Lazy <ICrmEntitySearchResultPage>(GetResultPage, LazyThreadSafetyMode.None);
            _results    = new Lazy <SearchIndexQueryResultDrop[]>(GetResults, LazyThreadSafetyMode.None);
        }
            public ICrmEntitySearchResultPage Search(ICrmEntityQuery query)
            {
                var portal             = PortalCrmConfigurationManager.CreatePortalContext(_portalName);
                var serviceContext     = PortalCrmConfigurationManager.CreateServiceContext(_portalName);
                var dependencyProvider = PortalCrmConfigurationManager.CreateDependencyProvider(_portalName);
                var security           = GetSecurityAssertion(_portalName);

                var metadataCache = new Dictionary <string, EntityMetadata>();

                var paginator = new TopPaginator <ICrmEntitySearchResult>(
                    query.PageSize,
                    top => GetTopSearchResults(top, query, serviceContext, portal, security, dependencyProvider, entityName => GetEntityMetadata(serviceContext, entityName, metadataCache)),
                    result => result != null);

                var results = paginator.GetPage(query.PageNumber);

                return(new CrmEntitySearchResultPage(results, results.TotalUnfilteredItems, query.PageNumber, query.PageSize));
            }
Example #10
0
        /// <summary>
        /// Override to get faceted results
        /// </summary>
        /// <param name="query">The query</param>
        /// <param name="searchLimit">The max number of results we want</param>
        /// <param name="offset">The number of already processed results to skip.</param>
        /// <returns>The results of the search unfiltered for the user.</returns>
        protected override RawSearchResultSet GetRawSearchResults(ICrmEntityQuery query, int searchLimit, int offset)
        {
            var stopwatch = new Stopwatch();

            stopwatch.Start();

            var br = new BrowseRequest();

            br.Count  = searchLimit;
            br.Sort   = this.GetSortField(query);
            br.Query  = this.CreateQuery(query);
            br.Offset = offset;

            this.AddFacetConstraints(br, query.FacetConstraints);

            // add preconfigured facet specs
            foreach (var fieldToSpec in this.specs)
            {
                br.SetFacetSpec(fieldToSpec.Key, fieldToSpec.Value);
            }

            // execute the query
            IBrowsable browser = new BoboBrowser(this.boboReader);

            foreach (var facetConfiguration in this.config.GetConfiguredFacets())
            {
                if (facetConfiguration.FacetHandlerType == FacetHandlerType.Dynamic)
                {
                    browser.SetFacetHandler(facetConfiguration.FacetHandler);
                }
            }
            var browseResult = browser.Browse(br);

            stopwatch.Stop();

            ADXTrace.Instance.TraceInfo(TraceCategory.Application, string.Format("Lucene(faceted/BoboBrowse): {0} total hits ({1}ms)", browseResult.NumHits, stopwatch.ElapsedMilliseconds));

            PortalFeatureTrace.TraceInstance.LogSearch(FeatureTraceCategory.Search, browseResult.NumHits, stopwatch.ElapsedMilliseconds, string.Format("Lucene(faceted/BoboBrowse): {0} total hits ({1}ms)", browseResult.NumHits, stopwatch.ElapsedMilliseconds));

            return(this.ConvertBoboBrowseResultsToRawSearchResultSet(browseResult, offset, query.FacetConstraints));
        }
Example #11
0
        public ICrmEntitySearchResultPage Search(ICrmEntityQuery query)
        {
            var rawResults = _searcher.Search(query);

            var infoCache     = new Dictionary <string, ExtendedAttributeSearchResultInfo>();
            var metadataCache = new Dictionary <string, EntityMetadata>();

            var results = rawResults.Select(result =>
            {
                var info = GetExtendedAttributeInfo(_dataContextName, result.Entity.LogicalName, infoCache, metadataCache);

                var extendedAttributes = info.GetAttributes(result.Entity, metadataCache);

                return(new CrmEntitySearchResult(result.Entity, result.Score, result.ResultNumber, result.Title, result.Url, extendedAttributes)
                {
                    Fragment = result.Fragment
                } as ICrmEntitySearchResult);
            });

            return(new CrmEntitySearchResultPage(results, rawResults.ApproximateTotalHits, rawResults.PageNumber, rawResults.PageSize));
        }
Example #12
0
        public SearchDataSourceStatusEventArgs(SearchProvider provider, ICrmEntityQuery query, ICrmEntitySearchResultPage results)
        {
            if (provider == null)
            {
                throw new ArgumentNullException("provider");
            }

            if (query == null)
            {
                throw new ArgumentNullException("query");
            }

            if (results == null)
            {
                throw new ArgumentNullException("results");
            }

            Provider = provider;
            Query    = query;
            Results  = results;
        }
        public ICrmEntitySearchResultPage Search(ICrmEntityQuery query)
        {
            // If we don't get a read lock quickly, just fail fast to constructing and using a new index searcher.
            if (!_searcherLock.TryEnterReadLock(TimeSpan.FromSeconds(1)))
            {
                ADXTrace.Instance.TraceInfo(TraceCategory.Application, "Failed to acquire read lock on shared index searcher. Creating new single-use index searcher.");

                using (var searcher = _searcherFactory())
                {
                    return(searcher.Search(query));
                }
            }

            try
            {
                return(_searcher.Search(query));
            }
            finally
            {
                _searcherLock.ExitReadLock();
            }
        }
            private TopPaginator <ICrmEntitySearchResult> .Top GetTopSearchResults(int top, ICrmEntityQuery query, OrganizationServiceContext serviceContext, IPortalContext portal, Func <OrganizationServiceContext, Entity, bool> assertSecurity, IDependencyProvider dependencyProvider, Func <string, EntityMetadata> getEntityMetadata)
            {
                EntitySearchResultPage searchResponse;

                try
                {
                    searchResponse = _service.Search(query.QueryText, 1, top, string.Join(",", query.LogicalNames.ToArray()), portal.Website.Id.ToString(), query.Filter);
                }
                catch (Exception e)
                {
                    ADXTrace.Instance.TraceError(TraceCategory.Application, string.Format("Service exception: {0}", e.ToString()));

                    throw;
                }

                if (searchResponse.IndexNotFound)
                {
                    throw new IndexNotFoundException("Search index not found. Please ensure that the search index is constructed before attempting a query.");
                }

                var currentResultNumber = 0;

                var items = searchResponse.Results.Select(result =>
                {
                    var metadata = getEntityMetadata(result.EntityLogicalName);

                    if (metadata == null)
                    {
                        return(null);
                    }

                    var entity = serviceContext.CreateQuery(metadata.LogicalName)
                                 .FirstOrDefault(e => e.GetAttributeValue <Guid>(metadata.PrimaryIdAttribute) == result.EntityID);

                    if (entity == null)
                    {
                        return(null);
                    }

                    if (!assertSecurity(serviceContext, entity))
                    {
                        return(null);
                    }

                    var urlProvider = dependencyProvider.GetDependency <IEntityUrlProvider>();

                    var path = urlProvider.GetUrl(serviceContext, entity);

                    if (path == null)
                    {
                        return(null);
                    }

                    Uri url;

                    if (!Uri.TryCreate(path, UriKind.RelativeOrAbsolute, out url))
                    {
                        return(null);
                    }

                    currentResultNumber++;

                    return(new CrmEntitySearchResult(entity, result.Score, currentResultNumber, result.Title, url)
                    {
                        Fragment = result.Fragment
                    });
                });

                return(new TopPaginator <ICrmEntitySearchResult> .Top(items, searchResponse.ApproximateTotalHits));
            }
        public ICrmEntitySearchResultPage Search(ICrmEntityQuery query)
        {
            var logicalNames = query.LogicalNames.Any() ? string.Join(",", query.LogicalNames.ToArray()) : string.Empty;

            return(Channel.Search(query.QueryText, query.PageNumber, query.PageSize, logicalNames, SearchProvider));
        }
Example #16
0
        /// <summary>
        /// Overrides Search behavior to do faceted search with BoboBrowse.Net
        /// </summary>
        /// <param name="query">
        /// The search query.
        /// </param>
        /// <returns>
        /// The <see cref="Query"/>.
        /// </returns>
        protected override Query CreateQuery(ICrmEntityQuery query)
        {
            if (FeatureCheckHelper.IsFeatureEnabled(FeatureNames.CmsEnabledSearching))
            {
                var baseQuery      = base.CreateQuery(query);
                var compositeQuery = new BooleanQuery()
                {
                    { baseQuery, Occur.MUST }
                };
                var contentAccessLevelProvider = new ContentAccessLevelProvider();

                compositeQuery.Add(new TermQuery(new Term("_logicalname", "annotation")), Occur.MUST_NOT);

                if (contentAccessLevelProvider.IsEnabled())
                {
                    var calQuery = new BooleanQuery();
                    var userCals = contentAccessLevelProvider.GetContentAccessLevels();

                    ADXTrace.Instance.TraceInfo(TraceCategory.Monitoring, "Adding User CALs to Lucene query");

                    foreach (var cal in userCals)
                    {
                        ADXTrace.Instance.TraceInfo(TraceCategory.Monitoring, string.Format("User CAL {0}", cal.Id));
                        calQuery.Add(new TermQuery(new Term(FixedFacetsConfiguration.ContentAccessLevel, cal.Id.ToString())), Occur.SHOULD);
                    }
                    calQuery.Add(new TermQuery(new Term(FixedFacetsConfiguration.ContentAccessLevel, "public")), Occur.SHOULD);

                    compositeQuery.Add(calQuery, Occur.MUST);
                }

                var productAccessProvider = new ProductAccessProvider();

                if (productAccessProvider.IsEnabled())
                {
                    var productFilteringQuery = new BooleanQuery
                    {
                        {
                            new TermQuery(
                                new Term(FixedFacetsConfiguration.ProductFieldFacetName, this.Index.ProductAccessNonKnowledgeArticleDefaultValue)),
                            Occur.SHOULD
                        }
                    };
                    var userProducts = productAccessProvider.GetProducts();
                    ADXTrace.Instance.TraceInfo(TraceCategory.Monitoring, "Adding User products to Lucene query");

                    foreach (var product in userProducts)
                    {
                        ADXTrace.Instance.TraceInfo(TraceCategory.Monitoring, string.Format("User product {0}", product));
                        productFilteringQuery.Add(
                            new TermQuery(new Term(FixedFacetsConfiguration.ProductFieldFacetName, product.ToString())),
                            Occur.SHOULD);
                    }

                    if (PortalContext.Current.User != null)
                    {
                        if (productAccessProvider.DisplayArticlesWithoutAssociatedProductsEnabled())
                        {
                            productFilteringQuery.Add(
                                new TermQuery(new Term(FixedFacetsConfiguration.ProductFieldFacetName, this.Index.ProductAccessDefaultValue)),
                                Occur.SHOULD);
                        }
                    }
                    else
                    {
                        productFilteringQuery.Add(
                            new TermQuery(new Term(FixedFacetsConfiguration.ProductFieldFacetName, "unauthenticatedUser")),
                            Occur.SHOULD);
                    }

                    compositeQuery.Add(productFilteringQuery, Occur.MUST);
                }

                ADXTrace.Instance.TraceInfo(TraceCategory.Monitoring, string.Format("Adding User WebRoleDefaultValue to Lucene query: {0}", this.Index.WebRoleDefaultValue));

                var cmsQuery = new BooleanQuery
                {
                    {
                        new TermQuery(
                            new Term(this.Index.WebRoleFieldName, this.Index.WebRoleDefaultValue)),
                        Occur.SHOULD
                    }
                };

                // Windows Live ID Server decided to return null for an unauthenticated user's name
                // A null username, however, breaks the Roles.GetRolesForUser() because it expects an empty string.
                var currentUsername = (HttpContext.Current != null && HttpContext.Current.User != null && HttpContext.Current.User.Identity != null)
                                        ? HttpContext.Current.User.Identity.Name ?? string.Empty
                                        : string.Empty;

                var userRoles = Roles.GetRolesForUser(currentUsername);

                ADXTrace.Instance.TraceInfo(TraceCategory.Monitoring, "Adding user role to Lucene query");
                foreach (var role in userRoles)
                {
                    ADXTrace.Instance.TraceInfo(TraceCategory.Monitoring, string.Format("User role: {0}", role));
                    cmsQuery.Add(new TermQuery(new Term(this.Index.WebRoleFieldName, role)), Occur.SHOULD);
                }

                compositeQuery.Add(cmsQuery, Occur.MUST);

                // Add the Url Defined Part to the Query.
                var urlDefinedQuery = new BooleanQuery
                {
                    {
                        new TermQuery(
                            new Term(this.Index.IsUrlDefinedFieldName, bool.TrueString)),
                        Occur.SHOULD
                    }
                };
                compositeQuery.Add(urlDefinedQuery, Occur.MUST);

                // Add knowledgearticle to the query
                compositeQuery.Add(
                    new TermQuery(new Term(FixedFacetsConfiguration.RecordTypeFacetFieldName, FixedFacetsConfiguration.KnowledgeArticleConstraintName)),
                    Occur.SHOULD);

                return(compositeQuery);
            }
            else
            {
                return(base.CreateQuery(query));
            }
        }
Example #17
0
        private IEnumerable <Document> GetRelatedAnnotations(RawSearchResultSet rawSearchResults, ICrmEntityQuery query)
        {
            Query textQuery;
            var   noteQuery   = new BooleanQuery();
            var   queryParser = new QueryParser(Index.Version, Index.ContentFieldName, Index.GetQuerySpecificAnalyzer(query.MultiLanguageEnabled, query.ContextLanguage));

            try
            {
                textQuery = queryParser.Parse(string.Format("+({0}) filename:({0}) notetext:({0}) _logicalname:annotation~0.9^0.3", query.QueryTerm));
            }
            catch (ParseException)
            {
                textQuery = queryParser.Parse(QueryParser.Escape(string.Format("+({0}) filename:({0}) notetext:({0}) _logicalname:annotation~0.9^0.3", query.QueryTerm)));
            }

            noteQuery.Add(textQuery, Occur.MUST);
            noteQuery.Add(new TermQuery(new Term("_logicalname", "annotation")), Occur.MUST);
            foreach (var scoreDoc in rawSearchResults.Results)
            {
                var resultField = _searcher.Doc(scoreDoc.Doc).GetField("_logicalname");
                if (resultField != null && resultField.StringValue == "knowledgearticle")
                {
                    var primaryKey = _searcher.Doc(scoreDoc.Doc).GetField("_primarykey");
                    noteQuery.Add(new TermQuery(new Term("annotation_knowledgearticleid", primaryKey.StringValue)), Occur.SHOULD);
                }
            }

            var rawNoteResults = GetRawSearchResults(noteQuery, 30, 0);

            return(rawNoteResults.Results.Select(rawNoteResult => _searcher.Doc(rawNoteResult.Doc)).ToList());
        }
Example #18
0
        /// <summary>
        /// Get the processed search results provided by the query available to the searching user.
        /// </summary>
        /// <param name="query">Search query.</param>
        /// <param name="searchLimit">Number of results to obtain from the underlying search library.</param>
        /// <param name="initialOffset">Number of already processed results to skip.</param>
        /// <param name="resultLimit">Number of results to return in the result page.</param>
        /// <param name="resultFactory">Factory to generate the ICrmEntitySearchResults</param>
        /// <param name="pageNumber">Page number</param>
        /// <param name="pageSize">Page size</param>
        /// <param name="results">Processed results so far.</param>
        /// <returns></returns>
        protected ICrmEntitySearchResultPage GetUserSearchResults(ICrmEntityQuery query, int searchLimit, int initialOffset, int resultLimit, ICrmEntitySearchResultFactory resultFactory, int pageNumber, int pageSize, ICollection <ICrmEntitySearchResult> results)
        {
            ADXTrace.Instance.TraceInfo(TraceCategory.Application, string.Format("(searchLimit={0},rawOffset={1},resultLimit={2})", searchLimit, initialOffset, resultLimit));
            RawSearchResultSet rawSearchResults = GetRawSearchResults(query, searchLimit, initialOffset);

            if (initialOffset >= rawSearchResults.TotalHits)
            {
                return(GenerateResultPage(results, rawSearchResults.TotalHits, pageNumber, pageSize, rawSearchResults));
            }

            var stopwatch = new Stopwatch();

            stopwatch.Start();
            var groupedNotes = new List <IGrouping <EntityReference, ICrmEntitySearchResult> >();
            var displayNotes = IsAnnotationSearchEnabled();

            if (displayNotes && !string.IsNullOrEmpty(query.QueryTerm))
            {
                var rawNotes = this.GetRelatedAnnotations(rawSearchResults, query);

                var notes =
                    rawNotes.Select(document => resultFactory.GetResult(document, 1, results.Count + 1)).ToList();

                //Grouping Notes by related Knowledge Articles
                groupedNotes =
                    notes.Where(note => note.EntityLogicalName == "annotation")
                    .GroupBy(note => note.Entity.GetAttributeValue <EntityReference>("objectid"))
                    .ToList();
            }

            var offsetForNextIteration = initialOffset;

            foreach (var scoreDoc in rawSearchResults.Results)
            {
                offsetForNextIteration++;

                var result = resultFactory.GetResult(_searcher.Doc(scoreDoc.Doc), scoreDoc.Score, results.Count + 1);

                // Not a valid user result, filter out
                if (result == null)
                {
                    continue;
                }

                if (result.EntityLogicalName == "knowledgearticle" && displayNotes)
                {
                    var relatedNotes = groupedNotes.Where(a => a.Key.Id == result.EntityID).SelectMany(i => i).Take(3).ToList();

                    if (relatedNotes.Any(note => note.Fragment == result.Fragment))
                    {
                        result.Fragment = GetKnowledgeArticleDescription(result);
                    }
                    result.Entity["relatedNotes"] = relatedNotes;
                }

                results.Add(result);

                if (results.Count >= resultLimit)
                {
                    stopwatch.Stop();

                    ADXTrace.Instance.TraceInfo(TraceCategory.Application, string.Format("Gathered {0} results, done ({1}ms)", results.Count, stopwatch.ElapsedMilliseconds));

                    PortalFeatureTrace.TraceInstance.LogSearch(FeatureTraceCategory.Search, results.Count, stopwatch.ElapsedMilliseconds, string.Format("Gathered {0} results, done ({1}ms)", results.Count, stopwatch.ElapsedMilliseconds));

                    return(GenerateResultPage(results, rawSearchResults.TotalHits, pageNumber, pageSize, rawSearchResults));
                }
            }

            stopwatch.Stop();

            // We asked for more hits than we got back from Lucene, and we still didn't gather enough valid
            // results. That's all we're going to get, so the number of results we got is the number of hits.
            if (searchLimit >= rawSearchResults.TotalHits)
            {
                ADXTrace.Instance.TraceInfo(TraceCategory.Application, string.Format("All available results ({0}) gathered, done ({1}ms)", results.Count, stopwatch.ElapsedMilliseconds));

                PortalFeatureTrace.TraceInstance.LogSearch(FeatureTraceCategory.Search, results.Count, stopwatch.ElapsedMilliseconds, string.Format("All available results ({0}) gathered, done ({1}ms)", results.Count, stopwatch.ElapsedMilliseconds));

                return(GenerateResultPage(results, results.Count, pageNumber, pageSize, rawSearchResults));
            }

            ADXTrace.Instance.TraceInfo(TraceCategory.Application, string.Format("{0} results gathered so far ({1}ms)", results.Count, stopwatch.ElapsedMilliseconds));

            PortalFeatureTrace.TraceInstance.LogSearch(FeatureTraceCategory.Search, results.Count, stopwatch.ElapsedMilliseconds, string.Format("{0} results gathered so far ({1}ms)", results.Count, stopwatch.ElapsedMilliseconds));

            return(GetUserSearchResults(query, searchLimit * ExtendedSearchLimitMultiple, offsetForNextIteration, resultLimit, resultFactory, pageNumber, pageSize, results));
        }