PageRenderTime 96ms CodeModel.GetById 32ms RepoModel.GetById 1ms app.codeStats 0ms

/umbraco_6f33e2b81175/umbraco/presentation/content.cs

https://github.com/jracabado/justEdit-
C# | 1168 lines | 748 code | 163 blank | 257 comment | 100 complexity | b7f51bc5771322a91e87a5510e695969 MD5 | raw file
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. using System.IO;
  6. using System.Threading;
  7. using System.Web;
  8. using System.Xml;
  9. using System.Xml.XPath;
  10. using umbraco.BusinessLogic;
  11. using umbraco.BusinessLogic.Actions;
  12. using umbraco.cms.businesslogic.cache;
  13. using umbraco.cms.businesslogic.web;
  14. using umbraco.DataLayer;
  15. using umbraco.IO;
  16. using umbraco.BusinessLogic.Utils;
  17. using umbraco.presentation.nodeFactory;
  18. namespace umbraco
  19. {
  20. /// <summary>
  21. /// Handles umbraco content
  22. /// </summary>
  23. public class content
  24. {
  25. #region Declarations
  26. private string _umbracoXmlDiskCacheFileName = string.Empty;
  27. /// <summary>
  28. /// Gets the path of the umbraco XML disk cache file.
  29. /// </summary>
  30. /// <value>The name of the umbraco XML disk cache file.</value>
  31. public string UmbracoXmlDiskCacheFileName
  32. {
  33. get
  34. {
  35. if (string.IsNullOrEmpty(_umbracoXmlDiskCacheFileName))
  36. {
  37. _umbracoXmlDiskCacheFileName = IOHelper.MapPath(SystemFiles.ContentCacheXml);
  38. }
  39. return _umbracoXmlDiskCacheFileName;
  40. }
  41. set
  42. {
  43. _umbracoXmlDiskCacheFileName = value;
  44. }
  45. }
  46. private readonly string XmlContextContentItemKey = "UmbracoXmlContextContent";
  47. // Current content
  48. private volatile XmlDocument _xmlContent = null;
  49. // Sync access to disk file
  50. private static object _readerWriterSyncLock = new object();
  51. // Sync access to internal cache
  52. private static object _xmlContentInternalSyncLock = new object();
  53. // Sync database access
  54. private static object _dbReadSyncLock = new object();
  55. #endregion
  56. #region Constructors
  57. public content()
  58. {
  59. ;
  60. }
  61. static content()
  62. {
  63. //Trace.Write("Initializing content");
  64. //ThreadPool.QueueUserWorkItem(
  65. // delegate
  66. // {
  67. // XmlDocument xmlDoc = Instance.XmlContentInternal;
  68. // Trace.WriteLine("Content initialized");
  69. // });
  70. Trace.WriteLine("Checking for xml content initialisation...");
  71. Instance.CheckXmlContentPopulation();
  72. }
  73. #endregion
  74. #region Singleton
  75. public static content Instance
  76. {
  77. get { return Singleton<content>.Instance; }
  78. }
  79. #endregion
  80. #region Properties
  81. /// <summary>
  82. /// Get content. First call to this property will initialize xmldoc
  83. /// subsequent calls will be blocked until initialization is done
  84. /// Further we cache(in context) xmlContent for each request to ensure that
  85. /// we always have the same XmlDoc throughout the whole request.
  86. /// </summary>
  87. public virtual XmlDocument XmlContent
  88. {
  89. get
  90. {
  91. if (HttpContext.Current == null)
  92. return XmlContentInternal;
  93. XmlDocument content = HttpContext.Current.Items[XmlContextContentItemKey] as XmlDocument;
  94. if (content == null)
  95. {
  96. content = XmlContentInternal;
  97. HttpContext.Current.Items[XmlContextContentItemKey] = content;
  98. }
  99. return content;
  100. }
  101. }
  102. [Obsolete("Please use: content.Instance.XmlContent")]
  103. public static XmlDocument xmlContent
  104. {
  105. get { return Instance.XmlContent; }
  106. }
  107. public virtual bool isInitializing
  108. {
  109. get { return _xmlContent == null; }
  110. }
  111. /// <summary>
  112. /// Internal reference to XmlContent
  113. /// </summary>
  114. protected virtual XmlDocument XmlContentInternal
  115. {
  116. get
  117. {
  118. CheckXmlContentPopulation();
  119. return _xmlContent;
  120. }
  121. set
  122. {
  123. lock (_xmlContentInternalSyncLock)
  124. {
  125. // Clear macro cache
  126. Cache.ClearCacheObjectTypes("umbraco.MacroCacheContent");
  127. // Clear library cache
  128. if (UmbracoSettings.UmbracoLibraryCacheDuration > 0)
  129. {
  130. Cache.ClearCacheObjectTypes("MS.Internal.Xml.XPath.XPathSelectionIterator");
  131. }
  132. requestHandler.ClearProcessedRequests();
  133. _xmlContent = value;
  134. if (!UmbracoSettings.isXmlContentCacheDisabled && UmbracoSettings.continouslyUpdateXmlDiskCache)
  135. QueueXmlForPersistence();
  136. else
  137. // Clear cache...
  138. DeleteXmlCache();
  139. }
  140. }
  141. }
  142. /// <summary>
  143. /// Triggers the XML content population if necessary.
  144. /// </summary>
  145. /// <returns></returns>
  146. private bool CheckXmlContentPopulation()
  147. {
  148. if (isInitializing)
  149. {
  150. lock (_xmlContentInternalSyncLock)
  151. {
  152. if (isInitializing)
  153. {
  154. Trace.WriteLine(string.Format("Initializing content on thread '{0}' (Threadpool? {1})", Thread.CurrentThread.Name, Thread.CurrentThread.IsThreadPoolThread.ToString()));
  155. _xmlContent = LoadContent();
  156. Trace.WriteLine("Content initialized (loaded)");
  157. // Only save new XML cache to disk if we just repopulated it
  158. // TODO: Re-architect this so that a call to this method doesn't invoke a new thread for saving disk cache
  159. if (!UmbracoSettings.isXmlContentCacheDisabled && !IsValidDiskCachePresent())
  160. {
  161. QueueXmlForPersistence();
  162. }
  163. return true;
  164. }
  165. }
  166. }
  167. Trace.WriteLine("Content initialized (was already in context)");
  168. return false;
  169. }
  170. #endregion
  171. protected static ISqlHelper SqlHelper
  172. {
  173. get { return Application.SqlHelper; }
  174. }
  175. protected static void ReGenerateSchema(XmlDocument xmlDoc)
  176. {
  177. string dtd = DocumentType.GenerateXmlDocumentType();
  178. // remove current doctype
  179. XmlNode n = xmlDoc.FirstChild;
  180. while (n.NodeType != XmlNodeType.DocumentType && n.NextSibling != null)
  181. {
  182. n = n.NextSibling;
  183. }
  184. if (n.NodeType == XmlNodeType.DocumentType)
  185. {
  186. xmlDoc.RemoveChild(n);
  187. }
  188. XmlDocumentType docType = xmlDoc.CreateDocumentType("root", null, null, dtd);
  189. xmlDoc.InsertAfter(docType, xmlDoc.FirstChild);
  190. }
  191. protected static void ValidateSchema(string docTypeAlias, XmlDocument xmlDoc)
  192. {
  193. // if doctype is not defined i)n schema, then regenerate it
  194. if (!xmlDoc.DocumentType.InternalSubset.Contains(String.Format("<!ATTLIST {0} id ID #REQUIRED>", docTypeAlias)))
  195. {
  196. // we need to re-load the content, else the dtd changes won't be picked up by the XmlDocument
  197. content.Instance.XmlContentInternal = content.Instance.LoadContentFromDatabase();
  198. }
  199. }
  200. #region Public Methods
  201. /// <summary>
  202. /// Load content from database in a background thread
  203. /// Replaces active content when done.
  204. /// </summary>
  205. public virtual void RefreshContentFromDatabaseAsync()
  206. {
  207. cms.businesslogic.RefreshContentEventArgs e = new umbraco.cms.businesslogic.RefreshContentEventArgs();
  208. FireBeforeRefreshContent(e);
  209. if (!e.Cancel)
  210. {
  211. ThreadPool.QueueUserWorkItem(
  212. delegate
  213. {
  214. XmlDocument xmlDoc = LoadContentFromDatabase();
  215. XmlContentInternal = xmlDoc;
  216. // It is correct to manually call PersistXmlToFile here event though the setter of XmlContentInternal
  217. // queues this up, because this delegate is executing on a different thread and may complete
  218. // after the request which invoked it (which would normally persist the file on completion)
  219. // So we are responsible for ensuring the content is persisted in this case.
  220. if (!UmbracoSettings.isXmlContentCacheDisabled && UmbracoSettings.continouslyUpdateXmlDiskCache)
  221. PersistXmlToFile(xmlDoc);
  222. });
  223. FireAfterRefreshContent(e);
  224. }
  225. }
  226. public static void TransferValuesFromDocumentXmlToPublishedXml(XmlNode DocumentNode, XmlNode PublishedNode)
  227. {
  228. // Remove all attributes and data nodes from the published node
  229. PublishedNode.Attributes.RemoveAll();
  230. string xpath = UmbracoSettings.UseLegacyXmlSchema ? "./data" : "./* [not(@id)]";
  231. foreach (XmlNode n in PublishedNode.SelectNodes(xpath))
  232. PublishedNode.RemoveChild(n);
  233. // Append all attributes and datanodes from the documentnode to the publishednode
  234. foreach (XmlAttribute att in DocumentNode.Attributes)
  235. ((XmlElement)PublishedNode).SetAttribute(att.Name, att.Value);
  236. foreach (XmlElement el in DocumentNode.SelectNodes(xpath))
  237. {
  238. XmlNode newDatael = PublishedNode.OwnerDocument.ImportNode(el, true);
  239. PublishedNode.AppendChild(newDatael);
  240. }
  241. }
  242. /// <summary>
  243. /// Used by all overloaded publish methods to do the actual "noderepresentation to xml"
  244. /// </summary>
  245. /// <param name="d"></param>
  246. /// <param name="xmlContentCopy"></param>
  247. public static void PublishNodeDo(Document d, XmlDocument xmlContentCopy, bool updateSitemapProvider)
  248. {
  249. // check if document *is* published, it could be unpublished by an event
  250. if (d.Published)
  251. {
  252. int parentId = d.Level == 1 ? -1 : d.Parent.Id;
  253. AppendDocumentXml(d.Id, d.Level, parentId, getPreviewOrPublishedNode(d, xmlContentCopy, false), xmlContentCopy);
  254. // update sitemapprovider
  255. if (updateSitemapProvider && SiteMap.Provider is presentation.nodeFactory.UmbracoSiteMapProvider)
  256. {
  257. presentation.nodeFactory.UmbracoSiteMapProvider prov = (presentation.nodeFactory.UmbracoSiteMapProvider)SiteMap.Provider;
  258. prov.UpdateNode(new umbraco.NodeFactory.Node(d.Id, true));
  259. }
  260. }
  261. }
  262. public static void AppendDocumentXml(int id, int level, int parentId, XmlNode docXml, XmlDocument xmlContentCopy)
  263. {
  264. // Validate schema (that a definition of the current document type exists in the DTD
  265. if (!UmbracoSettings.UseLegacyXmlSchema)
  266. {
  267. ValidateSchema(docXml.Name, xmlContentCopy);
  268. }
  269. // Find the document in the xml cache
  270. XmlNode x = xmlContentCopy.GetElementById(id.ToString());
  271. // Find the parent (used for sortering and maybe creation of new node)
  272. XmlNode parentNode;
  273. if (level == 1)
  274. parentNode = xmlContentCopy.DocumentElement;
  275. else
  276. parentNode = xmlContentCopy.GetElementById(parentId.ToString());
  277. if (parentNode != null)
  278. {
  279. if (x == null)
  280. {
  281. x = docXml;
  282. parentNode.AppendChild(x);
  283. }
  284. else
  285. TransferValuesFromDocumentXmlToPublishedXml(docXml, x);
  286. // TODO: Update with new schema!
  287. string xpath = UmbracoSettings.UseLegacyXmlSchema ? "./node" : "./* [@id]";
  288. XmlNodeList childNodes = parentNode.SelectNodes(xpath);
  289. // Maybe sort the nodes if the added node has a lower sortorder than the last
  290. if (childNodes.Count > 0)
  291. {
  292. int siblingSortOrder = int.Parse(childNodes[childNodes.Count - 1].Attributes.GetNamedItem("sortOrder").Value);
  293. int currentSortOrder = int.Parse(x.Attributes.GetNamedItem("sortOrder").Value);
  294. if (childNodes.Count > 1 && siblingSortOrder > currentSortOrder)
  295. {
  296. SortNodes(ref parentNode);
  297. }
  298. }
  299. }
  300. }
  301. private static XmlNode getPreviewOrPublishedNode(Document d, XmlDocument xmlContentCopy, bool isPreview)
  302. {
  303. if (isPreview)
  304. {
  305. return d.ToPreviewXml(xmlContentCopy);
  306. }
  307. else
  308. {
  309. return d.ToXml(xmlContentCopy, false);
  310. }
  311. }
  312. /// <summary>
  313. /// Sorts the documents.
  314. /// </summary>
  315. /// <param name="parentNode">The parent node.</param>
  316. public static void SortNodes(ref XmlNode parentNode)
  317. {
  318. XmlNode n = parentNode.CloneNode(true);
  319. // remove all children from original node
  320. string xpath = UmbracoSettings.UseLegacyXmlSchema ? "./node" : "./* [@id]";
  321. foreach (XmlNode child in parentNode.SelectNodes(xpath))
  322. parentNode.RemoveChild(child);
  323. XPathNavigator nav = n.CreateNavigator();
  324. XPathExpression expr = nav.Compile(xpath);
  325. expr.AddSort("@sortOrder", XmlSortOrder.Ascending, XmlCaseOrder.None, "", XmlDataType.Number);
  326. XPathNodeIterator iterator = nav.Select(expr);
  327. while (iterator.MoveNext())
  328. parentNode.AppendChild(
  329. ((IHasXmlNode)iterator.Current).GetNode());
  330. }
  331. /// <summary>
  332. /// Updates the document cache.
  333. /// </summary>
  334. /// <param name="pageId">The page id.</param>
  335. public virtual void UpdateDocumentCache(int pageId)
  336. {
  337. Document d = new Document(pageId);
  338. UpdateDocumentCache(d);
  339. }
  340. /// <summary>
  341. /// Updates the document cache.
  342. /// </summary>
  343. /// <param name="d">The d.</param>
  344. public virtual void UpdateDocumentCache(Document d)
  345. {
  346. cms.businesslogic.DocumentCacheEventArgs e = new umbraco.cms.businesslogic.DocumentCacheEventArgs();
  347. FireBeforeUpdateDocumentCache(d, e);
  348. if (!e.Cancel)
  349. {
  350. // We need to lock content cache here, because we cannot allow other threads
  351. // making changes at the same time, they need to be queued
  352. // Adding log entry before locking the xmlfile
  353. lock (_xmlContentInternalSyncLock)
  354. {
  355. // Make copy of memory content, we cannot make changes to the same document
  356. // the is read from elsewhere
  357. if (UmbracoSettings.CloneXmlCacheOnPublish)
  358. {
  359. XmlDocument xmlContentCopy = CloneXmlDoc(XmlContentInternal);
  360. PublishNodeDo(d, xmlContentCopy, true);
  361. XmlContentInternal = xmlContentCopy;
  362. }
  363. else
  364. {
  365. PublishNodeDo(d, XmlContentInternal, true);
  366. XmlContentInternal = _xmlContent;
  367. }
  368. ClearContextCache();
  369. }
  370. // clear cached field values
  371. if (HttpContext.Current != null)
  372. {
  373. System.Web.Caching.Cache httpCache = HttpContext.Current.Cache;
  374. string cachedFieldKeyStart = String.Format("contentItem{0}_", d.Id);
  375. List<string> foundKeys = new List<string>();
  376. foreach (DictionaryEntry cacheItem in httpCache)
  377. {
  378. string key = cacheItem.Key.ToString();
  379. if (key.StartsWith(cachedFieldKeyStart))
  380. foundKeys.Add(key);
  381. }
  382. foreach (string foundKey in foundKeys)
  383. {
  384. httpCache.Remove(foundKey);
  385. }
  386. }
  387. umbraco.BusinessLogic.Actions.Action.RunActionHandlers(d, ActionPublish.Instance);
  388. FireAfterUpdateDocumentCache(d, e);
  389. }
  390. }
  391. /// <summary>
  392. /// Updates the document cache for multiple documents
  393. /// </summary>
  394. /// <param name="Documents">The documents.</param>
  395. public virtual void UpdateDocumentCache(List<Document> Documents)
  396. {
  397. // We need to lock content cache here, because we cannot allow other threads
  398. // making changes at the same time, they need to be queued
  399. int parentid = Documents[0].Id;
  400. lock (_xmlContentInternalSyncLock)
  401. {
  402. // Make copy of memory content, we cannot make changes to the same document
  403. // the is read from elsewhere
  404. XmlDocument xmlContentCopy = CloneXmlDoc(XmlContentInternal);
  405. foreach (Document d in Documents)
  406. {
  407. PublishNodeDo(d, xmlContentCopy, true);
  408. }
  409. XmlContentInternal = xmlContentCopy;
  410. ClearContextCache();
  411. }
  412. foreach (Document d in Documents)
  413. {
  414. umbraco.BusinessLogic.Actions.Action.RunActionHandlers(d, ActionPublish.Instance);
  415. }
  416. }
  417. [Obsolete("Method obsolete in version 4.1 and later, please use UpdateDocumentCache", true)]
  418. /// <summary>
  419. /// Updates the document cache async.
  420. /// </summary>
  421. /// <param name="documentId">The document id.</param>
  422. public virtual void UpdateDocumentCacheAsync(int documentId)
  423. {
  424. ThreadPool.QueueUserWorkItem(delegate { UpdateDocumentCache(documentId); });
  425. }
  426. [Obsolete("Method obsolete in version 4.1 and later, please use ClearDocumentCache", true)]
  427. /// <summary>
  428. /// Clears the document cache async.
  429. /// </summary>
  430. /// <param name="documentId">The document id.</param>
  431. public virtual void ClearDocumentCacheAsync(int documentId)
  432. {
  433. ThreadPool.QueueUserWorkItem(delegate { ClearDocumentCache(documentId); });
  434. }
  435. /// <summary>
  436. /// Clears the document cache and removes the document from the xml db cache.
  437. /// This means the node gets unpublished from the website.
  438. /// </summary>
  439. /// <param name="documentId">The document id.</param>
  440. public virtual void ClearDocumentCache(int documentId)
  441. {
  442. // Get the document
  443. Document d = new Document(documentId);
  444. cms.businesslogic.DocumentCacheEventArgs e = new umbraco.cms.businesslogic.DocumentCacheEventArgs();
  445. FireBeforeClearDocumentCache(d, e);
  446. if (!e.Cancel)
  447. {
  448. XmlNode x;
  449. // remove from xml db cache
  450. d.XmlRemoveFromDB();
  451. // Check if node present, before cloning
  452. x = XmlContentInternal.GetElementById(d.Id.ToString());
  453. if (x == null)
  454. return;
  455. // We need to lock content cache here, because we cannot allow other threads
  456. // making changes at the same time, they need to be queued
  457. lock (_xmlContentInternalSyncLock)
  458. {
  459. // Make copy of memory content, we cannot make changes to the same document
  460. // the is read from elsewhere
  461. XmlDocument xmlContentCopy = CloneXmlDoc(XmlContentInternal);
  462. // Find the document in the xml cache
  463. x = xmlContentCopy.GetElementById(d.Id.ToString());
  464. if (x != null)
  465. {
  466. // The document already exists in cache, so repopulate it
  467. x.ParentNode.RemoveChild(x);
  468. XmlContentInternal = xmlContentCopy;
  469. ClearContextCache();
  470. }
  471. }
  472. if (x != null)
  473. {
  474. // Run Handler
  475. umbraco.BusinessLogic.Actions.Action.RunActionHandlers(d, ActionUnPublish.Instance);
  476. }
  477. // update sitemapprovider
  478. if (SiteMap.Provider is presentation.nodeFactory.UmbracoSiteMapProvider)
  479. {
  480. presentation.nodeFactory.UmbracoSiteMapProvider prov = (presentation.nodeFactory.UmbracoSiteMapProvider)SiteMap.Provider;
  481. prov.RemoveNode(d.Id);
  482. }
  483. FireAfterClearDocumentCache(d, e);
  484. }
  485. }
  486. /// <summary>
  487. /// Unpublishes the node.
  488. /// </summary>
  489. /// <param name="documentId">The document id.</param>
  490. [Obsolete("Please use: umbraco.content.ClearDocumentCache", true)]
  491. public virtual void UnPublishNode(int documentId)
  492. {
  493. ClearDocumentCache(documentId);
  494. }
  495. /// <summary>
  496. /// Uns the publish node async.
  497. /// </summary>
  498. /// <param name="documentId">The document id.</param>
  499. [Obsolete("Please use: umbraco.content.ClearDocumentCacheAsync", true)]
  500. public virtual void UnPublishNodeAsync(int documentId)
  501. {
  502. ThreadPool.QueueUserWorkItem(delegate { ClearDocumentCache(documentId); });
  503. }
  504. /// <summary>
  505. /// Legacy method - you should use the overloaded publishnode(document d) method whenever possible
  506. /// </summary>
  507. /// <param name="documentId"></param>
  508. [Obsolete("Please use: umbraco.content.UpdateDocumentCache", true)]
  509. public virtual void PublishNode(int documentId)
  510. {
  511. // Get the document
  512. Document d = new Document(documentId);
  513. PublishNode(d);
  514. }
  515. /// <summary>
  516. /// Publishes the node async.
  517. /// </summary>
  518. /// <param name="documentId">The document id.</param>
  519. [Obsolete("Please use: umbraco.content.UpdateDocumentCacheAsync", true)]
  520. public virtual void PublishNodeAsync(int documentId)
  521. {
  522. UpdateDocumentCacheAsync(documentId);
  523. }
  524. /// <summary>
  525. /// Publishes the node.
  526. /// </summary>
  527. /// <param name="Documents">The documents.</param>
  528. [Obsolete("Please use: umbraco.content.UpdateDocumentCache", true)]
  529. public virtual void PublishNode(List<Document> Documents)
  530. {
  531. UpdateDocumentCache(Documents);
  532. }
  533. /// <summary>
  534. /// Publishes the node.
  535. /// </summary>
  536. /// <param name="d">The document.</param>
  537. [Obsolete("Please use: umbraco.content.UpdateDocumentCache", true)]
  538. public virtual void PublishNode(Document d)
  539. {
  540. UpdateDocumentCache(d);
  541. }
  542. public delegate void DocumentCacheEventHandler(Document sender, cms.businesslogic.DocumentCacheEventArgs e);
  543. public delegate void RefreshContentEventHandler(Document sender, cms.businesslogic.RefreshContentEventArgs e);
  544. /// <summary>
  545. /// Occurs when [before document cache update].
  546. /// </summary>
  547. public static event DocumentCacheEventHandler BeforeUpdateDocumentCache;
  548. /// <summary>
  549. /// Fires the before document cache.
  550. /// </summary>
  551. /// <param name="sender">The sender.</param>
  552. /// <param name="e">The <see cref="umbraco.cms.businesslogic.DocumentCacheEventArgs"/> instance containing the event data.</param>
  553. protected virtual void FireBeforeUpdateDocumentCache(Document sender, cms.businesslogic.DocumentCacheEventArgs e)
  554. {
  555. if (BeforeUpdateDocumentCache != null)
  556. {
  557. BeforeUpdateDocumentCache(sender, e);
  558. }
  559. }
  560. /// <summary>
  561. /// Occurs when [after document cache update].
  562. /// </summary>
  563. public static event DocumentCacheEventHandler AfterUpdateDocumentCache;
  564. /// <summary>
  565. /// Fires after document cache updater.
  566. /// </summary>
  567. /// <param name="sender">The sender.</param>
  568. /// <param name="e">The <see cref="umbraco.cms.businesslogic.DocumentCacheEventArgs"/> instance containing the event data.</param>
  569. protected virtual void FireAfterUpdateDocumentCache(Document sender, cms.businesslogic.DocumentCacheEventArgs e)
  570. {
  571. if (AfterUpdateDocumentCache != null)
  572. {
  573. AfterUpdateDocumentCache(sender, e);
  574. }
  575. }
  576. /// <summary>
  577. /// Occurs when [before document cache unpublish].
  578. /// </summary>
  579. public static event DocumentCacheEventHandler BeforeClearDocumentCache;
  580. /// <summary>
  581. /// Fires the before document cache unpublish.
  582. /// </summary>
  583. /// <param name="sender">The sender.</param>
  584. /// <param name="e">The <see cref="umbraco.cms.businesslogic.DocumentCacheEventArgs"/> instance containing the event data.</param>
  585. protected virtual void FireBeforeClearDocumentCache(Document sender, cms.businesslogic.DocumentCacheEventArgs e)
  586. {
  587. if (BeforeClearDocumentCache != null)
  588. {
  589. BeforeClearDocumentCache(sender, e);
  590. }
  591. }
  592. public static event DocumentCacheEventHandler AfterClearDocumentCache;
  593. /// <summary>
  594. /// Fires the after document cache unpublish.
  595. /// </summary>
  596. /// <param name="sender">The sender.</param>
  597. /// <param name="e">The <see cref="umbraco.cms.businesslogic.DocumentCacheEventArgs"/> instance containing the event data.</param>
  598. protected virtual void FireAfterClearDocumentCache(Document sender, cms.businesslogic.DocumentCacheEventArgs e)
  599. {
  600. if (AfterClearDocumentCache != null)
  601. {
  602. AfterClearDocumentCache(sender, e);
  603. }
  604. }
  605. /// <summary>
  606. /// Occurs when [before refresh content].
  607. /// </summary>
  608. public static event RefreshContentEventHandler BeforeRefreshContent;
  609. /// <summary>
  610. /// Fires the content of the before refresh.
  611. /// </summary>
  612. /// <param name="sender">The sender.</param>
  613. /// <param name="e">The <see cref="umbraco.cms.businesslogic.RefreshContentEventArgs"/> instance containing the event data.</param>
  614. protected virtual void FireBeforeRefreshContent(cms.businesslogic.RefreshContentEventArgs e)
  615. {
  616. if (BeforeRefreshContent != null)
  617. {
  618. BeforeRefreshContent(null, e);
  619. }
  620. }
  621. /// <summary>
  622. /// Occurs when [after refresh content].
  623. /// </summary>
  624. public static event RefreshContentEventHandler AfterRefreshContent;
  625. /// <summary>
  626. /// Fires the content of the after refresh.
  627. /// </summary>
  628. /// <param name="sender">The sender.</param>
  629. /// <param name="e">The <see cref="umbraco.cms.businesslogic.RefreshContentEventArgs"/> instance containing the event data.</param>
  630. protected virtual void FireAfterRefreshContent(cms.businesslogic.RefreshContentEventArgs e)
  631. {
  632. if (AfterRefreshContent != null)
  633. {
  634. AfterRefreshContent(null, e);
  635. }
  636. }
  637. #endregion
  638. #region Protected & Private methods
  639. /// <summary>
  640. /// Invalidates the disk content cache file. Effectively just deletes it.
  641. /// </summary>
  642. private void DeleteXmlCache()
  643. {
  644. lock (_readerWriterSyncLock)
  645. {
  646. if (File.Exists(UmbracoXmlDiskCacheFileName))
  647. {
  648. // Reset file attributes, to make sure we can delete file
  649. try
  650. {
  651. File.SetAttributes(UmbracoXmlDiskCacheFileName, FileAttributes.Normal);
  652. }
  653. finally
  654. {
  655. File.Delete(UmbracoXmlDiskCacheFileName);
  656. }
  657. }
  658. }
  659. }
  660. /// <summary>
  661. /// Clear HTTPContext cache if any
  662. /// </summary>
  663. private void ClearContextCache()
  664. {
  665. // If running in a context very important to reset context cache orelse new nodes are missing
  666. if (HttpContext.Current != null && HttpContext.Current.Items.Contains(XmlContextContentItemKey))
  667. HttpContext.Current.Items.Remove(XmlContextContentItemKey);
  668. }
  669. /// <summary>
  670. /// Invalidates the disk content cache file. Effectively just deletes it.
  671. /// </summary>
  672. [Obsolete("This method is obsolete in version 4.1 and above, please use DeleteXmlCache", true)]
  673. private void ClearDiskCacheAsync()
  674. {
  675. // Queue file deletion
  676. // We queue this function, because there can be a write process running at the same time
  677. // and we don't want this method to block web request
  678. ThreadPool.QueueUserWorkItem(
  679. delegate { DeleteXmlCache(); });
  680. }
  681. /// <summary>
  682. /// Load content from either disk or database
  683. /// </summary>
  684. /// <returns></returns>
  685. private XmlDocument LoadContent()
  686. {
  687. if (!UmbracoSettings.isXmlContentCacheDisabled && IsValidDiskCachePresent())
  688. {
  689. try
  690. {
  691. return LoadContentFromDiskCache();
  692. }
  693. catch (Exception e)
  694. {
  695. // This is really bad, loading from cache file failed for some reason, now fallback to loading from database
  696. Debug.WriteLine("Content file cache load failed: " + e);
  697. DeleteXmlCache();
  698. }
  699. }
  700. return LoadContentFromDatabase();
  701. }
  702. private bool IsValidDiskCachePresent()
  703. {
  704. if (File.Exists(UmbracoXmlDiskCacheFileName))
  705. {
  706. // Only return true if we don't have a zero-byte file
  707. var f = new FileInfo(UmbracoXmlDiskCacheFileName);
  708. if (f.Length > 0)
  709. return true;
  710. }
  711. return false;
  712. }
  713. /// <summary>
  714. /// Load content from cache file
  715. /// </summary>
  716. private XmlDocument LoadContentFromDiskCache()
  717. {
  718. lock (_readerWriterSyncLock)
  719. {
  720. XmlDocument xmlDoc = new XmlDocument();
  721. Log.Add(LogTypes.System, User.GetUser(0), -1, "Loading content from disk cache...");
  722. xmlDoc.Load(UmbracoXmlDiskCacheFileName);
  723. return xmlDoc;
  724. }
  725. }
  726. private static void InitContentDocument(XmlDocument xmlDoc, string dtd)
  727. {
  728. // Prime the xml document with an inline dtd and a root element
  729. xmlDoc.LoadXml(String.Format("<?xml version=\"1.0\" encoding=\"utf-8\" ?>{0}{1}{0}<root id=\"-1\"/>",
  730. Environment.NewLine,
  731. dtd));
  732. }
  733. /// <summary>
  734. /// Load content from database
  735. /// </summary>
  736. private XmlDocument LoadContentFromDatabase()
  737. {
  738. // Alex N - 2010 06 - Very generic try-catch simply because at the moment, unfortunately, this method gets called inside a ThreadPool thread
  739. // and we need to guarantee it won't tear down the app pool by throwing an unhandled exception
  740. try
  741. {
  742. // Moved User to a local variable - why are we causing user 0 to load from the DB though?
  743. // Alex N 20100212
  744. User staticUser = null;
  745. try
  746. {
  747. staticUser = User.GetCurrent(); //User.GetUser(0);
  748. }
  749. catch
  750. {
  751. /* We don't care later if the staticUser is null */
  752. }
  753. // Try to log to the DB
  754. Log.Add(LogTypes.System, staticUser, -1, "Loading content from database...");
  755. var hierarchy = new Dictionary<int, List<int>>();
  756. var nodeIndex = new Dictionary<int, XmlNode>();
  757. try
  758. {
  759. Log.Add(LogTypes.Debug, staticUser, -1, "Republishing starting");
  760. // Lets cache the DTD to save on the DB hit on the subsequent use
  761. var dtd = DocumentType.GenerateDtd();
  762. // Prepare an XmlDocument with an appropriate inline DTD to match
  763. // the expected content
  764. var xmlDoc = new XmlDocument();
  765. InitContentDocument(xmlDoc, dtd);
  766. // Esben Carlsen: At some point we really need to put all data access into to a tier of its own.
  767. string sql =
  768. @"select umbracoNode.id, umbracoNode.parentId, umbracoNode.sortOrder, cmsContentXml.xml from umbracoNode
  769. inner join cmsContentXml on cmsContentXml.nodeId = umbracoNode.id and umbracoNode.nodeObjectType = @type
  770. order by umbracoNode.level, umbracoNode.sortOrder";
  771. lock (_dbReadSyncLock)
  772. {
  773. using (IRecordsReader dr = SqlHelper.ExecuteReader(sql, SqlHelper.CreateParameter("@type", new Guid("C66BA18E-EAF3-4CFF-8A22-41B16D66A972"))))
  774. {
  775. while (dr.Read())
  776. {
  777. int currentId = dr.GetInt("id");
  778. int parentId = dr.GetInt("parentId");
  779. // Retrieve the xml content from the database
  780. // and parse it into a DOM node
  781. xmlDoc.LoadXml(dr.GetString("xml"));
  782. nodeIndex.Add(currentId, xmlDoc.FirstChild);
  783. // Build the content hierarchy
  784. List<int> children;
  785. if (!hierarchy.TryGetValue(parentId, out children))
  786. {
  787. // No children for this parent, so add one
  788. children = new List<int>();
  789. hierarchy.Add(parentId, children);
  790. }
  791. children.Add(currentId);
  792. }
  793. }
  794. }
  795. Log.Add(LogTypes.Debug, staticUser, -1, "Xml Pages loaded");
  796. try
  797. {
  798. // If we got to here we must have successfully retrieved the content from the DB so
  799. // we can safely initialise and compose the final content DOM.
  800. // Note: We are reusing the XmlDocument used to create the xml nodes above so
  801. // we don't have to import them into a new XmlDocument
  802. // Initialise the document ready for the final composition of content
  803. InitContentDocument(xmlDoc, dtd);
  804. // Start building the content tree recursively from the root (-1) node
  805. GenerateXmlDocument(hierarchy, nodeIndex, -1, xmlDoc.DocumentElement);
  806. Log.Add(LogTypes.Debug, staticUser, -1, "Done republishing Xml Index");
  807. return xmlDoc;
  808. }
  809. catch (Exception ee)
  810. {
  811. Log.Add(LogTypes.Error, staticUser, -1,
  812. string.Format("Error while generating XmlDocument from database: {0}", ee));
  813. }
  814. }
  815. catch (OutOfMemoryException)
  816. {
  817. Log.Add(LogTypes.Error, staticUser, -1,
  818. string.Format("Error Republishing: Out Of Memory. Parents: {0}, Nodes: {1}",
  819. hierarchy.Count, nodeIndex.Count));
  820. }
  821. catch (Exception ee)
  822. {
  823. Log.Add(LogTypes.Error, staticUser, -1, string.Format("Error Republishing: {0}", ee));
  824. }
  825. }
  826. catch (Exception ee)
  827. {
  828. Log.Add(LogTypes.Error, -1, string.Format("Error Republishing: {0}", ee));
  829. }
  830. // An error of some sort must have stopped us from successfully generating
  831. // the content tree, so lets return null signifying there is no content available
  832. return null;
  833. }
  834. private static void GenerateXmlDocument(IDictionary<int, List<int>> hierarchy, IDictionary<int, XmlNode> nodeIndex, int parentId, XmlNode parentNode)
  835. {
  836. List<int> children;
  837. if (hierarchy.TryGetValue(parentId, out children))
  838. {
  839. XmlNode childContainer = UmbracoSettings.UseLegacyXmlSchema || String.IsNullOrEmpty(UmbracoSettings.TEMP_FRIENDLY_XML_CHILD_CONTAINER_NODENAME)
  840. ? parentNode
  841. : parentNode.SelectSingleNode(UmbracoSettings.TEMP_FRIENDLY_XML_CHILD_CONTAINER_NODENAME);
  842. if (!UmbracoSettings.UseLegacyXmlSchema && !String.IsNullOrEmpty(UmbracoSettings.TEMP_FRIENDLY_XML_CHILD_CONTAINER_NODENAME))
  843. {
  844. if (childContainer == null)
  845. {
  846. childContainer = xmlHelper.addTextNode(parentNode.OwnerDocument, UmbracoSettings.TEMP_FRIENDLY_XML_CHILD_CONTAINER_NODENAME, "");
  847. parentNode.AppendChild(childContainer);
  848. }
  849. }
  850. foreach (int childId in children)
  851. {
  852. var childNode = nodeIndex[childId];
  853. if (UmbracoSettings.UseLegacyXmlSchema || String.IsNullOrEmpty(UmbracoSettings.TEMP_FRIENDLY_XML_CHILD_CONTAINER_NODENAME))
  854. {
  855. parentNode.AppendChild(childNode);
  856. }
  857. else
  858. {
  859. childContainer.AppendChild(childNode);
  860. }
  861. // Recursively build the content tree under the current child
  862. GenerateXmlDocument(hierarchy, nodeIndex, childId, childNode);
  863. }
  864. }
  865. }
  866. public void PersistXmlToFile()
  867. {
  868. PersistXmlToFile(_xmlContent);
  869. }
  870. /// <summary>
  871. /// Persist a XmlDocument to the Disk Cache
  872. /// </summary>
  873. /// <param name="xmlDoc"></param>
  874. internal void PersistXmlToFile(XmlDocument xmlDoc)
  875. {
  876. lock (_readerWriterSyncLock)
  877. {
  878. if (xmlDoc != null)
  879. {
  880. Trace.Write(string.Format("Saving content to disk on thread '{0}' (Threadpool? {1})", Thread.CurrentThread.Name, Thread.CurrentThread.IsThreadPoolThread.ToString()));
  881. // Moved the user into a variable and avoided it throwing an error if one can't be loaded (e.g. empty / corrupt db on initial install)
  882. User staticUser = null;
  883. try
  884. {
  885. staticUser = User.GetCurrent();
  886. }
  887. catch
  888. { }
  889. try
  890. {
  891. Stopwatch stopWatch = Stopwatch.StartNew();
  892. DeleteXmlCache();
  893. // Try to create directory for cache path if it doesn't yet exist
  894. if (!File.Exists(UmbracoXmlDiskCacheFileName) && !Directory.Exists(Path.GetDirectoryName(UmbracoXmlDiskCacheFileName)))
  895. {
  896. // We're already in a try-catch and saving will fail if this does, so don't need another
  897. Directory.CreateDirectory(UmbracoXmlDiskCacheFileName);
  898. }
  899. xmlDoc.Save(UmbracoXmlDiskCacheFileName);
  900. Trace.Write(string.Format("Saved content on thread '{0}' in {1} (Threadpool? {2})", Thread.CurrentThread.Name, stopWatch.Elapsed, Thread.CurrentThread.IsThreadPoolThread.ToString()));
  901. Log.Add(LogTypes.Debug, staticUser, -1, string.Format("Xml saved in {0}", stopWatch.Elapsed));
  902. }
  903. catch (Exception ee)
  904. {
  905. // If for whatever reason something goes wrong here, invalidate disk cache
  906. DeleteXmlCache();
  907. Trace.Write(string.Format("Error saving content on thread '{0}' due to '{1}' (Threadpool? {2})", Thread.CurrentThread.Name, ee.Message, Thread.CurrentThread.IsThreadPoolThread.ToString()));
  908. Log.Add(LogTypes.Error, staticUser, -1, string.Format("Xml wasn't saved: {0}", ee));
  909. }
  910. }
  911. }
  912. }
  913. internal const string PersistenceFlagContextKey = "vnc38ykjnkjdnk2jt98ygkxjng";
  914. /// <summary>
  915. /// Marks a flag in the HttpContext so that, upon page execution completion, the Xml cache will
  916. /// get persisted to disk. Ensure this method is only called from a thread executing a page request
  917. /// since umbraco.presentation.requestModule is the only monitor of this flag and is responsible
  918. /// for enacting the persistence at the PostRequestHandlerExecute stage of the page lifecycle.
  919. /// </summary>
  920. private void QueueXmlForPersistence()
  921. {
  922. /* Alex Norcliffe 2010 06 03 - removing all launching of ThreadPool threads, instead we just
  923. * flag on the context that the Xml should be saved and an event in the requestModule
  924. * will check for this and call PersistXmlToFile() if necessary */
  925. if (HttpContext.Current != null)
  926. {
  927. HttpContext.Current.Application.Lock();
  928. if (HttpContext.Current.Application[PersistenceFlagContextKey] != null)
  929. HttpContext.Current.Application.Add(PersistenceFlagContextKey, null);
  930. HttpContext.Current.Application[PersistenceFlagContextKey] = DateTime.Now;
  931. HttpContext.Current.Application.UnLock();
  932. }
  933. else
  934. {
  935. //// Save copy of content
  936. if (UmbracoSettings.CloneXmlCacheOnPublish)
  937. {
  938. XmlDocument xmlContentCopy = CloneXmlDoc(_xmlContent);
  939. ThreadPool.QueueUserWorkItem(
  940. delegate { PersistXmlToFile(xmlContentCopy); });
  941. }
  942. else
  943. ThreadPool.QueueUserWorkItem(
  944. delegate { PersistXmlToFile(); });
  945. }
  946. }
  947. internal bool IsXmlQueuedForPersistenceToFile
  948. {
  949. get
  950. {
  951. if (HttpContext.Current != null)
  952. {
  953. var val = HttpContext.Current.Application[PersistenceFlagContextKey] != null;
  954. if (val != false)
  955. {
  956. DateTime persistenceTime = DateTime.MinValue;
  957. try
  958. {
  959. persistenceTime = (DateTime)HttpContext.Current.Application[PersistenceFlagContextKey];
  960. if (persistenceTime > GetCacheFileUpdateTime())
  961. {
  962. return true;
  963. }
  964. else
  965. {
  966. HttpContext.Current.Application.Lock();
  967. HttpContext.Current.Application[PersistenceFlagContextKey] = null;
  968. HttpContext.Current.Application.UnLock();
  969. }
  970. }
  971. catch
  972. {
  973. // Nothing to catch here - we'll just persist
  974. }
  975. }
  976. }
  977. return false;
  978. }
  979. }
  980. internal DateTime GetCacheFileUpdateTime()
  981. {
  982. if (System.IO.File.Exists(UmbracoXmlDiskCacheFileName))
  983. {
  984. return new FileInfo(UmbracoXmlDiskCacheFileName).LastWriteTime;
  985. }
  986. return DateTime.MinValue;
  987. }
  988. /// <summary>
  989. /// Make a copy of a XmlDocument
  990. /// </summary>
  991. /// <param name="xmlDoc"></param>
  992. /// <returns></returns>
  993. private static XmlDocument CloneXmlDoc(XmlDocument xmlDoc)
  994. {
  995. if (xmlDoc == null) return null;
  996. Log.Add(LogTypes.Debug, -1, "Cloning...");
  997. // Save copy of content
  998. XmlDocument xmlCopy = new XmlDocument();
  999. xmlCopy.LoadXml(xmlDoc.OuterXml);
  1000. Log.Add(LogTypes.Debug, -1, "Cloning ended...");
  1001. return xmlCopy;
  1002. }
  1003. #endregion
  1004. }
  1005. }