PageRenderTime 50ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

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

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