2013-11-07 17:16:22 +01:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Xml ;
using System.Xml.Serialization ;
using System.Xml.XPath ;
using Umbraco.Core ;
2016-05-26 17:12:04 +02:00
using Umbraco.Core.Cache ;
2017-05-12 14:49:44 +02:00
using Umbraco.Core.Configuration ;
2013-11-07 17:16:22 +01:00
using Umbraco.Core.Models.PublishedContent ;
using Umbraco.Web.Models ;
namespace Umbraco.Web.PublishedCache.XmlPublishedCache
{
/// <summary>
/// Represents an IPublishedContent which is created based on an Xml structure.
/// </summary>
[Serializable]
[XmlType(Namespace = "http://umbraco.org/webservices/")]
2016-06-09 19:38:51 +02:00
internal class XmlPublishedContent : PublishedContentBase
2013-11-07 17:16:22 +01:00
{
2016-07-21 11:07:25 +02:00
private XmlPublishedContent ( XmlNode xmlNode , bool isPreviewing , ICacheProvider cacheProvider , PublishedContentTypeCache contentTypeCache )
{
_xmlNode = xmlNode ;
_isPreviewing = isPreviewing ;
_cacheProvider = cacheProvider ;
_contentTypeCache = contentTypeCache ;
}
private readonly XmlNode _xmlNode ;
2016-06-23 09:39:31 +02:00
private readonly bool _isPreviewing ;
2016-06-29 14:46:53 +02:00
private readonly ICacheProvider _cacheProvider ; // at facade/request level (see PublishedContentCache)
2016-05-26 17:12:04 +02:00
private readonly PublishedContentTypeCache _contentTypeCache ;
2016-06-23 09:39:31 +02:00
private bool _nodeInitialized ;
private bool _parentInitialized ;
2013-11-07 17:16:22 +01:00
private bool _childrenInitialized ;
2016-06-23 09:39:31 +02:00
private IEnumerable < IPublishedContent > _children = Enumerable . Empty < IPublishedContent > ( ) ;
2013-11-07 17:16:22 +01:00
private IPublishedContent _parent ;
2016-06-23 09:39:31 +02:00
private PublishedContentType _contentType ;
private Dictionary < string , IPublishedProperty > _properties ;
private int _id ;
2015-08-31 18:59:51 +02:00
private Guid _key ;
2013-11-07 17:16:22 +01:00
private int _template ;
private string _name ;
private string _docTypeAlias ;
private int _docTypeId ;
private string _writerName ;
private string _creatorName ;
private int _writerId ;
private int _creatorId ;
private string _urlName ;
private string _path ;
private DateTime _createDate ;
private DateTime _updateDate ;
private Guid _version ;
private int _sortOrder ;
private int _level ;
private bool _isDraft ;
public override IEnumerable < IPublishedContent > Children
{
get
{
2016-06-23 09:39:31 +02:00
if ( _nodeInitialized = = false ) InitializeNode ( ) ;
if ( _childrenInitialized = = false ) InitializeChildren ( ) ;
return _children ;
2013-11-07 17:16:22 +01:00
}
}
public override IPublishedProperty GetProperty ( string alias )
{
2016-06-23 11:31:32 +02:00
if ( _nodeInitialized = = false ) InitializeNode ( ) ;
IPublishedProperty property ;
2016-06-23 09:39:31 +02:00
return _properties . TryGetValue ( alias , out property ) ? property : null ;
2013-11-07 17:16:22 +01:00
}
// override to implement cache
// cache at context level, ie once for the whole request
// but cache is not shared by requests because we wouldn't know how to clear it
public override IPublishedProperty GetProperty ( string alias , bool recurse )
{
if ( recurse = = false ) return GetProperty ( alias ) ;
2016-07-20 12:38:57 +02:00
var key = $"XmlPublishedCache.PublishedContentCache:RecursiveProperty-{Id}-{alias.ToLowerInvariant()}" ;
2016-05-26 17:12:04 +02:00
var cacheProvider = _cacheProvider ;
return cacheProvider . GetCacheItem < IPublishedProperty > ( key , ( ) = > base . GetProperty ( alias , true ) ) ;
2013-11-07 17:16:22 +01:00
2016-05-26 17:12:04 +02:00
// note: cleared by PublishedContentCache.Resync - any change here must be applied there
2013-11-07 17:16:22 +01:00
}
2016-07-20 12:38:57 +02:00
public override PublishedItemType ItemType = > PublishedItemType . Content ;
2013-11-07 17:16:22 +01:00
2016-07-20 12:38:57 +02:00
public override IPublishedContent Parent
2013-11-07 17:16:22 +01:00
{
get
{
2016-06-23 09:39:31 +02:00
if ( _nodeInitialized = = false ) InitializeNode ( ) ;
if ( _parentInitialized = = false ) InitializeParent ( ) ;
2013-11-07 17:16:22 +01:00
return _parent ;
}
}
2017-05-12 14:49:44 +02:00
public override int Id
2013-11-07 17:16:22 +01:00
{
get
{
2016-06-23 09:39:31 +02:00
if ( _nodeInitialized = = false ) InitializeNode ( ) ;
2013-11-07 17:16:22 +01:00
return _id ;
}
}
2015-08-31 18:59:51 +02:00
public override Guid Key
{
get
{
2016-06-23 09:39:31 +02:00
if ( _nodeInitialized = = false ) InitializeNode ( ) ;
2015-08-31 18:59:51 +02:00
return _key ;
}
}
2013-11-07 17:16:22 +01:00
public override int TemplateId
{
get
{
2016-06-23 09:39:31 +02:00
if ( _nodeInitialized = = false ) InitializeNode ( ) ;
2013-11-07 17:16:22 +01:00
return _template ;
}
}
public override int SortOrder
{
get
{
2016-06-23 09:39:31 +02:00
if ( _nodeInitialized = = false ) InitializeNode ( ) ;
2013-11-07 17:16:22 +01:00
return _sortOrder ;
}
}
public override string Name
{
get
{
2016-06-23 09:39:31 +02:00
if ( _nodeInitialized = = false ) InitializeNode ( ) ;
2013-11-07 17:16:22 +01:00
return _name ;
}
}
public override string DocumentTypeAlias
{
get
{
2016-06-23 09:39:31 +02:00
if ( _nodeInitialized = = false ) InitializeNode ( ) ;
2013-11-07 17:16:22 +01:00
return _docTypeAlias ;
}
}
public override int DocumentTypeId
{
get
{
2016-06-23 09:39:31 +02:00
if ( _nodeInitialized = = false ) InitializeNode ( ) ;
2013-11-07 17:16:22 +01:00
return _docTypeId ;
}
}
public override string WriterName
{
get
{
2016-06-23 09:39:31 +02:00
if ( _nodeInitialized = = false ) InitializeNode ( ) ;
2013-11-07 17:16:22 +01:00
return _writerName ;
}
}
public override string CreatorName
{
get
{
2016-06-23 09:39:31 +02:00
if ( _nodeInitialized = = false ) InitializeNode ( ) ;
2013-11-07 17:16:22 +01:00
return _creatorName ;
}
}
public override int WriterId
{
get
{
2016-06-23 09:39:31 +02:00
if ( _nodeInitialized = = false ) InitializeNode ( ) ;
2013-11-07 17:16:22 +01:00
return _writerId ;
}
}
public override int CreatorId
{
get
{
2016-06-23 09:39:31 +02:00
if ( _nodeInitialized = = false ) InitializeNode ( ) ;
2013-11-07 17:16:22 +01:00
return _creatorId ;
}
}
public override string Path
{
get
{
2016-06-23 09:39:31 +02:00
if ( _nodeInitialized = = false ) InitializeNode ( ) ;
2013-11-07 17:16:22 +01:00
return _path ;
}
}
public override DateTime CreateDate
{
get
{
2016-06-23 09:39:31 +02:00
if ( _nodeInitialized = = false ) InitializeNode ( ) ;
2013-11-07 17:16:22 +01:00
return _createDate ;
}
}
public override DateTime UpdateDate
{
get
{
2016-06-23 09:39:31 +02:00
if ( _nodeInitialized = = false ) InitializeNode ( ) ;
2013-11-07 17:16:22 +01:00
return _updateDate ;
}
}
public override Guid Version
{
get
{
2016-06-23 09:39:31 +02:00
if ( _nodeInitialized = = false ) InitializeNode ( ) ;
2013-11-07 17:16:22 +01:00
return _version ;
}
}
public override string UrlName
{
get
{
2016-06-23 09:39:31 +02:00
if ( _nodeInitialized = = false ) InitializeNode ( ) ;
2013-11-07 17:16:22 +01:00
return _urlName ;
}
}
public override int Level
{
get
{
2016-06-23 09:39:31 +02:00
if ( _nodeInitialized = = false ) InitializeNode ( ) ;
2013-11-07 17:16:22 +01:00
return _level ;
}
}
public override bool IsDraft
{
get
{
2016-06-23 09:39:31 +02:00
if ( _nodeInitialized = = false ) InitializeNode ( ) ;
2013-11-07 17:16:22 +01:00
return _isDraft ;
}
}
2016-06-10 16:37:28 +02:00
public override IEnumerable < IPublishedProperty > Properties
2013-11-07 17:16:22 +01:00
{
get
{
2016-06-23 09:39:31 +02:00
if ( _nodeInitialized = = false ) InitializeNode ( ) ;
return _properties . Values ;
2013-11-07 17:16:22 +01:00
}
}
public override PublishedContentType ContentType
{
get
{
2016-06-23 09:39:31 +02:00
if ( _nodeInitialized = = false ) InitializeNode ( ) ;
2013-11-07 17:16:22 +01:00
return _contentType ;
}
}
2016-06-23 09:39:31 +02:00
private void InitializeParent ( )
2013-11-07 17:16:22 +01:00
{
2016-07-20 12:38:57 +02:00
var parent = _xmlNode ? . ParentNode ;
2013-11-07 17:16:22 +01:00
if ( parent = = null ) return ;
2016-07-20 12:38:57 +02:00
if ( parent . Attributes ? . GetNamedItem ( "isDoc" ) ! = null )
_parent = Get ( parent , _isPreviewing , _cacheProvider , _contentTypeCache ) ;
2016-06-23 09:39:31 +02:00
// warn: this is not thread-safe...
_parentInitialized = true ;
2013-11-07 17:16:22 +01:00
}
2016-06-23 09:39:31 +02:00
private void InitializeNode ( )
2013-11-07 17:16:22 +01:00
{
2017-05-12 14:49:44 +02:00
InitializeNode ( _xmlNode , _isPreviewing ,
out _id , out _key , out _template , out _sortOrder , out _name , out _writerName ,
out _urlName , out _creatorName , out _creatorId , out _writerId , out _docTypeAlias , out _docTypeId , out _path ,
out _version , out _createDate , out _updateDate , out _level , out _isDraft , out _contentType , out _properties ,
_contentTypeCache . Get ) ;
// warn: this is not thread-safe...
_nodeInitialized = true ;
}
2013-11-07 17:16:22 +01:00
2017-05-12 14:49:44 +02:00
internal static void InitializeNode ( XmlNode xmlNode , bool isPreviewing ,
out int id , out Guid key , out int template , out int sortOrder , out string name , out string writerName , out string urlName ,
out string creatorName , out int creatorId , out int writerId , out string docTypeAlias , out int docTypeId , out string path ,
out Guid version , out DateTime createDate , out DateTime updateDate , out int level , out bool isDraft ,
out PublishedContentType contentType , out Dictionary < string , IPublishedProperty > properties ,
Func < PublishedItemType , string , PublishedContentType > getPublishedContentType )
{
//initialize the out params with defaults:
writerName = null ;
docTypeAlias = null ;
id = template = sortOrder = template = creatorId = writerId = docTypeId = level = default ( int ) ;
key = version = default ( Guid ) ;
name = writerName = urlName = creatorName = docTypeAlias = path = null ;
createDate = updateDate = default ( DateTime ) ;
isDraft = false ;
contentType = null ;
properties = null ;
if ( xmlNode = = null ) return ;
if ( xmlNode . Attributes ! = null )
2013-11-07 17:16:22 +01:00
{
2017-05-12 14:49:44 +02:00
id = int . Parse ( xmlNode . Attributes . GetNamedItem ( "id" ) . Value ) ;
if ( xmlNode . Attributes . GetNamedItem ( "key" ) ! = null ) // because, migration
key = Guid . Parse ( xmlNode . Attributes . GetNamedItem ( "key" ) . Value ) ;
if ( xmlNode . Attributes . GetNamedItem ( "template" ) ! = null )
template = int . Parse ( xmlNode . Attributes . GetNamedItem ( "template" ) . Value ) ;
if ( xmlNode . Attributes . GetNamedItem ( "sortOrder" ) ! = null )
sortOrder = int . Parse ( xmlNode . Attributes . GetNamedItem ( "sortOrder" ) . Value ) ;
if ( xmlNode . Attributes . GetNamedItem ( "nodeName" ) ! = null )
name = xmlNode . Attributes . GetNamedItem ( "nodeName" ) . Value ;
if ( xmlNode . Attributes . GetNamedItem ( "writerName" ) ! = null )
writerName = xmlNode . Attributes . GetNamedItem ( "writerName" ) . Value ;
if ( xmlNode . Attributes . GetNamedItem ( "urlName" ) ! = null )
urlName = xmlNode . Attributes . GetNamedItem ( "urlName" ) . Value ;
if ( xmlNode . Attributes . GetNamedItem ( "creatorName" ) ! = null )
creatorName = xmlNode . Attributes . GetNamedItem ( "creatorName" ) . Value ;
2013-11-07 17:16:22 +01:00
2016-06-23 11:31:32 +02:00
//Added the actual userID, as a user cannot be looked up via full name only...
2017-05-12 14:49:44 +02:00
if ( xmlNode . Attributes . GetNamedItem ( "creatorID" ) ! = null )
creatorId = int . Parse ( xmlNode . Attributes . GetNamedItem ( "creatorID" ) . Value ) ;
if ( xmlNode . Attributes . GetNamedItem ( "writerID" ) ! = null )
writerId = int . Parse ( xmlNode . Attributes . GetNamedItem ( "writerID" ) . Value ) ;
docTypeAlias = xmlNode . Name ;
if ( xmlNode . Attributes . GetNamedItem ( "nodeType" ) ! = null )
docTypeId = int . Parse ( xmlNode . Attributes . GetNamedItem ( "nodeType" ) . Value ) ;
if ( xmlNode . Attributes . GetNamedItem ( "path" ) ! = null )
path = xmlNode . Attributes . GetNamedItem ( "path" ) . Value ;
if ( xmlNode . Attributes . GetNamedItem ( "version" ) ! = null )
version = new Guid ( xmlNode . Attributes . GetNamedItem ( "version" ) . Value ) ;
if ( xmlNode . Attributes . GetNamedItem ( "createDate" ) ! = null )
createDate = DateTime . Parse ( xmlNode . Attributes . GetNamedItem ( "createDate" ) . Value ) ;
if ( xmlNode . Attributes . GetNamedItem ( "updateDate" ) ! = null )
updateDate = DateTime . Parse ( xmlNode . Attributes . GetNamedItem ( "updateDate" ) . Value ) ;
if ( xmlNode . Attributes . GetNamedItem ( "level" ) ! = null )
level = int . Parse ( xmlNode . Attributes . GetNamedItem ( "level" ) . Value ) ;
isDraft = xmlNode . Attributes . GetNamedItem ( "isDraft" ) ! = null ;
2013-11-07 17:16:22 +01:00
}
2017-05-12 14:49:44 +02:00
//dictionary to store the property node data
var propertyNodes = new Dictionary < string , XmlNode > ( ) ;
2013-11-07 17:16:22 +01:00
2017-05-12 14:49:44 +02:00
foreach ( XmlNode n in xmlNode . ChildNodes )
{
var e = n as XmlElement ;
if ( e = = null ) continue ;
if ( e . HasAttribute ( "isDoc" ) = = false )
{
PopulatePropertyNodes ( propertyNodes , e , false ) ;
}
else break ; //we are not longer on property elements
}
2013-11-07 17:16:22 +01:00
2017-05-12 14:49:44 +02:00
//lookup the content type and create the properties collection
try
{
contentType = getPublishedContentType ( PublishedItemType . Content , docTypeAlias ) ;
2013-11-07 17:16:22 +01:00
2017-05-12 14:49:44 +02:00
}
catch ( InvalidOperationException e )
{
// fixme - enable!
//content.Instance.RefreshContentFromDatabase();
throw new InvalidOperationException ( $"{e.Message}. This usually indicates that the content cache is corrupt; the content cache has been rebuilt in an attempt to self-fix the issue." ) ;
}
2013-11-07 17:16:22 +01:00
2017-05-12 14:49:44 +02:00
//fill in the property collection
properties = new Dictionary < string , IPublishedProperty > ( StringComparer . OrdinalIgnoreCase ) ;
foreach ( var propertyType in contentType . PropertyTypes )
{
var val = propertyNodes . TryGetValue ( propertyType . PropertyTypeAlias . ToLowerInvariant ( ) , out XmlNode n )
? new XmlPublishedProperty ( propertyType , isPreviewing , n )
: new XmlPublishedProperty ( propertyType , isPreviewing ) ;
properties [ propertyType . PropertyTypeAlias ] = val ;
}
2013-11-07 17:16:22 +01:00
}
2017-05-12 14:49:44 +02:00
private static void PopulatePropertyNodes ( IDictionary < string , XmlNode > propertyNodes , XmlNode n , bool legacy )
{
var attrs = n . Attributes ;
if ( attrs = = null ) return ;
var alias = legacy
? attrs . GetNamedItem ( "alias" ) . Value
: n . Name ;
propertyNodes [ alias . ToLowerInvariant ( ) ] = n ;
}
private void InitializeChildren ( )
2013-11-07 17:16:22 +01:00
{
if ( _xmlNode = = null ) return ;
// load children
2016-07-08 16:32:06 +02:00
const string childXPath = "* [@isDoc]" ;
2013-11-07 17:16:22 +01:00
var nav = _xmlNode . CreateNavigator ( ) ;
var expr = nav . Compile ( childXPath ) ;
2016-06-23 09:39:31 +02:00
//expr.AddSort("@sortOrder", XmlSortOrder.Ascending, XmlCaseOrder.None, "", XmlDataType.Number);
2013-11-07 17:16:22 +01:00
var iterator = nav . Select ( expr ) ;
2016-06-23 09:39:31 +02:00
_children = iterator . Cast < XPathNavigator > ( )
2016-07-20 12:38:57 +02:00
. Select ( n = > Get ( ( ( IHasXmlNode ) n ) . GetNode ( ) , _isPreviewing , _cacheProvider , _contentTypeCache ) )
2016-06-23 09:39:31 +02:00
. OrderBy ( x = > x . SortOrder )
. ToList ( ) ;
2013-11-07 17:16:22 +01:00
// warn: this is not thread-safe
_childrenInitialized = true ;
}
2016-06-23 09:39:31 +02:00
/// <summary>
/// Gets an IPublishedContent corresponding to an Xml cache node.
/// </summary>
/// <param name="node">The Xml node.</param>
/// <param name="isPreviewing">A value indicating whether we are previewing or not.</param>
2016-07-20 12:38:57 +02:00
/// <param name="cacheProvider">A cache provider.</param>
/// <param name="contentTypeCache">A content type cache.</param>
2016-06-23 09:39:31 +02:00
/// <returns>The IPublishedContent corresponding to the Xml cache node.</returns>
/// <remarks>Maintains a per-request cache of IPublishedContent items in order to make
/// sure that we create only one instance of each for the duration of a request. The
/// returned IPublishedContent is a model, if models are enabled.</remarks>
2016-07-20 12:38:57 +02:00
public static IPublishedContent Get ( XmlNode node , bool isPreviewing , ICacheProvider cacheProvider , PublishedContentTypeCache contentTypeCache )
2016-06-23 09:39:31 +02:00
{
// only 1 per request
var attrs = node . Attributes ;
2016-07-20 12:38:57 +02:00
var id = attrs ? . GetNamedItem ( "id" ) . Value ;
2016-06-23 09:39:31 +02:00
if ( id . IsNullOrWhiteSpace ( ) ) throw new InvalidOperationException ( "Node has no ID attribute." ) ;
2016-06-29 14:46:53 +02:00
var key = CacheKeyPrefix + id ; // dont bother with preview, wont change during request in Xml cache
return ( IPublishedContent ) cacheProvider . GetCacheItem ( key , ( ) = > ( new XmlPublishedContent ( node , isPreviewing , cacheProvider , contentTypeCache ) ) . CreateModel ( ) ) ;
2016-06-23 09:39:31 +02:00
}
2016-07-04 18:21:36 +02:00
public static void ClearRequest ( )
{
2016-09-01 19:06:08 +02:00
Current . ApplicationCache . RequestCache . ClearCacheByKeySearch ( CacheKeyPrefix ) ;
2016-07-04 18:21:36 +02:00
}
private const string CacheKeyPrefix = "CONTENTCACHE_XMLPUBLISHEDCONTENT_" ;
}
2013-09-05 17:47:13 +02:00
}