2012-07-20 01:04:35 +06:00
|
|
|
using System;
|
2012-10-04 01:31:08 +05:00
|
|
|
using System.Collections.Generic;
|
2013-03-20 16:01:49 -01:00
|
|
|
using System.Globalization;
|
2012-07-20 01:04:35 +06:00
|
|
|
using System.Text;
|
|
|
|
|
using System.Xml;
|
2013-04-03 11:19:10 -02:00
|
|
|
using System.Xml.XPath;
|
2013-09-13 18:36:41 +10:00
|
|
|
using Umbraco.Core.Configuration;
|
2013-06-20 15:57:23 +10:00
|
|
|
using Umbraco.Core;
|
2013-09-13 15:39:29 +02:00
|
|
|
using Umbraco.Core.Models.PublishedContent;
|
2013-02-14 16:23:56 -01:00
|
|
|
using Umbraco.Core.Xml;
|
2013-03-20 16:01:49 -01:00
|
|
|
using Umbraco.Web.Routing;
|
2012-09-13 12:19:56 +07:00
|
|
|
using System.Linq;
|
2016-05-26 17:12:04 +02:00
|
|
|
using Umbraco.Core.Cache;
|
2016-10-28 14:33:44 +02:00
|
|
|
using Umbraco.Core.Services;
|
|
|
|
|
using Task = System.Threading.Tasks.Task;
|
2012-07-20 01:04:35 +06:00
|
|
|
|
2013-03-22 15:02:26 -01:00
|
|
|
namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
2012-07-20 01:04:35 +06:00
|
|
|
{
|
2016-05-26 17:12:04 +02:00
|
|
|
internal class PublishedContentCache : PublishedCacheBase, IPublishedContentCache
|
2013-03-19 17:51:55 -01:00
|
|
|
{
|
2016-05-26 17:12:04 +02:00
|
|
|
private readonly ICacheProvider _cacheProvider;
|
|
|
|
|
private readonly RoutesCache _routesCache;
|
|
|
|
|
private readonly IDomainCache _domainCache;
|
|
|
|
|
private readonly DomainHelper _domainHelper;
|
|
|
|
|
private readonly PublishedContentTypeCache _contentTypeCache;
|
|
|
|
|
|
|
|
|
|
// initialize a PublishedContentCache instance with
|
|
|
|
|
// an XmlStore containing the master xml
|
|
|
|
|
// an ICacheProvider that should be at request-level
|
|
|
|
|
// a RoutesCache - need to cleanup that one
|
|
|
|
|
// a preview token string (or null if not previewing)
|
|
|
|
|
public PublishedContentCache(
|
|
|
|
|
XmlStore xmlStore, // an XmlStore containing the master xml
|
|
|
|
|
IDomainCache domainCache, // an IDomainCache implementation
|
|
|
|
|
ICacheProvider cacheProvider, // an ICacheProvider that should be at request-level
|
|
|
|
|
PublishedContentTypeCache contentTypeCache, // a PublishedContentType cache
|
|
|
|
|
RoutesCache routesCache, // a RoutesCache
|
|
|
|
|
string previewToken) // a preview token string (or null if not previewing)
|
|
|
|
|
: base(previewToken.IsNullOrWhiteSpace() == false)
|
2015-12-21 17:09:31 +01:00
|
|
|
{
|
2016-05-26 17:12:04 +02:00
|
|
|
_cacheProvider = cacheProvider;
|
|
|
|
|
_routesCache = routesCache; // may be null for unit-testing
|
|
|
|
|
_contentTypeCache = contentTypeCache;
|
|
|
|
|
_domainCache = domainCache;
|
|
|
|
|
_domainHelper = new DomainHelper(_domainCache);
|
|
|
|
|
|
|
|
|
|
_xmlStore = xmlStore;
|
|
|
|
|
_xml = _xmlStore.Xml; // capture - because the cache has to remain consistent
|
|
|
|
|
|
|
|
|
|
if (previewToken.IsNullOrWhiteSpace() == false)
|
|
|
|
|
_previewContent = new PreviewContent(_xmlStore, previewToken);
|
2015-01-21 12:48:08 +11:00
|
|
|
}
|
|
|
|
|
|
2016-05-26 17:12:04 +02:00
|
|
|
#region Unit Tests
|
2013-03-20 16:01:49 -01:00
|
|
|
|
|
|
|
|
// for INTERNAL, UNIT TESTS use ONLY
|
2016-05-26 17:12:04 +02:00
|
|
|
internal RoutesCache RoutesCache => _routesCache;
|
2013-03-20 16:01:49 -01:00
|
|
|
|
|
|
|
|
// for INTERNAL, UNIT TESTS use ONLY
|
2016-05-26 17:12:04 +02:00
|
|
|
internal XmlStore XmlStore => _xmlStore;
|
|
|
|
|
|
|
|
|
|
#endregion
|
2013-03-20 16:01:49 -01:00
|
|
|
|
2016-05-26 17:12:04 +02:00
|
|
|
#region Routes
|
|
|
|
|
|
|
|
|
|
public virtual IPublishedContent GetByRoute(bool preview, string route, bool? hideTopLevelNode = null)
|
2013-03-20 16:01:49 -01:00
|
|
|
{
|
2016-05-26 17:12:04 +02:00
|
|
|
if (route == null) throw new ArgumentNullException(nameof(route));
|
2013-03-20 16:01:49 -01:00
|
|
|
|
|
|
|
|
// try to get from cache if not previewing
|
2016-05-26 17:12:04 +02:00
|
|
|
var contentId = (preview || _routesCache == null) ? 0 : _routesCache.GetNodeId(route);
|
2013-03-20 16:01:49 -01:00
|
|
|
|
|
|
|
|
// if found id in cache then get corresponding content
|
|
|
|
|
// and clear cache if not found - for whatever reason
|
|
|
|
|
IPublishedContent content = null;
|
|
|
|
|
if (contentId > 0)
|
|
|
|
|
{
|
2016-05-26 17:12:04 +02:00
|
|
|
content = GetById(preview, contentId);
|
2013-03-20 16:01:49 -01:00
|
|
|
if (content == null)
|
2016-05-26 17:12:04 +02:00
|
|
|
_routesCache?.ClearNode(contentId);
|
2013-03-20 16:01:49 -01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// still have nothing? actually determine the id
|
|
|
|
|
hideTopLevelNode = hideTopLevelNode ?? GlobalSettings.HideTopLevelNodeFromPath; // default = settings
|
2016-05-26 17:12:04 +02:00
|
|
|
content = content ?? DetermineIdByRoute(preview, route, hideTopLevelNode.Value);
|
2013-03-20 16:01:49 -01:00
|
|
|
|
|
|
|
|
// cache if we have a content and not previewing
|
2016-05-26 17:12:04 +02:00
|
|
|
if (content != null && preview == false && _routesCache != null)
|
2016-06-02 10:03:14 +02:00
|
|
|
AddToCacheIfDeepestRoute(content, route);
|
2013-03-20 16:01:49 -01:00
|
|
|
|
|
|
|
|
return content;
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-02 10:03:14 +02:00
|
|
|
private void AddToCacheIfDeepestRoute(IPublishedContent content, string route)
|
2016-05-04 12:45:20 +02:00
|
|
|
{
|
|
|
|
|
var domainRootNodeId = route.StartsWith("/") ? -1 : int.Parse(route.Substring(0, route.IndexOf('/')));
|
|
|
|
|
|
|
|
|
|
// so we have a route that maps to a content... say "1234/path/to/content" - however, there could be a
|
|
|
|
|
// domain set on "to" and route "4567/content" would also map to the same content - and due to how
|
|
|
|
|
// urls computing work (by walking the tree up to the first domain we find) it is that second route
|
|
|
|
|
// that would be returned - the "deepest" route - and that is the route we want to cache, *not* the
|
|
|
|
|
// longer one - so make sure we don't cache the wrong route
|
|
|
|
|
|
2016-06-02 10:03:14 +02:00
|
|
|
var deepest = DomainHelper.ExistsDomainInPath(_domainCache.GetAll(false), content.Path, domainRootNodeId) == false;
|
2016-05-04 12:45:20 +02:00
|
|
|
|
|
|
|
|
if (deepest)
|
|
|
|
|
_routesCache.Store(content.Id, route);
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-26 17:12:04 +02:00
|
|
|
public IPublishedContent GetByRoute(string route, bool? hideTopLevelNode = null)
|
|
|
|
|
{
|
|
|
|
|
return GetByRoute(PreviewDefault, route, hideTopLevelNode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public virtual string GetRouteById(bool preview, int contentId)
|
2013-03-20 16:01:49 -01:00
|
|
|
{
|
|
|
|
|
// try to get from cache if not previewing
|
2016-05-26 17:12:04 +02:00
|
|
|
var route = (preview || _routesCache == null) ? null : _routesCache.GetRoute(contentId);
|
2013-03-20 16:01:49 -01:00
|
|
|
|
|
|
|
|
// if found in cache then return
|
|
|
|
|
if (route != null)
|
|
|
|
|
return route;
|
|
|
|
|
|
|
|
|
|
// else actually determine the route
|
2016-05-26 17:12:04 +02:00
|
|
|
route = DetermineRouteById(preview, contentId);
|
2013-03-20 16:01:49 -01:00
|
|
|
|
2016-05-04 12:45:20 +02:00
|
|
|
// node not found
|
|
|
|
|
if (route == null)
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
// find the content back, detect routes collisions: we should find ourselves back,
|
|
|
|
|
// else it means that another content with "higher priority" is sharing the same route.
|
|
|
|
|
// perf impact:
|
|
|
|
|
// - non-colliding, adds one complete "by route" lookup, only on the first time a url is computed (then it's cached anyways)
|
|
|
|
|
// - colliding, adds one "by route" lookup, the first time the url is computed, then one dictionary looked each time it is computed again
|
|
|
|
|
// assuming no collisions, the impact is one complete "by route" lookup the first time each url is computed
|
2016-10-28 14:33:44 +02:00
|
|
|
//
|
|
|
|
|
// U4-9121 - this lookup is too expensive when computing a large amount of urls on a front-end (eg menu)
|
|
|
|
|
// ... thinking about moving the lookup out of the path into its own async task, so we are not reporting errors
|
|
|
|
|
// in the back-office anymore, but at least we are not polluting the cache
|
|
|
|
|
// instead, refactored DeterminedIdByRoute to stop using XPath, with a 16x improvement according to benchmarks
|
|
|
|
|
// will it be enough?
|
|
|
|
|
|
2016-06-02 10:03:14 +02:00
|
|
|
var loopId = preview ? 0 : (_routesCache?.GetNodeId(route) ?? 0); // might be cached already in case of collision
|
2016-05-04 12:45:20 +02:00
|
|
|
if (loopId == 0)
|
|
|
|
|
{
|
2016-06-02 10:03:14 +02:00
|
|
|
var content = DetermineIdByRoute(preview, route, GlobalSettings.HideTopLevelNodeFromPath);
|
2016-05-04 12:45:20 +02:00
|
|
|
|
|
|
|
|
// add the other route to cache so next time we have it already
|
|
|
|
|
if (content != null && preview == false)
|
2016-06-02 10:03:14 +02:00
|
|
|
AddToCacheIfDeepestRoute(content, route);
|
2016-05-04 12:45:20 +02:00
|
|
|
|
2016-06-02 10:03:14 +02:00
|
|
|
loopId = content?.Id ?? 0; // though... 0 here would be quite weird?
|
2016-05-04 12:45:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// cache if we have a route and not previewing and it's not a colliding route
|
|
|
|
|
// (the result of DetermineRouteById is always the deepest route)
|
|
|
|
|
if (/*route != null &&*/ preview == false && loopId == contentId)
|
2016-05-26 17:12:04 +02:00
|
|
|
_routesCache?.Store(contentId, route);
|
2013-03-20 16:01:49 -01:00
|
|
|
|
2016-05-04 12:45:20 +02:00
|
|
|
// return route if no collision, else report collision
|
|
|
|
|
return loopId == contentId ? route : ("err/" + loopId);
|
2013-03-20 16:01:49 -01:00
|
|
|
}
|
|
|
|
|
|
2016-05-26 17:12:04 +02:00
|
|
|
public string GetRouteById(int contentId)
|
|
|
|
|
{
|
|
|
|
|
return GetRouteById(PreviewDefault, contentId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IPublishedContent DetermineIdByRoute(bool preview, string route, bool hideTopLevelNode)
|
2013-03-20 16:01:49 -01:00
|
|
|
{
|
2016-05-26 17:12:04 +02:00
|
|
|
if (route == null) throw new ArgumentNullException(nameof(route));
|
2013-03-20 16:01:49 -01:00
|
|
|
|
|
|
|
|
//the route always needs to be lower case because we only store the urlName attribute in lower case
|
|
|
|
|
route = route.ToLowerInvariant();
|
|
|
|
|
|
|
|
|
|
var pos = route.IndexOf('/');
|
|
|
|
|
var path = pos == 0 ? route : route.Substring(pos);
|
|
|
|
|
var startNodeId = pos == 0 ? 0 : int.Parse(route.Substring(0, pos));
|
|
|
|
|
|
|
|
|
|
//check if we can find the node in our xml cache
|
2016-11-04 18:40:42 +01:00
|
|
|
var id = NavigateRoute(preview, startNodeId, path, hideTopLevelNode);
|
|
|
|
|
if (id > 0) return GetById(preview, id);
|
2012-07-20 01:04:35 +06:00
|
|
|
|
2013-03-20 16:01:49 -01:00
|
|
|
// if hideTopLevelNodePath is true then for url /foo we looked for /*/foo
|
|
|
|
|
// but maybe that was the url of a non-default top-level node, so we also
|
|
|
|
|
// have to look for /foo (see note in ApplyHideTopLevelNodeFromPath).
|
2016-10-28 14:33:44 +02:00
|
|
|
if (hideTopLevelNode && path.Length > 1 && path.IndexOf('/', 1) < 0)
|
2013-03-20 16:01:49 -01:00
|
|
|
{
|
2016-11-04 18:40:42 +01:00
|
|
|
var id2 = NavigateRoute(preview, startNodeId, path, false);
|
|
|
|
|
if (id2 > 0) return GetById(preview, id2);
|
2013-03-20 16:01:49 -01:00
|
|
|
}
|
|
|
|
|
|
2016-10-28 14:33:44 +02:00
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2016-11-04 18:40:42 +01:00
|
|
|
private int NavigateRoute(bool preview, int startNodeId, string path, bool hideTopLevelNode)
|
2016-10-28 14:33:44 +02:00
|
|
|
{
|
2016-11-04 18:40:42 +01:00
|
|
|
var xml = GetXml(preview);
|
2016-10-28 14:33:44 +02:00
|
|
|
XmlElement elt;
|
|
|
|
|
|
|
|
|
|
// empty path
|
|
|
|
|
if (path == string.Empty || path == "/")
|
|
|
|
|
{
|
|
|
|
|
if (startNodeId > 0)
|
|
|
|
|
{
|
|
|
|
|
elt = xml.GetElementById(startNodeId.ToString(CultureInfo.InvariantCulture));
|
|
|
|
|
return elt == null ? -1 : startNodeId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
elt = null;
|
|
|
|
|
var min = int.MaxValue;
|
|
|
|
|
foreach (XmlElement e in xml.DocumentElement.ChildNodes)
|
|
|
|
|
{
|
|
|
|
|
var sortOrder = int.Parse(e.GetAttribute("sortOrder"));
|
|
|
|
|
if (sortOrder < min)
|
|
|
|
|
{
|
|
|
|
|
min = sortOrder;
|
|
|
|
|
elt = e;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return elt == null ? -1 : int.Parse(elt.GetAttribute("id"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// non-empty path
|
|
|
|
|
elt = startNodeId <= 0
|
|
|
|
|
? xml.DocumentElement
|
|
|
|
|
: xml.GetElementById(startNodeId.ToString(CultureInfo.InvariantCulture));
|
|
|
|
|
if (elt == null) return -1;
|
|
|
|
|
|
|
|
|
|
var urlParts = path.Split(SlashChar, StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
|
|
|
|
|
|
if (hideTopLevelNode && startNodeId <= 0)
|
|
|
|
|
{
|
|
|
|
|
foreach (XmlElement e in elt.ChildNodes)
|
|
|
|
|
{
|
|
|
|
|
var id = NavigateElementRoute(e, urlParts);
|
|
|
|
|
if (id > 0) return id;
|
|
|
|
|
}
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NavigateElementRoute(elt, urlParts);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private int NavigateElementRoute(XmlElement elt, string[] urlParts)
|
|
|
|
|
{
|
|
|
|
|
var found = true;
|
|
|
|
|
var i = 0;
|
|
|
|
|
while (found && i < urlParts.Length)
|
|
|
|
|
{
|
|
|
|
|
found = false;
|
|
|
|
|
foreach (XmlElement child in elt.ChildNodes)
|
|
|
|
|
{
|
2016-11-04 18:40:42 +01:00
|
|
|
var noNode = child.GetAttributeNode("isDoc") == null;
|
2016-10-28 14:33:44 +02:00
|
|
|
if (noNode) continue;
|
|
|
|
|
if (child.GetAttribute("urlName") != urlParts[i]) continue;
|
|
|
|
|
|
|
|
|
|
found = true;
|
|
|
|
|
elt = child;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
i++;
|
|
|
|
|
}
|
|
|
|
|
return found ? int.Parse(elt.GetAttribute("id")) : -1;
|
2013-03-20 16:01:49 -01:00
|
|
|
}
|
|
|
|
|
|
2016-05-26 17:12:04 +02:00
|
|
|
string DetermineRouteById(bool preview, int contentId)
|
2013-03-20 16:01:49 -01:00
|
|
|
{
|
2016-05-26 17:12:04 +02:00
|
|
|
var node = GetById(preview, contentId);
|
2016-11-04 18:40:42 +01:00
|
|
|
if (node == null) return null;
|
2013-03-20 16:01:49 -01:00
|
|
|
|
|
|
|
|
// walk up from that node until we hit a node with a domain,
|
|
|
|
|
// or we reach the content root, collecting urls in the way
|
|
|
|
|
var pathParts = new List<string>();
|
|
|
|
|
var n = node;
|
2016-05-26 17:12:04 +02:00
|
|
|
var hasDomains = _domainHelper.NodeHasDomains(n.Id);
|
2015-01-21 19:49:22 +11:00
|
|
|
while (hasDomains == false && n != null) // n is null at root
|
2013-03-20 16:01:49 -01:00
|
|
|
{
|
|
|
|
|
// get the url
|
|
|
|
|
var urlName = n.UrlName;
|
|
|
|
|
pathParts.Add(urlName);
|
|
|
|
|
|
|
|
|
|
// move to parent node
|
|
|
|
|
n = n.Parent;
|
2016-05-26 17:12:04 +02:00
|
|
|
hasDomains = n != null && _domainHelper.NodeHasDomains(n.Id);
|
2013-03-20 16:01:49 -01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// no domain, respect HideTopLevelNodeFromPath for legacy purposes
|
2015-12-23 13:51:16 +01:00
|
|
|
if (hasDomains == false && GlobalSettings.HideTopLevelNodeFromPath)
|
2016-05-26 17:12:04 +02:00
|
|
|
ApplyHideTopLevelNodeFromPath(node, pathParts, preview);
|
2013-03-20 16:01:49 -01:00
|
|
|
|
|
|
|
|
// assemble the route
|
|
|
|
|
pathParts.Reverse();
|
|
|
|
|
var path = "/" + string.Join("/", pathParts); // will be "/" or "/foo" or "/foo/bar" etc
|
2016-05-26 17:12:04 +02:00
|
|
|
var route = (n?.Id.ToString(CultureInfo.InvariantCulture) ?? "") + path;
|
2013-03-20 16:01:49 -01:00
|
|
|
|
|
|
|
|
return route;
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-26 17:12:04 +02:00
|
|
|
void ApplyHideTopLevelNodeFromPath(IPublishedContent content, IList<string> segments, bool preview)
|
2013-03-20 16:01:49 -01:00
|
|
|
{
|
|
|
|
|
// in theory if hideTopLevelNodeFromPath is true, then there should be only once
|
|
|
|
|
// top-level node, or else domains should be assigned. but for backward compatibility
|
|
|
|
|
// we add this check - we look for the document matching "/" and if it's not us, then
|
|
|
|
|
// we do not hide the top level path
|
|
|
|
|
// it has to be taken care of in GetByRoute too so if
|
2016-02-11 11:53:03 +01:00
|
|
|
// "/foo" fails (looking for "/*/foo") we try also "/foo".
|
2013-03-20 16:01:49 -01:00
|
|
|
// this does not make much sense anyway esp. if both "/foo/" and "/bar/foo" exist, but
|
|
|
|
|
// that's the way it works pre-4.10 and we try to be backward compat for the time being
|
2016-05-26 17:12:04 +02:00
|
|
|
if (content.Parent == null)
|
2013-03-20 16:01:49 -01:00
|
|
|
{
|
2016-05-26 17:12:04 +02:00
|
|
|
var rootNode = GetByRoute(preview, "/", true);
|
2013-03-31 18:40:55 -02:00
|
|
|
if (rootNode == null)
|
|
|
|
|
throw new Exception("Failed to get node at /.");
|
2016-05-26 17:12:04 +02:00
|
|
|
if (rootNode.Id == content.Id) // remove only if we're the default node
|
|
|
|
|
segments.RemoveAt(segments.Count - 1);
|
2013-03-20 16:01:49 -01:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2016-05-26 17:12:04 +02:00
|
|
|
segments.RemoveAt(segments.Count - 1);
|
2013-03-20 16:01:49 -01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region XPath Strings
|
|
|
|
|
|
2016-05-26 17:12:04 +02:00
|
|
|
static class XPathStrings
|
|
|
|
|
{
|
|
|
|
|
public const string Root = "/root";
|
|
|
|
|
public const string RootDocuments = "/root/* [@isDoc]";
|
|
|
|
|
}
|
2012-10-05 08:00:06 -02:00
|
|
|
|
2016-05-26 17:12:04 +02:00
|
|
|
#endregion
|
2012-10-05 08:00:06 -02:00
|
|
|
|
2013-03-19 17:51:55 -01:00
|
|
|
#region Converters
|
|
|
|
|
|
2016-06-29 14:46:53 +02:00
|
|
|
private IPublishedContent ConvertToDocument(XmlNode xmlNode, bool isPreviewing)
|
2012-08-10 13:08:47 +06:00
|
|
|
{
|
2016-07-21 11:07:25 +02:00
|
|
|
return xmlNode == null ? null : XmlPublishedContent.Get(xmlNode, isPreviewing, _cacheProvider, _contentTypeCache);
|
2013-09-05 17:47:13 +02:00
|
|
|
}
|
2012-08-10 13:08:47 +06:00
|
|
|
|
2016-06-29 14:46:53 +02:00
|
|
|
private IEnumerable<IPublishedContent> ConvertToDocuments(XmlNodeList xmlNodes, bool isPreviewing)
|
2013-02-05 06:31:13 -01:00
|
|
|
{
|
2013-09-13 15:39:29 +02:00
|
|
|
return xmlNodes.Cast<XmlNode>()
|
2016-07-21 11:07:25 +02:00
|
|
|
.Select(xmlNode => XmlPublishedContent.Get(xmlNode, isPreviewing, _cacheProvider, _contentTypeCache));
|
2013-02-05 06:31:13 -01:00
|
|
|
}
|
2012-09-08 11:59:01 +07:00
|
|
|
|
2013-03-19 17:51:55 -01:00
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Getters
|
|
|
|
|
|
2016-05-26 17:12:04 +02:00
|
|
|
public override IPublishedContent GetById(bool preview, int nodeId)
|
2013-03-19 17:51:55 -01:00
|
|
|
{
|
2016-06-29 14:46:53 +02:00
|
|
|
return ConvertToDocument(GetXml(preview).GetElementById(nodeId.ToString(CultureInfo.InvariantCulture)), preview);
|
2012-09-08 11:59:01 +07:00
|
|
|
}
|
2012-07-20 01:04:35 +06:00
|
|
|
|
2016-11-03 10:31:44 +01:00
|
|
|
public override IPublishedContent GetById(bool preview, Guid nodeId)
|
2013-03-31 18:47:25 -02:00
|
|
|
{
|
2016-11-03 10:31:44 +01:00
|
|
|
// todo - implement in a more efficient way
|
|
|
|
|
const string xpath = "//* [@isDoc and @key=$guid]";
|
|
|
|
|
return GetSingleByXPath(preview, xpath, new [] { new XPathVariable("guid", nodeId.ToString()) });
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-26 17:12:04 +02:00
|
|
|
public override bool HasById(bool preview, int contentId)
|
|
|
|
|
{
|
|
|
|
|
return GetXml(preview).CreateNavigator().MoveToId(contentId.ToString(CultureInfo.InvariantCulture));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override IEnumerable<IPublishedContent> GetAtRoot(bool preview)
|
2013-03-31 18:47:25 -02:00
|
|
|
{
|
2016-06-29 14:46:53 +02:00
|
|
|
return ConvertToDocuments(GetXml(preview).SelectNodes(XPathStrings.RootDocuments), preview);
|
2012-10-04 01:31:08 +05:00
|
|
|
}
|
|
|
|
|
|
2016-05-26 17:12:04 +02:00
|
|
|
public override IPublishedContent GetSingleByXPath(bool preview, string xpath, XPathVariable[] vars)
|
2013-02-05 06:31:13 -01:00
|
|
|
{
|
2016-05-26 17:12:04 +02:00
|
|
|
if (xpath == null) throw new ArgumentNullException(nameof(xpath));
|
2013-02-05 06:31:13 -01:00
|
|
|
if (string.IsNullOrWhiteSpace(xpath)) return null;
|
|
|
|
|
|
2016-05-26 17:12:04 +02:00
|
|
|
var xml = GetXml(preview);
|
2013-02-05 06:31:13 -01:00
|
|
|
var node = vars == null
|
|
|
|
|
? xml.SelectSingleNode(xpath)
|
|
|
|
|
: xml.SelectSingleNode(xpath, vars);
|
2016-06-29 14:46:53 +02:00
|
|
|
return ConvertToDocument(node, preview);
|
2013-02-05 06:31:13 -01:00
|
|
|
}
|
|
|
|
|
|
2016-05-26 17:12:04 +02:00
|
|
|
public override IPublishedContent GetSingleByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars)
|
2013-04-10 12:49:45 -02:00
|
|
|
{
|
2016-05-26 17:12:04 +02:00
|
|
|
if (xpath == null) throw new ArgumentNullException(nameof(xpath));
|
2013-04-10 12:49:45 -02:00
|
|
|
|
2016-05-26 17:12:04 +02:00
|
|
|
var xml = GetXml(preview);
|
2013-04-10 12:49:45 -02:00
|
|
|
var node = vars == null
|
|
|
|
|
? xml.SelectSingleNode(xpath)
|
|
|
|
|
: xml.SelectSingleNode(xpath, vars);
|
2016-06-29 14:46:53 +02:00
|
|
|
return ConvertToDocument(node, preview);
|
2013-04-10 12:49:45 -02:00
|
|
|
}
|
|
|
|
|
|
2016-05-26 17:12:04 +02:00
|
|
|
public override IEnumerable<IPublishedContent> GetByXPath(bool preview, string xpath, XPathVariable[] vars)
|
2013-02-05 06:31:13 -01:00
|
|
|
{
|
2016-05-26 17:12:04 +02:00
|
|
|
if (xpath == null) throw new ArgumentNullException(nameof(xpath));
|
2013-02-05 06:31:13 -01:00
|
|
|
if (string.IsNullOrWhiteSpace(xpath)) return Enumerable.Empty<IPublishedContent>();
|
|
|
|
|
|
2016-05-26 17:12:04 +02:00
|
|
|
var xml = GetXml(preview);
|
2013-04-10 12:49:45 -02:00
|
|
|
var nodes = vars == null
|
|
|
|
|
? xml.SelectNodes(xpath)
|
|
|
|
|
: xml.SelectNodes(xpath, vars);
|
2016-06-29 14:46:53 +02:00
|
|
|
return ConvertToDocuments(nodes, preview);
|
2013-04-10 12:49:45 -02:00
|
|
|
}
|
|
|
|
|
|
2016-05-26 17:12:04 +02:00
|
|
|
public override IEnumerable<IPublishedContent> GetByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars)
|
2013-04-10 12:49:45 -02:00
|
|
|
{
|
2016-05-26 17:12:04 +02:00
|
|
|
if (xpath == null) throw new ArgumentNullException(nameof(xpath));
|
2013-04-10 12:49:45 -02:00
|
|
|
|
2016-05-26 17:12:04 +02:00
|
|
|
var xml = GetXml(preview);
|
2013-02-05 06:31:13 -01:00
|
|
|
var nodes = vars == null
|
|
|
|
|
? xml.SelectNodes(xpath)
|
|
|
|
|
: xml.SelectNodes(xpath, vars);
|
2016-06-29 14:46:53 +02:00
|
|
|
return ConvertToDocuments(nodes, preview);
|
2013-02-05 06:31:13 -01:00
|
|
|
}
|
2012-07-20 01:04:35 +06:00
|
|
|
|
2016-05-26 17:12:04 +02:00
|
|
|
public override bool HasContent(bool preview)
|
2012-09-28 07:04:33 -02:00
|
|
|
{
|
2016-05-26 17:12:04 +02:00
|
|
|
var xml = GetXml(preview);
|
|
|
|
|
var node = xml?.SelectSingleNode(XPathStrings.RootDocuments);
|
2012-10-05 08:00:06 -02:00
|
|
|
return node != null;
|
2012-09-28 07:04:33 -02:00
|
|
|
}
|
|
|
|
|
|
2016-05-26 17:12:04 +02:00
|
|
|
public override XPathNavigator CreateNavigator(bool preview)
|
2013-04-03 11:19:10 -02:00
|
|
|
{
|
2016-05-26 17:12:04 +02:00
|
|
|
var xml = GetXml(preview);
|
2013-04-03 11:19:10 -02:00
|
|
|
return xml.CreateNavigator();
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-26 17:12:04 +02:00
|
|
|
public override XPathNavigator CreateNodeNavigator(int id, bool preview)
|
|
|
|
|
{
|
|
|
|
|
// hackish - backward compatibility ;-(
|
|
|
|
|
|
|
|
|
|
XPathNavigator navigator = null;
|
|
|
|
|
|
|
|
|
|
if (preview)
|
|
|
|
|
{
|
|
|
|
|
var node = _xmlStore.GetPreviewXmlNode(id);
|
|
|
|
|
if (node != null)
|
|
|
|
|
{
|
|
|
|
|
navigator = node.CreateNavigator();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var node = GetXml(false).GetElementById(id.ToInvariantString());
|
|
|
|
|
if (node != null)
|
|
|
|
|
{
|
|
|
|
|
var doc = new XmlDocument();
|
|
|
|
|
var clone = doc.ImportNode(node, false);
|
|
|
|
|
var props = node.SelectNodes("./* [not(@id)]");
|
|
|
|
|
if (props == null) throw new Exception("oops");
|
|
|
|
|
foreach (var n in props.Cast<XmlNode>())
|
|
|
|
|
clone.AppendChild(doc.ImportNode(n, true));
|
|
|
|
|
navigator = node.CreateNavigator();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return navigator;
|
|
|
|
|
}
|
2013-06-11 09:52:41 +02:00
|
|
|
|
2013-03-19 17:51:55 -01:00
|
|
|
#endregion
|
2012-07-20 01:04:35 +06:00
|
|
|
|
2013-03-19 17:51:55 -01:00
|
|
|
#region Legacy Xml
|
2012-07-20 01:04:35 +06:00
|
|
|
|
2016-05-26 17:12:04 +02:00
|
|
|
private readonly XmlStore _xmlStore;
|
|
|
|
|
private XmlDocument _xml;
|
|
|
|
|
private readonly PreviewContent _previewContent;
|
|
|
|
|
|
|
|
|
|
internal XmlDocument GetXml(bool preview)
|
|
|
|
|
{
|
|
|
|
|
// not trying to be thread-safe here, that's not the point
|
|
|
|
|
|
|
|
|
|
if (preview == false)
|
|
|
|
|
return _xml;
|
|
|
|
|
|
|
|
|
|
// Xml cache does not support retrieving preview content when not previewing
|
|
|
|
|
if (_previewContent == null)
|
|
|
|
|
throw new InvalidOperationException("Cannot retrieve preview content when not previewing.");
|
2013-05-03 20:09:15 -02:00
|
|
|
|
2016-05-26 17:12:04 +02:00
|
|
|
// PreviewContent tries to load the Xml once and if it fails,
|
|
|
|
|
// it invalidates itself and always return null for XmlContent.
|
|
|
|
|
var previewXml = _previewContent.XmlContent;
|
|
|
|
|
return previewXml ?? _xml;
|
|
|
|
|
}
|
2013-03-19 17:51:55 -01:00
|
|
|
|
2016-05-26 17:12:04 +02:00
|
|
|
internal void Resync()
|
2013-03-19 17:51:55 -01:00
|
|
|
{
|
2016-05-26 17:12:04 +02:00
|
|
|
_xml = _xmlStore.Xml; // re-capture
|
|
|
|
|
|
|
|
|
|
// note: we're not resyncing "preview" because that would mean re-building the whole
|
|
|
|
|
// preview set which is costly, so basically when previewing, there will be no resync.
|
|
|
|
|
|
|
|
|
|
// clear recursive properties cached by XmlPublishedContent.GetProperty
|
|
|
|
|
// assume that nothing else is going to cache IPublishedProperty items (else would need to do ByKeySearch)
|
|
|
|
|
// NOTE also clears all the media cache properties, which is OK (see media cache)
|
|
|
|
|
_cacheProvider.ClearCacheObjectTypes<IPublishedProperty>();
|
|
|
|
|
//_cacheProvider.ClearCacheByKeySearch("XmlPublishedCache.PublishedContentCache:RecursiveProperty-");
|
2013-03-19 17:51:55 -01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region XPathQuery
|
|
|
|
|
|
2016-05-26 17:12:04 +02:00
|
|
|
static readonly char[] SlashChar = { '/' };
|
2012-07-20 01:04:35 +06:00
|
|
|
|
2013-03-19 17:51:55 -01:00
|
|
|
#endregion
|
2014-03-03 13:29:33 +01:00
|
|
|
|
2016-05-26 17:12:04 +02:00
|
|
|
#region Content types
|
|
|
|
|
|
|
|
|
|
public override PublishedContentType GetContentType(int id)
|
|
|
|
|
{
|
|
|
|
|
return _contentTypeCache.Get(PublishedItemType.Content, id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override PublishedContentType GetContentType(string alias)
|
|
|
|
|
{
|
|
|
|
|
return _contentTypeCache.Get(PublishedItemType.Content, alias);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override IEnumerable<IPublishedContent> GetByContentType(PublishedContentType contentType)
|
|
|
|
|
{
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
2012-07-20 01:04:35 +06:00
|
|
|
}
|
2014-04-02 11:22:38 +02:00
|
|
|
}
|