WIP - new IEntityXmlSerializer, IPackageCreation, PackageActionRunner, large refactor of entity serialization, no more IPackagingService.Export methods, ports legacy package creation code to new IPackageCreation, more more ExportEventArgs (makes no sense)

This commit is contained in:
Shannon
2019-01-10 12:44:57 +11:00
parent e27b873a5a
commit 5f972384b1
64 changed files with 1543 additions and 1435 deletions

View File

@@ -0,0 +1,608 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Web;
using System.Xml.Linq;
using Newtonsoft.Json;
using Umbraco.Core.Composing;
using Umbraco.Core.Models;
using Umbraco.Core.Strings;
namespace Umbraco.Core.Services.Implement
{
/// <summary>
/// Serializes entities to XML
/// </summary>
internal class EntityXmlSerializer : IEntityXmlSerializer
{
private readonly IContentTypeService _contentTypeService;
private readonly IMediaService _mediaService;
private readonly IContentService _contentService;
private readonly IDataTypeService _dataTypeService;
private readonly IUserService _userService;
private readonly ILocalizationService _localizationService;
private readonly IEnumerable<IUrlSegmentProvider> _urlSegmentProviders;
public EntityXmlSerializer(
IContentService contentService,
IMediaService mediaService,
IDataTypeService dataTypeService,
IUserService userService,
ILocalizationService localizationService,
IContentTypeService contentTypeService,
IEnumerable<IUrlSegmentProvider> urlSegmentProviders)
{
_contentTypeService = contentTypeService;
_mediaService = mediaService;
_contentService = contentService;
_dataTypeService = dataTypeService;
_userService = userService;
_localizationService = localizationService;
_urlSegmentProviders = urlSegmentProviders;
}
/// <summary>
/// Exports an IContent item as an XElement.
/// </summary>
public XElement Serialize(IContent content,
bool published,
bool withDescendants = false) //fixme take care of usage! only used for the packager
{
if (content == null) throw new ArgumentNullException(nameof(content));
// nodeName should match Casing.SafeAliasWithForcingCheck(content.ContentType.Alias);
var nodeName = content.ContentType.Alias.ToSafeAliasWithForcingCheck();
var xml = SerializeContentBase(content, content.GetUrlSegment(_urlSegmentProviders), nodeName, published);
xml.Add(new XAttribute("nodeType", content.ContentType.Id));
xml.Add(new XAttribute("nodeTypeAlias", content.ContentType.Alias));
xml.Add(new XAttribute("creatorName", content.GetCreatorProfile(_userService)?.Name ?? "??"));
//xml.Add(new XAttribute("creatorID", content.CreatorId));
xml.Add(new XAttribute("writerName", content.GetWriterProfile(_userService)?.Name ?? "??"));
xml.Add(new XAttribute("writerID", content.WriterId));
xml.Add(new XAttribute("template", content.Template?.Id.ToString(CultureInfo.InvariantCulture) ?? "0"));
xml.Add(new XAttribute("isPublished", content.Published));
if (withDescendants)
{
const int pageSize = 500;
var page = 0;
var total = long.MaxValue;
while(page * pageSize < total)
{
var children = _contentService.GetPagedChildren(content.Id, page++, pageSize, out total);
SerializeChildren(children, xml, published);
}
}
return xml;
}
/// <summary>
/// Exports an IMedia item as an XElement.
/// </summary>
public XElement Serialize(
IMedia media,
bool withDescendants = false)
{
if (_mediaService == null) throw new ArgumentNullException(nameof(_mediaService));
if (_dataTypeService == null) throw new ArgumentNullException(nameof(_dataTypeService));
if (_userService == null) throw new ArgumentNullException(nameof(_userService));
if (_localizationService == null) throw new ArgumentNullException(nameof(_localizationService));
if (media == null) throw new ArgumentNullException(nameof(media));
if (_urlSegmentProviders == null) throw new ArgumentNullException(nameof(_urlSegmentProviders));
// nodeName should match Casing.SafeAliasWithForcingCheck(content.ContentType.Alias);
var nodeName = media.ContentType.Alias.ToSafeAliasWithForcingCheck();
const bool published = false; // always false for media
var xml = SerializeContentBase(media, media.GetUrlSegment(_urlSegmentProviders), nodeName, published);
xml.Add(new XAttribute("nodeType", media.ContentType.Id));
xml.Add(new XAttribute("nodeTypeAlias", media.ContentType.Alias));
//xml.Add(new XAttribute("creatorName", media.GetCreatorProfile(userService).Name));
//xml.Add(new XAttribute("creatorID", media.CreatorId));
xml.Add(new XAttribute("writerName", media.GetWriterProfile(_userService)?.Name ?? string.Empty));
xml.Add(new XAttribute("writerID", media.WriterId));
//xml.Add(new XAttribute("template", 0)); // no template for media
if (withDescendants)
{
const int pageSize = 500;
var page = 0;
var total = long.MaxValue;
while (page * pageSize < total)
{
var children = _mediaService.GetPagedChildren(media.Id, page++, pageSize, out total);
SerializeChildren(children, xml);
}
}
return xml;
}
/// <summary>
/// Exports an IMember item as an XElement.
/// </summary>
public XElement Serialize(IMember member)
{
// nodeName should match Casing.SafeAliasWithForcingCheck(content.ContentType.Alias);
var nodeName = member.ContentType.Alias.ToSafeAliasWithForcingCheck();
const bool published = false; // always false for member
var xml = SerializeContentBase(member, "", nodeName, published);
xml.Add(new XAttribute("nodeType", member.ContentType.Id));
xml.Add(new XAttribute("nodeTypeAlias", member.ContentType.Alias));
// what about writer/creator/version?
xml.Add(new XAttribute("loginName", member.Username));
xml.Add(new XAttribute("email", member.Email));
xml.Add(new XAttribute("icon", member.ContentType.Icon));
return xml;
}
/// <summary>
/// Exports a list of Data Types
/// </summary>
/// <param name="dataTypeDefinitions">List of data types to export</param>
/// <returns><see cref="XElement"/> containing the xml representation of the IDataTypeDefinition objects</returns>
public XElement Serialize(IEnumerable<IDataType> dataTypeDefinitions)
{
var container = new XElement("DataTypes");
foreach (var dataTypeDefinition in dataTypeDefinitions)
{
container.Add(Serialize(dataTypeDefinition));
}
return container;
}
public XElement Serialize(IDataType dataType)
{
var xml = new XElement("DataType");
xml.Add(new XAttribute("Name", dataType.Name));
//The 'ID' when exporting is actually the property editor alias (in pre v7 it was the IDataType GUID id)
xml.Add(new XAttribute("Id", dataType.EditorAlias));
xml.Add(new XAttribute("Definition", dataType.Key));
xml.Add(new XAttribute("DatabaseType", dataType.DatabaseType.ToString()));
xml.Add(new XAttribute("Configuration", JsonConvert.SerializeObject(dataType.Configuration)));
var folderNames = string.Empty;
if (dataType.Level != 1)
{
//get url encoded folder names
var folders = _dataTypeService.GetContainers(dataType)
.OrderBy(x => x.Level)
.Select(x => HttpUtility.UrlEncode(x.Name));
folderNames = string.Join("/", folders.ToArray());
}
if (string.IsNullOrWhiteSpace(folderNames) == false)
xml.Add(new XAttribute("Folders", folderNames));
return xml;
}
/// <summary>
/// Exports a list of <see cref="IDictionaryItem"/> items to xml as an <see cref="XElement"/>
/// </summary>
/// <param name="dictionaryItem">List of dictionary items to export</param>
/// <param name="includeChildren">Optional boolean indicating whether or not to include children</param>
/// <returns><see cref="XElement"/> containing the xml representation of the IDictionaryItem objects</returns>
public XElement Serialize(IEnumerable<IDictionaryItem> dictionaryItem, bool includeChildren = true)
{
var xml = new XElement("DictionaryItems");
foreach (var item in dictionaryItem)
{
xml.Add(Serialize(item, includeChildren));
}
return xml;
}
/// <summary>
/// Exports a single <see cref="IDictionaryItem"/> item to xml as an <see cref="XElement"/>
/// </summary>
/// <param name="dictionaryItem">Dictionary Item to export</param>
/// <param name="includeChildren">Optional boolean indicating whether or not to include children</param>
/// <returns><see cref="XElement"/> containing the xml representation of the IDictionaryItem object</returns>
public XElement Serialize(IDictionaryItem dictionaryItem, bool includeChildren)
{
var xml = Serialize(dictionaryItem);
if (includeChildren)
{
var children = _localizationService.GetDictionaryItemChildren(dictionaryItem.Key);
foreach (var child in children)
{
xml.Add(Serialize(child, true));
}
}
return xml;
}
private XElement Serialize(IDictionaryItem dictionaryItem)
{
var xml = new XElement("DictionaryItem", new XAttribute("Key", dictionaryItem.ItemKey));
foreach (var translation in dictionaryItem.Translations)
{
xml.Add(new XElement("Value",
new XAttribute("LanguageId", translation.Language.Id),
new XAttribute("LanguageCultureAlias", translation.Language.IsoCode),
new XCData(translation.Value)));
}
return xml;
}
public XElement Serialize(Stylesheet stylesheet)
{
var xml = new XElement("Stylesheet",
new XElement("Name", stylesheet.Alias),
new XElement("FileName", stylesheet.Path),
new XElement("Content", new XCData(stylesheet.Content)));
var props = new XElement("Properties");
xml.Add(props);
foreach (var prop in stylesheet.Properties)
{
props.Add(new XElement("Property",
new XElement("Name", prop.Name),
new XElement("Alias", prop.Alias),
new XElement("Value", prop.Value)));
}
return xml;
}
/// <summary>
/// Exports a list of <see cref="ILanguage"/> items to xml as an <see cref="XElement"/>
/// </summary>
/// <param name="languages">List of Languages to export</param>
/// <returns><see cref="XElement"/> containing the xml representation of the ILanguage objects</returns>
public XElement Serialize(IEnumerable<ILanguage> languages)
{
var xml = new XElement("Languages");
foreach (var language in languages)
{
xml.Add(Serialize(language));
}
return xml;
}
public XElement Serialize(ILanguage language)
{
var xml = new XElement("Language",
new XAttribute("Id", language.Id),
new XAttribute("CultureAlias", language.IsoCode),
new XAttribute("FriendlyName", language.CultureName));
return xml;
}
public XElement Serialize(ITemplate template)
{
var xml = new XElement("Template");
xml.Add(new XElement("Name", template.Name));
xml.Add(new XElement("Alias", template.Alias));
xml.Add(new XElement("Design", new XCData(template.Content)));
if (template is Template concreteTemplate && concreteTemplate.MasterTemplateId != null)
{
if (concreteTemplate.MasterTemplateId.IsValueCreated &&
concreteTemplate.MasterTemplateId.Value != default)
{
xml.Add(new XElement("Master", concreteTemplate.MasterTemplateId.ToString()));
xml.Add(new XElement("MasterAlias", concreteTemplate.MasterTemplateAlias));
}
}
return xml;
}
/// <summary>
/// Exports a list of <see cref="ITemplate"/> items to xml as an <see cref="XElement"/>
/// </summary>
/// <param name="templates"></param>
/// <returns></returns>
public XElement Serialize(IEnumerable<ITemplate> templates)
{
var xml = new XElement("Templates");
foreach (var item in templates)
{
xml.Add(Serialize(item));
}
return xml;
}
public XElement Serialize(IMediaType mediaType)
{
var info = new XElement("Info",
new XElement("Name", mediaType.Name),
new XElement("Alias", mediaType.Alias),
new XElement("Icon", mediaType.Icon),
new XElement("Thumbnail", mediaType.Thumbnail),
new XElement("Description", mediaType.Description),
new XElement("AllowAtRoot", mediaType.AllowedAsRoot.ToString()));
var masterContentType = mediaType.CompositionAliases().FirstOrDefault();
if (masterContentType != null)
info.Add(new XElement("Master", masterContentType));
var structure = new XElement("Structure");
foreach (var allowedType in mediaType.AllowedContentTypes)
{
structure.Add(new XElement("MediaType", allowedType.Alias));
}
var genericProperties = new XElement("GenericProperties"); // actually, all of them
foreach (var propertyType in mediaType.PropertyTypes)
{
var definition = _dataTypeService.GetDataType(propertyType.DataTypeId);
var propertyGroup = propertyType.PropertyGroupId == null // true generic property
? null
: mediaType.PropertyGroups.FirstOrDefault(x => x.Id == propertyType.PropertyGroupId.Value);
var genericProperty = new XElement("GenericProperty",
new XElement("Name", propertyType.Name),
new XElement("Alias", propertyType.Alias),
new XElement("Type", propertyType.PropertyEditorAlias),
new XElement("Definition", definition.Key),
new XElement("Tab", propertyGroup == null ? "" : propertyGroup.Name),
new XElement("Mandatory", propertyType.Mandatory.ToString()),
new XElement("Validation", propertyType.ValidationRegExp),
new XElement("Description", new XCData(propertyType.Description)));
genericProperties.Add(genericProperty);
}
var tabs = new XElement("Tabs");
foreach (var propertyGroup in mediaType.PropertyGroups)
{
var tab = new XElement("Tab",
new XElement("Id", propertyGroup.Id.ToString(CultureInfo.InvariantCulture)),
new XElement("Caption", propertyGroup.Name),
new XElement("SortOrder", propertyGroup.SortOrder));
tabs.Add(tab);
}
var xml = new XElement("MediaType",
info,
structure,
genericProperties,
tabs);
return xml;
}
/// <summary>
/// Exports a list of <see cref="IMacro"/> items to xml as an <see cref="XElement"/>
/// </summary>
/// <param name="macros">Macros to export</param>
/// <returns><see cref="XElement"/> containing the xml representation of the IMacro objects</returns>
public XElement Serialize(IEnumerable<IMacro> macros)
{
var xml = new XElement("Macros");
foreach (var item in macros)
{
xml.Add(Serialize(item));
}
return xml;
}
public XElement Serialize(IMacro macro)
{
var xml = new XElement("macro");
xml.Add(new XElement("name", macro.Name));
xml.Add(new XElement("alias", macro.Alias));
xml.Add(new XElement("macroType", macro.MacroType));
xml.Add(new XElement("macroSource", macro.MacroSource));
xml.Add(new XElement("useInEditor", macro.UseInEditor.ToString()));
xml.Add(new XElement("dontRender", macro.DontRender.ToString()));
xml.Add(new XElement("refreshRate", macro.CacheDuration.ToString(CultureInfo.InvariantCulture)));
xml.Add(new XElement("cacheByMember", macro.CacheByMember.ToString()));
xml.Add(new XElement("cacheByPage", macro.CacheByPage.ToString()));
var properties = new XElement("properties");
foreach (var property in macro.Properties)
{
properties.Add(new XElement("property",
new XAttribute("name", property.Name),
new XAttribute("alias", property.Alias),
new XAttribute("sortOrder", property.SortOrder),
new XAttribute("propertyType", property.EditorAlias)));
}
xml.Add(properties);
return xml;
}
public XElement Serialize(IContentType contentType)
{
var info = new XElement("Info",
new XElement("Name", contentType.Name),
new XElement("Alias", contentType.Alias),
new XElement("Icon", contentType.Icon),
new XElement("Thumbnail", contentType.Thumbnail),
new XElement("Description", contentType.Description),
new XElement("AllowAtRoot", contentType.AllowedAsRoot.ToString()),
new XElement("IsListView", contentType.IsContainer.ToString()));
var masterContentType = contentType.ContentTypeComposition.FirstOrDefault(x => x.Id == contentType.ParentId);
if(masterContentType != null)
info.Add(new XElement("Master", masterContentType.Alias));
var compositionsElement = new XElement("Compositions");
var compositions = contentType.ContentTypeComposition;
foreach (var composition in compositions)
{
compositionsElement.Add(new XElement("Composition", composition.Alias));
}
info.Add(compositionsElement);
var allowedTemplates = new XElement("AllowedTemplates");
foreach (var template in contentType.AllowedTemplates)
{
allowedTemplates.Add(new XElement("Template", template.Alias));
}
info.Add(allowedTemplates);
if (contentType.DefaultTemplate != null && contentType.DefaultTemplate.Id != 0)
info.Add(new XElement("DefaultTemplate", contentType.DefaultTemplate.Alias));
else
info.Add(new XElement("DefaultTemplate", ""));
var structure = new XElement("Structure");
foreach (var allowedType in contentType.AllowedContentTypes)
{
structure.Add(new XElement("DocumentType", allowedType.Alias));
}
var genericProperties = new XElement("GenericProperties"); // actually, all of them
foreach (var propertyType in contentType.PropertyTypes)
{
var definition = _dataTypeService.GetDataType(propertyType.DataTypeId);
var propertyGroup = propertyType.PropertyGroupId == null // true generic property
? null
: contentType.PropertyGroups.FirstOrDefault(x => x.Id == propertyType.PropertyGroupId.Value);
var genericProperty = new XElement("GenericProperty",
new XElement("Name", propertyType.Name),
new XElement("Alias", propertyType.Alias),
new XElement("Type", propertyType.PropertyEditorAlias),
new XElement("Definition", definition.Key),
new XElement("Tab", propertyGroup == null ? "" : propertyGroup.Name),
new XElement("SortOrder", propertyType.SortOrder),
new XElement("Mandatory", propertyType.Mandatory.ToString()),
propertyType.ValidationRegExp != null ? new XElement("Validation", propertyType.ValidationRegExp) : null,
propertyType.Description != null ? new XElement("Description", new XCData(propertyType.Description)) : null);
genericProperties.Add(genericProperty);
}
var tabs = new XElement("Tabs");
foreach (var propertyGroup in contentType.PropertyGroups)
{
var tab = new XElement("Tab",
new XElement("Id", propertyGroup.Id.ToString(CultureInfo.InvariantCulture)),
new XElement("Caption", propertyGroup.Name),
new XElement("SortOrder", propertyGroup.SortOrder));
tabs.Add(tab);
}
var xml = new XElement("DocumentType",
info,
structure,
genericProperties,
tabs);
var folderNames = string.Empty;
//don't add folders if this is a child doc type
if (contentType.Level != 1 && masterContentType == null)
{
//get url encoded folder names
var folders = _contentTypeService.GetContainers(contentType)
.OrderBy(x => x.Level)
.Select(x => HttpUtility.UrlEncode(x.Name));
folderNames = string.Join("/", folders.ToArray());
}
if (string.IsNullOrWhiteSpace(folderNames) == false)
xml.Add(new XAttribute("Folders", folderNames));
return xml;
}
// exports an IContentBase (IContent, IMedia or IMember) as an XElement.
private XElement SerializeContentBase(IContentBase contentBase, string urlValue, string nodeName, bool published)
{
var xml = new XElement(nodeName,
new XAttribute("id", contentBase.Id),
new XAttribute("key", contentBase.Key),
new XAttribute("parentID", contentBase.Level > 1 ? contentBase.ParentId : -1),
new XAttribute("level", contentBase.Level),
new XAttribute("creatorID", contentBase.CreatorId),
new XAttribute("sortOrder", contentBase.SortOrder),
new XAttribute("createDate", contentBase.CreateDate.ToString("s")),
new XAttribute("updateDate", contentBase.UpdateDate.ToString("s")),
new XAttribute("nodeName", contentBase.Name),
new XAttribute("urlName", urlValue),
new XAttribute("path", contentBase.Path),
new XAttribute("isDoc", ""));
foreach (var property in contentBase.Properties)
xml.Add(SerializeProperty(property, published));
return xml;
}
// exports a property as XElements.
private IEnumerable<XElement> SerializeProperty(Property property, bool published)
{
var propertyType = property.PropertyType;
// get the property editor for this property and let it convert it to the xml structure
var propertyEditor = Current.PropertyEditors[propertyType.PropertyEditorAlias];
return propertyEditor == null
? Array.Empty<XElement>()
: propertyEditor.GetValueEditor().ConvertDbToXml(property, _dataTypeService, _localizationService, published);
}
// exports an IContent item descendants.
private void SerializeChildren(IEnumerable<IContent> children, XElement xml, bool published)
{
foreach (var child in children)
{
// add the child xml
var childXml = Serialize(child, published);
xml.Add(childXml);
const int pageSize = 500;
var page = 0;
var total = long.MaxValue;
while(page * pageSize < total)
{
var grandChildren = _contentService.GetPagedChildren(child.Id, page++, pageSize, out total);
// recurse
SerializeChildren(grandChildren, childXml, published);
}
}
}
// exports an IMedia item descendants.
private void SerializeChildren(IEnumerable<IMedia> children, XElement xml)
{
foreach (var child in children)
{
// add the child xml
var childXml = Serialize(child);
xml.Add(childXml);
const int pageSize = 500;
var page = 0;
var total = long.MaxValue;
while (page * pageSize < total)
{
var grandChildren = _mediaService.GetPagedChildren(child.Id, page++, pageSize, out total);
// recurse
SerializeChildren(grandChildren, childXml);
}
}
}
}
}