/// <summary> /// Validates the search criteria. /// </summary> /// <param name="criteria">The criteria.</param> /// <param name="cultureName">Name of the culture.</param> /// <param name="validationException">The validation exception.</param> /// <returns></returns> public bool ValidateSearchCriteria(SearchCriteriaCollection criteria, string cultureName, out Exception validationException) { validationException = null; if (criteria == null || criteria.Count == 0) { validationException = new SearchException(ComponentsText.ExceptionSearchNoSearchCriteria); return(false); } SearchCriteriaString criteriaProperty; foreach (ISearchCriteria sc in criteria) { criteriaProperty = sc as SearchCriteriaString; if (criteriaProperty != null) { if (StringExpressionKind.RegularExpression == criteriaProperty.WhatKind) { validationException = new SearchException(String.Format(ComponentsText.ExceptionLuceneSearchKindNotSupported, criteriaProperty.WhatKind)); break; // yet a warning } if (StringExpressionKind.XPathExpression == criteriaProperty.WhatKind) { validationException = new SearchException(String.Format(ComponentsText.ExceptionLuceneSearchKindNotSupported, criteriaProperty.WhatKind)); return(false); // error } } } try { if (null == BuildLuceneQuery(criteria, null, GetAnalyzer(cultureName))) { validationException = new SearchException(ComponentsText.ExceptionSearchQueryBuilder); return(false); } } catch (Exception ex) { validationException = new SearchException(String.Format(ComponentsText.ExceptionSearchQueryBuilderFatal, ex.Message), ex); return(false); } return(true); }
private static Query BuildLuceneQuery(SearchCriteriaCollection criteria, INewsFeed[] scope, Analyzer analyzer) { BooleanQuery masterQuery = null; BooleanQuery bTerms = new BooleanQuery(); BooleanQuery bRanges = new BooleanQuery(); for (int i = 0; criteria != null && i < criteria.Count; i++) { ISearchCriteria sc = criteria[i]; if (sc is SearchCriteriaString) { SearchCriteriaString c = (SearchCriteriaString)sc; if (string.IsNullOrEmpty(c.What)) { continue; } if (c.Where == SearchStringElement.Undefined) { AddBooleanClauseShould(bTerms, QueryFromStringExpression(c, IndexDocument.ItemContent, analyzer)); } else { if ((c.Where & SearchStringElement.Title) > 0) { AddBooleanClauseShould(bTerms, QueryFromStringExpression(c, Keyword.ItemTitle, analyzer)); } if ((c.Where & SearchStringElement.Link) > 0) { AddBooleanClauseShould(bTerms, QueryFromStringExpression(c, Keyword.ItemLink, analyzer)); } if ((c.Where & SearchStringElement.Content) > 0) { AddBooleanClauseShould(bTerms, QueryFromStringExpression(c, IndexDocument.ItemContent, analyzer)); } if ((c.Where & SearchStringElement.Subject) > 0) { AddBooleanClauseShould(bTerms, QueryFromStringExpression(c, Keyword.ItemTopic, analyzer)); } if ((c.Where & SearchStringElement.Author) > 0) { AddBooleanClauseShould(bTerms, QueryFromStringExpression(c, Keyword.ItemAuthor, analyzer)); } } } else if (sc is SearchCriteriaAge) { SearchCriteriaAge c = (SearchCriteriaAge)sc; Term left, right; string pastDate = "19900101", pastDateTime = "199001010001"; string futureDate = DateTime.Now.AddYears(20).DateToInteger().ToString(NumberFormatInfo.InvariantInfo), futureDateTime = DateTime.Now.AddYears(20).DateToInteger().ToString(NumberFormatInfo.InvariantInfo) + "0001"; if (c.WhatRelativeToToday.CompareTo(TimeSpan.Zero) == 0) { // compare date only: //TODO: validate provided date(s) to be in the allowed ranges (pastDate, futureDate)! switch (c.WhatKind) { case DateExpressionKind.Equal: AddBooleanClauseMust(bRanges, new PrefixQuery(new Term(Keyword.ItemDate, c.WhatAsIntDateOnly.ToString(NumberFormatInfo.InvariantInfo)))); //itemDate == whatYearOnly; break; case DateExpressionKind.OlderThan: left = new Term(Keyword.ItemDate, pastDate); right = new Term(Keyword.ItemDate, c.What.DateToInteger().ToString(NumberFormatInfo.InvariantInfo)); AddBooleanClauseMust(bRanges, new RangeQuery(left, right, true)); // return itemDate < whatYearOnly; break; case DateExpressionKind.NewerThan: left = new Term(Keyword.ItemDate, c.What.DateToInteger().ToString(NumberFormatInfo.InvariantInfo)); right = new Term(Keyword.ItemDate, futureDate); AddBooleanClauseMust(bRanges, new RangeQuery(left, right, true)); // return itemDate > whatYearOnly; break; default: break; } } else { DateTime dt = DateTime.Now.ToUniversalTime().Subtract(c.WhatRelativeToToday); switch (c.WhatKind) { case DateExpressionKind.OlderThan: left = new Term(Keyword.ItemDate, pastDateTime); right = new Term(Keyword.ItemDate, DateTools.TimeToString(dt.Ticks, DateTools.Resolution.MINUTE)); AddBooleanClauseMust(bRanges, new RangeQuery(left, right, true)); break; case DateExpressionKind.NewerThan: left = new Term(Keyword.ItemDate, DateTools.TimeToString(dt.Ticks, DateTools.Resolution.MINUTE)); right = new Term(Keyword.ItemDate, futureDateTime); AddBooleanClauseMust(bRanges, new RangeQuery(left, right, true)); break; default: break; } } } else if (sc is SearchCriteriaDateRange) { SearchCriteriaDateRange c = (SearchCriteriaDateRange)sc; Term left = new Term(Keyword.ItemDate, c.Bottom.DateToInteger().ToString(NumberFormatInfo.InvariantInfo)); Term right = new Term(Keyword.ItemDate, c.Top.DateToInteger().ToString(NumberFormatInfo.InvariantInfo)); AddBooleanClauseMust(bRanges, new RangeQuery(left, right, true)); // return itemDate > whatYearOnly; } } // now we build: +(terms...) +ranges if (bTerms.GetClauses().Length > 0) { masterQuery = new BooleanQuery(); AddBooleanClauseMust(masterQuery, bTerms); } if (bRanges.GetClauses().Length > 0) { if (masterQuery != null) { AddBooleanClauseMust(masterQuery, bRanges); // AND } else { masterQuery = bRanges; } } // +scope if (scope != null && scope.Length > 0 && masterQuery != null) { StringBuilder scopeQuery = new StringBuilder("("); for (int i = 0; i < scope.Length; i++) { scopeQuery.Append(scope[i].id); scopeQuery.Append(" "); } scopeQuery[scopeQuery.Length - 1] = ')'; // AND AddBooleanClauseMust(masterQuery, QueryFromStringExpression(scopeQuery.ToString(), IndexDocument.FeedID, analyzer)); } return(masterQuery); }
/// <summary> /// Executes a search. /// </summary> /// <param name="criteria">The criteria.</param> /// <param name="scope">The scope.</param> /// <param name="feedSources">The news handlers.</param> /// <param name="cultureName">Name of the culture.</param> /// <returns></returns> public Result ExecuteSearch(SearchCriteriaCollection criteria, INewsFeed[] scope, IEnumerable <FeedSource> feedSources, string cultureName) { if (!UseIndex) { return(null); } Query q = BuildLuceneQuery(criteria, scope, GetAnalyzer(cultureName)); if (q == null) // not validated { return(new Result(0, 0, GetList <SearchHitNewsItem> .Empty, GetArrayList.Empty)); } //TODO: to be fixed - // next line causes issues with concurrent thread access to the search index: IndexSearcher searcher = new IndexSearcher(this._settings.GetIndexDirectory()); Hits hits = null; while (hits == null) { try { DateTime start = DateTime.Now; hits = searcher.Search(q, Sort.RELEVANCE); TimeSpan timeRequired = TimeSpan.FromTicks(DateTime.Now.Ticks - start.Ticks); _log.Info(String.Format("Found {0} document(s) that matched query '{1}' (time required: {2})", hits.Length(), q, timeRequired)); } catch (BooleanQuery.TooManyClauses) { BooleanQuery.SetMaxClauseCount(BooleanQuery.GetMaxClauseCount() * 2); _log.Info(String.Format("Search failed with error 'BooleanQuery.TooManyClauses'. Retry with BooleanQuery.MaxClauseCount == {0}", BooleanQuery.GetMaxClauseCount())); } } List <SearchHitNewsItem> items = new List <SearchHitNewsItem>(hits.Length()); HybridDictionary matchedFeeds = new HybridDictionary(); for (int i = 0; i < hits.Length(); i++) { Document doc = hits.Doc(i); INewsFeed f = null; string feedLink = doc.Get(Keyword.FeedLink); if (matchedFeeds.Contains(feedLink)) { f = (INewsFeed)matchedFeeds[feedLink]; } if (f == null) { foreach (FeedSource h in feedSources) { if (h.IsSubscribed(feedLink)) { f = h.GetFeeds()[feedLink]; break; } } } if (f == null) { continue; } SearchHitNewsItem item = new SearchHitNewsItem(f, doc.Get(Keyword.ItemTitle), doc.Get(Keyword.ItemLink), doc.Get(IndexDocument.ItemSummary), doc.Get(Keyword.ItemAuthor), new DateTime(DateTools.StringToTime(doc.Get(Keyword.ItemDate))), LuceneNewsItemSearch.NewsItemIDFromUID(doc.Get(IndexDocument.ItemID))); items.Add(item); if (!matchedFeeds.Contains(feedLink)) { matchedFeeds.Add(feedLink, f); } } return(new Result(items.Count, matchedFeeds.Count, items, new ArrayList(matchedFeeds.Values))); }