/// <summary> /// index all supplied nodes (and their detached content) /// </summary> /// <param name="nodes">collection of nodes (and any detached content they may have) to be indexed</param> internal void Index(IPublishedContent[] nodes) { var indexWriter = this.GetIndexWriter(); foreach (var node in nodes) { var indexingContext = new IndexingContext(null, node, this.Name); var document = new Document(); LookService.Index(indexingContext, document); indexWriter.AddDocument(document); foreach (var detachedContent in node.GetFlatDetachedDescendants()) { indexingContext = new IndexingContext(node, detachedContent, this.Name); document = new Document(); LookService.Index(indexingContext, document); indexWriter.AddDocument(document); // index each detached item } indexWriter.Commit(); //indexWriter.Optimize(); } }
/// <summary> /// Wire-up events to maintain Look indexes 'as and when' Umbraco data changes /// </summary> /// <param name="umbracoApplication"></param> /// <param name="applicationContext"></param> protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) { // set once, as they will be configured in the config this._lookIndexers = ExamineManager .Instance .IndexProviderCollection .Select(x => x as LookIndexer) .Where(x => x != null) .ToArray(); if (this._lookIndexers.Any()) { this._umbracoHelper = new UmbracoHelper(UmbracoContext.Current); LookService.Initialize(this._umbracoHelper); ContentService.Published += ContentService_Published; MediaService.Saved += this.MediaService_Saved; MemberService.Saved += this.MemberService_Saved; ContentService.UnPublished += ContentService_UnPublished; MediaService.Deleted += this.MediaService_Deleted; MemberService.Deleted += this.MemberService_Deleted; } }
/// <summary> /// Umbraco started /// </summary> /// <param name="umbracoApplication"></param> /// <param name="applicationContext"></param> protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) { LookService.Initialize(new UmbracoHelper(UmbracoContext.Current)); // if consumer hasn't (yet) set the examine indexers, then register them all (the consumer can always change them again) if (!LookService.ExamineIndexersConfigured) { // register all LookService.SetExamineIndexers(); } }
/// <summary> /// Use this Skip for performace (and then cast each result to a LookMatch) /// This skip method, performs the skip on the Lucene results array before each is inflated into a LookMatch /// </summary> /// <param name="skip"></param> /// <returns></returns> public IEnumerable <SearchResult> Skip(int skip) { if (this._hasMatches && skip > 0) { var scoreDocs = this._topDocs.ScoreDocs.Skip(skip).ToArray(); return(LookService .GetLookMatches( this._lookQuery.SearcherName, this._lookQuery.SearchingContext.IndexSearcher, scoreDocs, this._lookQuery.RequestFields, this._lookQuery.Compiled.GetHighlight, this._lookQuery.Compiled.GetDistance)); } return(this.Matches); }
//protected override void AddDocument(Dictionary<string, string> fields, IndexWriter writer, int nodeId, string type) //{ // base.AddDocument(fields, writer, nodeId, type); //} //protected override void AddSingleNodeToIndex(XElement node, string type) //{ // base.AddSingleNodeToIndex(node, type); //} //protected override IndexWriter CreateIndexWriter() //{ // var debug = base.CreateIndexWriter(); // return debug; //} //public override void DeleteFromIndex(string nodeId) //{ // base.DeleteFromIndex(nodeId); //} //protected override Dictionary<string, string> GetDataToIndex(XElement node, string type) //{ // var debug = base.GetDataToIndex(node, type); // return debug; //} //protected override IIndexCriteria GetIndexerData(IndexSet indexSet) //{ // var debug = base.GetIndexerData(indexSet); // return debug; //} //public override Directory GetLuceneDirectory() //{ // var debug = base.GetLuceneDirectory(); // return debug; //} //protected override FieldIndexTypes GetPolicy(string fieldName) //{ // var debug = base.GetPolicy(fieldName); // return debug; //} //protected override Dictionary<string, string> GetSpecialFieldsToIndex(Dictionary<string, string> allValuesForIndexing) //{ // var debug = base.GetSpecialFieldsToIndex(allValuesForIndexing); // return debug; //} //public override void IndexAll(string type) //{ // base.IndexAll(type); //} //public override bool IndexExists() //{ // var debug = base.IndexExists(); // return debug; //} //protected override void OnDocumentWriting(DocumentWritingEventArgs docArgs) //{ // base.OnDocumentWriting(docArgs); //} //protected override void OnDuplicateFieldWarning(int nodeId, string indexSetName, string fieldName) //{ // base.OnDuplicateFieldWarning(nodeId, indexSetName, fieldName); //} //protected override void OnGatheringFieldData(IndexingFieldDataEventArgs e) //{ // base.OnGatheringFieldData(e); //} //protected override void OnGatheringNodeData(IndexingNodeDataEventArgs e) //{ // base.OnGatheringNodeData(e); //} //protected override void OnIgnoringNode(IndexingNodeDataEventArgs e) //{ // base.OnIgnoringNode(e); //} //protected override void OnIndexDeleted(DeleteIndexEventArgs e) //{ // base.OnIndexDeleted(e); //} //protected override void OnIndexingError(IndexingErrorEventArgs e) //{ // base.OnIndexingError(e); //} //protected override void OnIndexOperationComplete(EventArgs e) //{ // base.OnIndexOperationComplete(e); //} //protected override void OnIndexOptimized(EventArgs e) //{ // base.OnIndexOptimized(e); //} //protected override void OnIndexOptimizing(EventArgs e) //{ // base.OnIndexOptimizing(e); //} //protected override void OnNodeIndexed(IndexedNodeEventArgs e) //{ // base.OnNodeIndexed(e); //} //protected override void OnNodeIndexing(IndexingNodeEventArgs e) //{ // base.OnNodeIndexing(e); //} //protected override void OnNodesIndexed(IndexedNodesEventArgs e) //{ // base.OnNodesIndexed(e); //} //protected override void OnNodesIndexing(IndexingNodesEventArgs e) //{ // base.OnNodesIndexing(e); //} //public override void ReIndexNode(XElement node, string type) //{ // base.ReIndexNode(node, type); //} //protected override bool ValidateDocument(XElement node) //{ // return base.ValidateDocument(node); //} /// <summary> /// index all supplied nodes (and their detached content) /// </summary> /// <param name="nodes">collection of nodes (and any detached content they may have) to be indexed</param> internal void Index(IPublishedContent[] nodes) { #if DEBUG var stopwatch = Stopwatch.StartNew(); #endif var indexWriter = this.GetIndexWriter(); foreach (var node in nodes) { var indexingContext = new IndexingContext( hostNode: null, node: node, indexerName: this.Name); var document = new Document(); LookService.Index(indexingContext, document); indexWriter.AddDocument(document); foreach (var detachedNode in node.GetDetachedDescendants()) { indexingContext = new IndexingContext( hostNode: node, node: detachedNode, indexerName: this.Name); document = new Document(); LookService.Index(indexingContext, document); indexWriter.AddDocument(document); // index each detached item } } indexWriter.Commit(); #if DEBUG stopwatch.Stop(); LogHelper.Debug(typeof(LookService), $"Indexing { nodes.Length } Item(s) Took { stopwatch.ElapsedMilliseconds }ms"); #endif }
/// <summary> /// Update the Lucene document in all indexes /// </summary> /// <param name="publishedContentItems"></param> /// <param name="publishedItemType"></param> private void Update(IPublishedContent[] publishedContentItems, PublishedItemType publishedItemType) { if (publishedContentItems == null || !publishedContentItems.Any()) { return; } foreach (var lookIndexer in this._lookIndexers) { lookIndexer.Remove(publishedContentItems.Select(x => x.Id).ToArray()); var indexerConfiguration = LookService.GetIndexerConfiguration(lookIndexer.Name); var indexItem = publishedItemType == PublishedItemType.Content && indexerConfiguration.ShouldIndexContent || publishedItemType == PublishedItemType.Media && indexerConfiguration.ShouldIndexMedia || publishedItemType == PublishedItemType.Member && indexerConfiguration.ShouldIndexMembers; var indexDetached = publishedItemType == PublishedItemType.Content && indexerConfiguration.ShouldIndexDetachedContent || publishedItemType == PublishedItemType.Media && indexerConfiguration.ShouldIndexDetachedMedia || publishedItemType == PublishedItemType.Member && indexerConfiguration.ShouldIndexDetachedMembers; lookIndexer.Index(publishedContentItems, indexItem, indexDetached); // both flags could be false (indicating no indexing should take place) } }
/// <summary> /// Perform the query /// </summary> /// <returns></returns> public LookResult Search() { return(LookService.Search(this)); }
/// <summary> /// Perform a Look search /// </summary> /// <param name="lookQuery">A LookQuery model for the search criteria</param> /// <returns>A LookResult model for the search response</returns> public static LookResult RunQuery(LookQuery lookQuery) { // flag to indicate whether there are any query clauses in the supplied LookQuery bool hasQuery = lookQuery?.Compiled != null ? true : false; if (lookQuery == null) { return(new LookResult("LookQuery object was null")); } if (lookQuery.SearchingContext == null) // supplied by unit test to skip examine dependency { // attempt to get searching context from examine searcher name lookQuery.SearchingContext = LookService.GetSearchingContext(lookQuery.SearcherName); if (lookQuery.SearchingContext == null) { return(new LookResult("SearchingContext was null")); } } if (lookQuery.Compiled == null) { BooleanQuery query = null; // the lucene query being built Filter filter = null; // used for geospatial queries Sort sort = null; Func <string, IHtmlString> getHighlight = x => null; Func <int, double?> getDistance = x => null; query = new BooleanQuery(); #region RawQuery if (!string.IsNullOrWhiteSpace(lookQuery.RawQuery)) { hasQuery = true; query.Add( new QueryParser(Lucene.Net.Util.Version.LUCENE_29, null, lookQuery.SearchingContext.Analyzer).Parse(lookQuery.RawQuery), BooleanClause.Occur.MUST); } #endregion #region ExamineQuery if (lookQuery.ExamineQuery != null) { var luceneSearchCriteria = lookQuery.ExamineQuery as LuceneSearchCriteria; if (luceneSearchCriteria.Query != null) { hasQuery = true; query.Add(luceneSearchCriteria.Query, BooleanClause.Occur.MUST); } } #endregion #region NodeQuery if (lookQuery.NodeQuery != null) { hasQuery = true; query.Add(new TermQuery(new Term(LookConstants.HasNodeField, "1")), BooleanClause.Occur.MUST); if (lookQuery.NodeQuery.Types != null && lookQuery.NodeQuery.Types.Any()) { var nodeTypeQuery = new BooleanQuery(); foreach (var nodeType in lookQuery.NodeQuery.Types) { nodeTypeQuery.Add( new TermQuery( new Term(LookConstants.NodeTypeField, nodeType.ToString())), BooleanClause.Occur.SHOULD); } query.Add(nodeTypeQuery, BooleanClause.Occur.MUST); } switch (lookQuery.NodeQuery.DetachedQuery) { case DetachedQuery.ExcludeDetached: query.Add( new TermQuery(new Term(LookConstants.IsDetachedField, "1")), BooleanClause.Occur.MUST_NOT); break; case DetachedQuery.OnlyDetached: query.Add( new TermQuery(new Term(LookConstants.IsDetachedField, "1")), BooleanClause.Occur.MUST); break; } if (lookQuery.NodeQuery.Cultures != null && lookQuery.NodeQuery.Cultures.Any()) { var nodeCultureQuery = new BooleanQuery(); foreach (var nodeCulture in lookQuery.NodeQuery.Cultures) { nodeCultureQuery.Add( new TermQuery( new Term(LookConstants.CultureField, nodeCulture.LCID.ToString())), BooleanClause.Occur.SHOULD); } query.Add(nodeCultureQuery, BooleanClause.Occur.MUST); } if (lookQuery.NodeQuery.Aliases != null && lookQuery.NodeQuery.Aliases.Any()) { var nodeAliasQuery = new BooleanQuery(); foreach (var typeAlias in lookQuery.NodeQuery.Aliases) { nodeAliasQuery.Add( new TermQuery(new Term(LookConstants.NodeAliasField, typeAlias.ToLower())), BooleanClause.Occur.SHOULD); } query.Add(nodeAliasQuery, BooleanClause.Occur.MUST); } if (lookQuery.NodeQuery.NotIds != null && lookQuery.NodeQuery.NotIds.Any()) { foreach (var exculudeId in lookQuery.NodeQuery.NotIds) { query.Add( new TermQuery(new Term(LookConstants.NodeIdField, exculudeId.ToString())), BooleanClause.Occur.MUST_NOT); } } if (lookQuery.NodeQuery.NotKeys != null && lookQuery.NodeQuery.NotKeys.Any()) { foreach (var excludeKey in lookQuery.NodeQuery.NotKeys) { query.Add( new TermQuery(new Term(LookConstants.NodeKeyField, excludeKey)), BooleanClause.Occur.MUST_NOT); } } } #endregion #region NameQuery if (lookQuery.NameQuery != null) { hasQuery = true; query.Add(new TermQuery(new Term(LookConstants.HasNameField, "1")), BooleanClause.Occur.MUST); string wildcard1 = null; string wildcard2 = null; // incase Contains specified with StartsWith and/or EndsWith if (!string.IsNullOrEmpty(lookQuery.NameQuery.StartsWith)) { if (!string.IsNullOrEmpty(lookQuery.NameQuery.Is)) { if (!lookQuery.NameQuery.Is.StartsWith(lookQuery.NameQuery.StartsWith)) { return(new LookResult("Conflict in NameQuery between Is and StartsWith")); } } else { wildcard1 = lookQuery.NameQuery.StartsWith + "*"; } } if (!string.IsNullOrEmpty(lookQuery.NameQuery.EndsWith)) { if (!string.IsNullOrEmpty(lookQuery.NameQuery.Is)) { if (!lookQuery.NameQuery.Is.EndsWith(lookQuery.NameQuery.EndsWith)) { return(new LookResult("Conflict in NameQuery between Is and EndsWith")); } } else { if (wildcard1 == null) { wildcard1 = "*" + lookQuery.NameQuery.EndsWith; } else { wildcard1 += lookQuery.NameQuery.EndsWith; } } } if (!string.IsNullOrEmpty(lookQuery.NameQuery.Contains)) { if (!string.IsNullOrEmpty(lookQuery.NameQuery.Is)) { if (!lookQuery.NameQuery.Is.Contains(lookQuery.NameQuery.Contains)) { return(new LookResult("Conflict in NameQuery between Is and Contains")); } } else { if (wildcard1 == null) { wildcard1 = "*" + lookQuery.NameQuery.Contains + "*"; } else { wildcard2 = "*" + lookQuery.NameQuery.Contains + "*"; } } } var nameField = lookQuery.NameQuery.CaseSensitive ? LookConstants.NameField : LookConstants.NameField + "_Lowered"; if (wildcard1 != null) { var wildcard = lookQuery.NameQuery.CaseSensitive ? wildcard1 : wildcard1.ToLower(); query.Add(new WildcardQuery(new Term(nameField, wildcard)), BooleanClause.Occur.MUST); if (wildcard2 != null) { wildcard = lookQuery.NameQuery.CaseSensitive ? wildcard2 : wildcard2.ToLower(); query.Add(new WildcardQuery(new Term(nameField, wildcard)), BooleanClause.Occur.MUST); } } if (!string.IsNullOrEmpty(lookQuery.NameQuery.Is)) { var isText = lookQuery.NameQuery.CaseSensitive ? lookQuery.NameQuery.Is : lookQuery.NameQuery.Is.ToLower(); query.Add(new TermQuery(new Term(nameField, isText)), BooleanClause.Occur.MUST); } } #endregion #region DateQuery if (lookQuery.DateQuery != null) { hasQuery = true; query.Add(new TermQuery(new Term(LookConstants.HasDateField, "1")), BooleanClause.Occur.MUST); if (lookQuery.DateQuery.After.HasValue || lookQuery.DateQuery.Before.HasValue) { var includeLower = lookQuery.DateQuery.After == null || lookQuery.DateQuery.Boundary == DateBoundary.Inclusive || lookQuery.DateQuery.Boundary == DateBoundary.BeforeExclusiveAfterInclusive; var includeUpper = lookQuery.DateQuery.Before == null || lookQuery.DateQuery.Boundary == DateBoundary.Inclusive || lookQuery.DateQuery.Boundary == DateBoundary.BeforeInclusiveAfterExclusive; query.Add( new TermRangeQuery( LookConstants.DateField, lookQuery.DateQuery.After.DateToLuceneString() ?? DateTime.MinValue.DateToLuceneString(), lookQuery.DateQuery.Before.DateToLuceneString() ?? DateTime.MaxValue.DateToLuceneString(), includeLower, includeUpper), BooleanClause.Occur.MUST); } } #endregion #region TextQuery if (lookQuery.TextQuery != null) { hasQuery = true; query.Add(new TermQuery(new Term(LookConstants.HasTextField, "1")), BooleanClause.Occur.MUST); if (!string.IsNullOrWhiteSpace(lookQuery.TextQuery.SearchText)) { var queryParser = new QueryParser(Lucene.Net.Util.Version.LUCENE_29, LookConstants.TextField, lookQuery.SearchingContext.Analyzer); Query searchTextQuery = null; try { searchTextQuery = queryParser.Parse(lookQuery.TextQuery.SearchText); } catch { return(new LookResult($"Unable to parse LookQuery.TextQuery.SearchText: '{ lookQuery.TextQuery.SearchText }' into a Lucene query")); } if (searchTextQuery != null) { query.Add(searchTextQuery, BooleanClause.Occur.MUST); if (lookQuery.TextQuery.GetHighlight) { var queryScorer = new QueryScorer(searchTextQuery.Rewrite(lookQuery.SearchingContext.IndexSearcher.GetIndexReader())); var highlighter = new Highlighter(new SimpleHTMLFormatter("<strong>", "</strong>"), queryScorer); getHighlight = (x) => { var tokenStream = lookQuery.SearchingContext.Analyzer.TokenStream(LookConstants.TextField, new StringReader(x)); var highlight = highlighter.GetBestFragments( tokenStream, x, 1, // max number of fragments "..."); return(new HtmlString(highlight)); }; } } } } #endregion #region TagQuery if (lookQuery.TagQuery != null) { hasQuery = true; query.Add(new TermQuery(new Term(LookConstants.HasTagsField, "1")), BooleanClause.Occur.MUST); if (lookQuery.TagQuery.All != null) { if (lookQuery.TagQuery.Not != null) { var conflictTags = lookQuery.TagQuery.All.Where(x => !lookQuery.TagQuery.Not.Contains(x)); if (conflictTags.Any()) { return(new LookResult($"Conflict in TagQuery, tags: '{ string.Join(",", conflictTags) }' are in both AllTags and NotTags")); } } if (lookQuery.TagQuery.All.Any()) { foreach (var tag in lookQuery.TagQuery.All) { query.Add( new TermQuery(new Term(LookConstants.TagsField + tag.Group, tag.Name)), BooleanClause.Occur.MUST); } } } if (lookQuery.TagQuery.Any != null) { if (lookQuery.TagQuery.Not != null) { var conflictTags = lookQuery.TagQuery.Any.Where(x => !lookQuery.TagQuery.Not.Contains(x)); if (conflictTags.Any()) { return(new LookResult($"Conflict in TagQuery, tags: '{ string.Join(",", conflictTags) }' are in both AnyTags and NotTags")); } } if (lookQuery.TagQuery.Any.Any()) { var anyTagQuery = new BooleanQuery(); foreach (var tag in lookQuery.TagQuery.Any) { anyTagQuery.Add( new TermQuery(new Term(LookConstants.TagsField + tag.Group, tag.Name)), BooleanClause.Occur.SHOULD); } query.Add(anyTagQuery, BooleanClause.Occur.MUST); } } if (lookQuery.TagQuery.Not != null && lookQuery.TagQuery.Not.Any()) { foreach (var tag in lookQuery.TagQuery.Not) { query.Add( new TermQuery(new Term(LookConstants.TagsField + tag.Group, tag.Name)), BooleanClause.Occur.MUST_NOT); } } } #endregion #region LocationQuery if (lookQuery.LocationQuery != null) { hasQuery = true; query.Add(new TermQuery(new Term(LookConstants.HasLocationField, "1")), BooleanClause.Occur.MUST); if (lookQuery.LocationQuery.Location != null) { double maxDistance = LookService.MaxDistance; if (lookQuery.LocationQuery.MaxDistance != null) { maxDistance = Math.Min(lookQuery.LocationQuery.MaxDistance.GetMiles(), maxDistance); } var distanceQueryBuilder = new DistanceQueryBuilder( lookQuery.LocationQuery.Location.Latitude, lookQuery.LocationQuery.Location.Longitude, maxDistance, LookConstants.LocationField + "_Latitude", LookConstants.LocationField + "_Longitude", LookConstants.LocationTierFieldPrefix, true); filter = distanceQueryBuilder.Filter; if (lookQuery.SortOn == SortOn.Distance) { sort = new Sort( new SortField( LookConstants.DistanceField, new DistanceFieldComparatorSource(distanceQueryBuilder.DistanceFilter))); } getDistance = new Func <int, double?>(x => { if (distanceQueryBuilder.DistanceFilter.Distances.ContainsKey(x)) { return(distanceQueryBuilder.DistanceFilter.Distances[x]); } return(null); }); } } #endregion if (hasQuery) { switch (lookQuery.SortOn) { case SortOn.Name: // a -> z sort = new Sort(new SortField(LuceneIndexer.SortedFieldNamePrefix + LookConstants.NameField, SortField.STRING)); break; case SortOn.DateAscending: // oldest -> newest sort = new Sort(new SortField(LuceneIndexer.SortedFieldNamePrefix + LookConstants.DateField, SortField.LONG, false)); break; case SortOn.DateDescending: // newest -> oldest sort = new Sort(new SortField(LuceneIndexer.SortedFieldNamePrefix + LookConstants.DateField, SortField.LONG, true)); break; // SortOn.Distance already set (if valid) } lookQuery.Compiled = new LookQueryCompiled( lookQuery, query, filter, sort ?? new Sort(SortField.FIELD_SCORE), getHighlight, getDistance); } } if (!hasQuery) { return(new LookResult("No query clauses supplied")); // empty failure } TopDocs topDocs = lookQuery .SearchingContext .IndexSearcher .Search( lookQuery.Compiled.Query, lookQuery.Compiled.Filter, LookService.MaxLuceneResults, lookQuery.Compiled.Sort); if (topDocs.TotalHits > 0) { List <Facet> facets = null; if (lookQuery.TagQuery != null && lookQuery.TagQuery.FacetOn != null) { facets = new List <Facet>(); Query facetQuery = lookQuery.Compiled.Filter != null ? (Query) new FilteredQuery(lookQuery.Compiled.Query, lookQuery.Compiled.Filter) : lookQuery.Compiled.Query; // do a facet query for each group in the array foreach (var group in lookQuery.TagQuery.FacetOn.TagGroups) { var simpleFacetedSearch = new SimpleFacetedSearch( lookQuery.SearchingContext.IndexSearcher.GetIndexReader(), LookConstants.TagsField + group); var facetResult = simpleFacetedSearch.Search(facetQuery); facets.AddRange( facetResult .HitsPerFacet .Select( x => new Facet() { Tags = new LookTag[] { new LookTag(group, x.Name.ToString()) }, Count = Convert.ToInt32(x.HitCount) } )); } } return(new LookResult( LookService.GetLookMatches( lookQuery.SearchingContext.IndexSearcher, topDocs, lookQuery.RequestFields ?? LookService.Instance.RequestFields, lookQuery.Compiled.GetHighlight, lookQuery.Compiled.GetDistance), topDocs.TotalHits, facets != null ? facets.ToArray() : new Facet[] { })); } return(new LookResult()); // empty success }
/// <summary> /// Perform the query - this is a shortcut to and does the same thing as LookService.RunQuery(this) /// </summary> /// <returns></returns> public LookResult Run() { return(LookService.RunQuery(this)); }
/// <summary> /// index all supplied nodes (and their detached content) /// </summary> /// <param name="nodes">collection of nodes conent/media/member nodes to be indexed</param> /// <param name="indexItem">when true, indicates the IPublishedContent nodes should be indexed</param> /// <param name="indexDetached">when true, indicates the detached items for each node should be indexed</param> internal void Index(IEnumerable <IPublishedContent> nodes, bool indexItem, bool indexDetached) { if (!nodes.Any()) { return; } if (!indexItem && !indexDetached) { return; // possible } var stopwatch = Stopwatch.StartNew(); var counter = 0; var indexWriter = this.GetIndexWriter(); foreach (var node in nodes) { IndexingContext indexingContext; Document document; if (indexItem) { indexingContext = new IndexingContext(null, node, this.Name); document = new Document(); LookService.Index(indexingContext, document); if (!indexingContext.Cancelled) { counter++; indexWriter.AddDocument(document); } } if (indexDetached) { IPublishedContent[] detachedNodes = null; try { // SEOChecker prior to 2.2 doesn't handle IPublishedContent without an ID detachedNodes = node.GetDetachedDescendants(); } catch (Exception exception) { LogHelper.WarnWithException(typeof(LookIndexer), "Error handling Detached items", exception); } finally { if (detachedNodes != null) { foreach (var detachedNode in detachedNodes) { indexingContext = new IndexingContext(node, detachedNode, this.Name); document = new Document(); LookService.Index(indexingContext, document); if (!indexingContext.Cancelled) { counter++; indexWriter.AddDocument(document); // index each detached item } } } } } } indexWriter.Commit(); stopwatch.Stop(); if (counter > 0) { LogHelper.Debug(typeof(LookIndexer), $"Indexing { counter } Item(s) Took { stopwatch.ElapsedMilliseconds }ms"); } }