From 41d1e3096bc899bf5f3bf28a06a6e66da0728c1b Mon Sep 17 00:00:00 2001 From: Paul Schaeflein Date: Thu, 17 Nov 2022 15:48:35 -0600 Subject: [PATCH] Update Json provider to support schema and hierarchy --- src/lib/.editorconfig | 85 +++ .../ProvisioningTemplates/DomainModelTests.cs | 2 +- src/lib/PnP.Framework.sln | 5 +- src/lib/PnP.Framework/PnP.Framework.csproj | 4 + .../SiteCollectionsAndSitesConverter.cs | 75 ++ .../Json/Converters/SiteColumnsConverter.cs | 44 ++ .../Providers/Json/JsonPnPFormatter.cs | 53 -- .../Json/JsonPnPSchemaBaseSerializer.cs | 706 ++++++++++++++++++ .../Providers/Json/JsonPnPSchemaFormatter.cs | 138 ++++ .../Json/JsonPnPSchemaV202209Serializer.cs | 24 + .../Providers/Json/JsonTemplateProvider.cs | 81 +- .../Markdown/MarkdownTemplateProvider.cs | 14 +- .../Providers/TemplateProviderBase.cs | 22 +- .../Providers/Xml/XMLTemplateProvider.cs | 53 +- 14 files changed, 1199 insertions(+), 107 deletions(-) create mode 100644 src/lib/.editorconfig create mode 100644 src/lib/PnP.Framework/Provisioning/Providers/Json/Converters/SiteCollectionsAndSitesConverter.cs create mode 100644 src/lib/PnP.Framework/Provisioning/Providers/Json/Converters/SiteColumnsConverter.cs delete mode 100644 src/lib/PnP.Framework/Provisioning/Providers/Json/JsonPnPFormatter.cs create mode 100644 src/lib/PnP.Framework/Provisioning/Providers/Json/JsonPnPSchemaBaseSerializer.cs create mode 100644 src/lib/PnP.Framework/Provisioning/Providers/Json/JsonPnPSchemaFormatter.cs create mode 100644 src/lib/PnP.Framework/Provisioning/Providers/Json/JsonPnPSchemaV202209Serializer.cs diff --git a/src/lib/.editorconfig b/src/lib/.editorconfig new file mode 100644 index 000000000..a3c517978 --- /dev/null +++ b/src/lib/.editorconfig @@ -0,0 +1,85 @@ + +[*.{cs,vb}] +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +indent_size = 4 +end_of_line = crlf +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_simplified_interpolation = true:suggestion +dotnet_style_namespace_match_folder = true:suggestion + +[*.cs] +csharp_indent_labels = one_less_than_current +csharp_using_directive_placement = outside_namespace:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_prefer_braces = true:silent +csharp_style_namespace_declarations = block_scoped:silent +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = true:silent +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent +csharp_style_throw_expression = true:suggestion \ No newline at end of file diff --git a/src/lib/PnP.Framework.Test/Framework/ProvisioningTemplates/DomainModelTests.cs b/src/lib/PnP.Framework.Test/Framework/ProvisioningTemplates/DomainModelTests.cs index 774ff7e5f..66ec1ce57 100644 --- a/src/lib/PnP.Framework.Test/Framework/ProvisioningTemplates/DomainModelTests.cs +++ b/src/lib/PnP.Framework.Test/Framework/ProvisioningTemplates/DomainModelTests.cs @@ -444,7 +444,7 @@ public void CanHandleDomainObjectWithJsonFormatter() { using (Stream _formattedTemplate = new FileStream(this._provisioningTemplatePath7, FileMode.Open, FileAccess.Read, FileShare.Read)) { - ITemplateFormatter formatter = new JsonPnPFormatter(); + ITemplateFormatter formatter = new JsonPnPSchemaFormatter(); JsonTemplateProvider provider = new JsonFileSystemTemplateProvider( diff --git a/src/lib/PnP.Framework.sln b/src/lib/PnP.Framework.sln index 5c1bdd30a..f53748bde 100644 --- a/src/lib/PnP.Framework.sln +++ b/src/lib/PnP.Framework.sln @@ -1,10 +1,11 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30503.244 +# Visual Studio Version 17 +VisualStudioVersion = 17.4.33110.190 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C3C026AD-0B78-4779-9DC4-F875B42A48F4}" ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig CHANGELOG.md = CHANGELOG.md EndProjectSection EndProject diff --git a/src/lib/PnP.Framework/PnP.Framework.csproj b/src/lib/PnP.Framework/PnP.Framework.csproj index 2e2903965..aaeda8231 100644 --- a/src/lib/PnP.Framework/PnP.Framework.csproj +++ b/src/lib/PnP.Framework/PnP.Framework.csproj @@ -304,4 +304,8 @@ + + + + diff --git a/src/lib/PnP.Framework/Provisioning/Providers/Json/Converters/SiteCollectionsAndSitesConverter.cs b/src/lib/PnP.Framework/Provisioning/Providers/Json/Converters/SiteCollectionsAndSitesConverter.cs new file mode 100644 index 000000000..7d64d506c --- /dev/null +++ b/src/lib/PnP.Framework/Provisioning/Providers/Json/Converters/SiteCollectionsAndSitesConverter.cs @@ -0,0 +1,75 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using PnP.Framework.Provisioning.Model; +using PnP.Framework.Provisioning.Providers.Xml; +using System; + +namespace PnP.Framework.Provisioning.Providers.Json.Converters +{ + public class SiteCollectionsAndSitesConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + var siteTypeName = $"{PnPSerializationScope.Current?.BaseSchemaNamespace}.SiteCollection, {PnPSerializationScope.Current?.BaseSchemaAssemblyName}"; + var siteType = Type.GetType(siteTypeName, true); + return siteType.IsAssignableFrom(objectType); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + // Define the specific source schema types + var communicationSiteTypeName = $"{PnPSerializationScope.Current?.BaseSchemaNamespace}.CommunicationSite, {PnPSerializationScope.Current?.BaseSchemaAssemblyName}"; + var communicationSiteType = Type.GetType(communicationSiteTypeName, true); + var teamSiteTypeName = $"{PnPSerializationScope.Current?.BaseSchemaNamespace}.TeamSite, {PnPSerializationScope.Current?.BaseSchemaAssemblyName}"; + var teamSiteType = Type.GetType(teamSiteTypeName, true); + var teamSiteNoGroupTypeName = $"{PnPSerializationScope.Current?.BaseSchemaNamespace}.TeamSiteNoGroup, {PnPSerializationScope.Current?.BaseSchemaAssemblyName}"; + var teamSiteNoGroupType = Type.GetType(teamSiteNoGroupTypeName, true); + var teamSubSiteNoGroupTypeName = $"{PnPSerializationScope.Current?.BaseSchemaNamespace}.TeamSubSiteNoGroup, {PnPSerializationScope.Current?.BaseSchemaAssemblyName}"; + var teamSubSiteNoGroupType = Type.GetType(teamSubSiteNoGroupTypeName, true); + + JObject sourceJObject = JObject.Load(reader); + JToken discriminatorToken = sourceJObject.SelectToken("Type"); + string discriminator = discriminatorToken?.Value() ?? "CommunicationSite"; + discriminatorToken.Parent.Remove(); + + Object targetItem = null; + switch (discriminator) + { + case "CommunicationSite": + targetItem = sourceJObject.ToObject(communicationSiteType); + break; + case "TeamSite": + targetItem = sourceJObject.ToObject(teamSiteType); + break; + case "TeamSiteNoGroup": + targetItem = sourceJObject.ToObject(teamSiteNoGroupType); + break; + case "TeamNoGroupSubSite": + targetItem = sourceJObject.ToObject(teamSubSiteNoGroupType); + break; + } + + return targetItem; + } + + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + JToken t = JToken.FromObject(value); + + if (t.Type != JTokenType.Object) + { + t.WriteTo(writer); + } + else + { + string typeProperty =value.GetType().Name; + + JObject o = (JObject)t; + o.AddFirst(new JProperty("Type", typeProperty)); + + o.WriteTo(writer); + } + } + } +} diff --git a/src/lib/PnP.Framework/Provisioning/Providers/Json/Converters/SiteColumnsConverter.cs b/src/lib/PnP.Framework/Provisioning/Providers/Json/Converters/SiteColumnsConverter.cs new file mode 100644 index 000000000..ed914c0a0 --- /dev/null +++ b/src/lib/PnP.Framework/Provisioning/Providers/Json/Converters/SiteColumnsConverter.cs @@ -0,0 +1,44 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using PnP.Framework.Extensions; +using PnP.Framework.Provisioning.Providers.Xml; +using System; + +namespace PnP.Framework.Provisioning.Providers.Json.Converters +{ + public class SiteColumnsConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + var fieldsTypeName = $"{PnPSerializationScope.Current?.BaseSchemaNamespace}.ProvisioningTemplateSiteFields, {PnPSerializationScope.Current?.BaseSchemaAssemblyName}"; + var fieldsType = Type.GetType(fieldsTypeName, true); + return fieldsType.IsAssignableFrom(objectType); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + JArray sourceJObject = JArray.Load(reader); + var e = sourceJObject.ToObject(); + + var fields = Activator.CreateInstance(objectType); + fields.SetPublicInstancePropertyValue("Any", e); + + return fields; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + JToken t = JToken.FromObject(value); + + if (t.Type != JTokenType.Object) + { + t.WriteTo(writer); + } + else + { + JArray a = (JArray)t.SelectToken("Any"); + a.WriteTo(writer); + } + } + } +} diff --git a/src/lib/PnP.Framework/Provisioning/Providers/Json/JsonPnPFormatter.cs b/src/lib/PnP.Framework/Provisioning/Providers/Json/JsonPnPFormatter.cs deleted file mode 100644 index e787dab38..000000000 --- a/src/lib/PnP.Framework/Provisioning/Providers/Json/JsonPnPFormatter.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Newtonsoft.Json; -using System; -using System.IO; -using System.Text; - -namespace PnP.Framework.Provisioning.Providers.Json -{ - public class JsonPnPFormatter : ITemplateFormatterWithValidation - { - private TemplateProviderBase _provider; - - public void Initialize(TemplateProviderBase provider) - { - this._provider = provider; - } - - public bool IsValid(Stream template) - { - return GetValidationResults(template).IsValid; - } - - public ValidationResult GetValidationResults(System.IO.Stream template) - { - // We do not provide JSON validation capabilities - return new ValidationResult { IsValid = true, Exceptions = null }; - } - - public System.IO.Stream ToFormattedTemplate(Model.ProvisioningTemplate template) - { - String jsonString = JsonConvert.SerializeObject(template, new BasePermissionsConverter()); - Byte[] jsonBytes = System.Text.Encoding.Unicode.GetBytes(jsonString); - MemoryStream jsonStream = new MemoryStream(jsonBytes) - { - Position = 0 - }; - - return (jsonStream); - } - - public Model.ProvisioningTemplate ToProvisioningTemplate(System.IO.Stream template) - { - return (this.ToProvisioningTemplate(template, null)); - } - - public Model.ProvisioningTemplate ToProvisioningTemplate(System.IO.Stream template, string identifier) - { - StreamReader sr = new StreamReader(template, Encoding.Unicode); - String jsonString = sr.ReadToEnd(); - Model.ProvisioningTemplate result = JsonConvert.DeserializeObject(jsonString, new BasePermissionsConverter()); - return (result); - } - } -} diff --git a/src/lib/PnP.Framework/Provisioning/Providers/Json/JsonPnPSchemaBaseSerializer.cs b/src/lib/PnP.Framework/Provisioning/Providers/Json/JsonPnPSchemaBaseSerializer.cs new file mode 100644 index 000000000..59b218cac --- /dev/null +++ b/src/lib/PnP.Framework/Provisioning/Providers/Json/JsonPnPSchemaBaseSerializer.cs @@ -0,0 +1,706 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using PnP.Framework.Extensions; +using PnP.Framework.Provisioning.Model; +using PnP.Framework.Provisioning.Providers.Json.Converters; +using PnP.Framework.Provisioning.Providers.Xml; +using System; +using System.Collections; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Xml.Serialization; + +namespace PnP.Framework.Provisioning.Providers.Json +{ + internal abstract class JsonPnPSchemaBaseSerializer : ITemplateFormatter, IProvisioningHierarchyFormatter + where TSchemaTemplate : new() + { + private TemplateProviderBase _provider; + + protected TemplateProviderBase Provider => _provider; + + public JsonPnPSchemaBaseSerializer() { } + + public JsonPnPSchemaBaseSerializer(JsonSerializerSettings serializerSettings) + { + this.serializerSettings = serializerSettings; + } + + // we need to use our converters, so use the property getter/setter to enforce that + private JsonSerializerSettings serializerSettings + { + get + { + if (_serializerSettings == null) + { + _serializerSettings = new JsonSerializerSettings(); + _serializerSettings.Converters.Add(new SiteCollectionsAndSitesConverter()); + _serializerSettings.Converters.Add(new SiteColumnsConverter()); + } + return _serializerSettings; + } + set + { + _serializerSettings = value; + _serializerSettings.Converters.Add(new SiteCollectionsAndSitesConverter()); + _serializerSettings.Converters.Add(new SiteColumnsConverter()); + } + } + private JsonSerializerSettings _serializerSettings; + + #region Serializer methods + + /// + /// Deserializes a JSON-based object, created with JSONSerializer, into a Provisioning Template implementing the specified schema + /// + /// The JSON-based object + /// The resulting template + protected virtual void DeserializeTemplate(Object persistenceTemplate, ProvisioningTemplate template) + { + // Get all ProvisioningTemplate-level serializers to run in automated mode, ordered by DeserializationSequence + var serializers = GetSerializersForCurrentContext(SerializerScope.ProvisioningTemplate, a => a?.DeserializationSequence); + + // Invoke all the ProvisioningTemplate-level serializers + InvokeSerializers(template, persistenceTemplate, serializers, SerializationAction.Deserialize); + } + + /// + /// Serializes a ProvisioningTemplate into a JSON-based object generated with JSONSerializer + /// + /// The ProvisioningTemplate to serialize + /// The JSON-based object to serialize the template into + protected virtual void SerializeTemplate(ProvisioningTemplate template, Object persistenceTemplate) + { + // Get all ProvisioningTemplate-level serializers to run in automated mode, ordered by DeserializationSequence + var serializers = GetSerializersForCurrentContext(SerializerScope.ProvisioningTemplate, a => a?.SerializationSequence); + + // Invoke all the ProvisioningTemplate-level serializers + InvokeSerializers(template, persistenceTemplate, serializers, SerializationAction.Serialize); + } + + #endregion + + #region ITemplateFormatter + + public void Initialize(TemplateProviderBase provider) + { + this._provider = provider; + } + + /// + /// NOT IMPLEMENTED: Returns true + /// + /// The source Stream (the JSON) + /// True (Not implemented for JSON) + public bool IsValid(Stream template) + { + return true; + } + + public Stream ToFormattedTemplate(ProvisioningTemplate template) + { + if (template == null) + { + throw new ArgumentNullException(nameof(template)); + } + + using (var scope = new PnPSerializationScope(typeof(TSchemaTemplate))) + { + var result = new TSchemaTemplate(); + Stream output = null; + + // Process the template to generate the output stream + output = ProcessOutputStream(template, result); + + return (output); + } + } + + /// + /// Deserializes a Stream of bytes (the JSON) into a Provisioning Template + /// + /// The source Stream of bytes (the JSON) + /// The deserialized Provisioning Template + public ProvisioningTemplate ToProvisioningTemplate(Stream template) + { + return this.ToProvisioningTemplate(template, null); + } + + /// + /// Deserializes a Stream of bytes (the JSON) into a Provisioning Template + /// + /// The source Stream of bytes (the JSON) + /// An optional identifier for the template to deserialize + /// The deserialized Provisioning Template + public ProvisioningTemplate ToProvisioningTemplate(Stream template, string identifier) + { + using (var scope = new PnPSerializationScope(typeof(TSchemaTemplate))) + { + // Prepare a variable to hold the resulting ProvisioningTemplate instance + var result = new ProvisioningTemplate(); + + // Prepare a variable to hold the single source formatted template + // We provide the result instance of ProvisioningTemplate in order + // to configure the tenant/hierarchy level items + // We get back the XML-based object to use with the other serializers + var source = ProcessInputStream(template, identifier, result); + + // We process the chain of deserialization + // with the Provisioning-level serializers + DeserializeTemplate(source, result); + + return (result); + } + } + + #endregion + + #region IProvisioningHierarchyFormattion + + /// + /// Serializes a ProvisioningHierarchy into a Stream (the JSON) + /// + /// The ProvisioningHierarchy to serialize + /// The resulting Stream (the JSON) + public Stream ToFormattedHierarchy(ProvisioningHierarchy hierarchy) + { + if (hierarchy == null) + { + throw new ArgumentNullException(nameof(hierarchy)); + } + + using (var scope = new PnPSerializationScope(typeof(TSchemaTemplate))) + { + // We prepare a dummy template to leverage the existing deserialization infrastructure + var dummyTemplate = new ProvisioningTemplate(); + dummyTemplate.Id = $"DUMMY-{Guid.NewGuid()}"; + hierarchy.Templates.Add(dummyTemplate); + + // Prepare the output wrapper + ProcessOutputHierarchy(dummyTemplate, out Type wrapperType, out object wrapper, out Array templates, out object templatesItem); + + // Handle the Sequences, if any + // Get all ProvisioningHierarchy-level serializers to run in automated mode, ordered by SerializationSequence + var serializers = GetSerializersForCurrentContext(SerializerScope.ProvisioningHierarchy, a => a?.SerializationSequence); + + // Invoke all the ProvisioningHierarchy-level serializers + InvokeSerializers(dummyTemplate, wrapper, serializers, SerializationAction.Serialize); + + // Remove the dummy template + hierarchy.Templates.Remove(dummyTemplate); + + // Add every single template to the output + var provisioningTemplates = Array.CreateInstance(typeof(TSchemaTemplate), hierarchy.Templates.Count); + for (int c = 0; c < hierarchy.Templates.Count; c++) + { + // Prepare variable to hold the output template + var outputTemplate = new TSchemaTemplate(); + + // Serialize the real templates + SerializeTemplate(hierarchy.Templates[c], outputTemplate); + + // Add the serialized template to the output + provisioningTemplates.SetValue(outputTemplate, c); + } + + templatesItem.SetPublicInstancePropertyValue("ProvisioningTemplate", provisioningTemplates); + + templates.SetValue(templatesItem, 0); + + if (provisioningTemplates.Length > 0) + { + wrapper.SetPublicInstancePropertyValue("Templates", templates); + } + + /* + * JSON.Net will close a stream after it's written. We need to pass a stream back, + * so this hack will serialize to a different stream and copy... + */ + + MemoryStream output = new MemoryStream(); + + MemoryStream temp = new MemoryStream(); + using (StreamWriter sw = new StreamWriter(temp)) + using (JsonWriter jw = new JsonTextWriter(sw)) + { + if (this.serializerSettings == null) + { + JsonSerializer.CreateDefault().Serialize(jw, wrapper); + } + else + { + JsonSerializer.Create(this.serializerSettings).Serialize(jw, wrapper); + } + + sw.Flush(); + temp.Position = 0; + temp.WriteTo(output); + } + + // Re-base the Stream and return it + output.Position = 0; + return (output); + } + } + + /// + /// Deserializes a source Stream (the JSON) into a ProvisioningHierarchy + /// + /// The source Stream (the JSON) + /// The resulting ProvisioningHierarchy object + public ProvisioningHierarchy ToProvisioningHierarchy(Stream hierarchy) + { + // Create a copy of the source stream + MemoryStream sourceStream = new MemoryStream(); + hierarchy.Position = 0; + hierarchy.CopyTo(sourceStream); + sourceStream.Position = 0; + + // Prepare the output variable + ProvisioningHierarchy resultHierarchy = new ProvisioningHierarchy(); + + // load the JSON we can inspect it + JObject json = default; + + using (TextReader sr = new StreamReader(sourceStream)) + using (JsonReader jr = new JsonTextReader(sr)) + { + json = (JObject)JToken.ReadFrom(jr); + } + + // Determine if we're working on a wrapped ProvisioningTemplate or not + if (!json.ContainsKey("Sequence") && + !json.ContainsKey("SequenceItems")) + { + throw new ApplicationException("The provided provisioning file is not a Hierarchy!"); + } + + // Process all the provisioning templates included in the hierarchy + var templates = (JArray)json.SelectToken("Templates"); + + using (var scope = new PnPSerializationScope(typeof(TSchemaTemplate))) + { + foreach (var template in templates) + { + var provisioningTemplates = (JArray)template.SelectToken("ProvisioningTemplate"); + foreach (var provisioningTemplateJson in provisioningTemplates) + { + //var provisioningTemplate = ToProvisioningTemplate(provisioningTemplateJson as JObject, provisioningTemplateJson.Value("ID")); + + // Prepare a variable to hold the resulting ProvisioningTemplate instance + var result = new ProvisioningTemplate(); + // Prepare a variable to hold the single source formatted template + var source = ProcessInputJObject(provisioningTemplateJson as JObject, provisioningTemplateJson.Value("ID"), result); + DeserializeTemplate(source, result); + + // Add the generated template to the resulting hierarchy + resultHierarchy.Templates.Add(result); + } + } + + // And now process the top level children elements + // using schema specific serializers + + // We prepare a dummy template to leverage the existing serialization infrastructure + var dummyTemplate = new ProvisioningTemplate(); + dummyTemplate.Id = $"DUMMY-{Guid.NewGuid()}"; + resultHierarchy.Templates.Add(dummyTemplate); + + // Deserialize the whole wrapper + Object wrapper = null; + var wrapperType = Type.GetType($"{PnPSerializationScope.Current?.BaseSchemaNamespace}.Provisioning, {PnPSerializationScope.Current?.BaseSchemaAssemblyName}", true); + wrapper = Activator.CreateInstance(wrapperType); + + using (var sr = json.CreateReader()) + { + if (serializerSettings == null) + { + JsonSerializer.CreateDefault().Populate(sr, wrapper); + } + else + { + JsonSerializer.Create(serializerSettings).Populate(sr, wrapper); + } + } + + #region Process Provisioning level serializers + + // Get all serializers to run in automated mode, ordered by DeserializationSequence + var serializers = GetSerializersForCurrentContext(SerializerScope.Provisioning, a => a?.DeserializationSequence); + + // Invoke all the serializers + InvokeSerializers(dummyTemplate, wrapper, serializers, SerializationAction.Deserialize); + + #endregion + + #region Process Tenant level serializers + + // Get all serializers to run in automated mode, ordered by DeserializationSequence + serializers = GetSerializersForCurrentContext(SerializerScope.Tenant, a => a?.DeserializationSequence); + + // Invoke all the serializers + InvokeSerializers(dummyTemplate, wrapper, serializers, SerializationAction.Deserialize); + + #endregion + + #region Process ProvisioningHierarchy level serializers + + // Get all serializers to run in automated mode, ordered by DeserializationSequence + serializers = GetSerializersForCurrentContext(SerializerScope.ProvisioningHierarchy, a => a?.DeserializationSequence); + + // Invoke all the serializers + InvokeSerializers(dummyTemplate, wrapper, serializers, SerializationAction.Deserialize); + + #endregion + + // Remove the dummy template from the hierarchy + resultHierarchy.Templates.Remove(dummyTemplate); + } + + return (resultHierarchy); + } + + #endregion + + #region Private methods + + /// + /// Converts a Stream of bytes (the JSON) into a schema-based object + /// + /// The source Stream of bytes (the JSON) + /// An optional identifier for the template to extract from the JSON + /// A reference ProvisioningTemplate object + /// The resulting schema-based object extracted from the Stream + protected Object ProcessInputStream(Stream template, string identifier, ProvisioningTemplate result) + { + if (template == null) + { + throw new ArgumentNullException(nameof(template)); + } + + // Crate a copy of the source stream + MemoryStream sourceStream = new MemoryStream(); + template.CopyTo(sourceStream); + sourceStream.Position = 0; + + JObject json = default; + + using (TextReader sr = new StreamReader(sourceStream, true)) + using (JsonReader jr = new JsonTextReader(sr)) + { + json = (JObject)JToken.ReadFrom(jr); + } + + return ProcessInputJObject(json, identifier, result); + } + + private Object ProcessInputJObject(JObject json, string identifier, ProvisioningTemplate result) + { + // Prepare a variable to hold the single source formatted template + TSchemaTemplate source = default(TSchemaTemplate); + + // Determine if we're working on a wrapped ProvisioningTemplate or not + if (json.ContainsKey("Templates")) + { + // Deserialize the whole wrapper + Object wrapper = null; + var wrapperType = Type.GetType($"{PnPSerializationScope.Current?.BaseSchemaNamespace}.Provisioning, {PnPSerializationScope.Current?.BaseSchemaAssemblyName}", true); + wrapper = Activator.CreateInstance(wrapperType); + + using (var sr = json.CreateReader()) + { + if (serializerSettings == null) + { + JsonSerializer.CreateDefault().Populate(sr, wrapper); + } + else + { + JsonSerializer.Create(serializerSettings).Populate(sr, wrapper); + } + } + + // Get all Provisioning-level serializers to run in automated mode, ordered by DeserializationSequence + var serializers = GetSerializersForCurrentContext(SerializerScope.Provisioning, a => a?.DeserializationSequence); + + // Invoke all the Provisioning-level serializers + InvokeSerializers(result, wrapper, serializers, SerializationAction.Deserialize); + + // Get the list of templates, if any, wrapped by the wrapper + var wrapperTemplates = wrapper.GetPublicInstancePropertyValue("Templates"); + + if (wrapperTemplates != null) + { + // Search for the requested Provisioning Template + foreach (var templates in (IEnumerable)wrapperTemplates) + { + // Let's see if we have an in-place template with the provided ID or if we don't have a provided ID at all + var provisioningTemplates = templates.GetPublicInstancePropertyValue("ProvisioningTemplate"); + + if (provisioningTemplates != null) + { + foreach (var t in (IEnumerable)provisioningTemplates) + { + var templateId = t.GetPublicInstancePropertyValue("ID") as String; + + if ((templateId != null && templateId == identifier) || String.IsNullOrEmpty(identifier)) + { + source = (TSchemaTemplate)t; + } + } + + if (source == null) + { + var provisioningTemplateFiles = templates.GetPublicInstancePropertyValue("ProvisioningTemplateFile"); + + // If we don't have a template, but there are external file references + if (source == null && provisioningTemplateFiles != null) + { + foreach (var f in (IEnumerable)provisioningTemplateFiles) + { + var templateId = f.GetPublicInstancePropertyValue("ID") as String; + + if ((templateId != null && templateId == identifier) || String.IsNullOrEmpty(identifier)) + { + // Let's see if we have an external file for the template + var externalFile = f.GetPublicInstancePropertyValue("File") as String; + + if (!String.IsNullOrEmpty(externalFile)) + { + /* + * Not implementing file references in JSON + */ + throw new NotImplementedException(); + } + } + } + } + } + } + + if (source != null) + { + break; + } + } + } + } + else + { + var IdAttribute = json.SelectToken("ID"); + + // If there is a provided ID, and if it doesn't equal the current ID + if (!String.IsNullOrEmpty(identifier) && + json.ContainsKey("ID") && + json.Value("ID") != identifier) + { + // TODO: Use resource file + throw new ApplicationException("The provided template identifier is not available!"); + } + else + { + using (var sr = json.CreateReader()) + { + if (serializerSettings == null) + { + source = JsonSerializer.CreateDefault().Deserialize(sr); + } + else + { + source = JsonSerializer.Create(serializerSettings).Deserialize(sr); + } + } + } + } + + return (source); + } + + + /// + /// Serializes an in-memory ProvisioningTemplate into a Stream (the XML) + /// + /// The ProvisioningTemplate to serialize + /// The typed XML-based object defined using XmlSerializer + /// The resulting Stream (the XML) + protected Stream ProcessOutputStream(ProvisioningTemplate template, TSchemaTemplate result) + { + // Prepare the output wrapper + Type wrapperType; + object wrapper, templatesItem; + Array templates; + + // Process the hierarchy part of the template + ProcessOutputHierarchy(template, out wrapperType, out wrapper, out templates, out templatesItem); + + // Add the single template to the output + var provisioningTemplates = Array.CreateInstance(typeof(TSchemaTemplate), 1); + provisioningTemplates.SetValue(result, 0); + + templatesItem.SetPublicInstancePropertyValue("ProvisioningTemplate", provisioningTemplates); + + templates.SetValue(templatesItem, 0); + + wrapper.SetPublicInstancePropertyValue("Templates", templates); + + // Serialize the template mapping the ProvisioningTemplate object to the XML-based object + SerializeTemplate(template, result); + + // Serialize the XML-based object into a Stream (the XML) + XmlSerializerNamespaces ns = + new XmlSerializerNamespaces(); + ns.Add(((IXMLSchemaFormatter)this).NamespacePrefix, + ((IXMLSchemaFormatter)this).NamespaceUri); + + MemoryStream output = new MemoryStream(); + XmlSerializer xmlSerializer = new XmlSerializer(wrapperType); + if (ns != null) + { + xmlSerializer.Serialize(output, wrapper, ns); + } + else + { + xmlSerializer.Serialize(output, wrapper); + } + + // Re-base the Stream and return it + output.Position = 0; + return (output); + } + + /// + /// Prepares a ProvisioningTemplate to be wrapped into the Hierarchy container object + /// + /// The ProvisioningTemplate to wrap + /// The Type of the wrapper + /// The wrapper + /// The collection of template within the wrapper + /// The template to add + private void ProcessOutputHierarchy(ProvisioningTemplate template, out Type wrapperType, out object wrapper, out Array templates, out object templatesItem) + { + // Create the wrapper + wrapperType = Type.GetType($"{PnPSerializationScope.Current?.BaseSchemaNamespace}.Provisioning, {PnPSerializationScope.Current?.BaseSchemaAssemblyName}", true); + wrapper = Activator.CreateInstance(wrapperType); + + // Create the Preferences + var preferencesType = Type.GetType($"{PnPSerializationScope.Current?.BaseSchemaNamespace}.Preferences, {PnPSerializationScope.Current?.BaseSchemaAssemblyName}", true); + Object preferences = Activator.CreateInstance(preferencesType); + + wrapper.SetPublicInstancePropertyValue("Preferences", preferences); + + // Get all Provisioning-level serializers to run in automated mode, ordered by SerializationSequence + var serializers = GetSerializersForCurrentContext(SerializerScope.Provisioning, a => a?.SerializationSequence); + + // Invoke all the Provisioning-level serializers + InvokeSerializers(template, wrapper, serializers, SerializationAction.Serialize); + + // Get all Tenant-levelserializers to run in automated mode, ordered by SerializationSequence + serializers = GetSerializersForCurrentContext(SerializerScope.Tenant, a => a?.SerializationSequence); + + // Invoke all the Tenant-levelserializers + InvokeSerializers(template, wrapper, serializers, SerializationAction.Serialize); + + // Configure the basic properties of the wrapper + if (template.ParentHierarchy != null) + { + wrapper.SetPublicInstancePropertyValue("Author", template.ParentHierarchy.Author); + wrapper.SetPublicInstancePropertyValue("DisplayName", template.ParentHierarchy.DisplayName); + wrapper.SetPublicInstancePropertyValue("Description", template.ParentHierarchy.Description); + wrapper.SetPublicInstancePropertyValue("ImagePreviewUrl", template.ParentHierarchy.ImagePreviewUrl); + wrapper.SetPublicInstancePropertyValue("Generator", template.ParentHierarchy.Generator); + wrapper.SetPublicInstancePropertyValue("Version", (Decimal)template.ParentHierarchy.Version); + } + + // Configure the Generator + preferences.SetPublicInstancePropertyValue("Generator", this.GetType().Assembly.FullName); + + // Configure the output Template + var templatesType = Type.GetType($"{PnPSerializationScope.Current?.BaseSchemaNamespace}.Templates, {PnPSerializationScope.Current?.BaseSchemaAssemblyName}", true); + templates = Array.CreateInstance(templatesType, 1); + templatesItem = Activator.CreateInstance(templatesType); + templatesItem.SetPublicInstancePropertyValue("ID", $"CONTAINER-{template.Id}"); + } + + private IOrderedEnumerable> GetSerializersForCurrentContext(SerializerScope scope, + Func sortingSelector) + { + // Get all serializers to run in automated mode, ordered by sortingSelector + var currentAssembly = this.GetType().Assembly; + + XMLPnPSchemaVersion currentSchemaVersion = GetCurrentSchemaVersion(); + + var serializers = currentAssembly.GetTypes() + // Get all the serializers + .Where(t => t.GetInterface(typeof(IPnPSchemaSerializer).FullName) != null + && t.BaseType.Name == typeof(Xml.PnPBaseSchemaSerializer<>).Name) + // Filter out those that are not targeting the current schema version or that are not in scope Template + .Where(t => + { + var a = t.GetCustomAttributes(false).FirstOrDefault(); + return (a.MinimalSupportedSchemaVersion <= currentSchemaVersion && a.Scope == scope); + }) + // Order the remainings by supported schema version descendant, to get first the newest ones + .OrderByDescending(s => + { + var a = s.GetCustomAttributes(false).FirstOrDefault(); + return (a.MinimalSupportedSchemaVersion); + } + ) + // Group those with the same target type (which is the first generic Type argument) + .GroupBy(t => t.BaseType.GenericTypeArguments.FirstOrDefault()?.FullName) + // Order the result by SerializationSequence + .OrderBy(g => + { + var maxInGroup = g.OrderByDescending(s => + { + var a = s.GetCustomAttributes(false).FirstOrDefault(); + return (a.MinimalSupportedSchemaVersion); + } + ).FirstOrDefault(); + return sortingSelector(maxInGroup.GetCustomAttributes(false).FirstOrDefault()); + }); + return serializers; + } + + private static void InvokeSerializers(ProvisioningTemplate template, object persistenceTemplate, + IOrderedEnumerable> serializers, SerializationAction action) + { + foreach (var group in serializers) + { + // Get the first serializer only for each group (i.e. the most recent one for the current schema) + var serializerType = group.FirstOrDefault(); + if (serializerType != null) + { + // Create an instance of the serializer + var serializer = Activator.CreateInstance(serializerType) as IPnPSchemaSerializer; + if (serializer != null) + { + // And run the Deserialize/Serialize method + if (action == SerializationAction.Serialize) + { + serializer.Serialize(template, persistenceTemplate); + } + else + { + serializer.Deserialize(persistenceTemplate, template); + } + } + } + } + } + + /// + /// Retrieves the current XML Schema version + /// + /// The current XML schema version + private static XMLPnPSchemaVersion GetCurrentSchemaVersion() + { + var currentSchemaTemplateNamespace = typeof(TSchemaTemplate).Namespace; + var currentSchemaVersionString = $"V{currentSchemaTemplateNamespace.Substring(currentSchemaTemplateNamespace.IndexOf(".Xml.") + 6)}"; + var currentSchemaVersion = (XMLPnPSchemaVersion)Enum.Parse(typeof(XMLPnPSchemaVersion), currentSchemaVersionString); + return currentSchemaVersion; + } + + #endregion + } +} diff --git a/src/lib/PnP.Framework/Provisioning/Providers/Json/JsonPnPSchemaFormatter.cs b/src/lib/PnP.Framework/Provisioning/Providers/Json/JsonPnPSchemaFormatter.cs new file mode 100644 index 000000000..72a2d5773 --- /dev/null +++ b/src/lib/PnP.Framework/Provisioning/Providers/Json/JsonPnPSchemaFormatter.cs @@ -0,0 +1,138 @@ +using Newtonsoft.Json; +using PnP.Framework.Provisioning.Providers.Xml; +using System; + +namespace PnP.Framework.Provisioning.Providers.Json +{ + /// + /// Helper class that abstracts from any specific version of JsonPnPSchemaFormatter + /// + public class JsonPnPSchemaFormatter : ITemplateFormatter + { + private TemplateProviderBase _provider; + + public void Initialize(TemplateProviderBase provider) + { + this._provider = provider; + } + + #region Static methods and properties + + /// + /// Static method to retrieve an instance of the latest JsonPnPSchemaFormatter + /// + public static ITemplateFormatter LatestFormatter() + { + return LatestFormatter(null); + } + + /// + /// Static method to retrieve an instance of the latest JsonPnPSchemaFormatter + /// + /// Custom to use + /// + public static ITemplateFormatter LatestFormatter(JsonSerializerSettings serializerSettings) + { + return new JsonPnPSchemaV202209Serializer(serializerSettings); + } + + + /// + /// Static method to retrieve a specific JsonPnPSchemaFormatter instance + /// + /// Provisioning schema version + /// + public static ITemplateFormatter GetSpecificFormatter(XMLPnPSchemaVersion version) + { + return GetSpecificFormatter(version, null); + } + + public static ITemplateFormatter GetSpecificFormatter(XMLPnPSchemaVersion version, JsonSerializerSettings serializerSettings) + { + switch (version) + { + case XMLPnPSchemaVersion.V202209: + default: + return (new JsonPnPSchemaV202209Serializer(serializerSettings)); + } + } + /// + /// Static method to retrieve a specific JsonPnPSchemaFormatter instance + /// + /// + /// + public static ITemplateFormatter GetSpecificFormatter(string namespaceUri) + { + return GetSpecificFormatter(namespaceUri, null); + } + + public static ITemplateFormatter GetSpecificFormatter(string namespaceUri, JsonSerializerSettings serializerSettings) + { + switch (namespaceUri) + { + case XMLConstants.PROVISIONING_SCHEMA_NAMESPACE_2022_09: + default: + return new JsonPnPSchemaV202209Serializer(serializerSettings); + } + } + + #endregion + + public bool IsValid(System.IO.Stream template) + { + return true; + } + + public System.IO.Stream ToFormattedTemplate(Model.ProvisioningTemplate template) + { + String jsonString = JsonConvert.SerializeObject(template, new BasePermissionsConverter()); + Byte[] jsonBytes = System.Text.Encoding.Unicode.GetBytes(jsonString); + System.IO.MemoryStream jsonStream = new System.IO.MemoryStream(jsonBytes) + { + Position = 0 + }; + + return (jsonStream); + } + + public Model.ProvisioningTemplate ToProvisioningTemplate(System.IO.Stream template) + { + return (this.ToProvisioningTemplate(template, null)); + } + + public Model.ProvisioningTemplate ToProvisioningTemplate(System.IO.Stream template, string identifier) + { + using System.IO.StreamReader sr = new System.IO.StreamReader(template, System.Text.Encoding.Unicode); + String jsonString = sr.ReadToEnd(); + + Model.ProvisioningTemplate result = JsonConvert.DeserializeObject(jsonString, new BasePermissionsConverter()); + return (result); + } + + public System.IO.Stream ToFormattedHierarchy(Model.ProvisioningHierarchy hierarchy) + { + if (hierarchy == null) + { + throw new ArgumentNullException(nameof(hierarchy)); + } + + String jsonString = JsonConvert.SerializeObject(hierarchy, new BasePermissionsConverter()); + Byte[] jsonBytes = System.Text.Encoding.Unicode.GetBytes(jsonString); + System.IO.MemoryStream jsonStream = new System.IO.MemoryStream(jsonBytes) + { + Position = 0 + }; + + return (jsonStream); + } + + public Model.ProvisioningHierarchy ToProvisioningHierarchy(System.IO.Stream hierarchy) + { + using System.IO.StreamReader sr = new System.IO.StreamReader(hierarchy, System.Text.Encoding.Unicode); + String jsonString = sr.ReadToEnd(); + Model.ProvisioningHierarchy result = JsonConvert.DeserializeObject(jsonString, new BasePermissionsConverter()); + return (result); + } + + } +} diff --git a/src/lib/PnP.Framework/Provisioning/Providers/Json/JsonPnPSchemaV202209Serializer.cs b/src/lib/PnP.Framework/Provisioning/Providers/Json/JsonPnPSchemaV202209Serializer.cs new file mode 100644 index 000000000..0edd534fd --- /dev/null +++ b/src/lib/PnP.Framework/Provisioning/Providers/Json/JsonPnPSchemaV202209Serializer.cs @@ -0,0 +1,24 @@ +using Newtonsoft.Json; + +namespace PnP.Framework.Provisioning.Providers.Json +{ + internal class JsonPnPSchemaV202209Serializer : JsonPnPSchemaBaseSerializer + { + + public JsonPnPSchemaV202209Serializer() + : this(null) { } + + public JsonPnPSchemaV202209Serializer(JsonSerializerSettings serializerSettings) + : base(serializerSettings) { } + + protected override void DeserializeTemplate(object persistenceTemplate, Model.ProvisioningTemplate template) + { + base.DeserializeTemplate(persistenceTemplate, template); + } + + protected override void SerializeTemplate(Model.ProvisioningTemplate template, object persistenceTemplate) + { + base.SerializeTemplate(template, persistenceTemplate); + } + } +} diff --git a/src/lib/PnP.Framework/Provisioning/Providers/Json/JsonTemplateProvider.cs b/src/lib/PnP.Framework/Provisioning/Providers/Json/JsonTemplateProvider.cs index 22cc28aed..f24c02800 100644 --- a/src/lib/PnP.Framework/Provisioning/Providers/Json/JsonTemplateProvider.cs +++ b/src/lib/PnP.Framework/Provisioning/Providers/Json/JsonTemplateProvider.cs @@ -25,11 +25,43 @@ protected JsonTemplateProvider(FileConnectorBase connector) #region Base class overrides + public override ProvisioningHierarchy GetHierarchy(string uri) + { + return this.GetHierarchy(uri, null); + } + + public override ProvisioningHierarchy GetHierarchy(string uri, IProvisioningHierarchyFormatter formatter) + { + if (uri == null) + { + throw new ArgumentNullException(nameof(uri)); + } + + if (formatter is null) + { + var latestFormatter = JsonPnPSchemaFormatter.LatestFormatter(); + latestFormatter.Initialize(this); + formatter = (IProvisioningHierarchyFormatter)latestFormatter; + } + + ProvisioningHierarchy result = null; + + var stream = this.Connector.GetFileStream(uri); + + if (stream != null) + { + result = formatter.ToProvisioningHierarchy(stream); + } + + return (result); + + } + public override List GetTemplates() { - var formatter = new JsonPnPFormatter(); + var formatter = new JsonPnPSchemaFormatter(); formatter.Initialize(this); - return (this.GetTemplates(formatter)); + return this.GetTemplates(formatter); } public override List GetTemplates(ITemplateFormatter formatter) @@ -58,11 +90,6 @@ public override List GetTemplates(ITemplateFormatter forma return (result); } - public override ProvisioningHierarchy GetHierarchy(string uri) - { - throw new NotImplementedException(); - } - public override ProvisioningTemplate GetTemplate(string uri) { return (this.GetTemplate(uri, (ITemplateProviderExtension[])null)); @@ -97,7 +124,7 @@ public override ProvisioningTemplate GetTemplate(string uri, string identifier, if (formatter == null) { - formatter = new JsonPnPFormatter(); + formatter = new JsonPnPSchemaFormatter(); formatter.Initialize(this); } @@ -158,7 +185,7 @@ public override ProvisioningTemplate GetTemplate(Stream stream, string identifie if (formatter == null) { - formatter = new JsonPnPFormatter(); + formatter = new JsonPnPSchemaFormatter(); formatter.Initialize(this); } @@ -179,7 +206,34 @@ public override ProvisioningTemplate GetTemplate(Stream stream, string identifie public override void Save(ProvisioningHierarchy hierarchy) { - throw new NotImplementedException(); + this.SaveAs(hierarchy, this.Uri); + } + + public override void SaveAs(ProvisioningHierarchy hierarchy, string uri, ITemplateFormatter formatter = null) + { + if (hierarchy is null) + { + throw new ArgumentNullException(nameof(hierarchy)); + } + + if (uri == null) + { + throw new ArgumentNullException(nameof(uri)); + } + + if (formatter is null) + { + formatter = JsonPnPSchemaFormatter.LatestFormatter(); + } + + var stream = ((IProvisioningHierarchyFormatter)formatter).ToFormattedHierarchy(hierarchy); + + this.Connector.SaveFileStream(uri, stream); + + if (this.Connector is ICommitableFileConnector) + { + ((ICommitableFileConnector)this.Connector).Commit(); + } } public override void Save(ProvisioningTemplate template) @@ -202,11 +256,6 @@ public override void Save(ProvisioningTemplate template, ITemplateFormatter form this.SaveAs(template, this.Uri, formatter, extensions); } - public override void SaveAs(ProvisioningHierarchy hierarchy, string uri, ITemplateFormatter formatter = null) - { - throw new NotImplementedException(); - } - public override void SaveAs(ProvisioningTemplate template, string uri) { this.SaveAs(template, uri, (ITemplateProviderExtension[])null); @@ -236,7 +285,7 @@ public override void SaveAs(ProvisioningTemplate template, string uri, ITemplate if (formatter == null) { - formatter = new JsonPnPFormatter(); + formatter = new JsonPnPSchemaFormatter(); } SaveToConnector(template, uri, formatter, extensions); diff --git a/src/lib/PnP.Framework/Provisioning/Providers/Markdown/MarkdownTemplateProvider.cs b/src/lib/PnP.Framework/Provisioning/Providers/Markdown/MarkdownTemplateProvider.cs index da5fae971..0cfb0bb5c 100644 --- a/src/lib/PnP.Framework/Provisioning/Providers/Markdown/MarkdownTemplateProvider.cs +++ b/src/lib/PnP.Framework/Provisioning/Providers/Markdown/MarkdownTemplateProvider.cs @@ -25,6 +25,15 @@ protected MarkdownTemplateProvider(FileConnectorBase connector) #region Base class overrides + public override ProvisioningHierarchy GetHierarchy(string uri) + { + throw new NotImplementedException(); + } + public override ProvisioningHierarchy GetHierarchy(string uri, IProvisioningHierarchyFormatter formatter) + { + throw new NotImplementedException(); + } + public override List GetTemplates() { var formatter = new MarkdownPnPFormatter(); @@ -58,11 +67,6 @@ public override List GetTemplates(ITemplateFormatter forma return (result); } - public override ProvisioningHierarchy GetHierarchy(string uri) - { - throw new NotImplementedException(); - } - public override ProvisioningTemplate GetTemplate(string uri) { return (this.GetTemplate(uri, (ITemplateProviderExtension[])null)); diff --git a/src/lib/PnP.Framework/Provisioning/Providers/TemplateProviderBase.cs b/src/lib/PnP.Framework/Provisioning/Providers/TemplateProviderBase.cs index 38554bb3d..56c2a4658 100644 --- a/src/lib/PnP.Framework/Provisioning/Providers/TemplateProviderBase.cs +++ b/src/lib/PnP.Framework/Provisioning/Providers/TemplateProviderBase.cs @@ -105,6 +105,21 @@ public String Uri #endregion #region Abstract Methods + /// + /// Gets ProvisioningHierarchy + /// + /// The source uri + /// Returns a ProvisioningHierarchy + public abstract ProvisioningHierarchy GetHierarchy(string uri); + + /// + /// Gets ProvisioningHierarchy + /// + /// The source uri + /// Provisioning Hierarchy formatter + /// Returns a ProvisioningHierarchy + public abstract ProvisioningHierarchy GetHierarchy(string uri, IProvisioningHierarchyFormatter formatter); + /// /// Gets list of ProvisioningTemplates /// @@ -118,13 +133,6 @@ public String Uri /// Returns collection of ProvisioningTemplate public abstract List GetTemplates(ITemplateFormatter formatter); - /// - /// Gets ProvisioningHierarchy - /// - /// The source uri - /// Returns a ProvisioningHierarchy - public abstract ProvisioningHierarchy GetHierarchy(string uri); - /// /// Gets ProvisioningTemplate /// diff --git a/src/lib/PnP.Framework/Provisioning/Providers/Xml/XMLTemplateProvider.cs b/src/lib/PnP.Framework/Provisioning/Providers/Xml/XMLTemplateProvider.cs index 6e96a1688..b646a161c 100644 --- a/src/lib/PnP.Framework/Provisioning/Providers/Xml/XMLTemplateProvider.cs +++ b/src/lib/PnP.Framework/Provisioning/Providers/Xml/XMLTemplateProvider.cs @@ -29,6 +29,36 @@ protected XMLTemplateProvider(FileConnectorBase connector) #region Base class overrides + public override ProvisioningHierarchy GetHierarchy(string uri) + { + return this.GetHierarchy(uri, null); + } + + public override ProvisioningHierarchy GetHierarchy(string uri, IProvisioningHierarchyFormatter formatter) + { + if (uri == null) + { + throw new ArgumentNullException(nameof(uri)); + } + + ProvisioningHierarchy result = null; + + var stream = this.Connector.GetFileStream(uri); + + if (stream != null) + { + if (formatter == null) + { + ITemplateFormatter specificFormatter = new XMLPnPSchemaFormatter().GetSpecificFormatterInternal(ref stream); + specificFormatter.Initialize(this); + + formatter = (IProvisioningHierarchyFormatter)specificFormatter; + } + result = formatter.ToProvisioningHierarchy(stream); + } + return (result); + } + public override List GetTemplates() { var formatter = new XMLPnPSchemaFormatter(); @@ -71,29 +101,6 @@ public override List GetTemplates(ITemplateFormatter forma return (result); } - public override ProvisioningHierarchy GetHierarchy(string uri) - { - if (uri == null) - { - throw new ArgumentNullException(nameof(uri)); - } - - ProvisioningHierarchy result = null; - - var stream = this.Connector.GetFileStream(uri); - - if (stream != null) - { - var formatter = new XMLPnPSchemaFormatter(); - - ITemplateFormatter specificFormatter = formatter.GetSpecificFormatterInternal(ref stream); - specificFormatter.Initialize(this); - result = ((IProvisioningHierarchyFormatter)specificFormatter).ToProvisioningHierarchy(stream); - } - - return (result); - } - public override ProvisioningTemplate GetTemplate(string uri) { return (this.GetTemplate(uri, (ITemplateProviderExtension[])null));