diff --git a/src/Umbraco.Core/Dynamics/DynamicBackingItem.cs b/src/Umbraco.Core/Dynamics/DynamicBackingItem.cs index f80b204bd4..de7a024858 100644 --- a/src/Umbraco.Core/Dynamics/DynamicBackingItem.cs +++ b/src/Umbraco.Core/Dynamics/DynamicBackingItem.cs @@ -106,10 +106,7 @@ namespace Umbraco.Core.Dynamics //if we're looking for a user defined property if (checkUserProperty) { - var prop = content.GetProperty(alias) - ?? (alias[0].IsUpperCase() //if it's null, try to get it with a different casing format (pascal vs camel) - ? content.GetProperty(alias.ConvertCase(StringAliasCaseType.CamelCase)) - : content.GetProperty(alias.ConvertCase(StringAliasCaseType.PascalCase))); + var prop = content.GetProperty(alias); return prop == null ? null diff --git a/src/Umbraco.Core/Models/DocumentExtensions.cs b/src/Umbraco.Core/Models/DocumentExtensions.cs new file mode 100644 index 0000000000..84be18b9ae --- /dev/null +++ b/src/Umbraco.Core/Models/DocumentExtensions.cs @@ -0,0 +1,21 @@ +using System.Linq; + +namespace Umbraco.Core.Models +{ + /// + /// Extension methods for IDocument + /// + public static class DocumentExtensions + { + /// + /// Returns the property based on the case insensitive match of the alias + /// + /// + /// + /// + public static IDocumentProperty GetProperty(this IDocument d, string alias) + { + return d.Properties.FirstOrDefault(p => p.Alias.InvariantEquals(alias)); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/IDocument.cs b/src/Umbraco.Core/Models/IDocument.cs index c162157eab..fae2866ff2 100644 --- a/src/Umbraco.Core/Models/IDocument.cs +++ b/src/Umbraco.Core/Models/IDocument.cs @@ -31,7 +31,6 @@ namespace Umbraco.Core.Models Guid Version { get; } int Level { get; } Collection Properties { get; } - IEnumerable Children { get; } - IDocumentProperty GetProperty(string alias); + IEnumerable Children { get; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 221f573c1f..5fdeed9fec 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -90,6 +90,7 @@ + diff --git a/src/Umbraco.Tests/DynamicDocument/DynamicDocumentTests.cs b/src/Umbraco.Tests/DynamicDocument/DynamicDocumentTests.cs index 40aec8da96..b25a87ff65 100644 --- a/src/Umbraco.Tests/DynamicDocument/DynamicDocumentTests.cs +++ b/src/Umbraco.Tests/DynamicDocument/DynamicDocumentTests.cs @@ -54,7 +54,7 @@ namespace Umbraco.Tests.DynamicDocument { var template = Template.MakeNew("test", new User(0)); var ctx = GetUmbracoContext("/test", template.Id); - var contentStore = new XmlPublishedContentStore(); + var contentStore = new DefaultPublishedContentStore(); var doc = contentStore.GetDocumentById(ctx, id); Assert.IsNotNull(doc); var dynamicNode = new Core.Dynamics.DynamicDocument(doc); diff --git a/src/Umbraco.Tests/DynamicDocument/DynamicNodeTests.cs b/src/Umbraco.Tests/DynamicDocument/DynamicNodeTests.cs index 020451936a..61a510001a 100644 --- a/src/Umbraco.Tests/DynamicDocument/DynamicNodeTests.cs +++ b/src/Umbraco.Tests/DynamicDocument/DynamicNodeTests.cs @@ -35,7 +35,7 @@ namespace Umbraco.Tests.DynamicDocument { var template = Template.MakeNew("test", new User(0)); var ctx = GetUmbracoContext("/test", template.Id); - var contentStore = new XmlPublishedContentStore(); + var contentStore = new DefaultPublishedContentStore(); var node = new DynamicNode( new DynamicBackingItem( new Node(ctx.GetXml().SelectSingleNode("//*[@id='" + id + "' and @isDoc]")))); diff --git a/src/Umbraco.Tests/PublishContentStoreTests.cs b/src/Umbraco.Tests/PublishContentStoreTests.cs index d4decac6e5..e686582e9a 100644 --- a/src/Umbraco.Tests/PublishContentStoreTests.cs +++ b/src/Umbraco.Tests/PublishContentStoreTests.cs @@ -13,7 +13,7 @@ namespace Umbraco.Tests { private FakeHttpContextFactory _httpContextFactory; private UmbracoContext _umbracoContext; - private XmlPublishedContentStore _publishedContentStore; + private DefaultPublishedContentStore _publishedContentStore; [SetUp] public void SetUp() @@ -56,7 +56,7 @@ namespace Umbraco.Tests return xDoc; }; - _publishedContentStore = new XmlPublishedContentStore(); + _publishedContentStore = new DefaultPublishedContentStore(); } diff --git a/src/Umbraco.Tests/Routing/BaseRoutingTest.cs b/src/Umbraco.Tests/Routing/BaseRoutingTest.cs index eb489b01d0..2dc58d9937 100644 --- a/src/Umbraco.Tests/Routing/BaseRoutingTest.cs +++ b/src/Umbraco.Tests/Routing/BaseRoutingTest.cs @@ -38,7 +38,7 @@ namespace Umbraco.Tests.Routing protected RoutingContext GetRoutingContext(string url, int templateId, RouteData routeData = null) { var umbracoContext = GetUmbracoContext(url, templateId, routeData); - var contentStore = new XmlPublishedContentStore(); + var contentStore = new DefaultPublishedContentStore(); var niceUrls = new NiceUrlProvider(contentStore, umbracoContext); var routingRequest = new RoutingContext( umbracoContext, diff --git a/src/Umbraco.Web/XmlPublishedContentStore.cs b/src/Umbraco.Web/DefaultPublishedContentStore.cs similarity index 73% rename from src/Umbraco.Web/XmlPublishedContentStore.cs rename to src/Umbraco.Web/DefaultPublishedContentStore.cs index 8eec803f05..2e41db2819 100644 --- a/src/Umbraco.Web/XmlPublishedContentStore.cs +++ b/src/Umbraco.Web/DefaultPublishedContentStore.cs @@ -1,7 +1,7 @@ using System; using System.Text; using System.Xml; -using Umbraco.Core; +using System.Xml.Linq; using Umbraco.Core.Models; using Umbraco.Web.Routing; using umbraco; @@ -11,9 +11,9 @@ using umbraco.interfaces; namespace Umbraco.Web { /// - /// An IContentStore which uses the Xml cache system to return data + /// An IPublishedContentStore which uses the Xml cache system to return data /// - internal class XmlPublishedContentStore : IPublishedContentStore + internal class DefaultPublishedContentStore : DefaultPublishedMediaStore, IPublishedContentStore { private IDocument ConvertToDocument(XmlNode xmlNode) @@ -24,7 +24,7 @@ namespace Umbraco.Web return new Models.XmlDocument(xmlNode); } - public IDocument GetDocumentById(UmbracoContext umbracoContext, int nodeId) + public override IDocument GetDocumentById(UmbracoContext umbracoContext, int nodeId) { if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); @@ -84,37 +84,6 @@ namespace Umbraco.Web return ConvertToDocument(GetXml(umbracoContext).SelectSingleNode(xpath)); } - //public IDocument GetNodeParent(IDocument node) - //{ - // return node.Parent; - //} - - public string GetDocumentProperty(UmbracoContext umbracoContext, IDocument node, string propertyAlias) - { - if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); - if (node == null) throw new ArgumentNullException("node"); - if (propertyAlias == null) throw new ArgumentNullException("propertyAlias"); - - if (propertyAlias.StartsWith("@")) - { - //if it starts with an @ then its a property of the object, not a user defined property - var propName = propertyAlias.TrimStart('@'); - var prop = TypeHelper.GetProperty(typeof(IDocument), propName, true, false, false, false); - if (prop == null) - throw new ArgumentException("The property name " + propertyAlias + " was not found on type " + typeof(IDocument)); - var val = prop.GetValue(node, null); - var valAsString = val == null ? "" : val.ToString(); - return valAsString; - } - else - { - var prop = node.GetProperty(propertyAlias); - return prop == null ? null : Convert.ToString(prop.Value); - //var propertyNode = node.SelectSingleNode("./" + propertyAlias); - //return propertyNode == null ? null : propertyNode.InnerText; - } - } - XmlDocument GetXml(UmbracoContext umbracoContext) { if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); diff --git a/src/Umbraco.Web/DefaultPublishedMediaStore.cs b/src/Umbraco.Web/DefaultPublishedMediaStore.cs new file mode 100644 index 0000000000..ec8ad95dc5 --- /dev/null +++ b/src/Umbraco.Web/DefaultPublishedMediaStore.cs @@ -0,0 +1,230 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Xml.XPath; +using Examine; +using Umbraco.Core; +using Umbraco.Core.Dynamics; +using Umbraco.Core.Models; + +namespace Umbraco.Web +{ + /// + /// An IPublishedMediaStore that first checks for the media in Examine, and then reverts to the database + /// + internal class DefaultPublishedMediaStore : IPublishedMediaStore + { + public virtual IDocument GetDocumentById(UmbracoContext umbracoContext, int nodeId) + { + return GetUmbracoMedia(nodeId); + } + + public virtual string GetDocumentProperty(UmbracoContext umbracoContext, IDocument node, string propertyAlias) + { + if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); + if (node == null) throw new ArgumentNullException("node"); + if (propertyAlias == null) throw new ArgumentNullException("propertyAlias"); + + if (propertyAlias.StartsWith("@")) + { + //if it starts with an @ then its a property of the object, not a user defined property + var propName = propertyAlias.TrimStart('@'); + var prop = TypeHelper.GetProperty(typeof(IDocument), propName, true, false, false, false); + if (prop == null) + throw new ArgumentException("The property name " + propertyAlias + " was not found on type " + typeof(IDocument)); + var val = prop.GetValue(node, null); + var valAsString = val == null ? "" : val.ToString(); + return valAsString; + } + else + { + var prop = node.GetProperty(propertyAlias); + return prop == null ? null : Convert.ToString(prop.Value); + //var propertyNode = node.SelectSingleNode("./" + propertyAlias); + //return propertyNode == null ? null : propertyNode.InnerText; + } + } + + private IDocument GetUmbracoMedia(int id) + { + + try + { + //first check in Examine as this is WAY faster + var criteria = ExamineManager.Instance + .SearchProviderCollection["InternalSearcher"] + .CreateSearchCriteria("media"); + var filter = criteria.Id(id); + var results = ExamineManager + .Instance.SearchProviderCollection["InternalSearcher"] + .Search(filter.Compile()); + 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! + } + + var media = global::umbraco.library.GetMedia(id, true); + if (media != null && media.Current != null) + { + media.MoveNext(); + return ConvertFromXPathNavigator(media.Current); + } + + return null; + } + + internal IDocument ConvertFromSearchResult(SearchResult searchResult) + { + //TODO: Unit test this + throw new NotImplementedException(); + } + + internal IDocument ConvertFromXPathNavigator(XPathNavigator xpath) + { + //TODO: Unit test this + + if (xpath == null) throw new ArgumentNullException("xpath"); + + var values = new Dictionary {{"nodeName", xpath.GetAttribute("nodeName", "")}}; + + 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()) + { + values.Add(result.Current.Name, result.Current.Value); + while (result.Current.MoveToNextAttribute()) + { + values.Add(result.Current.Name, result.Current.Value); + } + 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); + } + } + + return new DictionaryDocument(values, d => d.ParentId.HasValue ? GetUmbracoMedia(d.ParentId.Value) : null); + } + + /// + /// An IDocument that is represented all by a dictionary. + /// + /// + /// This is a helper class and definitely not intended for public use, it expects that all of the values required + /// to create an IDocument exist in the dictionary by specific aliases. + /// + internal class DictionaryDocument : IDocument + { + + //TODO: Unit test this! + + public DictionaryDocument(IDictionary valueDictionary, Func getParent) + { + if (valueDictionary == null) throw new ArgumentNullException("valueDictionary"); + if (getParent == null) throw new ArgumentNullException("getParent"); + + _getParent = getParent; + + 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"); + ValidateAndSetProperty(valueDictionary, val => DocumentTypeAlias = val, "nodeTypeAlias", "__NodeTypeAlias"); + ValidateAndSetProperty(valueDictionary, val => DocumentTypeId = int.Parse(val), "nodeType"); + ValidateAndSetProperty(valueDictionary, val => WriterName = val, "writerName"); + ValidateAndSetProperty(valueDictionary, val => CreatorName = val, "creatorName"); + ValidateAndSetProperty(valueDictionary, val => WriterId = int.Parse(val), "writerID"); + ValidateAndSetProperty(valueDictionary, val => CreatorId = int.Parse(val), "creatorID"); + ValidateAndSetProperty(valueDictionary, val => Path = val, "path", "__Path"); + ValidateAndSetProperty(valueDictionary, val => CreateDate = DateTime.Parse(val), "createDate"); + ValidateAndSetProperty(valueDictionary, val => Level = int.Parse(val), "level"); + ValidateAndSetProperty(valueDictionary, val => + { + int pId; + ParentId = null; + if (int.TryParse(val, out pId)) + { + ParentId = pId; + } + }, "parentID"); + + Properties = new Collection(); + + //loop through remaining values that haven't been applied + foreach (var i in valueDictionary.Where(x => !_keysAdded.Contains(x.Key))) + { + //this is taken from examine + Properties.Add(i.Key.InvariantStartsWith("__") + ? new PropertyResult(i.Key, i.Value, Guid.Empty, PropertyResultType.CustomProperty) + : new PropertyResult(i.Key, i.Value, Guid.Empty, PropertyResultType.UserProperty)); + } + } + + private readonly Func _getParent; + public IDocument Parent + { + get { return _getParent(this); } + } + + public int? ParentId { get; private set; } + public int Id { get; private set; } + public int TemplateId { get; private set; } + public int SortOrder { get; private set; } + public string Name { get; private set; } + public string UrlName { get; private set; } + public string DocumentTypeAlias { get; private set; } + public int DocumentTypeId { get; private set; } + public string WriterName { get; private set; } + public string CreatorName { get; private set; } + public int WriterId { get; private set; } + public int CreatorId { get; private set; } + public string Path { get; private set; } + public DateTime CreateDate { get; private set; } + public DateTime UpdateDate { get; private set; } + public Guid Version { get; private set; } + public int Level { get; private set; } + public Collection Properties { get; private set; } + public IEnumerable Children { get; private set; } + + private readonly List _keysAdded = new List(); + private void ValidateAndSetProperty(IDictionary valueDictionary, Action setProperty, params string[] potentialKeys) + { + foreach (var s in potentialKeys) + { + if (valueDictionary[s] == null) + throw new FormatException("The valueDictionary is not formatted correctly and is missing the '" + s + "' element"); + setProperty(valueDictionary[s]); + _keysAdded.Add(s); + break; + } + + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/IPublishedContentStore.cs b/src/Umbraco.Web/IPublishedContentStore.cs index 3b89537340..22ee23ba7a 100644 --- a/src/Umbraco.Web/IPublishedContentStore.cs +++ b/src/Umbraco.Web/IPublishedContentStore.cs @@ -2,11 +2,12 @@ using Umbraco.Core.Models; namespace Umbraco.Web { - internal interface IPublishedContentStore - { - IDocument GetDocumentById(UmbracoContext umbracoContext, int nodeId); + /// + /// Defines the methods to access published content + /// + internal interface IPublishedContentStore : IPublishedMediaStore + { IDocument GetDocumentByRoute(UmbracoContext umbracoContext, string route, bool? hideTopLevelNode = null); IDocument GetDocumentByUrlAlias(UmbracoContext umbracoContext, int rootNodeId, string alias); - string GetDocumentProperty(UmbracoContext umbracoContext, IDocument node, string propertyAlias); } } \ No newline at end of file diff --git a/src/Umbraco.Web/IPublishedMediaStore.cs b/src/Umbraco.Web/IPublishedMediaStore.cs new file mode 100644 index 0000000000..78efe3488f --- /dev/null +++ b/src/Umbraco.Web/IPublishedMediaStore.cs @@ -0,0 +1,13 @@ +using Umbraco.Core.Models; + +namespace Umbraco.Web +{ + /// + /// Defines the methods to access published media + /// + internal interface IPublishedMediaStore + { + IDocument GetDocumentById(UmbracoContext umbracoContext, int nodeId); + string GetDocumentProperty(UmbracoContext umbracoContext, IDocument node, string propertyAlias); + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/XmlDocument.cs b/src/Umbraco.Web/Models/XmlDocument.cs index cf782d642a..f51e8dc973 100644 --- a/src/Umbraco.Web/Models/XmlDocument.cs +++ b/src/Umbraco.Web/Models/XmlDocument.cs @@ -236,11 +236,6 @@ namespace Umbraco.Web.Models } } - //public string NiceUrl - //{ - // get { return _niceUrlProvider.GetNiceUrl(Id); } - //} - public int Level { get @@ -260,12 +255,7 @@ namespace Umbraco.Web.Models return _properties; } } - - - public IDocumentProperty GetProperty(string alias) - { - return Properties.FirstOrDefault(p => p.Alias.InvariantEquals(alias)); - } + private void InitializeStructure() { diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 5f723910fe..9a23411e87 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -241,6 +241,7 @@ + @@ -249,6 +250,7 @@ + @@ -271,7 +273,7 @@ - + diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index f045af6a89..1887d0151c 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -133,7 +133,7 @@ namespace Umbraco.Web PropertyEditorValueConvertersResolver.Current.RemoveType(); PropertyEditorValueConvertersResolver.Current.AddType(); - PublishedContentStoreResolver.Current = new PublishedContentStoreResolver(new XmlPublishedContentStore()); + PublishedContentStoreResolver.Current = new PublishedContentStoreResolver(new DefaultPublishedContentStore()); FilteredControllerFactoriesResolver.Current = new FilteredControllerFactoriesResolver( //add all known factories, devs can then modify this list on application startup either by binding to events