working on U4-6030

This commit is contained in:
Shannon
2014-12-18 14:58:49 +11:00
parent e643f2a0ba
commit aa439cacda
2 changed files with 307 additions and 110 deletions

View File

@@ -9,6 +9,7 @@ using Umbraco.Core.Cache;
using Umbraco.Core.IO;
using Umbraco.Core.Events;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using umbraco.DataLayer;
namespace umbraco.BusinessLogic
@@ -24,6 +25,23 @@ namespace umbraco.BusinessLogic
internal const string TreeConfigFileName = "trees.config";
private static string _treeConfig;
private static readonly object Locker = new object();
private static volatile bool _isInitialized = false;
private static IEnumerable<ApplicationTree> _allAvailableTrees;
/// <summary>
/// Initializes the service with any trees found in plugins
/// </summary>
/// <param name="allAvailableTrees">
/// A collection of all available tree found in assemblies in the application
/// </param>
/// <remarks>
/// This will update the trees.config with the found tree plugins that are not currently listed in the file when the first
/// access is made to resolve the tree collection
/// </remarks>
internal static void Intitialize(IEnumerable<ApplicationTree> allAvailableTrees)
{
_allAvailableTrees = allAvailableTrees;
}
/// <summary>
/// gets/sets the trees.config file path
@@ -45,58 +63,136 @@ namespace umbraco.BusinessLogic
}
/// <summary>
/// The cache storage for all application trees
/// The main entry point to get application trees
/// </summary>
private static List<ApplicationTree> AppTrees
/// <remarks>
/// This lazily on first access will scan for plugin trees and ensure the trees.config is up-to-date with the plugins. If plugins
/// haven't changed on disk then the file will not be saved. The trees are all then loaded from this config file into cache and returned.
/// </remarks>
private static List<ApplicationTree> GetAppTrees()
{
get
{
return ApplicationContext.Current.ApplicationCache.GetCacheItem(
CacheKeys.ApplicationTreeCacheKey,
() =>
return ApplicationContext.Current.ApplicationCache.GetCacheItem<List<ApplicationTree>>(
CacheKeys.ApplicationTreeCacheKey,
() =>
{
var list = ReadFromXmlAndSort();
//On first access we need to do some initialization
if (_isInitialized == false)
{
lock (Locker)
{
var list = new List<ApplicationTree>();
LoadXml(doc =>
if (_isInitialized == false)
{
foreach (var addElement in doc.Root.Elements("add").OrderBy(x =>
{
var sortOrderAttr = x.Attribute("sortOrder");
return sortOrderAttr != null ? Convert.ToInt32(sortOrderAttr.Value) : 0;
}))
//now we can check the non-volatile flag
if (_allAvailableTrees != null)
{
var hasChanges = false;
var applicationAlias = (string)addElement.Attribute("application");
var type = (string)addElement.Attribute("type");
var assembly = (string)addElement.Attribute("assembly");
//check if the tree definition (applicationAlias + type + assembly) is already in the list
if (!list.Any(tree => tree.ApplicationAlias.InvariantEquals(applicationAlias)
&& tree.Type.InvariantEquals(type)
&& tree.AssemblyName.InvariantEquals(assembly)))
LoadXml(doc =>
{
list.Add(new ApplicationTree(
addElement.Attribute("silent") != null ? Convert.ToBoolean(addElement.Attribute("silent").Value) : false,
addElement.Attribute("initialize") != null ? Convert.ToBoolean(addElement.Attribute("initialize").Value) : true,
addElement.Attribute("sortOrder") != null ? Convert.ToByte(addElement.Attribute("sortOrder").Value) : (byte)0,
addElement.Attribute("application").Value,
addElement.Attribute("alias").Value,
addElement.Attribute("title").Value,
addElement.Attribute("iconClosed").Value,
addElement.Attribute("iconOpen").Value,
(string)addElement.Attribute("assembly"), //this could be empty: http://issues.umbraco.org/issue/U4-1360
addElement.Attribute("type").Value,
addElement.Attribute("action") != null ? addElement.Attribute("action").Value : ""));
//Now, load in the xml structure and update it with anything that is not declared there and save the file.
//NOTE: On the first iteration here, it will lazily scan all trees, etc... this is because this ienumerable is lazy
// based on the ApplicationTreeRegistrar - and as noted there this is not an ideal way to do things but were stuck like this
// currently because of the legacy assemblies and types not in the Core.
//Get all the trees not registered in the config
var unregistered = _allAvailableTrees
.Where(x => list.Any(l => l.Alias == x.Alias) == false)
.ToArray();
hasChanges = unregistered.Any();
if (hasChanges == false) return false;
//add the unregistered ones to the list and re-save the file if any changes were found
var count = 0;
foreach (var tree in unregistered)
{
doc.Root.Add(new XElement("add",
new XAttribute("initialize", tree.Initialize),
new XAttribute("sortOrder", tree.SortOrder),
new XAttribute("alias", tree.Alias),
new XAttribute("application", tree.ApplicationAlias),
new XAttribute("title", tree.Title),
new XAttribute("iconClosed", tree.IconClosed),
new XAttribute("iconOpen", tree.IconOpened),
new XAttribute("type", tree.Type)));
count++;
}
//don't save if there's no changes
return count > 0;
}, true);
if (hasChanges)
{
//If there were changes, we need to re-read the structures from the XML
list = ReadFromXmlAndSort();
}
}
}, false);
return list;
});
}
_isInitialized = true;
}
}
}
return list;
});
}
private static List<ApplicationTree> ReadFromXmlAndSort()
{
var list = new List<ApplicationTree>();
//read in the xml file containing trees and convert them all to ApplicationTree instances
LoadXml(doc =>
{
foreach (var addElement in doc.Root.Elements("add").OrderBy(x =>
{
var sortOrderAttr = x.Attribute("sortOrder");
return sortOrderAttr != null ? Convert.ToInt32(sortOrderAttr.Value) : 0;
}))
{
var applicationAlias = (string)addElement.Attribute("application");
var type = (string)addElement.Attribute("type");
var assembly = (string)addElement.Attribute("assembly");
var clrType = System.Type.GetType(type);
if (clrType == null)
{
LogHelper.Warn(typeof(ApplicationTree), "The tree definition: " + addElement.ToString() + " could not be resolved to a .Net object type");
continue;
}
//check if the tree definition (applicationAlias + type + assembly) is already in the list
if (list.Any(tree => tree.ApplicationAlias.InvariantEquals(applicationAlias) && tree.GetRuntimeType() == clrType) == false)
{
list.Add(new ApplicationTree(
addElement.Attribute("silent") != null && Convert.ToBoolean(addElement.Attribute("silent").Value),
addElement.Attribute("initialize") == null || Convert.ToBoolean(addElement.Attribute("initialize").Value),
addElement.Attribute("sortOrder") != null ? Convert.ToByte(addElement.Attribute("sortOrder").Value) : (byte)0,
addElement.Attribute("application").Value,
addElement.Attribute("alias").Value,
addElement.Attribute("title").Value,
addElement.Attribute("iconClosed").Value,
addElement.Attribute("iconOpen").Value,
(string)addElement.Attribute("assembly"), //this could be empty: http://issues.umbraco.org/issue/U4-1360
addElement.Attribute("type").Value,
addElement.Attribute("action") != null ? addElement.Attribute("action").Value : ""));
}
}
return false;
}, false);
return list;
}
/// <summary>
@@ -256,6 +352,9 @@ namespace umbraco.BusinessLogic
new XAttribute("type", type),
new XAttribute("action", string.IsNullOrEmpty(action) ? "" : action)));
}
return true;
}, true);
OnNew(new ApplicationTree(silent, initialize, sortOrder, applicationAlias, alias, title, iconClosed, iconOpened, assemblyName, type, action), new EventArgs());
@@ -287,6 +386,8 @@ namespace umbraco.BusinessLogic
el.Add(new XAttribute("action", string.IsNullOrEmpty(this.Action) ? "" : this.Action));
}
return true;
}, true);
OnUpdated(this, new EventArgs());
@@ -304,6 +405,9 @@ namespace umbraco.BusinessLogic
{
doc.Root.Elements("add").Where(x => x.Attribute("application") != null && x.Attribute("application").Value == this.ApplicationAlias &&
x.Attribute("alias") != null && x.Attribute("alias").Value == this.Alias).Remove();
return true;
}, true);
OnDeleted(this, new EventArgs());
@@ -317,7 +421,7 @@ namespace umbraco.BusinessLogic
/// <returns>An ApplicationTree instance</returns>
public static ApplicationTree getByAlias(string treeAlias)
{
return AppTrees.Find(t => (t.Alias == treeAlias));
return GetAppTrees().Find(t => (t.Alias == treeAlias));
}
@@ -327,7 +431,7 @@ namespace umbraco.BusinessLogic
/// <returns>Returns a ApplicationTree Array</returns>
public static ApplicationTree[] getAll()
{
return AppTrees.OrderBy(x => x.SortOrder).ToArray();
return GetAppTrees().OrderBy(x => x.SortOrder).ToArray();
}
/// <summary>
@@ -348,7 +452,7 @@ namespace umbraco.BusinessLogic
/// <returns>Returns a ApplicationTree Array</returns>
public static ApplicationTree[] getApplicationTree(string applicationAlias, bool onlyInitializedApplications)
{
var list = AppTrees.FindAll(
var list = GetAppTrees().FindAll(
t =>
{
if (onlyInitializedApplications)
@@ -360,21 +464,34 @@ namespace umbraco.BusinessLogic
return list.OrderBy(x => x.SortOrder).ToArray();
}
internal static void LoadXml(Action<XDocument> callback, bool saveAfterCallback)
/// <summary>
/// Loads in the xml structure from disk if one is found, otherwise loads in an empty xml structure, calls the
/// callback with the xml document and saves the structure back to disk if saveAfterCallback is true.
/// </summary>
/// <param name="callback"></param>
/// <param name="saveAfterCallbackIfChanges"></param>
internal static void LoadXml(Func<XDocument, bool> callback, bool saveAfterCallbackIfChanges)
{
lock (Locker)
{
var doc = File.Exists(TreeConfigFilePath)
? XDocument.Load(TreeConfigFilePath)
: XDocument.Parse("<?xml version=\"1.0\"?><trees />");
if (doc.Root != null)
{
callback.Invoke(doc);
var hasChanges = callback.Invoke(doc);
if (saveAfterCallback)
if (saveAfterCallbackIfChanges && hasChanges
//Don't save it if it is empty, in some very rare cases if the app domain get's killed in the middle of this process
// in some insane way the file saved will be empty. I'm pretty sure it's not actually anything to do with the xml doc and
// more about the IO trying to save the XML doc, but it doesn't hurt to check.
&& doc.Root != null && doc.Root.Elements().Any())
{
//ensures the folder exists
Directory.CreateDirectory(Path.GetDirectoryName(TreeConfigFilePath));
//saves it
doc.Save(TreeConfigFilePath);
//remove the cache now that it has changed SD: I'm leaving this here even though it

View File

@@ -1,4 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Umbraco.Core;
@@ -9,81 +11,159 @@ using umbraco.interfaces;
namespace umbraco.BusinessLogic
{
public class ApplicationTreeRegistrar : IApplicationStartupHandler
public class ApplicationTreeRegistrar : ApplicationEventHandler
{
public ApplicationTreeRegistrar()
protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
//don't do anything if the application or database is not configured!
if (ApplicationContext.Current == null
|| !ApplicationContext.Current.IsConfigured
|| !ApplicationContext.Current.DatabaseContext.IsDatabaseConfigured)
return;
//Call initialize on the tree service with the lazy enumerable class below, when the tree service needs to resolve,
// it will lazily do the scanning and comparing so it's not actually done on app start.
ApplicationTree.Intitialize(new LazyEnumerableTrees());
}
// Load all Trees by attribute and add them to the XML config
var types = PluginManager.Current.ResolveAttributedTrees();
//public ApplicationTreeRegistrar()
//{
// //don't do anything if the application or database is not configured!
// if (ApplicationContext.Current == null
// || !ApplicationContext.Current.IsConfigured
// || !ApplicationContext.Current.DatabaseContext.IsDatabaseConfigured)
// return;
var items = types
.Select(x =>
new Tuple<Type, TreeAttribute>(x, x.GetCustomAttributes<TreeAttribute>(false).Single()))
.Where(x => ApplicationTree.getByAlias(x.Item2.Alias) == null);
// // Load all Trees by attribute and add them to the XML config
// var types = PluginManager.Current.ResolveAttributedTrees();
var allAliases = ApplicationTree.getAll().Select(x => x.Alias).Concat(items.Select(x => x.Item2.Alias));
var inString = "'" + string.Join("','", allAliases) + "'";
// var items = types
// .Select(x =>
// new Tuple<Type, TreeAttribute>(x, x.GetCustomAttributes<TreeAttribute>(false).Single()))
// .Where(x => ApplicationTree.getByAlias(x.Item2.Alias) == null);
ApplicationTree.LoadXml(doc =>
// var allAliases = ApplicationTree.getAll().Select(x => x.Alias).Concat(items.Select(x => x.Item2.Alias));
// var inString = "'" + string.Join("','", allAliases) + "'";
// ApplicationTree.LoadXml(doc =>
// {
// foreach (var tuple in items)
// {
// var type = tuple.Item1;
// var attr = tuple.Item2;
// //Add the new tree that doesn't exist in the config that was found by type finding
// doc.Root.Add(new XElement("add",
// new XAttribute("silent", attr.Silent),
// new XAttribute("initialize", attr.Initialize),
// new XAttribute("sortOrder", attr.SortOrder),
// new XAttribute("alias", attr.Alias),
// new XAttribute("application", attr.ApplicationAlias),
// new XAttribute("title", attr.Title),
// new XAttribute("iconClosed", attr.IconClosed),
// new XAttribute("iconOpen", attr.IconOpen),
// // don't add the assembly, we don't need this:
// // http://issues.umbraco.org/issue/U4-1360
// //new XAttribute("assembly", assemblyName),
// //new XAttribute("type", typeName),
// // instead, store the assembly type name
// new XAttribute("type", type.GetFullNameWithAssembly()),
// new XAttribute("action", attr.Action)));
// }
// //add any trees that were found in the database that don't exist in the config
// var db = ApplicationContext.Current.DatabaseContext.Database;
// var exist = db.TableExist("umbracoAppTree");
// if (exist)
// {
// var appTrees = db.Fetch<AppTreeDto>("WHERE treeAlias NOT IN (" + inString + ")");
// foreach (var appTree in appTrees)
// {
// var action = appTree.Action;
// doc.Root.Add(new XElement("add",
// new XAttribute("silent", appTree.Silent),
// new XAttribute("initialize", appTree.Initialize),
// new XAttribute("sortOrder", appTree.SortOrder),
// new XAttribute("alias", appTree.Alias),
// new XAttribute("application", appTree.AppAlias),
// new XAttribute("title", appTree.Title),
// new XAttribute("iconClosed", appTree.IconClosed),
// new XAttribute("iconOpen", appTree.IconOpen),
// new XAttribute("assembly", appTree.HandlerAssembly),
// new XAttribute("type", appTree.HandlerType),
// new XAttribute("action", string.IsNullOrEmpty(action) ? "" : action)));
// }
// }
// }, true);
//}
/// <summary>
/// This class is here so that we can provide lazy access to tree scanning for when it is needed
/// </summary>
private class LazyEnumerableTrees : IEnumerable<ApplicationTree>
{
public LazyEnumerableTrees()
{
foreach (var tuple in items)
_lazyTrees = new Lazy<IEnumerable<ApplicationTree>>(() =>
{
var type = tuple.Item1;
var attr = tuple.Item2;
//Add the new tree that doesn't exist in the config that was found by type finding
var added = new List<string>();
doc.Root.Add(new XElement("add",
new XAttribute("silent", attr.Silent),
new XAttribute("initialize", attr.Initialize),
new XAttribute("sortOrder", attr.SortOrder),
new XAttribute("alias", attr.Alias),
new XAttribute("application", attr.ApplicationAlias),
new XAttribute("title", attr.Title),
new XAttribute("iconClosed", attr.IconClosed),
new XAttribute("iconOpen", attr.IconOpen),
// don't add the assembly, we don't need this:
// http://issues.umbraco.org/issue/U4-1360
//new XAttribute("assembly", assemblyName),
//new XAttribute("type", typeName),
// instead, store the assembly type name
new XAttribute("type", type.GetFullNameWithAssembly()),
new XAttribute("action", attr.Action)));
}
// Load all Controller Trees by attribute
var types = PluginManager.Current.ResolveAttributedTrees();
//convert them to ApplicationTree instances
var items = types
.Select(x =>
new Tuple<Type, TreeAttribute>(x, x.GetCustomAttributes<TreeAttribute>(false).Single()))
.Select(x => new ApplicationTree(
x.Item2.Silent, x.Item2.Initialize, (byte) x.Item2.SortOrder, x.Item2.ApplicationAlias, x.Item2.Alias, x.Item2.Title, x.Item2.IconClosed, x.Item2.IconOpen,
"",
x.Item1.GetFullNameWithAssembly(),
x.Item2.Action))
.ToArray();
//add any trees that were found in the database that don't exist in the config
added.AddRange(items.Select(x => x.Alias));
var db = ApplicationContext.Current.DatabaseContext.Database;
var exist = db.TableExist("umbracoAppTree");
if (exist)
{
var appTrees = db.Fetch<AppTreeDto>("WHERE treeAlias NOT IN (" + inString + ")");
foreach (var appTree in appTrees)
{
var action = appTree.Action;
//find the legacy trees
var legacyTreeTypes = PluginManager.Current.ResolveAttributedTrees();
//convert them to ApplicationTree instances
var legacyItems = legacyTreeTypes
.Select(x =>
new Tuple<Type, global::umbraco.businesslogic.TreeAttribute>(
x,
x.GetCustomAttributes<global::umbraco.businesslogic.TreeAttribute>(false).SingleOrDefault()))
.Where(x => x.Item2 != null)
//make sure the legacy tree isn't added on top of the controller tree!
.Where(x => added.InvariantContains(x.Item2.Alias) == false)
.Select(x => new ApplicationTree(x.Item2.Silent, x.Item2.Initialize, (byte) x.Item2.SortOrder, x.Item2.ApplicationAlias, x.Item2.Alias, x.Item2.Title, x.Item2.IconClosed, x.Item2.IconOpen,
"",
x.Item1.GetFullNameWithAssembly(),
x.Item2.Action));
doc.Root.Add(new XElement("add",
new XAttribute("silent", appTree.Silent),
new XAttribute("initialize", appTree.Initialize),
new XAttribute("sortOrder", appTree.SortOrder),
new XAttribute("alias", appTree.Alias),
new XAttribute("application", appTree.AppAlias),
new XAttribute("title", appTree.Title),
new XAttribute("iconClosed", appTree.IconClosed),
new XAttribute("iconOpen", appTree.IconOpen),
new XAttribute("assembly", appTree.HandlerAssembly),
new XAttribute("type", appTree.HandlerType),
new XAttribute("action", string.IsNullOrEmpty(action) ? "" : action)));
}
}
return items.Concat(legacyItems).ToArray();
});
}
}, true);
private readonly Lazy<IEnumerable<ApplicationTree>> _lazyTrees;
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>
/// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
/// </returns>
public IEnumerator<ApplicationTree> GetEnumerator()
{
return _lazyTrees.Value.GetEnumerator();
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
/// </returns>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
}