using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Xml.XPath; using Examine; using Examine.LuceneEngine.Providers; using Examine.LuceneEngine.SearchCriteria; using Examine.Providers; using Examine.SearchCriteria; using umbraco; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Dynamics; using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Core.Xml; using Umbraco.Web.Models; using Umbraco.Web.PublishedCache; namespace Umbraco.Web { /// /// A class used to query for published content, media items /// public class PublishedContentQuery : ITypedPublishedContentQuery, IDynamicPublishedContentQuery { private readonly ITypedPublishedContentQuery _typedContentQuery; private readonly IDynamicPublishedContentQuery _dynamicContentQuery; private readonly ContextualPublishedContentCache _contentCache; private readonly ContextualPublishedMediaCache _mediaCache; /// /// Constructor used to return results from the caches /// /// /// public PublishedContentQuery(ContextualPublishedContentCache contentCache, ContextualPublishedMediaCache mediaCache) { if (contentCache == null) throw new ArgumentNullException("contentCache"); if (mediaCache == null) throw new ArgumentNullException("mediaCache"); _contentCache = contentCache; _mediaCache = mediaCache; } /// /// Constructor used to wrap the ITypedPublishedContentQuery and IDynamicPublishedContentQuery objects passed in /// /// /// public PublishedContentQuery(ITypedPublishedContentQuery typedContentQuery, IDynamicPublishedContentQuery dynamicContentQuery) { if (typedContentQuery == null) throw new ArgumentNullException("typedContentQuery"); if (dynamicContentQuery == null) throw new ArgumentNullException("dynamicContentQuery"); _typedContentQuery = typedContentQuery; _dynamicContentQuery = dynamicContentQuery; } #region Content public IPublishedContent TypedContent(int id) { return _typedContentQuery == null ? TypedDocumentById(id, _contentCache) : _typedContentQuery.TypedContent(id); } public IPublishedContent TypedContent(Guid id) { return _typedContentQuery == null ? TypedDocumentById(id, _contentCache) : _typedContentQuery.TypedContent(id); } public IPublishedContent TypedContentSingleAtXPath(string xpath, params XPathVariable[] vars) { return _typedContentQuery == null ? TypedDocumentByXPath(xpath, vars, _contentCache) : _typedContentQuery.TypedContentSingleAtXPath(xpath, vars); } public IEnumerable TypedContent(IEnumerable ids) { return _typedContentQuery == null ? TypedDocumentsByIds(_contentCache, ids) : _typedContentQuery.TypedContent(ids); } public IEnumerable TypedContent(IEnumerable ids) { return _typedContentQuery == null ? TypedDocumentsByIds(_contentCache, ids) : _typedContentQuery.TypedContent(ids); } public IEnumerable TypedContentAtXPath(string xpath, params XPathVariable[] vars) { return _typedContentQuery == null ? TypedDocumentsByXPath(xpath, vars, _contentCache) : _typedContentQuery.TypedContentAtXPath(xpath, vars); } public IEnumerable TypedContentAtXPath(XPathExpression xpath, params XPathVariable[] vars) { return _typedContentQuery == null ? TypedDocumentsByXPath(xpath, vars, _contentCache) : _typedContentQuery.TypedContentAtXPath(xpath, vars); } public IEnumerable TypedContentAtRoot() { return _typedContentQuery == null ? TypedDocumentsAtRoot(_contentCache) : _typedContentQuery.TypedContentAtRoot(); } public dynamic Content(int id) { return _dynamicContentQuery == null ? DocumentById(id, _contentCache, DynamicNull.Null) : _dynamicContentQuery.Content(id); } public dynamic Content(Guid id) { return _dynamicContentQuery == null ? DocumentById(id, _contentCache, DynamicNull.Null) : _dynamicContentQuery.Content(id); } public dynamic ContentSingleAtXPath(string xpath, params XPathVariable[] vars) { return _dynamicContentQuery == null ? DocumentByXPath(xpath, vars, _contentCache, DynamicNull.Null) : _dynamicContentQuery.ContentSingleAtXPath(xpath, vars); } public dynamic ContentSingleAtXPath(XPathExpression xpath, params XPathVariable[] vars) { return _dynamicContentQuery == null ? DocumentByXPath(xpath, vars, _contentCache, DynamicNull.Null) : _dynamicContentQuery.ContentSingleAtXPath(xpath, vars); } public dynamic Content(IEnumerable ids) { return _dynamicContentQuery == null ? DocumentByIds(_contentCache, ids.ToArray()) : _dynamicContentQuery.Content(ids); } public dynamic Content(IEnumerable ids) { return _dynamicContentQuery == null ? DocumentByIds(_contentCache, ids.ToArray()) : _dynamicContentQuery.Content(ids); } public dynamic ContentAtXPath(string xpath, params XPathVariable[] vars) { return _dynamicContentQuery == null ? DocumentsByXPath(xpath, vars, _contentCache) : _dynamicContentQuery.ContentAtXPath(xpath, vars); } public dynamic ContentAtXPath(XPathExpression xpath, params XPathVariable[] vars) { return _dynamicContentQuery == null ? DocumentsByXPath(xpath, vars, _contentCache) : _dynamicContentQuery.ContentAtXPath(xpath, vars); } public dynamic ContentAtRoot() { return _dynamicContentQuery == null ? DocumentsAtRoot(_contentCache) : _dynamicContentQuery.ContentAtRoot(); } #endregion #region Media public IPublishedContent TypedMedia(int id) { return _typedContentQuery == null ? TypedDocumentById(id, _mediaCache) : _typedContentQuery.TypedMedia(id); } public IEnumerable TypedMedia(IEnumerable ids) { return _typedContentQuery == null ? TypedDocumentsByIds(_mediaCache, ids) : _typedContentQuery.TypedMedia(ids); } public IEnumerable TypedMediaAtRoot() { return _typedContentQuery == null ? TypedDocumentsAtRoot(_mediaCache) : _typedContentQuery.TypedMediaAtRoot(); } public dynamic Media(int id) { return _dynamicContentQuery == null ? DocumentById(id, _mediaCache, DynamicNull.Null) : _dynamicContentQuery.Media(id); } public dynamic Media(IEnumerable ids) { return _dynamicContentQuery == null ? DocumentByIds(_mediaCache, ids) : _dynamicContentQuery.Media(ids); } public dynamic MediaAtRoot() { return _dynamicContentQuery == null ? DocumentsAtRoot(_mediaCache) : _dynamicContentQuery.MediaAtRoot(); } #endregion #region Used by Content/Media private IPublishedContent TypedDocumentById(int id, ContextualPublishedCache cache) { var doc = cache.GetById(id); return doc; } private IPublishedContent TypedDocumentById(Guid key, ContextualPublishedCache cache) { return cache.GetById(key); } private IPublishedContent TypedDocumentByXPath(string xpath, XPathVariable[] vars, ContextualPublishedContentCache cache) { var doc = cache.GetSingleByXPath(xpath, vars); return doc; } //NOTE: Not used? //private IPublishedContent TypedDocumentByXPath(XPathExpression xpath, XPathVariable[] vars, ContextualPublishedContentCache cache) //{ // var doc = cache.GetSingleByXPath(xpath, vars); // return doc; //} private IEnumerable TypedDocumentsByIds(ContextualPublishedCache cache, IEnumerable ids) { return ids.Select(eachId => TypedDocumentById(eachId, cache)).WhereNotNull(); } private IEnumerable TypedDocumentsByIds(ContextualPublishedCache cache, IEnumerable ids) { return ids.Select(eachId => TypedDocumentById(eachId, cache)).WhereNotNull(); } private IEnumerable TypedDocumentsByXPath(string xpath, XPathVariable[] vars, ContextualPublishedContentCache cache) { var doc = cache.GetByXPath(xpath, vars); return doc; } private IEnumerable TypedDocumentsByXPath(XPathExpression xpath, XPathVariable[] vars, ContextualPublishedContentCache cache) { var doc = cache.GetByXPath(xpath, vars); return doc; } private IEnumerable TypedDocumentsAtRoot(ContextualPublishedCache cache) { return cache.GetAtRoot(); } private dynamic DocumentById(int id, ContextualPublishedCache cache, object ifNotFound) { var doc = cache.GetById(id); return doc == null ? ifNotFound : new DynamicPublishedContent(doc).AsDynamic(); } private dynamic DocumentById(Guid id, ContextualPublishedCache cache, object ifNotFound) { var doc = TypedDocumentById(id, cache); return doc == null ? ifNotFound : new DynamicPublishedContent(doc).AsDynamic(); } private dynamic DocumentByXPath(string xpath, XPathVariable[] vars, ContextualPublishedCache cache, object ifNotFound) { var doc = cache.GetSingleByXPath(xpath, vars); return doc == null ? ifNotFound : new DynamicPublishedContent(doc).AsDynamic(); } private dynamic DocumentByXPath(XPathExpression xpath, XPathVariable[] vars, ContextualPublishedCache cache, object ifNotFound) { var doc = cache.GetSingleByXPath(xpath, vars); return doc == null ? ifNotFound : new DynamicPublishedContent(doc).AsDynamic(); } private dynamic DocumentByIds(ContextualPublishedCache cache, IEnumerable ids) { var dNull = DynamicNull.Null; var nodes = ids.Select(eachId => DocumentById(eachId, cache, dNull)) .Where(x => TypeHelper.IsTypeAssignableFrom(x) == false) .Cast(); return new DynamicPublishedContentList(nodes); } private dynamic DocumentByIds(ContextualPublishedCache cache, IEnumerable ids) { var dNull = DynamicNull.Null; var nodes = ids.Select(eachId => DocumentById(eachId, cache, dNull)) .Where(x => TypeHelper.IsTypeAssignableFrom(x) == false) .Cast(); return new DynamicPublishedContentList(nodes); } private dynamic DocumentsByXPath(string xpath, XPathVariable[] vars, ContextualPublishedCache cache) { return new DynamicPublishedContentList( cache.GetByXPath(xpath, vars) .Select(publishedContent => new DynamicPublishedContent(publishedContent)) ); } private dynamic DocumentsByXPath(XPathExpression xpath, XPathVariable[] vars, ContextualPublishedCache cache) { return new DynamicPublishedContentList( cache.GetByXPath(xpath, vars) .Select(publishedContent => new DynamicPublishedContent(publishedContent)) ); } private dynamic DocumentsAtRoot(ContextualPublishedCache cache) { return new DynamicPublishedContentList( cache.GetAtRoot() .Select(publishedContent => new DynamicPublishedContent(publishedContent)) ); } #endregion #region Search /// /// Searches content /// /// /// /// /// public dynamic Search(string term, bool useWildCards = true, string searchProvider = null) { return _dynamicContentQuery == null ? new DynamicPublishedContentList( TypedSearch(term, useWildCards, searchProvider)) : _dynamicContentQuery.Search(term, useWildCards, searchProvider); } /// /// Searhes content /// /// /// /// public dynamic Search(ISearchCriteria criteria, BaseSearchProvider searchProvider = null) { return _dynamicContentQuery == null ? new DynamicPublishedContentList( TypedSearch(criteria, searchProvider)) : _dynamicContentQuery.Search(criteria, searchProvider); } /// /// Searches content /// /// /// /// /// /// /// /// public IEnumerable TypedSearch(int skip, int take, out int totalRecords, string term, bool useWildCards = true, string searchProvider = null) { if (_typedContentQuery != null) return _typedContentQuery.TypedSearch(skip, take, out totalRecords, term, useWildCards, searchProvider); var searcher = ExamineManager.Instance.DefaultSearchProvider; if (string.IsNullOrEmpty(searchProvider) == false) searcher = ExamineManager.Instance.SearchProviderCollection[searchProvider]; //if both are zero, use the native Examine API if (skip == 0 && take == 0) { var results = searcher.Search(term, useWildCards); totalRecords = results.TotalItemCount; return results.ConvertSearchResultToPublishedContent(_contentCache); } var luceneSearcher = searcher as BaseLuceneSearcher; //if the searcher is not a base lucene searcher, we'll have to use Linq Take (edge case) if (luceneSearcher == null) { var results = searcher.Search(term, useWildCards); totalRecords = results.TotalItemCount; return results .Skip(skip) //uses Examine Skip .ConvertSearchResultToPublishedContent(_contentCache) .Take(take); //uses Linq Take } //create criteria for all fields var allSearchFieldCriteria = SearchAllFields(term, useWildCards, luceneSearcher); return TypedSearch(skip, take, out totalRecords, allSearchFieldCriteria, searcher); } /// /// Searches content /// /// /// /// /// public IEnumerable TypedSearch(string term, bool useWildCards = true, string searchProvider = null) { int total; return TypedSearch(0, 0, out total, term, useWildCards, searchProvider); } /// /// Searhes content /// /// /// /// /// /// /// public IEnumerable TypedSearch(int skip, int take, out int totalRecords, ISearchCriteria criteria, BaseSearchProvider searchProvider = null) { if (_typedContentQuery != null) return _typedContentQuery.TypedSearch(skip, take, out totalRecords, criteria, searchProvider); var s = ExamineManager.Instance.DefaultSearchProvider; if (searchProvider != null) s = searchProvider; //if both are zero, use the simple native Examine API if (skip == 0 && take == 0) { var r = s.Search(criteria); totalRecords = r.TotalItemCount; return r.ConvertSearchResultToPublishedContent(_contentCache); } var maxResults = skip + take; var results = s.Search(criteria, //don't return more results than we need for the paging //this is the 'trick' - we need to be able to load enough search results to fill //all items to the maxResults maxResults: maxResults); totalRecords = results.TotalItemCount; //use examine to skip, this will ensure the lucene data is not loaded for those items var records = results.Skip(skip); return records.ConvertSearchResultToPublishedContent(_contentCache); } /// /// Searhes content /// /// /// /// public IEnumerable TypedSearch(Examine.SearchCriteria.ISearchCriteria criteria, Examine.Providers.BaseSearchProvider searchProvider = null) { var total = 0; return TypedSearch(0, 0, out total, criteria, searchProvider); } /// /// Helper method to create an ISearchCriteria for searching all fields in a /// /// /// /// /// /// /// This is here because some of this stuff is internal in Examine /// private ISearchCriteria SearchAllFields(string searchText, bool useWildcards, BaseLuceneSearcher searcher) { var sc = searcher.CreateSearchCriteria(); if (_examineGetSearchFields == null) { //get the GetSearchFields method from BaseLuceneSearcher _examineGetSearchFields = typeof(BaseLuceneSearcher).GetMethod("GetSearchFields", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); } //get the results of searcher.BaseLuceneSearcher() using ugly reflection since it's not public var searchFields = (IEnumerable)_examineGetSearchFields.Invoke(searcher, null); //this is what Examine does internally to create ISearchCriteria for searching all fields var strArray = searchText.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); sc = useWildcards == false ? sc.GroupedOr(searchFields, strArray).Compile() : sc.GroupedOr(searchFields, strArray.Select(x => new CustomExamineValue(Examineness.ComplexWildcard, x.MultipleCharacterWildcard().Value)).ToArray()).Compile(); return sc; } private static MethodInfo _examineGetSearchFields; //support class since Examine doesn't expose it's own ExamineValue class publicly private class CustomExamineValue : IExamineValue { public CustomExamineValue(Examineness vagueness, string value) { this.Examineness = vagueness; this.Value = value; this.Level = 1f; } public Examineness Examineness { get; private set; } public string Value { get; private set; } public float Level { get; private set; } } #endregion } }