PageRenderTime 47ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/Source/Libraries/Umbraco.Framework.Persistence.XmlStore/DataManagement/DataContext.cs

#
C# | 270 lines | 211 code | 38 blank | 21 comment | 17 complexity | 1277c69281a32829826547d58caf9cd2 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, BSD-3-Clause, CC-BY-SA-3.0, LGPL-2.1, MIT, Apache-2.0
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Concurrent;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. using System.Xml.Linq;
  7. using System.Xml.XPath;
  8. using Umbraco.Framework.Context;
  9. using Umbraco.Framework.DataManagement;
  10. using Umbraco.Framework.DataManagement.Linq;
  11. using Umbraco.Framework.DataManagement.Linq.QueryModel;
  12. using Umbraco.Framework.DataManagement.Linq.ResultBinding;
  13. using Umbraco.Framework.Persistence.DataManagement;
  14. using Umbraco.Framework.Persistence.ProviderSupport;
  15. using Umbraco.Framework.Persistence.XmlStore.DataManagement.Linq;
  16. namespace Umbraco.Framework.Persistence.XmlStore.DataManagement
  17. {
  18. public class DataContext : AbstractDataContext, IQueryableDataSource
  19. {
  20. private readonly ConcurrentStack<Transaction> _openTransactions = new ConcurrentStack<Transaction>();
  21. private readonly string _path;
  22. private readonly XDocument _xmlDoc;
  23. private readonly Func<object, SourceFieldBinder> _fieldBinderFactory;
  24. public DataContext(IHiveProvider hiveProvider, XDocument xmlDoc, string path, Func<object, SourceFieldBinder> fieldBinderFactory)
  25. : base(hiveProvider)
  26. {
  27. _path = path;
  28. _xmlDoc = xmlDoc;
  29. _fieldBinderFactory = fieldBinderFactory;
  30. FrameworkContext = hiveProvider.FrameworkContext;
  31. }
  32. public XDocument XmlDoc
  33. {
  34. get
  35. {
  36. this.CheckThrowObjectDisposed(base.IsDisposed, "XmlStore...DataContext:XmlDoc");
  37. return _xmlDoc;
  38. }
  39. }
  40. /// <summary>
  41. /// Begins a transaction.
  42. /// </summary>
  43. /// <returns></returns>
  44. /// <remarks></remarks>
  45. public override ITransaction BeginTransaction()
  46. {
  47. this.CheckThrowObjectDisposed(base.IsDisposed, "XmlStore...DataContext:BeginTransaction");
  48. var tranny = new Transaction(XmlDoc, _path);
  49. _openTransactions.Push(tranny);
  50. return tranny;
  51. }
  52. /// <summary>
  53. /// Gets the current transaction.
  54. /// </summary>
  55. /// <remarks></remarks>
  56. public override ITransaction CurrentTransaction
  57. {
  58. get
  59. {
  60. Transaction wrapper = null;
  61. _openTransactions.TryPeek(out wrapper);
  62. return wrapper;
  63. }
  64. }
  65. /// <summary>
  66. /// Gets the framework context.
  67. /// </summary>
  68. /// <remarks></remarks>
  69. public IFrameworkContext FrameworkContext { get; private set; }
  70. /// <summary>
  71. /// Flushes this instance. This action is optional, for example if a DataContext should be flushed multiple times before committing or rolling back a transaction.
  72. /// </summary>
  73. /// <remarks></remarks>
  74. public override void Flush()
  75. {
  76. this.CheckThrowObjectDisposed(base.IsDisposed, "XmlStore...DataContext:Flush");
  77. XmlDoc.Save(_path); // The XmlStore transaction method is to backup at the start of a transaction, and rollback overwrites from the backup, so just save to normal path
  78. }
  79. internal XElement FindRootItem()
  80. {
  81. return FindElementByExpression("/root").Descendants().First();
  82. }
  83. internal XElement FindItemByUrl(string url)
  84. {
  85. var slashChar = new[] { '/' };
  86. var periodChar = new[] { '.' };
  87. string[] urlParts = url.Split(slashChar, StringSplitOptions.RemoveEmptyEntries);
  88. if (urlParts.Length == 0) return FindRootItem();
  89. XElement xmlElement = FindPageByUrlName(urlParts);
  90. if (xmlElement != null)
  91. {
  92. return FindWithUmbracoRedirect(xmlElement);
  93. }
  94. xmlElement = FindPageByUrlAlias(url.TrimStart(slashChar).Split(periodChar)[0].TrimEnd(slashChar));
  95. if (xmlElement != null)
  96. {
  97. return FindWithUmbracoRedirect(xmlElement);
  98. }
  99. xmlElement = FindPageById(urlParts.Last().Split(periodChar)[0]);
  100. return xmlElement != null ? FindWithUmbracoRedirect(xmlElement) : null;
  101. }
  102. internal XElement FindPageById(string pageId)
  103. {
  104. int result = 0;
  105. if (int.TryParse(pageId, out result))
  106. {
  107. string expression = "/root//*[string-length(@id)>0 and @id=" + pageId + "]";
  108. return FindElementByExpression(expression);
  109. }
  110. return null;
  111. }
  112. internal XElement FindElementByExpression(string expression)
  113. {
  114. return FindElementsByExpression(expression).FirstOrDefault();
  115. }
  116. internal IEnumerable<XElement> FindElementsByExpression(string expression)
  117. {
  118. return ((IEnumerable)XmlDoc.XPathEvaluate(expression)).Cast<XElement>();
  119. }
  120. internal XElement FindPageByUrlAlias(string alias)
  121. {
  122. string expression = string.Concat(
  123. "/root//*[string-length(@id)>0 and data[@alias='umbracoUrlAlias']/text()=\"", alias, "\"]");
  124. return FindElementByExpression(expression);
  125. }
  126. internal XElement FindPageByUrlName(string[] urlParts)
  127. {
  128. string expression = "/root/";
  129. if (urlParts.Length != 0)
  130. {
  131. expression = urlParts
  132. .Aggregate(
  133. expression,
  134. (current, part) => current + string.Format("/*[@urlName=\"{0}\"]", part.Split(new[] { '.' })[0]));
  135. try
  136. {
  137. return FindElementByExpression(expression);
  138. }
  139. catch (ArgumentNullException exception)
  140. {
  141. throw new ArgumentNullException(
  142. "Error evaluating XPath statement: \"" + expression + "\" against URL: \"" +
  143. string.Join("/", urlParts) + "\"", exception);
  144. }
  145. }
  146. return null;
  147. }
  148. internal XElement FindWithUmbracoRedirect(XElement pageXml)
  149. {
  150. IEnumerable<XElement> redirectElements = FindElementsByExpression("./data[@alias='umbracoRedirect']");
  151. if (redirectElements.Count() > 0)
  152. {
  153. var elementAsString = (string)redirectElements.First();
  154. if (string.IsNullOrEmpty(elementAsString))
  155. {
  156. return pageXml;
  157. }
  158. XElement element = FindPageById(elementAsString);
  159. if (element != null)
  160. {
  161. pageXml = element;
  162. }
  163. }
  164. return pageXml;
  165. }
  166. /// <summary>
  167. /// Handles the disposal of resources. Derived from abstract class <see cref="DisposableObject"/> which handles common required locking logic.
  168. /// </summary>
  169. protected override void DisposeResources()
  170. {
  171. this.CheckThrowObjectDisposed(base.IsDisposed, "XmlStore...DataContext:Close");
  172. foreach (Transaction openTransaction in _openTransactions.Where(openTransaction => !openTransaction.WasCommitted))
  173. {
  174. openTransaction.Rollback();
  175. }
  176. }
  177. public T ExecuteScalar<T>(QueryDescription query, ObjectBinder objectBinder)
  178. {
  179. throw new NotImplementedException();
  180. }
  181. public T ExecuteSingle<T>(QueryDescription query, ObjectBinder objectBinder)
  182. {
  183. // TODO: Evaluate query here because it means we can avoid running a whole query if all we need is the first etc.
  184. return ExecuteMany<T>(query, objectBinder).FirstOrDefault();
  185. }
  186. public IEnumerable<T> ExecuteMany<T>(QueryDescription query, ObjectBinder objectBinder)
  187. {
  188. var sourceElement = _xmlDoc.Descendants().Where(x => IdMatches(x, query.From.StartId)).FirstOrDefault();
  189. var direction = query.From.HierarchyScope;
  190. var expression = new XElementCriteriaVisitor().Visit(query.Criteria);
  191. var allMatches = _xmlDoc.Descendants().Where(expression.Compile());
  192. var results = Enumerable.Empty<XElement>();
  193. switch (direction)
  194. {
  195. case HierarchyScope.AllOrNone:
  196. results = _xmlDoc.Descendants().Where(x => allMatches.Contains(x));
  197. break;
  198. case HierarchyScope.Ancestors:
  199. results = sourceElement.Ancestors().Where(x => allMatches.Contains(x));
  200. break;
  201. case HierarchyScope.AncestorsOrSelf:
  202. results = sourceElement.AncestorsAndSelf().Where(x => allMatches.Contains(x));
  203. break;
  204. case HierarchyScope.Descendents:
  205. results = sourceElement.Descendants().Where(x => allMatches.Contains(x));
  206. break;
  207. case HierarchyScope.DescendentsOrSelf:
  208. results = sourceElement.DescendantsAndSelf().Where(x => allMatches.Contains(x));
  209. break;
  210. case HierarchyScope.Parent:
  211. results = new[] { sourceElement.Ancestors().Where(x => allMatches.Contains(x)).Last() };
  212. break;
  213. case HierarchyScope.Children:
  214. results = sourceElement.Elements().Where(x => allMatches.Contains(x));
  215. break;
  216. }
  217. switch (query.ResultFilter.ResultFilterType)
  218. {
  219. case ResultFilterType.Take:
  220. results = results.Take(query.ResultFilter.SelectorArgument);
  221. break;
  222. }
  223. return typeof(T).IsAssignableFrom(query.ResultFilter.ResultType)
  224. ? results.Select(xElement => objectBinder.Execute(_fieldBinderFactory.Invoke(xElement))).Cast<T>()
  225. : Enumerable.Empty<T>();
  226. }
  227. private static bool IdMatches(XElement xElement, string id)
  228. {
  229. return xElement.Attributes("id").Any() && string.Equals((string)xElement.Attribute("id"), id, StringComparison.InvariantCultureIgnoreCase);
  230. }
  231. }
  232. }