2012-09-08 13:22:45 +07:00
using System ;
using System.Collections.Generic ;
using System.Collections.ObjectModel ;
using System.IO ;
using System.Linq ;
using System.Xml.XPath ;
using Examine ;
2013-02-20 00:13:35 +06:00
using Examine.LuceneEngine.SearchCriteria ;
2012-11-15 21:46:54 +05:00
using Examine.Providers ;
using Lucene.Net.Documents ;
2012-09-08 13:22:45 +07:00
using Umbraco.Core ;
2013-09-13 18:36:41 +10:00
using Umbraco.Core.Configuration ;
2012-09-08 13:22:45 +07:00
using Umbraco.Core.Dynamics ;
2013-02-22 04:12:24 +06:00
using Umbraco.Core.Logging ;
2012-09-08 13:22:45 +07:00
using Umbraco.Core.Models ;
2013-09-05 17:47:13 +02:00
using Umbraco.Core.Models.PublishedContent ;
2013-02-05 06:31:13 -01:00
using Umbraco.Core.Xml ;
2012-12-09 03:22:11 +05:00
using Umbraco.Web.Models ;
2013-02-19 22:46:44 +06:00
using UmbracoExamine ;
2012-09-20 07:13:45 +07:00
using umbraco ;
2012-09-20 14:17:40 +07:00
using umbraco.cms.businesslogic ;
2012-10-03 10:26:50 -02:00
using ContentType = umbraco . cms . businesslogic . ContentType ;
2012-09-08 13:22:45 +07:00
2013-03-22 15:02:26 -01:00
namespace Umbraco.Web.PublishedCache.XmlPublishedCache
2012-09-08 13:22:45 +07:00
{
/// <summary>
/// An IPublishedMediaStore that first checks for the media in Examine, and then reverts to the database
/// </summary>
2012-09-14 09:09:23 +07:00
/// <remarks>
/// NOTE: In the future if we want to properly cache all media this class can be extended or replaced when these classes/interfaces are exposed publicly.
/// </remarks>
2013-09-05 17:47:13 +02:00
internal class PublishedMediaCache : IPublishedMediaCache
2012-09-08 13:22:45 +07:00
{
2013-02-05 06:31:13 -01:00
public PublishedMediaCache ()
2012-11-15 21:46:54 +05:00
{
}
2013-02-22 04:12:24 +06:00
/// <summary>
/// Generally used for unit testing to use an explicit examine searcher
/// </summary>
/// <param name="searchProvider"></param>
/// <param name="indexProvider"></param>
2013-03-19 17:51:55 -01:00
internal PublishedMediaCache ( BaseSearchProvider searchProvider , BaseIndexProvider indexProvider )
{
2013-02-22 04:12:24 +06:00
_searchProvider = searchProvider ;
_indexProvider = indexProvider ;
2012-11-15 21:46:54 +05:00
}
2013-02-22 04:12:24 +06:00
private readonly BaseSearchProvider _searchProvider ;
private readonly BaseIndexProvider _indexProvider ;
2012-11-15 21:46:54 +05:00
2013-03-31 18:47:25 -02:00
public virtual IPublishedContent GetById ( UmbracoContext umbracoContext , bool preview , int nodeId )
2012-09-08 13:22:45 +07:00
{
return GetUmbracoMedia ( nodeId );
}
2013-03-31 18:47:25 -02:00
public virtual IEnumerable < IPublishedContent > GetAtRoot ( UmbracoContext umbracoContext , bool preview )
2012-10-04 01:31:08 +05:00
{
var rootMedia = global :: umbraco . cms . businesslogic . media . Media . GetRootMedias ();
var result = new List < IPublishedContent >();
//TODO: need to get a ConvertFromMedia method but we'll just use this for now.
foreach ( var media in rootMedia
. Select ( m => global :: umbraco . library . GetMedia ( m . Id , true ))
. Where ( media => media != null && media . Current != null ))
{
media . MoveNext ();
result . Add ( ConvertFromXPathNavigator ( media . Current ));
}
return result ;
}
2013-03-31 18:47:25 -02:00
public virtual IPublishedContent GetSingleByXPath ( UmbracoContext umbracoContext , bool preview , string xpath , XPathVariable [] vars )
2013-02-05 06:31:13 -01:00
{
2013-04-03 11:19:10 -02:00
throw new NotImplementedException ( "PublishedMediaCache does not support XPath." );
2013-02-05 06:31:13 -01:00
}
2013-04-10 12:49:45 -02:00
public virtual IPublishedContent GetSingleByXPath ( UmbracoContext umbracoContext , bool preview , XPathExpression xpath , XPathVariable [] vars )
{
throw new NotImplementedException ( "PublishedMediaCache does not support XPath." );
}
public virtual IEnumerable < IPublishedContent > GetByXPath ( UmbracoContext umbracoContext , bool preview , string xpath , XPathVariable [] vars )
{
throw new NotImplementedException ( "PublishedMediaCache does not support XPath." );
}
public virtual IEnumerable < IPublishedContent > GetByXPath ( UmbracoContext umbracoContext , bool preview , XPathExpression xpath , XPathVariable [] vars )
2013-02-05 06:31:13 -01:00
{
2013-04-03 11:19:10 -02:00
throw new NotImplementedException ( "PublishedMediaCache does not support XPath." );
2013-02-05 06:31:13 -01:00
}
2013-04-03 11:19:10 -02:00
2013-03-31 18:47:25 -02:00
public virtual XPathNavigator GetXPathNavigator ( UmbracoContext umbracoContext , bool preview )
2013-04-03 11:19:10 -02:00
{
throw new NotImplementedException ( "PublishedMediaCache does not support XPath." );
}
2013-06-11 09:52:41 +02:00
public bool XPathNavigatorIsNavigable { get { return false ; } }
2013-03-31 18:47:25 -02:00
public virtual bool HasContent ( UmbracoContext context , bool preview ) { throw new NotImplementedException (); }
2013-03-20 16:01:49 -01:00
2013-02-05 06:31:13 -01:00
private ExamineManager GetExamineManagerSafe ()
2012-09-08 13:22:45 +07:00
{
try
{
2012-11-15 04:18:23 +05:00
return ExamineManager . Instance ;
2012-09-08 13:22:45 +07:00
}
2012-11-15 04:18:23 +05:00
catch ( TypeInitializationException )
2012-09-08 13:22:45 +07:00
{
2012-11-15 04:18:23 +05:00
return null ;
}
}
2013-02-22 04:12:24 +06:00
private BaseIndexProvider GetIndexProviderSafe ()
{
if ( _indexProvider != null )
return _indexProvider ;
var eMgr = GetExamineManagerSafe ();
if ( eMgr != null )
{
try
{
//by default use the InternalSearcher
return eMgr . IndexProviderCollection [ "InternalIndexer" ];
}
catch ( Exception ex )
{
2013-02-05 06:31:13 -01:00
LogHelper . Error < PublishedMediaCache >( "Could not retreive the InternalIndexer" , ex );
2013-02-22 04:12:24 +06:00
//something didn't work, continue returning null.
}
}
return null ;
}
private BaseSearchProvider GetSearchProviderSafe ()
2012-11-15 04:18:23 +05:00
{
2012-11-15 21:46:54 +05:00
if ( _searchProvider != null )
return _searchProvider ;
2012-11-15 04:18:23 +05:00
var eMgr = GetExamineManagerSafe ();
if ( eMgr != null )
2012-11-15 21:46:54 +05:00
{
try
2012-09-08 13:22:45 +07:00
{
2012-11-15 21:46:54 +05:00
//by default use the InternalSearcher
return eMgr . SearchProviderCollection [ "InternalSearcher" ];
}
catch ( FileNotFoundException )
{
//Currently examine is throwing FileNotFound exceptions when we have a loadbalanced filestore and a node is published in umbraco
//See this thread: http://examine.cdodeplex.com/discussions/264341
//Catch the exception here for the time being, and just fallback to GetMedia
//TODO: Need to fix examine in LB scenarios!
2012-09-08 13:22:45 +07:00
}
}
2012-11-15 21:46:54 +05:00
return null ;
}
private IPublishedContent GetUmbracoMedia ( int id )
{
var searchProvider = GetSearchProviderSafe ();
if ( searchProvider != null )
2012-09-08 13:22:45 +07:00
{
2012-11-15 04:18:23 +05:00
try
{
//first check in Examine as this is WAY faster
2012-11-15 21:46:54 +05:00
var criteria = searchProvider . CreateSearchCriteria ( "media" );
2013-02-20 00:13:35 +06:00
var filter = criteria . Id ( id ). Not (). Field ( UmbracoContentIndexer . IndexPathFieldName , "-1,-21," . MultipleCharacterWildcard ());
//the above filter will create a query like this, NOTE: That since the use of the wildcard, it automatically escapes it in Lucene.
//+(+__NodeId:3113 -__Path:-1,-21,*) +__IndexType:media
2012-11-15 21:46:54 +05:00
var results = searchProvider . Search ( filter . Compile ());
2012-11-15 04:18:23 +05:00
if ( results . Any ())
{
return ConvertFromSearchResult ( results . First ());
}
}
catch ( FileNotFoundException )
{
//Currently examine is throwing FileNotFound exceptions when we have a loadbalanced filestore and a node is published in umbraco
//See this thread: http://examine.cdodeplex.com/discussions/264341
//Catch the exception here for the time being, and just fallback to GetMedia
//TODO: Need to fix examine in LB scenarios!
}
2012-09-08 13:22:45 +07:00
}
var media = global :: umbraco . library . GetMedia ( id , true );
if ( media != null && media . Current != null )
{
2012-11-15 20:11:10 +05:00
media . MoveNext ();
2012-11-16 00:38:03 +05:00
var moved = media . Current . MoveToFirstChild ();
//first check if we have an error
if ( moved )
2012-10-11 07:54:04 +05:00
{
2012-11-16 00:38:03 +05:00
if ( media . Current . Name . InvariantEquals ( "error" ))
2012-10-11 07:54:04 +05:00
{
return null ;
2012-11-16 00:38:03 +05:00
}
}
if ( moved )
{
//move back to the parent and return
media . Current . MoveToParent ();
}
2012-11-15 20:11:10 +05:00
return ConvertFromXPathNavigator ( media . Current );
2012-09-08 13:22:45 +07:00
}
return null ;
}
2012-10-02 01:35:39 +05:00
internal IPublishedContent ConvertFromSearchResult ( SearchResult searchResult )
2012-09-08 13:22:45 +07:00
{
2013-02-14 06:33:48 +06:00
//NOTE: Some fields will not be included if the config section for the internal index has been
//mucked around with. It should index everything and so the index definition should simply be:
// <IndexSet SetName="InternalIndexSet" IndexPath="~/App_Data/TEMP/ExamineIndexes/Internal/" />
2012-09-20 07:13:45 +07:00
var values = new Dictionary < string , string >( searchResult . Fields );
//we need to ensure some fields exist, because of the above issue
if (! new []{ "template" , "templateId" }. Any ( values . ContainsKey ))
values . Add ( "template" , 0. ToString ());
if (! new [] { "sortOrder" }. Any ( values . ContainsKey ))
values . Add ( "sortOrder" , 0. ToString ());
if (! new [] { "urlName" }. Any ( values . ContainsKey ))
values . Add ( "urlName" , "" );
if (! new [] { "nodeType" }. Any ( values . ContainsKey ))
values . Add ( "nodeType" , 0. ToString ());
if (! new [] { "creatorName" }. Any ( values . ContainsKey ))
values . Add ( "creatorName" , "" );
if (! new [] { "writerID" }. Any ( values . ContainsKey ))
values . Add ( "writerID" , 0. ToString ());
if (! new [] { "creatorID" }. Any ( values . ContainsKey ))
values . Add ( "creatorID" , 0. ToString ());
if (! new [] { "createDate" }. Any ( values . ContainsKey ))
values . Add ( "createDate" , default ( DateTime ). ToString ( "yyyy-MM-dd HH:mm:ss" ));
if (! new [] { "level" }. Any ( values . ContainsKey ))
{
values . Add ( "level" , values [ "__Path" ]. Split ( ',' ). Length . ToString ());
}
2012-09-20 14:17:40 +07:00
2012-09-20 07:13:45 +07:00
2013-09-13 15:39:29 +02:00
var content = new DictionaryPublishedContent ( values ,
2012-11-15 21:46:54 +05:00
d => d . ParentId != - 1 //parent should be null if -1
? GetUmbracoMedia ( d . ParentId )
: null ,
//callback to return the children of the current node
d => GetChildrenMedia ( d . Id ),
GetProperty ,
true );
2013-09-13 15:39:29 +02:00
return PublishedContentModelFactory . CreateModel ( content );
2012-09-08 13:22:45 +07:00
}
2012-10-02 01:35:39 +05:00
internal IPublishedContent ConvertFromXPathNavigator ( XPathNavigator xpath )
2012-09-08 13:22:45 +07:00
{
if ( xpath == null ) throw new ArgumentNullException ( "xpath" );
var values = new Dictionary < string , string > {{ "nodeName" , xpath . GetAttribute ( "nodeName" , "" )}};
2013-09-25 19:23:41 +10:00
if (! UmbracoConfig . For . UmbracoSettings (). Content . UseLegacyXmlSchema )
2012-09-20 07:13:45 +07:00
{
values . Add ( "nodeTypeAlias" , xpath . Name );
}
2012-09-08 13:22:45 +07:00
var result = xpath . SelectChildren ( XPathNodeType . Element );
//add the attributes e.g. id, parentId etc
if ( result . Current != null && result . Current . HasAttributes )
{
if ( result . Current . MoveToFirstAttribute ())
{
2012-09-20 07:13:45 +07:00
//checking for duplicate keys because of the 'nodeTypeAlias' might already be added above.
if (! values . ContainsKey ( result . Current . Name ))
{
values . Add ( result . Current . Name , result . Current . Value );
}
2012-09-08 13:22:45 +07:00
while ( result . Current . MoveToNextAttribute ())
{
2012-09-20 07:13:45 +07:00
if (! values . ContainsKey ( result . Current . Name ))
{
values . Add ( result . Current . Name , result . Current . Value );
}
2012-09-08 13:22:45 +07:00
}
result . Current . MoveToParent ();
}
}
//add the user props
while ( result . MoveNext ())
{
if ( result . Current != null && ! result . Current . HasAttributes )
{
string value = result . Current . Value ;
if ( string . IsNullOrEmpty ( value ))
{
if ( result . Current . HasAttributes || result . Current . SelectChildren ( XPathNodeType . Element ). Count > 0 )
{
value = result . Current . OuterXml ;
}
}
values . Add ( result . Current . Name , value );
}
}
2013-09-13 15:39:29 +02:00
var content = new DictionaryPublishedContent ( values ,
2012-09-20 07:13:45 +07:00
d => d . ParentId != - 1 //parent should be null if -1
? GetUmbracoMedia ( d . ParentId )
: null ,
//callback to return the children of the current node based on the xml structure already found
2012-11-15 20:11:10 +05:00
d => GetChildrenMedia ( d . Id , xpath ),
2012-11-15 21:46:54 +05:00
GetProperty ,
false );
2013-09-13 15:39:29 +02:00
return PublishedContentModelFactory . CreateModel ( content );
2012-09-20 14:17:40 +07:00
}
/// <summary>
/// We will need to first check if the document was loaded by Examine, if so we'll need to check if this property exists
/// in the results, if it does not, then we'll have to revert to looking up in the db.
/// </summary>
/// <param name="dd"> </param>
/// <param name="alias"></param>
/// <returns></returns>
2013-09-05 17:47:13 +02:00
private IPublishedProperty GetProperty ( DictionaryPublishedContent dd , string alias )
2012-09-20 14:17:40 +07:00
{
if ( dd . LoadedFromExamine )
{
//if this is from Examine, lets check if the alias does not exist on the document
2013-09-19 13:09:27 +02:00
if ( dd . Properties . All ( x => x . PropertyTypeAlias != alias ))
2012-09-20 14:17:40 +07:00
{
//ok it doesn't exist, we might assume now that Examine didn't index this property because the index is not set up correctly
//so before we go loading this from the database, we can check if the alias exists on the content type at all, this information
//is cached so will be quicker to look up.
2013-09-19 13:09:27 +02:00
if ( dd . Properties . Any ( x => x . PropertyTypeAlias == UmbracoContentIndexer . NodeTypeAliasFieldName ))
2012-09-20 14:17:40 +07:00
{
2013-09-23 13:08:45 +02:00
// so in dd.Properties, there is an IPublishedProperty with property type alias "__NodeTypeAlias" and
// that special property would contain the node type alias, which we use to get "aliases & names". That
2013-09-25 13:16:37 +02:00
// special property is going to be a PropertyResult (with Value == DataValue) and we
2013-09-23 13:08:45 +02:00
// want its value in the most simple way = it is OK to use DataValue here.
2013-09-19 13:09:27 +02:00
var aliasesAndNames = ContentType . GetAliasesAndNames ( dd . Properties . First ( x => x . PropertyTypeAlias . InvariantEquals ( UmbracoContentIndexer . NodeTypeAliasFieldName )). DataValue . ToString ());
2012-09-20 14:17:40 +07:00
if ( aliasesAndNames != null )
{
if (! aliasesAndNames . ContainsKey ( alias ))
{
//Ok, now we know it doesn't exist on this content type anyways
return null ;
}
}
}
//if we've made it here, that means it does exist on the content type but not in examine, we'll need to query the db :(
var media = global :: umbraco . library . GetMedia ( dd . Id , true );
if ( media != null && media . Current != null )
{
media . MoveNext ();
var mediaDoc = ConvertFromXPathNavigator ( media . Current );
2013-09-19 13:09:27 +02:00
return mediaDoc . Properties . FirstOrDefault ( x => x . PropertyTypeAlias . InvariantEquals ( alias ));
2012-09-20 14:17:40 +07:00
}
}
}
2013-05-06 14:36:47 -10:00
//We've made it here which means that the value is stored in the Examine index.
//We are going to check for a special field however, that is because in some cases we store a 'Raw'
//value in the index such as for xml/html.
2013-09-19 13:09:27 +02:00
var rawValue = dd . Properties . FirstOrDefault ( x => x . PropertyTypeAlias . InvariantEquals ( UmbracoContentIndexer . RawFieldPrefix + alias ));
2013-05-06 14:36:47 -10:00
return rawValue
2013-09-19 13:09:27 +02:00
?? dd . Properties . FirstOrDefault ( x => x . PropertyTypeAlias . InvariantEquals ( alias ));
2012-09-20 07:13:45 +07:00
}
/// <summary>
/// A Helper methods to return the children for media whther it is based on examine or xml
/// </summary>
/// <param name="parentId"></param>
/// <param name="xpath"></param>
/// <returns></returns>
2012-10-02 01:35:39 +05:00
private IEnumerable < IPublishedContent > GetChildrenMedia ( int parentId , XPathNavigator xpath = null )
2012-09-20 07:13:45 +07:00
{
//if there is no navigator, try examine first, then re-look it up
if ( xpath == null )
{
2012-11-15 21:46:54 +05:00
var searchProvider = GetSearchProviderSafe ();
2012-11-15 04:18:23 +05:00
2012-11-15 21:46:54 +05:00
if ( searchProvider != null )
2012-09-20 07:13:45 +07:00
{
2012-11-15 04:18:23 +05:00
try
2012-09-20 07:13:45 +07:00
{
2012-11-15 04:18:23 +05:00
//first check in Examine as this is WAY faster
2012-11-15 21:46:54 +05:00
var criteria = searchProvider . CreateSearchCriteria ( "media" );
2013-02-22 04:12:24 +06:00
var filter = criteria . ParentId ( parentId );
ISearchResults results ;
//we want to check if the indexer for this searcher has "sortOrder" flagged as sortable.
//if so, we'll use Lucene to do the sorting, if not we'll have to manually sort it (slower).
var indexer = GetIndexProviderSafe ();
var useLuceneSort = indexer != null && indexer . IndexerData . StandardFields . Any ( x => x . Name . InvariantEquals ( "sortOrder" ) && x . EnableSorting );
if ( useLuceneSort )
{
//we have a sortOrder field declared to be sorted, so we'll use Examine
results = searchProvider . Search (
filter . And (). OrderBy ( new SortableField ( "sortOrder" , SortType . Int )). Compile ());
}
else
{
results = searchProvider . Search ( filter . Compile ());
}
2012-11-15 04:18:23 +05:00
if ( results . Any ())
{
2013-05-07 19:02:36 -10:00
return useLuceneSort
? results . Select ( ConvertFromSearchResult ) //will already be sorted by lucene
: results . Select ( ConvertFromSearchResult ). OrderBy ( x => x . SortOrder );
}
else
{
//if there's no result then return null. Previously we defaulted back to library.GetMedia below
//but this will always get called for when we are getting descendents since many items won't have
//children and then we are hitting the database again!
//So instead we're going to rely on Examine to have the correct results like it should.
return Enumerable . Empty < IPublishedContent >();
2012-11-15 04:18:23 +05:00
}
2012-09-20 07:13:45 +07:00
}
2012-11-15 04:18:23 +05:00
catch ( FileNotFoundException )
{
//Currently examine is throwing FileNotFound exceptions when we have a loadbalanced filestore and a node is published in umbraco
//See this thread: http://examine.cdodeplex.com/discussions/264341
//Catch the exception here for the time being, and just fallback to GetMedia
}
2013-05-07 19:02:36 -10:00
}
//falling back to get media
2012-09-20 07:13:45 +07:00
var media = library . GetMedia ( parentId , true );
if ( media != null && media . Current != null )
{
2013-05-07 19:02:36 -10:00
media . MoveNext ();
2012-09-20 07:13:45 +07:00
xpath = media . Current ;
}
2012-11-15 20:11:10 +05:00
else
{
2013-05-07 19:02:36 -10:00
return Enumerable . Empty < IPublishedContent >();
2012-11-15 20:11:10 +05:00
}
2012-09-20 07:13:45 +07:00
}
2012-11-15 20:11:10 +05:00
//The xpath might be the whole xpath including the current ones ancestors so we need to select the current node
var item = xpath . Select ( "//*[@id='" + parentId + "']" );
if ( item . Current == null )
2012-09-20 07:13:45 +07:00
{
2013-05-07 19:02:36 -10:00
return Enumerable . Empty < IPublishedContent >();
2012-11-15 20:11:10 +05:00
}
var children = item . Current . SelectChildren ( XPathNodeType . Element );
2012-09-20 07:13:45 +07:00
2012-11-15 20:11:10 +05:00
var mediaList = new List < IPublishedContent >();
foreach ( XPathNavigator x in children )
{
2012-09-20 07:13:45 +07:00
//NOTE: I'm not sure why this is here, it is from legacy code of ExamineBackedMedia, but
// will leave it here as it must have done something!
2012-11-15 20:11:10 +05:00
if ( x . Name != "contents" )
2012-09-20 07:13:45 +07:00
{
//make sure it's actually a node, not a property
2012-11-15 20:11:10 +05:00
if (! string . IsNullOrEmpty ( x . GetAttribute ( "path" , "" )) &&
! string . IsNullOrEmpty ( x . GetAttribute ( "id" , "" )))
2012-09-20 07:13:45 +07:00
{
2012-11-15 20:11:10 +05:00
mediaList . Add ( ConvertFromXPathNavigator ( x ));
2012-09-20 07:13:45 +07:00
}
2012-11-15 20:11:10 +05:00
}
2012-09-20 07:13:45 +07:00
}
return mediaList ;
2012-09-08 13:22:45 +07:00
}
/// <summary>
2012-10-02 01:35:39 +05:00
/// An IPublishedContent that is represented all by a dictionary.
2012-09-08 13:22:45 +07:00
/// </summary>
/// <remarks>
/// This is a helper class and definitely not intended for public use, it expects that all of the values required
2012-10-02 01:35:39 +05:00
/// to create an IPublishedContent exist in the dictionary by specific aliases.
2012-09-08 13:22:45 +07:00
/// </remarks>
2012-12-09 03:22:11 +05:00
internal class DictionaryPublishedContent : PublishedContentBase
2012-09-08 13:22:45 +07:00
{
2013-09-05 17:47:13 +02:00
// note: I'm not sure this class fully complies with IPublishedContent rules especially
// I'm not sure that _properties contains all properties including those without a value,
// neither that GetProperty will return a property without a value vs. null... @zpqrtbnk
2012-09-08 13:22:45 +07:00
2014-01-30 08:45:43 +01:00
// List of properties that will appear in the XML and do not match
// anything in the ContentType, so they must be ignored.
static readonly string [] IgnoredKeys = { "version" , "isDoc" , "key" };
2012-10-02 01:35:39 +05:00
public DictionaryPublishedContent (
2012-09-20 03:47:24 +07:00
IDictionary < string , string > valueDictionary ,
2012-10-02 01:35:39 +05:00
Func < DictionaryPublishedContent , IPublishedContent > getParent ,
Func < DictionaryPublishedContent , IEnumerable < IPublishedContent >> getChildren ,
2013-09-05 17:47:13 +02:00
Func < DictionaryPublishedContent , string , IPublishedProperty > getProperty ,
2012-11-15 21:46:54 +05:00
bool fromExamine )
2012-09-08 13:22:45 +07:00
{
if ( valueDictionary == null ) throw new ArgumentNullException ( "valueDictionary" );
if ( getParent == null ) throw new ArgumentNullException ( "getParent" );
2012-09-20 14:17:40 +07:00
if ( getProperty == null ) throw new ArgumentNullException ( "getProperty" );
2012-09-08 13:22:45 +07:00
_getParent = getParent ;
2012-09-20 03:47:24 +07:00
_getChildren = getChildren ;
2012-09-20 14:17:40 +07:00
_getProperty = getProperty ;
2012-11-15 21:46:54 +05:00
LoadedFromExamine = fromExamine ;
2012-09-20 03:47:24 +07:00
2012-12-09 03:22:11 +05:00
ValidateAndSetProperty ( valueDictionary , val => _id = int . Parse ( val ), "id" , "nodeId" , "__NodeId" ); //should validate the int!
ValidateAndSetProperty ( valueDictionary , val => _templateId = int . Parse ( val ), "template" , "templateId" );
ValidateAndSetProperty ( valueDictionary , val => _sortOrder = int . Parse ( val ), "sortOrder" );
ValidateAndSetProperty ( valueDictionary , val => _name = val , "nodeName" , "__nodeName" );
ValidateAndSetProperty ( valueDictionary , val => _urlName = val , "urlName" );
2013-05-06 14:36:47 -10:00
ValidateAndSetProperty ( valueDictionary , val => _documentTypeAlias = val , "nodeTypeAlias" , UmbracoContentIndexer . NodeTypeAliasFieldName );
2012-12-09 03:22:11 +05:00
ValidateAndSetProperty ( valueDictionary , val => _documentTypeId = int . Parse ( val ), "nodeType" );
ValidateAndSetProperty ( valueDictionary , val => _writerName = val , "writerName" );
ValidateAndSetProperty ( valueDictionary , val => _creatorName = val , "creatorName" , "writerName" ); //this is a bit of a hack fix for: U4-1132
ValidateAndSetProperty ( valueDictionary , val => _writerId = int . Parse ( val ), "writerID" );
ValidateAndSetProperty ( valueDictionary , val => _creatorId = int . Parse ( val ), "creatorID" , "writerID" ); //this is a bit of a hack fix for: U4-1132
ValidateAndSetProperty ( valueDictionary , val => _path = val , "path" , "__Path" );
ValidateAndSetProperty ( valueDictionary , val => _createDate = ParseDateTimeValue ( val ), "createDate" );
ValidateAndSetProperty ( valueDictionary , val => _updateDate = ParseDateTimeValue ( val ), "updateDate" );
ValidateAndSetProperty ( valueDictionary , val => _level = int . Parse ( val ), "level" );
2012-09-08 13:22:45 +07:00
ValidateAndSetProperty ( valueDictionary , val =>
{
int pId ;
2012-09-20 07:13:45 +07:00
ParentId = - 1 ;
2012-09-08 13:22:45 +07:00
if ( int . TryParse ( val , out pId ))
{
ParentId = pId ;
}
}, "parentID" );
2013-09-05 17:47:13 +02:00
_contentType = PublishedContentType . Get ( PublishedItemType . Media , _documentTypeAlias );
_properties = new Collection < IPublishedProperty >();
2012-09-08 13:22:45 +07:00
//loop through remaining values that haven't been applied
foreach ( var i in valueDictionary . Where ( x => ! _keysAdded . Contains ( x . Key )))
{
2013-11-19 19:11:48 +01:00
IPublishedProperty property = null ;
2013-09-05 17:47:13 +02:00
2014-01-30 08:45:43 +01:00
// must ignore those
if ( IgnoredKeys . Contains ( i . Key )) continue ;
2013-09-29 15:17:53 +02:00
2013-09-05 17:47:13 +02:00
if ( i . Key . InvariantStartsWith ( "__" ))
{
2013-09-23 21:57:22 +02:00
// no type for that one, dunno how to convert
property = new PropertyResult ( i . Key , i . Value , PropertyResultType . CustomProperty );
2013-09-05 17:47:13 +02:00
}
else
{
// use property type to ensure proper conversion
var propertyType = _contentType . GetPropertyType ( i . Key );
2014-01-30 08:45:43 +01:00
if ( propertyType == null )
throw new Exception ( "Internal error, property '" + i . Key + "' is not a valid property for that type of content." );
2013-11-20 14:51:35 +01:00
property = new XmlPublishedProperty ( propertyType , false , i . Value ); // false :: never preview a media
2013-09-05 17:47:13 +02:00
}
_properties . Add ( property );
2012-09-08 13:22:45 +07:00
}
}
2012-11-15 21:46:54 +05:00
private DateTime ParseDateTimeValue ( string val )
{
if ( LoadedFromExamine )
{
try
{
//we might need to parse the date time using Lucene converters
return DateTools . StringToDate ( val );
}
catch ( FormatException )
{
//swallow exception, its not formatted correctly so revert to just trying to parse
}
}
return DateTime . Parse ( val );
}
2012-09-20 14:17:40 +07:00
/// <summary>
/// Flag to get/set if this was laoded from examine cache
/// </summary>
2012-11-15 21:46:54 +05:00
internal bool LoadedFromExamine { get ; private set ; }
2012-09-20 14:17:40 +07:00
2012-10-02 01:35:39 +05:00
private readonly Func < DictionaryPublishedContent , IPublishedContent > _getParent ;
private readonly Func < DictionaryPublishedContent , IEnumerable < IPublishedContent >> _getChildren ;
2013-09-05 17:47:13 +02:00
private readonly Func < DictionaryPublishedContent , string , IPublishedProperty > _getProperty ;
2012-09-20 03:47:24 +07:00
2012-12-09 03:22:11 +05:00
/// <summary>
/// Returns 'Media' as the item type
/// </summary>
public override PublishedItemType ItemType
{
get { return PublishedItemType . Media ; }
}
public override IPublishedContent Parent
2012-09-08 13:22:45 +07:00
{
get { return _getParent ( this ); }
}
2012-09-20 07:13:45 +07:00
public int ParentId { get ; private set ; }
2012-12-09 03:22:11 +05:00
public override int Id
{
get { return _id ; }
}
public override int TemplateId
{
2012-12-09 03:45:55 +05:00
get
{
//TODO: should probably throw a not supported exception since media doesn't actually support this.
return _templateId ;
}
2012-12-09 03:22:11 +05:00
}
public override int SortOrder
{
get { return _sortOrder ; }
}
public override string Name
{
get { return _name ; }
}
public override string UrlName
{
get { return _urlName ; }
}
public override string DocumentTypeAlias
{
get { return _documentTypeAlias ; }
}
public override int DocumentTypeId
{
get { return _documentTypeId ; }
}
public override string WriterName
{
get { return _writerName ; }
}
public override string CreatorName
{
get { return _creatorName ; }
}
public override int WriterId
{
get { return _writerId ; }
}
public override int CreatorId
{
get { return _creatorId ; }
}
public override string Path
{
get { return _path ; }
}
public override DateTime CreateDate
{
get { return _createDate ; }
}
public override DateTime UpdateDate
{
get { return _updateDate ; }
}
public override Guid Version
{
get { return _version ; }
}
public override int Level
{
get { return _level ; }
}
2013-09-05 17:47:13 +02:00
public override bool IsDraft
{
get { return false ; }
}
public override ICollection < IPublishedProperty > Properties
2012-12-09 03:22:11 +05:00
{
get { return _properties ; }
}
public override IEnumerable < IPublishedContent > Children
2012-09-20 03:47:24 +07:00
{
get { return _getChildren ( this ); }
}
2012-09-08 13:22:45 +07:00
2013-09-05 17:47:13 +02:00
public override IPublishedProperty GetProperty ( string alias )
2012-09-20 14:17:40 +07:00
{
return _getProperty ( this , alias );
}
2013-09-05 17:47:13 +02:00
public override PublishedContentType ContentType
{
get { return _contentType ; }
}
// 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 );
IPublishedProperty property ;
string key = null ;
var cache = UmbracoContextCache . Current ;
if ( cache != null )
{
key = string . Format ( "RECURSIVE_PROPERTY::{0}::{1}" , Id , alias . ToLowerInvariant ());
object o ;
if ( cache . TryGetValue ( key , out o ))
{
property = o as IPublishedProperty ;
if ( property == null )
throw new InvalidOperationException ( "Corrupted cache." );
return property ;
}
}
// else get it for real, no cache
property = base . GetProperty ( alias , true );
if ( cache != null )
cache [ key ] = property ;
return property ;
}
2012-09-08 13:22:45 +07:00
private readonly List < string > _keysAdded = new List < string >();
2012-12-09 03:22:11 +05:00
private int _id ;
private int _templateId ;
private int _sortOrder ;
private string _name ;
private string _urlName ;
private string _documentTypeAlias ;
private int _documentTypeId ;
private string _writerName ;
private string _creatorName ;
private int _writerId ;
private int _creatorId ;
private string _path ;
private DateTime _createDate ;
private DateTime _updateDate ;
private Guid _version ;
private int _level ;
2013-09-05 17:47:13 +02:00
private readonly ICollection < IPublishedProperty > _properties ;
private readonly PublishedContentType _contentType ;
2012-12-09 03:22:11 +05:00
2012-09-08 13:22:45 +07:00
private void ValidateAndSetProperty ( IDictionary < string , string > valueDictionary , Action < string > setProperty , params string [] potentialKeys )
{
2012-09-20 07:13:45 +07:00
var key = potentialKeys . FirstOrDefault ( x => valueDictionary . ContainsKey ( x ) && valueDictionary [ x ] != null );
if ( key == null )
2012-09-08 13:22:45 +07:00
{
2012-09-20 07:13:45 +07:00
throw new FormatException ( "The valueDictionary is not formatted correctly and is missing any of the '" + string . Join ( "," , potentialKeys ) + "' elements" );
2012-09-08 13:22:45 +07:00
}
2012-09-20 07:13:45 +07:00
setProperty ( valueDictionary [ key ]);
_keysAdded . Add ( key );
2012-09-08 13:22:45 +07:00
}
2013-09-05 17:47:13 +02:00
}
2012-09-08 13:22:45 +07:00
}
}