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