From ef91e152b4543d731448ee75eb54cb2af806e3a5 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 13 Sep 2017 11:50:37 +0200 Subject: [PATCH] U4-10409 - udi parsing and assembly scanning --- .../KnownTypeUdiJsonConverter.cs | 26 +++ .../Serialization/UdiJsonConverter.cs | 3 +- src/Umbraco.Core/Udi.cs | 171 ++++++++++++------ src/Umbraco.Core/Umbraco.Core.csproj | 1 + src/Umbraco.Tests/UdiTests.cs | 93 +++++++++- src/Umbraco.Web.UI/config/Dashboard.config | 10 + .../config/splashes/noNodes.aspx | 158 ++++++++++++---- src/Umbraco.Web.UI/config/trees.config | 1 + 8 files changed, 359 insertions(+), 104 deletions(-) create mode 100644 src/Umbraco.Core/Serialization/KnownTypeUdiJsonConverter.cs diff --git a/src/Umbraco.Core/Serialization/KnownTypeUdiJsonConverter.cs b/src/Umbraco.Core/Serialization/KnownTypeUdiJsonConverter.cs new file mode 100644 index 0000000000..e6473e7f8e --- /dev/null +++ b/src/Umbraco.Core/Serialization/KnownTypeUdiJsonConverter.cs @@ -0,0 +1,26 @@ +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Umbraco.Core.Serialization +{ + public class KnownTypeUdiJsonConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return typeof(Udi).IsAssignableFrom(objectType); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + writer.WriteValue(value.ToString()); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var jo = JToken.ReadFrom(reader); + var val = jo.ToObject(); + return val == null ? null : Udi.Parse(val, true); + } + } +} diff --git a/src/Umbraco.Core/Serialization/UdiJsonConverter.cs b/src/Umbraco.Core/Serialization/UdiJsonConverter.cs index ff62535825..f3dc678ce6 100644 --- a/src/Umbraco.Core/Serialization/UdiJsonConverter.cs +++ b/src/Umbraco.Core/Serialization/UdiJsonConverter.cs @@ -4,12 +4,11 @@ using Newtonsoft.Json.Linq; namespace Umbraco.Core.Serialization { - public class UdiJsonConverter : JsonConverter { public override bool CanConvert(Type objectType) { - return typeof(Udi).IsAssignableFrom(objectType); + return typeof (Udi).IsAssignableFrom(objectType); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) diff --git a/src/Umbraco.Core/Udi.cs b/src/Umbraco.Core/Udi.cs index 74e77e3fd2..a76c692661 100644 --- a/src/Umbraco.Core/Udi.cs +++ b/src/Umbraco.Core/Udi.cs @@ -22,7 +22,7 @@ namespace Umbraco.Core private static volatile bool _scanned; private static readonly object ScanLocker = new object(); - private static readonly Lazy> KnownUdiTypes; + private static ConcurrentDictionary _udiTypes; private static readonly ConcurrentDictionary RootUdis = new ConcurrentDictionary(); internal readonly Uri UriValue; // internal for UdiRange @@ -50,8 +50,15 @@ namespace Umbraco.Core static Udi() { // initialize with known (built-in) Udi types - // for non-known Udi types we'll try to parse a GUID and if that doesn't work, we'll decide that it's a string - KnownUdiTypes = new Lazy>(() => new ConcurrentDictionary(Constants.UdiEntityType.GetTypes())); + // we will add scanned types later on + _udiTypes = new ConcurrentDictionary(Constants.UdiEntityType.GetTypes()); + } + + // for tests, totally unsafe + internal static void ResetUdiTypes() + { + _udiTypes = new ConcurrentDictionary(Constants.UdiEntityType.GetTypes()); + _scanned = false; } /// @@ -79,46 +86,65 @@ namespace Umbraco.Core public static Udi Parse(string s) { Udi udi; - ParseInternal(s, false, out udi); + ParseInternal(s, false, false, out udi); return udi; } + /// + /// Converts the string representation of an entity identifier into the equivalent Udi instance. + /// + /// The string to convert. + /// A value indicating whether to only deal with known types. + /// An Udi instance that contains the value that was parsed. + /// + /// If is true, and the string could not be parsed because + /// the entity type was not known, the method succeeds but sets udito an + /// value. + /// If is true, assemblies are not scanned for types, + /// and therefore only builtin types may be known. Unless scanning already took place. + /// + public static Udi Parse(string s, bool knownTypes) + { + Udi udi; + ParseInternal(s, false, knownTypes, out udi); + return udi; + } + + /// + /// Converts the string representation of an entity identifier into the equivalent Udi instance. + /// + /// The string to convert. + /// An Udi instance that contains the value that was parsed. + /// A boolean value indicating whether the string could be parsed. public static bool TryParse(string s, out Udi udi) { - return ParseInternal(s, true, out udi); + return ParseInternal(s, true, false, out udi); } - private static UdiType GetUdiType(Uri uri, out string path) + /// + /// Converts the string representation of an entity identifier into the equivalent Udi instance. + /// + /// The string to convert. + /// A value indicating whether to only deal with known types. + /// An Udi instance that contains the value that was parsed. + /// A boolean value indicating whether the string could be parsed. + /// + /// If is true, and the string could not be parsed because + /// the entity type was not known, the method returns false but still sets udi + /// to an value. + /// If is true, assemblies are not scanned for types, + /// and therefore only builtin types may be known. Unless scanning already took place. + /// + public static bool TryParse(string s, bool knownTypes, out Udi udi) { - path = uri.AbsolutePath.TrimStart('/'); - - UdiType udiType; - if (KnownUdiTypes.Value.TryGetValue(uri.Host, out udiType)) - { - return udiType; - } - - // if it's empty and it's not in our known list then we don't know - if (path.IsNullOrWhiteSpace()) - return UdiType.Unknown; - - // try to parse into a Guid - // (note: root udis use Guid.Empty so this is ok) - Guid guidId; - if (Guid.TryParse(path, out guidId)) - { - //add it to our known list - KnownUdiTypes.Value.TryAdd(uri.Host, UdiType.GuidUdi); - return UdiType.GuidUdi; - } - - // add it to our known list - if it's not a GUID then it must a string - KnownUdiTypes.Value.TryAdd(uri.Host, UdiType.StringUdi); - return UdiType.StringUdi; + return ParseInternal(s, true, knownTypes, out udi); } - private static bool ParseInternal(string s, bool tryParse, out Udi udi) + private static bool ParseInternal(string s, bool tryParse, bool knownTypes, out Udi udi) { + if (knownTypes == false) + EnsureScanForUdiTypes(); + udi = null; Uri uri; @@ -129,24 +155,30 @@ namespace Umbraco.Core throw new FormatException(string.Format("String \"{0}\" is not a valid udi.", s)); } - // if it's a known entity type, GetUdiType will return it - // else it will try to guess based on the path, and register the type as known - string path; - var udiType = GetUdiType(uri, out path); - - if (path.IsNullOrWhiteSpace()) + var entityType = uri.Host; + UdiType udiType; + if (_udiTypes.TryGetValue(entityType, out udiType) == false) { - // path is empty which indicates we need to return the root udi - udi = GetRootUdi(uri.Host); - return true; + if (knownTypes) + { + // not knowing the type is not an error + // just return the unknown type udi + udi = UnknownTypeUdi.Instance; + return false; + } + if (tryParse) return false; + throw new FormatException(string.Format("Unknown entity type \"{0}\".", entityType)); } - // if the path is not empty, type should not be unknown - if (udiType == UdiType.Unknown) - throw new FormatException(string.Format("Could not determine the Udi type for string \"{0}\".", s)); + var path = uri.AbsolutePath.TrimStart('/'); if (udiType == UdiType.GuidUdi) { + if (path == string.Empty) + { + udi = GetRootUdi(uri.Host); + return true; + } Guid guid; if (Guid.TryParse(path, out guid) == false) { @@ -156,23 +188,25 @@ namespace Umbraco.Core udi = new GuidUdi(uri.Host, guid); return true; } + if (udiType == UdiType.StringUdi) { - udi = new StringUdi(uri.Host, Uri.UnescapeDataString(path)); + udi = path == string.Empty ? GetRootUdi(uri.Host) : new StringUdi(uri.Host, Uri.UnescapeDataString(path)); return true; } + if (tryParse) return false; - throw new InvalidOperationException("Internal error."); + throw new InvalidOperationException(string.Format("Invalid udi type \"{0}\".", udiType)); } private static Udi GetRootUdi(string entityType) { - ScanAllUdiTypes(); + EnsureScanForUdiTypes(); return RootUdis.GetOrAdd(entityType, x => { UdiType udiType; - if (KnownUdiTypes.Value.TryGetValue(x, out udiType) == false) + if (_udiTypes.TryGetValue(x, out udiType) == false) throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", entityType)); return udiType == UdiType.StringUdi ? (Udi)new StringUdi(entityType, string.Empty) @@ -186,7 +220,7 @@ namespace Umbraco.Core /// /// This is only required when needing to resolve root udis /// - private static void ScanAllUdiTypes() + private static void EnsureScanForUdiTypes() { if (_scanned) return; @@ -212,11 +246,9 @@ namespace Umbraco.Core } } - //merge these into the known list + // merge these into the known list foreach (var item in result) - { - KnownUdiTypes.Value.TryAdd(item.Key, item.Value); - } + _udiTypes.TryAdd(item.Key, item.Value); _scanned = true; } @@ -241,11 +273,13 @@ namespace Umbraco.Core public static Udi Create(string entityType, string id) { UdiType udiType; - if (KnownUdiTypes.Value.TryGetValue(entityType, out udiType) && udiType != UdiType.StringUdi) - throw new InvalidOperationException(string.Format("Entity type \"{0}\" is not a StringUdi.", entityType)); + if (_udiTypes.TryGetValue(entityType, out udiType) == false) + throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", entityType), "entityType"); if (string.IsNullOrWhiteSpace(id)) throw new ArgumentException("Value cannot be null or whitespace.", "id"); + if (udiType != UdiType.StringUdi) + throw new InvalidOperationException(string.Format("Entity type \"{0}\" does not have string udis.", entityType)); return new StringUdi(entityType, id); } @@ -259,11 +293,14 @@ namespace Umbraco.Core public static Udi Create(string entityType, Guid id) { UdiType udiType; - if (KnownUdiTypes.Value.TryGetValue(entityType, out udiType) && udiType != UdiType.GuidUdi) - throw new InvalidOperationException(string.Format("Entity type \"{0}\" is not a GuidUdi.", entityType)); + if (_udiTypes.TryGetValue(entityType, out udiType) == false) + throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", entityType), "entityType"); + if (udiType != UdiType.GuidUdi) + throw new InvalidOperationException(string.Format("Entity type \"{0}\" does not have guid udis.", entityType)); if (id == default(Guid)) throw new ArgumentException("Cannot be an empty guid.", "id"); + return new GuidUdi(entityType, id); } @@ -273,13 +310,14 @@ namespace Umbraco.Core // else fallback to parsing the string (and guess the type) UdiType udiType; - if (KnownUdiTypes.Value.TryGetValue(uri.Host, out udiType) == false) - return Parse(uri.ToString()); + if (_udiTypes.TryGetValue(uri.Host, out udiType) == false) + throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", uri.Host), "uri"); if (udiType == UdiType.GuidUdi) return new GuidUdi(uri); if (udiType == UdiType.GuidUdi) return new StringUdi(uri); + throw new ArgumentException(string.Format("Uri \"{0}\" is not a valid udi.", uri)); } @@ -328,6 +366,19 @@ namespace Umbraco.Core { return (udi1 == udi2) == false; } - } + private class UnknownTypeUdi : Udi + { + private UnknownTypeUdi() + : base("unknown", "umb://unknown/") + { } + + public static readonly UnknownTypeUdi Instance = new UnknownTypeUdi(); + + public override bool IsRoot + { + get { return false; } + } + } + } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 3850fb98b0..dc5bf4480b 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -644,6 +644,7 @@ + diff --git a/src/Umbraco.Tests/UdiTests.cs b/src/Umbraco.Tests/UdiTests.cs index 9c46b27a89..75f4536fd4 100644 --- a/src/Umbraco.Tests/UdiTests.cs +++ b/src/Umbraco.Tests/UdiTests.cs @@ -1,9 +1,11 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; using Newtonsoft.Json; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Deploy; using Umbraco.Core.Serialization; namespace Umbraco.Tests @@ -180,16 +182,6 @@ namespace Umbraco.Tests Assert.IsInstanceOf(udi); } - [Test] - public void NotKnownTypeTest() - { - var udi1 = Udi.Parse("umb://not-known-1/DA845952BE474EE9BD6F6194272AC750"); - Assert.IsInstanceOf(udi1); - - var udi2 = Udi.Parse("umb://not-known-2/this-is-not-a-guid"); - Assert.IsInstanceOf(udi2); - } - [Test] public void RangeTest() { @@ -265,5 +257,86 @@ namespace Umbraco.Tests Assert.AreEqual(0, types.Count, "Error in class Constants.UdiEntityType, GetTypes declares types that don't exist ({0}).", string.Join(",", types.Keys.Select(x => "\"" + x + "\""))); } + + [Test] + public void KnownTypes() + { + Udi udi; + + // cannot parse an unknown type, udi is null + // this will scan + Assert.IsFalse(Udi.TryParse("umb://whatever/1234", out udi)); + Assert.IsNull(udi); + + Udi.ResetUdiTypes(); + + // unless we want to know + Assert.IsFalse(Udi.TryParse("umb://whatever/1234", true, out udi)); + Assert.AreEqual(Constants.UdiEntityType.Unknown, udi.EntityType); + Assert.AreEqual("Umbraco.Core.Udi+UnknownTypeUdi", udi.GetType().FullName); + + Udi.ResetUdiTypes(); + + // not known + Assert.IsFalse(Udi.TryParse("umb://foo/A87F65C8D6B94E868F6949BA92C93045", true, out udi)); + Assert.AreEqual(Constants.UdiEntityType.Unknown, udi.EntityType); + Assert.AreEqual("Umbraco.Core.Udi+UnknownTypeUdi", udi.GetType().FullName); + + // scanned + Assert.IsTrue(Udi.TryParse("umb://foo/A87F65C8D6B94E868F6949BA92C93045", out udi)); + Assert.IsInstanceOf(udi); + + // known + Assert.IsTrue(Udi.TryParse("umb://foo/A87F65C8D6B94E868F6949BA92C93045", true, out udi)); + Assert.IsInstanceOf(udi); + + // can get method for Deploy compatibility + var method = typeof (Udi).GetMethod("Parse", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof (string), typeof (bool) }, null); + Assert.IsNotNull(method); + } + + [UdiDefinition("foo", UdiType.GuidUdi)] + public class FooConnector : IServiceConnector + { + public IArtifact GetArtifact(Udi udi) + { + throw new NotImplementedException(); + } + + public IArtifact GetArtifact(object entity) + { + throw new NotImplementedException(); + } + + public ArtifactDeployState ProcessInit(IArtifact art, IDeployContext context) + { + throw new NotImplementedException(); + } + + public void Process(ArtifactDeployState dart, IDeployContext context, int pass) + { + throw new NotImplementedException(); + } + + public void Explode(UdiRange range, List udis) + { + throw new NotImplementedException(); + } + + public NamedUdiRange GetRange(Udi udi, string selector) + { + throw new NotImplementedException(); + } + + public NamedUdiRange GetRange(string entityType, string sid, string selector) + { + throw new NotImplementedException(); + } + + public bool Compare(IArtifact art1, IArtifact art2, ICollection differences = null) + { + throw new NotImplementedException(); + } + } } } \ No newline at end of file diff --git a/src/Umbraco.Web.UI/config/Dashboard.config b/src/Umbraco.Web.UI/config/Dashboard.config index 322551545d..c8c7e32f27 100644 --- a/src/Umbraco.Web.UI/config/Dashboard.config +++ b/src/Umbraco.Web.UI/config/Dashboard.config @@ -119,4 +119,14 @@ +
+ + content + + + + /App_Plugins/Deploy/views/dashboards/dashboard.html + + +
\ No newline at end of file diff --git a/src/Umbraco.Web.UI/config/splashes/noNodes.aspx b/src/Umbraco.Web.UI/config/splashes/noNodes.aspx index d46ecd6113..64d4446e3d 100644 --- a/src/Umbraco.Web.UI/config/splashes/noNodes.aspx +++ b/src/Umbraco.Web.UI/config/splashes/noNodes.aspx @@ -1,7 +1,7 @@ -<%@ Page Language="C#" AutoEventWireup="True" Inherits="Umbraco.Web.UI.Config.Splashes.NoNodes" CodeBehind="NoNodes.aspx.cs" %> -<%@ Import Namespace="Umbraco.Core.Configuration" %> +<%@ Page Language="C#" AutoEventWireup="true"%> <%@ Import Namespace="Umbraco.Core.IO" %> - +<%@ Import Namespace="Umbraco.Deploy.UI" %> +<%@ Import Namespace="Umbraco.Web" %> @@ -11,51 +11,145 @@ - + - + + + + + + + +<% if(HttpContext.Current.Request.IsLocal == false){ %>
-
-
- +
+
+ -

Welcome to your Umbraco installation

-

You're seeing this wonderful page because your website doesn't contain any published content yet.

+

Welcome to your Umbraco installation

+

You're seeing the wonderful page because your website doesn't contain any published content yet.

- + -
-
-

Easy start with Umbraco.tv

-

We have created a bunch of 'how-to' videos, to get you easily started with Umbraco. Learn how to build projects in just a couple of minutes. Easiest CMS in the world.

- - Umbraco.tv → -
+
+
+

Easy start with Umbraco.tv

+

We have created a bunch of 'how-to' videos, to get you easily started with Umbraco. Learn how to build projects in just a couple of minutes. Easiest CMS in the world.

+ + Umbraco.tv → +
-
-

Be a part of the community

-

The Umbraco community is the best of its kind, be sure to visit, and if you have any questions, we're sure that you can get your answers from the community.

- - our.Umbraco → -
-
+
+

Be a part of the community

+

The Umbraco community is the best of its kind, be sure to visit, and if you have any questions, we’re sure that you can get your answers from the community.

+ + our.Umbraco → +
+
-
-
+
+
+
+ +<% }else{ %> + +
+
+ +
+ + +
+ +
+ +
+

Initializing...

+ Please wait while your site is loaded +
+ +

{{ vm.restore.restoreProgress }}% restored

+ {{ vm.restore.currentActivity }} + +
+

Initialize your site...

+ Press the button below to get started +
+ +
+
+
+ +
+

Restore from Umbraco Cloud

+ +
+ +
+

Restoring your website...

+

{{ vm.restore.restoreProgress }}% restored

+ {{ vm.restore.currentActivity }} +
+ +
+

Ready to rock n' roll!

+

Everything has been restored and is ready for use, click below to open Umbraco

+ +
+ + + + + <%--
+

An error occurred:

+

{{ vm.restore.error.exceptionMessage }}

+ Show details +
{{ vm.restore.error.log }}
+
--%> + +
+ +
+
- - +<%= NoNodesHelper.ServerVariables(HttpContext.Current.Request.RequestContext, UmbracoContext.Current) %> + + + + + + + + + +<% } %> - + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/config/trees.config b/src/Umbraco.Web.UI/config/trees.config index 13da9246b7..a6104af9ae 100644 --- a/src/Umbraco.Web.UI/config/trees.config +++ b/src/Umbraco.Web.UI/config/trees.config @@ -41,4 +41,5 @@ + \ No newline at end of file