Web.Routing - RoutesCache goes with PublishedCache
This commit is contained in:
@@ -1,9 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Xml;
|
||||
using Umbraco.Web.Routing;
|
||||
using umbraco;
|
||||
using System.Linq;
|
||||
using umbraco.BusinessLogic;
|
||||
@@ -13,9 +16,180 @@ namespace Umbraco.Web.PublishedCache.LegacyXmlCache
|
||||
{
|
||||
internal class PublishedContentCache : IPublishedContentCache
|
||||
{
|
||||
#region XPath Strings
|
||||
#region Routes cache
|
||||
|
||||
class XPathStringsDefinition
|
||||
private readonly RoutesCache _routesCache = new RoutesCache(!UnitTesting);
|
||||
|
||||
// for INTERNAL, UNIT TESTS use ONLY
|
||||
internal RoutesCache RoutesCache { get { return _routesCache; } }
|
||||
|
||||
// for INTERNAL, UNIT TESTS use ONLY
|
||||
internal static bool UnitTesting = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets content identified by a route.
|
||||
/// </summary>
|
||||
/// <param name="umbracoContext">The context.</param>
|
||||
/// <param name="route">The route</param>
|
||||
/// <param name="hideTopLevelNode">A value forcing the HideTopLevelNode setting.</param>
|
||||
/// <returns>The content, or null.</returns>
|
||||
/// <remarks>
|
||||
/// <para>A valid route is either a simple path eg <c>/foo/bar/nil</c> or a root node id and a path, eg <c>123/foo/bar/nil</c>.</para>
|
||||
/// <para>If <param name="hideTopLevelNode" /> is <c>null</c> then the settings value is used.</para>
|
||||
/// </remarks>
|
||||
public IPublishedContent GetByRoute(UmbracoContext umbracoContext, string route, bool? hideTopLevelNode = null)
|
||||
{
|
||||
if (route == null) throw new ArgumentNullException("route");
|
||||
|
||||
// try to get from cache if not previewing
|
||||
var contentId = umbracoContext.InPreviewMode
|
||||
? 0
|
||||
: _routesCache.GetNodeId(route);
|
||||
|
||||
// if found id in cache then get corresponding content
|
||||
// and clear cache if not found - for whatever reason
|
||||
IPublishedContent content = null;
|
||||
if (contentId > 0)
|
||||
{
|
||||
content = GetById(umbracoContext, contentId);
|
||||
if (content == null)
|
||||
_routesCache.ClearNode(contentId);
|
||||
}
|
||||
|
||||
// still have nothing? actually determine the id
|
||||
hideTopLevelNode = hideTopLevelNode ?? GlobalSettings.HideTopLevelNodeFromPath; // default = settings
|
||||
content = content ?? DetermineIdByRoute(umbracoContext, route, hideTopLevelNode.Value);
|
||||
|
||||
// cache if we have a content and not previewing
|
||||
if (content != null && !umbracoContext.InPreviewMode)
|
||||
{
|
||||
var domainRootNodeId = route.StartsWith("/") ? -1 : int.Parse(route.Substring(0, route.IndexOf('/')));
|
||||
var iscanon = !UnitTesting && !DomainHelper.ExistsDomainInPath(DomainHelper.GetAllDomains(false), content.Path, domainRootNodeId);
|
||||
// and only if this is the canonical url (the one GetUrl would return)
|
||||
if (iscanon)
|
||||
_routesCache.Store(contentId, route);
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the route for a content identified by its unique identifier.
|
||||
/// </summary>
|
||||
/// <param name="umbracoContext">The context.</param>
|
||||
/// <param name="contentId">The content unique identifier.</param>
|
||||
/// <returns>The route.</returns>
|
||||
public string GetRouteById(UmbracoContext umbracoContext, int contentId)
|
||||
{
|
||||
// try to get from cache if not previewing
|
||||
var route = umbracoContext.InPreviewMode
|
||||
? null
|
||||
: _routesCache.GetRoute(contentId);
|
||||
|
||||
// if found in cache then return
|
||||
if (route != null)
|
||||
return route;
|
||||
|
||||
// else actually determine the route
|
||||
route = DetermineRouteById(umbracoContext, contentId);
|
||||
|
||||
// cache if we have a route and not previewing
|
||||
if (route != null && !umbracoContext.InPreviewMode)
|
||||
_routesCache.Store(contentId, route);
|
||||
|
||||
return route;
|
||||
}
|
||||
|
||||
IPublishedContent DetermineIdByRoute(UmbracoContext umbracoContext, string route, bool hideTopLevelNode)
|
||||
{
|
||||
if (route == null) throw new ArgumentNullException("route");
|
||||
|
||||
//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));
|
||||
IEnumerable<XPathVariable> vars;
|
||||
|
||||
var xpath = CreateXpathQuery(startNodeId, path, hideTopLevelNode, out vars);
|
||||
|
||||
//check if we can find the node in our xml cache
|
||||
var content = GetSingleByXPath(umbracoContext, xpath, vars == null ? null : vars.ToArray());
|
||||
|
||||
// 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).
|
||||
if (content == null && hideTopLevelNode && path.Length > 1 && path.IndexOf('/', 1) < 0)
|
||||
{
|
||||
xpath = CreateXpathQuery(startNodeId, path, false, out vars);
|
||||
content = GetSingleByXPath(umbracoContext, xpath, vars == null ? null : vars.ToArray());
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
string DetermineRouteById(UmbracoContext umbracoContext, int contentId)
|
||||
{
|
||||
var node = GetById(umbracoContext, contentId);
|
||||
if (node == null)
|
||||
return null;
|
||||
|
||||
// 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;
|
||||
var hasDomains = DomainHelper.NodeHasDomains(n.Id);
|
||||
while (!hasDomains && n != null) // n is null at root
|
||||
{
|
||||
// get the url
|
||||
var urlName = n.UrlName;
|
||||
pathParts.Add(urlName);
|
||||
|
||||
// move to parent node
|
||||
n = n.Parent;
|
||||
hasDomains = n != null && DomainHelper.NodeHasDomains(n.Id);
|
||||
}
|
||||
|
||||
// no domain, respect HideTopLevelNodeFromPath for legacy purposes
|
||||
if (!hasDomains && global::umbraco.GlobalSettings.HideTopLevelNodeFromPath)
|
||||
ApplyHideTopLevelNodeFromPath(umbracoContext, node, pathParts);
|
||||
|
||||
// assemble the route
|
||||
pathParts.Reverse();
|
||||
var path = "/" + string.Join("/", pathParts); // will be "/" or "/foo" or "/foo/bar" etc
|
||||
var route = (n == null ? "" : n.Id.ToString(CultureInfo.InvariantCulture)) + path;
|
||||
|
||||
return route;
|
||||
}
|
||||
|
||||
static void ApplyHideTopLevelNodeFromPath(UmbracoContext umbracoContext, IPublishedContent node, IList<string> pathParts)
|
||||
{
|
||||
// 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
|
||||
// "/foo" fails (looking for "/*/foo") we try also "/foo".
|
||||
// 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
|
||||
if (node.Parent == null)
|
||||
{
|
||||
var rootNode = umbracoContext.ContentCache.GetByRoute("/", true);
|
||||
if (rootNode.Id == node.Id) // remove only if we're the default node
|
||||
pathParts.RemoveAt(pathParts.Count - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
pathParts.RemoveAt(pathParts.Count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region XPath Strings
|
||||
|
||||
class XPathStringsDefinition
|
||||
{
|
||||
public int Version { get; private set; }
|
||||
|
||||
@@ -134,39 +308,6 @@ namespace Umbraco.Web.PublishedCache.LegacyXmlCache
|
||||
return ConvertToDocuments(nodes);
|
||||
}
|
||||
|
||||
//FIXME keep here or remove?
|
||||
public IPublishedContent GetByRoute(UmbracoContext umbracoContext, string route, bool? hideTopLevelNode = null)
|
||||
{
|
||||
if (route == null) throw new ArgumentNullException("route");
|
||||
|
||||
//set the default to be what is in the settings
|
||||
hideTopLevelNode = hideTopLevelNode ?? GlobalSettings.HideTopLevelNodeFromPath;
|
||||
|
||||
//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));
|
||||
IEnumerable<XPathVariable> vars;
|
||||
|
||||
var xpath = CreateXpathQuery(startNodeId, path, hideTopLevelNode.Value, out vars);
|
||||
|
||||
//check if we can find the node in our xml cache
|
||||
var content = GetSingleByXPath(umbracoContext, xpath, vars == null ? null : vars.ToArray());
|
||||
|
||||
// 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 NiceUrlProvider).
|
||||
if (content == null && hideTopLevelNode.Value && path.Length > 1 && path.IndexOf('/', 1) < 0)
|
||||
{
|
||||
xpath = CreateXpathQuery(startNodeId, path, false, out vars);
|
||||
content = GetSingleByXPath(umbracoContext, xpath, vars == null ? null : vars.ToArray());
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
// FIXME MOVE THAT ONE OUT OF HERE?
|
||||
public IPublishedContent GetByUrlAlias(UmbracoContext umbracoContext, int rootNodeId, string alias)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user