Refactoring Import/Export optimizations

--HG--
branch : 1.x
This commit is contained in:
Sebastien Ros
2013-04-26 16:41:02 -07:00
parent b3924ba1d8
commit 64525b2f82
18 changed files with 162 additions and 226 deletions

View File

@@ -39,58 +39,6 @@ namespace Orchard.Tests.ContentManagement {
var identity2 = new ContentIdentity(@"/foo=bar/abaz=quux\/fr\\ed=foo/yarg=yiu=foo");
Assert.That(comparer.Equals(identity2, new ContentIdentity(identity2.ToString())));
}
[Test]
public void IdentitiesCanBeAddedWithNoPriority() {
var contentIdentity = new ContentIdentity();
contentIdentity.Add("identifier", "CAEEB150-F5E9-481D-9FF9-3053D23329C1");
contentIdentity.Add("alias", "some-unique-item-alias/sub-alias");
Assert.That("/alias=some-unique-item-alias\\/sub-alias/identifier=CAEEB150-F5E9-481D-9FF9-3053D23329C1", Is.EqualTo(contentIdentity.ToString()));
}
[Test]
public void IdentitiesCanBeAddedWithSamePriority() {
var contentIdentity = new ContentIdentity();
contentIdentity.Add("identifier", "CAEEB150-F5E9-481D-9FF9-3053D23329C1", 5);
contentIdentity.Add("alias", "some-unique-item-alias/sub-alias", 5);
var contentIdentityNegative = new ContentIdentity();
contentIdentityNegative.Add("identifier", "CAEEB150-F5E9-481D-9FF9-3053D23329C1", -5);
contentIdentityNegative.Add("alias", "some-unique-item-alias/sub-alias", -5);
Assert.That("/alias=some-unique-item-alias\\/sub-alias/identifier=CAEEB150-F5E9-481D-9FF9-3053D23329C1", Is.EqualTo(contentIdentity.ToString()));
Assert.That("/alias=some-unique-item-alias\\/sub-alias/identifier=CAEEB150-F5E9-481D-9FF9-3053D23329C1", Is.EqualTo(contentIdentityNegative.ToString()));
}
[Test]
public void LowestPriorityIdentityIsIgnored() {
var contentIdentity = new ContentIdentity();
contentIdentity.Add("alias", "some-unique-item-alias/sub-alias", 0);
contentIdentity.Add("identifier", "CAEEB150-F5E9-481D-9FF9-3053D23329C1", 5);
var contentIdentityNegative = new ContentIdentity();
contentIdentityNegative.Add("alias", "some-unique-item-alias/sub-alias", -5);
contentIdentityNegative.Add("identifier", "CAEEB150-F5E9-481D-9FF9-3053D23329C1", 0);
Assert.That("/identifier=CAEEB150-F5E9-481D-9FF9-3053D23329C1", Is.EqualTo(contentIdentity.ToString()));
Assert.That("/identifier=CAEEB150-F5E9-481D-9FF9-3053D23329C1", Is.EqualTo(contentIdentityNegative.ToString()));
}
[Test]
public void HighestPriorityIdentityIsRetained() {
var contentIdentity = new ContentIdentity();
contentIdentity.Add("identifier", "CAEEB150-F5E9-481D-9FF9-3053D23329C1", 5);
contentIdentity.Add("alias", "some-unique-item-alias/sub-alias", 0);
var contentIdentityNegative = new ContentIdentity();
contentIdentityNegative.Add("identifier", "CAEEB150-F5E9-481D-9FF9-3053D23329C1", 0);
contentIdentityNegative.Add("alias", "some-unique-item-alias/sub-alias", -5);
Assert.That("/identifier=CAEEB150-F5E9-481D-9FF9-3053D23329C1", Is.EqualTo(contentIdentity.ToString()));
Assert.That("/identifier=CAEEB150-F5E9-481D-9FF9-3053D23329C1", Is.EqualTo(contentIdentityNegative.ToString()));
}
}
}

View File

@@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Moq;
using Moq;
using NUnit.Framework;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Records;
@@ -42,7 +39,6 @@ namespace Orchard.Tests.ContentManagement {
_contentManager.Setup(m => m.New(It.IsAny<string>())).Returns(draftItem5);
_contentManager.Setup(m => m.HasResolverForIdentity(It.Is<ContentIdentity>(id => id.Get("ItemId") != null))).Returns(true);
_contentManager.Setup(m => m.ResolveIdentity(It.Is<ContentIdentity>(id => id.Get("ItemId") == "1"))).Returns(publishedItem);
}
#endregion

View File

@@ -1,5 +1,4 @@
using System;
using System.Linq;
using JetBrains.Annotations;
using Orchard.ContentManagement;
using Orchard.Core.Common.Models;
@@ -9,14 +8,10 @@ using Orchard.ContentManagement.Handlers;
namespace Orchard.Core.Common.Handlers {
[UsedImplicitly]
public class IdentityPartHandler : ContentHandler {
private readonly IContentManager _contentManager;
public IdentityPartHandler(IRepository<IdentityPartRecord> identityRepository,
IContentManager contentManager) {
Filters.Add(StorageFilter.For(identityRepository));
OnInitializing<IdentityPart>(AssignIdentity);
_contentManager = contentManager;
}
protected void AssignIdentity(InitializingContentContext context, IdentityPart part) {
@@ -30,24 +25,5 @@ namespace Orchard.Core.Common.Handlers {
context.Metadata.Identity.Add("Identifier", part.Identifier);
}
}
protected override void RegisterIdentityResolver(RegisterIdentityResolverContext context) {
context.Register(id => id.Get("Identifier") != null, ResolveIdentity);
}
private ContentItem ResolveIdentity(ContentIdentity identity) {
var identifier = identity.Get("Identifier");
if (identifier == null) {
return null;
}
var comparer = new ContentIdentity.ContentIdentityEqualityComparer();
return _contentManager
.Query<IdentityPart, IdentityPartRecord>()
.Where(p => p.Identifier == identifier)
.List<ContentItem>()
.FirstOrDefault(c => comparer.Equals(identity, _contentManager.GetItemMetadata(c).Identity));
}
}
}

View File

@@ -0,0 +1,40 @@
using System.Collections.Generic;
using System.Linq;
using Orchard.ContentManagement;
using Orchard.Core.Common.Models;
namespace Orchard.Core.Common.Services {
public class IdentifierResolverSelector : IIdentityResolverSelector {
private readonly IContentManager _contentManager;
public IdentifierResolverSelector(IContentManager contentManager) {
_contentManager = contentManager;
}
public IdentityResolverSelectorResult GetResolver(ContentIdentity contentIdentity) {
if (contentIdentity.Has("Identifier")) {
return new IdentityResolverSelectorResult {
Priority = 5,
Resolve = ResolveIdentity
};
}
return null;
}
private IEnumerable<ContentItem> ResolveIdentity(ContentIdentity identity) {
var identifier = identity.Get("Identifier");
if (identifier == null) {
return null;
}
var comparer = new ContentIdentity.ContentIdentityEqualityComparer();
return _contentManager
.Query<IdentityPart, IdentityPartRecord>()
.Where(p => p.Identifier == identifier)
.List<ContentItem>()
.Where(c => comparer.Equals(identity, _contentManager.GetItemMetadata(c).Identity));
}
}
}

View File

@@ -79,6 +79,7 @@
<Compile Include="Common\Models\IdentityPart.cs" />
<Compile Include="Common\ResourceManifest.cs" />
<Compile Include="Common\Routes.cs" />
<Compile Include="Common\Services\IdentifierResolverSelector.cs" />
<Compile Include="Common\Services\XmlRpcHandler.cs" />
<Compile Include="Common\DateEditor\DateEditorViewModel.cs" />
<Compile Include="Common\Settings\TextFieldSettingsEvents.cs" />

View File

@@ -95,6 +95,7 @@
<ItemGroup>
<Compile Include="Commands\AutorouteCommands.cs" />
<Compile Include="ResourceManifest.cs" />
<Compile Include="Services\AliasResolverSelector.cs" />
<Compile Include="Services\IRouteEvents.cs" />
<Compile Include="Settings\AutorouteSettingsEvents.cs" />
<Compile Include="Settings\RoutePattern.cs" />

View File

@@ -0,0 +1,37 @@
using System.Collections.Generic;
using Orchard.Autoroute.Models;
using Orchard.ContentManagement;
namespace Orchard.Autoroute.Services {
public class AliasResolverSelector : IIdentityResolverSelector {
private readonly IContentManager _contentManager;
public AliasResolverSelector(IContentManager contentManager) {
_contentManager = contentManager;
}
public IdentityResolverSelectorResult GetResolver(ContentIdentity contentIdentity) {
if (contentIdentity.Has("alias")) {
return new IdentityResolverSelectorResult {
Priority = 0,
Resolve = ResolveIdentity
};
}
return null;
}
private IEnumerable<ContentItem> ResolveIdentity(ContentIdentity identity) {
var identifier = identity.Get("alias");
if (identifier == null) {
return null;
}
return _contentManager
.Query<AutoroutePart, AutoroutePartRecord>()
.Where(p => p.DisplayAlias == identifier)
.List<ContentItem>();
}
}
}

View File

@@ -6,8 +6,6 @@ using System.Text;
namespace Orchard.ContentManagement {
public class ContentIdentity {
private readonly Dictionary<string, string> _dictionary;
private int _currentIdentityPriority = int.MinValue; //initialise to lowest possible priority
private string _encodedIdentity = null;
public ContentIdentity() {
_dictionary = new Dictionary<string, string>();
@@ -20,50 +18,36 @@ namespace Orchard.ContentManagement {
foreach (var identityEntry in identityEntries) {
var keyValuePair = GetIdentityKeyValue(identityEntry);
if (keyValuePair != null) {
Add(keyValuePair.Value.Key, UnencodeIdentityValue(keyValuePair.Value.Value));
_dictionary.Add(keyValuePair.Value.Key, UnencodeIdentityValue(keyValuePair.Value.Value));
}
}
}
}
public void Add(string name, string value) {
Add(name, value, 0/*default priority*/);
}
public void Add(string name, string value, int priority) {
if (priority < _currentIdentityPriority)
return; //lower priority, so ignore
if (priority > _currentIdentityPriority)
_dictionary.Clear(); //higher, so override and delete existing
//save the current highest priority
_currentIdentityPriority = priority;
//if equal or higher priority add to identity collection
if (_dictionary.ContainsKey(name)) {
_dictionary[name] = value;
}
else {
_dictionary.Add(name, value);
else {
_dictionary.Add(name, value);
}
_encodedIdentity = null;
}
public string Get(string name) {
return _dictionary.ContainsKey(name) ? _dictionary[name] : null;
return Has(name) ? _dictionary[name] : null;
}
public bool Has(string name) {
return _dictionary.ContainsKey(name);
}
public override string ToString() {
if (_encodedIdentity != null)
return _encodedIdentity;
var stringBuilder = new StringBuilder();
foreach (var key in _dictionary.Keys.OrderBy(key => key)) {
var escapedIdentity = EncodeIdentityValue(_dictionary[key]);
stringBuilder.Append("/" + key + "=" + escapedIdentity);
}
_encodedIdentity = stringBuilder.ToString();
return _encodedIdentity;
return stringBuilder.ToString();
}
private static string EncodeIdentityValue(string identityValue) {
@@ -161,11 +145,14 @@ namespace Orchard.ContentManagement {
public class ContentIdentityEqualityComparer : IEqualityComparer<ContentIdentity> {
public bool Equals(ContentIdentity contentIdentity1, ContentIdentity contentIdentity2) {
return contentIdentity1.ToString().Equals(contentIdentity2.ToString());
if (contentIdentity1._dictionary.Keys.Count != contentIdentity2._dictionary.Keys.Count)
return false;
return contentIdentity1._dictionary.OrderBy(kvp => kvp.Key).SequenceEqual(contentIdentity2._dictionary.OrderBy(kvp => kvp.Key));
}
public int GetHashCode(ContentIdentity contentIdentity) {
return contentIdentity.ToString().GetHashCode();
return contentIdentity._dictionary.OrderBy(kvp => kvp.Key).ToString().GetHashCode();
}
}

View File

@@ -33,6 +33,8 @@ namespace Orchard.ContentManagement {
private readonly Lazy<IContentDisplay> _contentDisplay;
private readonly Lazy<ISessionLocator> _sessionLocator;
private readonly Lazy<IEnumerable<IContentHandler>> _handlers;
private readonly Lazy<IEnumerable<IIdentityResolverSelector>> _identityResolverSelectors;
private const string Published = "Published";
private const string Draft = "Draft";
@@ -46,7 +48,8 @@ namespace Orchard.ContentManagement {
Func<IContentManagerSession> contentManagerSession,
Lazy<IContentDisplay> contentDisplay,
Lazy<ISessionLocator> sessionLocator,
Lazy<IEnumerable<IContentHandler>> handlers) {
Lazy<IEnumerable<IContentHandler>> handlers,
Lazy<IEnumerable<IIdentityResolverSelector>> identityResolverSelectors) {
_context = context;
_contentTypeRepository = contentTypeRepository;
_contentItemRepository = contentItemRepository;
@@ -54,6 +57,7 @@ namespace Orchard.ContentManagement {
_contentDefinitionManager = contentDefinitionManager;
_cacheManager = cacheManager;
_contentManagerSession = contentManagerSession;
_identityResolverSelectors = identityResolverSelectors;
_handlers = handlers;
_contentDisplay = contentDisplay;
_sessionLocator = sessionLocator;
@@ -514,18 +518,42 @@ namespace Orchard.ContentManagement {
}
}
public bool HasResolverForIdentity(ContentIdentity contentIdentity) {
var context = new RegisterIdentityResolverContext();
Handlers.Invoke(handler => handler.RegisterIdentityResolver(context), Logger);
return context.HasResolverForIdentity(contentIdentity);
}
/// <summary>
/// Lookup for a content item based on a <see cref="ContentIdentity"/>. If multiple
/// resolvers can give a result, the one with the highest priority is used. As soon as
/// only one content item is returned from resolvers, it is returned as the result.
/// </summary>
/// <param name="contentIdentity">The <see cref="ContentIdentity"/> instance to lookup</param>
/// <returns>The <see cref="ContentItem"/> instance represented by the identity object.</returns>
public ContentItem ResolveIdentity(ContentIdentity contentIdentity) {
var context = new RegisterIdentityResolverContext();
Handlers.Invoke(handler => handler.RegisterIdentityResolver(context), Logger);
return context.ResolveIdentity(contentIdentity);
}
var resolvers = _identityResolverSelectors.Value
.Select(x => x.GetResolver(contentIdentity))
.Where(x => x != null)
.OrderByDescending(x => x.Priority);
if (!resolvers.Any())
return null;
IEnumerable<ContentItem> contentItems = null;
foreach (var resolver in resolvers) {
var resolved = resolver.Resolve(contentIdentity).ToArray();
// first pass
if (contentItems == null) {
contentItems = resolved;
}
else { // subsquent passes means we need to intersect
contentItems = contentItems.Intersect(resolved).ToArray();
}
if (contentItems.Count() == 1) {
return contentItems.First();
}
}
return contentItems.FirstOrDefault();
}
public ContentItemMetadata GetItemMetadata(IContent content) {
var context = new GetContentItemMetadataContext {
ContentItem = content.ContentItem,
@@ -605,7 +633,7 @@ namespace Orchard.ContentManagement {
var identity = elementId.Value;
var status = element.Attribute("Status");
var item = importContentSession.Get(identity, VersionOptions.DraftRequired, XmlConvert.DecodeName(element.Name.LocalName));
var item = importContentSession.Get(identity, VersionOptions.Latest, XmlConvert.DecodeName(element.Name.LocalName));
if (item == null) {
item = New(XmlConvert.DecodeName(element.Name.LocalName));
if (status != null && status.Value == "Draft") {

View File

@@ -327,10 +327,6 @@ namespace Orchard.ContentManagement.Handlers {
Exported(context);
}
void IContentHandler.RegisterIdentityResolver(RegisterIdentityResolverContext context) {
RegisterIdentityResolver(context);
}
void IContentHandler.GetContentItemMetadata(GetContentItemMetadataContext context) {
foreach (var filter in Filters.OfType<IContentTemplateFilter>())
filter.GetContentItemMetadata(context);
@@ -386,7 +382,6 @@ namespace Orchard.ContentManagement.Handlers {
protected virtual void Exporting(ExportContentContext context) { }
protected virtual void Exported(ExportContentContext context) { }
protected virtual void RegisterIdentityResolver(RegisterIdentityResolverContext context) { }
protected virtual void GetItemMetadata(GetContentItemMetadataContext context) { }
protected virtual void BuildDisplayShape(BuildDisplayContext context) { }
protected virtual void BuildEditorShape(BuildEditorContext context) { }

View File

@@ -24,7 +24,6 @@
public virtual void Exporting(ExportContentContext context) {}
public virtual void Exported(ExportContentContext context) {}
public virtual void RegisterIdentityResolver(RegisterIdentityResolverContext context) { }
public virtual void GetContentItemMetadata(GetContentItemMetadataContext context) {}
public virtual void BuildDisplay(BuildDisplayContext context) {}
public virtual void BuildEditor(BuildEditorContext context) {}

View File

@@ -24,7 +24,6 @@
void Exporting(ExportContentContext context);
void Exported(ExportContentContext context);
void RegisterIdentityResolver(RegisterIdentityResolverContext context);
void GetContentItemMetadata(GetContentItemMetadataContext context);
void BuildDisplay(BuildDisplayContext context);
void BuildEditor(BuildEditorContext context);

View File

@@ -1,28 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Orchard.ContentManagement.Handlers {
public class RegisterIdentityResolverContext {
private readonly IList<Tuple<Func<ContentIdentity, bool>, Func<ContentIdentity, ContentItem>>> _resolvers;
public RegisterIdentityResolverContext() {
_resolvers = new List<Tuple<Func<ContentIdentity, bool>, Func<ContentIdentity, ContentItem>>>();
}
public void Register(Func<ContentIdentity, bool> isResolverForIdentity, Func<ContentIdentity, ContentItem> resolveIdentity) {
_resolvers.Add(new Tuple<Func<ContentIdentity, bool>, Func<ContentIdentity, ContentItem>>(
isResolverForIdentity, resolveIdentity));
}
public bool HasResolverForIdentity(ContentIdentity identity) {
return _resolvers.Any(r => r.Item1(identity));
}
public ContentItem ResolveIdentity(ContentIdentity identity) {
return _resolvers.Where(r => r.Item1(identity))
.Select(r => r.Item2(identity))
.FirstOrDefault(r => r != null);
}
}
}

View File

@@ -89,7 +89,6 @@ namespace Orchard.ContentManagement {
GroupInfo GetEditorGroupInfo(IContent contentItem, string groupInfoId);
GroupInfo GetDisplayGroupInfo(IContent contentItem, string groupInfoId);
bool HasResolverForIdentity(ContentIdentity contentIdentity);
ContentItem ResolveIdentity(ContentIdentity contentIdentity);
/// <summary>

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
namespace Orchard.ContentManagement {
public class IdentityResolverSelectorResult {
public int Priority { get; set; }
public Func<ContentIdentity, IEnumerable<ContentItem>> Resolve { get; set; }
}
public interface IIdentityResolverSelector : IDependency {
IdentityResolverSelectorResult GetResolver(ContentIdentity contentIdentity);
}
}

View File

@@ -20,11 +20,6 @@ namespace Orchard.ContentManagement {
private int _batchSize = int.MaxValue;
private int _currentIndex;
//for identity prefetch
private const int BulkPage = 128;
private bool _firstRequest = true;
private bool _allIdentitiesPrefetched = false;
public ImportContentSession(IContentManager contentManager) {
_identityComparer = new ContentIdentity.ContentIdentityEqualityComparer();
_contentManager = contentManager;
@@ -86,15 +81,6 @@ namespace Orchard.ContentManagement {
public ContentItem Get(string id, VersionOptions versionOptions, string contentTypeHint = null) {
var contentIdentity = new ContentIdentity(id);
if (_firstRequest) {
_firstRequest = false;
//If we know we have identities without a resolver
//just load all up-front
if (HasIdentitiesSetWithoutResolver()) {
PrefetchAllIdentities(true);
}
}
// lookup in local cache
if (_identities.ContainsKey(contentIdentity)) {
if (_draftVersionRecordIds.ContainsKey(_identities[contentIdentity])) {
@@ -111,32 +97,23 @@ namespace Orchard.ContentManagement {
}
}
if (!_allIdentitiesPrefetched) {
ContentItem existingItem = null;
ContentItem existingItem = _contentManager.ResolveIdentity(contentIdentity);
//try retrieve the item using handlers to resolve, otherwise fall back to full scan
if (_contentManager.HasResolverForIdentity(contentIdentity)) {
existingItem = _contentManager.ResolveIdentity(contentIdentity);
//ensure we have the correct version
if (existingItem != null) {
existingItem = _contentManager.Get(existingItem.Id, versionOptions);
}
//ensure we have the correct version
if (existingItem != null)
existingItem = _contentManager.Get(existingItem.Id, versionOptions);
}
else {
//may be a contentidentity without a resolver and all identities have not
//been prefetched yet e.g. is a dependency without resolver.
PrefetchAllIdentities(false);
if (_identities.ContainsKey(contentIdentity))
existingItem = _contentManager.Get(_identities[contentIdentity], versionOptions);
}
if (existingItem == null && _identities.ContainsKey(contentIdentity)) {
existingItem = _contentManager.Get(_identities[contentIdentity], versionOptions);
}
if (existingItem != null) {
_identities[contentIdentity] = existingItem.Id;
if (versionOptions.IsDraftRequired) {
_draftVersionRecordIds[existingItem.Id] = existingItem.VersionRecord.Id;
}
return existingItem;
if (existingItem != null) {
_identities[contentIdentity] = existingItem.Id;
if (versionOptions.IsDraftRequired) {
_draftVersionRecordIds[existingItem.Id] = existingItem.VersionRecord.Id;
}
return existingItem;
}
//create item if not found and draft was requested, or it is found later in the import queue
@@ -166,37 +143,5 @@ namespace Orchard.ContentManagement {
return null;
}
private bool HasIdentitiesSetWithoutResolver() {
return _allIdentitiesForImport.Any(id => !_contentManager.HasResolverForIdentity(id));
}
private void PrefetchAllIdentities(bool clearContentManager) {
if (_allIdentitiesPrefetched)
return;
IEnumerable<ContentItem> block;
int lastIndex = 0;
while ((block = _contentManager.HqlQuery()
.ForVersion(VersionOptions.Latest)
.OrderBy(x => x.ContentItemVersion(), x => x.Asc("Id"))
.Slice(lastIndex, BulkPage)).Any()) {
foreach (var item in block) {
lastIndex++;
var identity = _contentManager.GetItemMetadata(item).Identity;
// store mapping for later
_identities[identity] = item.Id;
}
//Clearing the ContentManger after import has started can cause errors
if (clearContentManager) {
_contentManager.Clear();
}
}
_allIdentitiesPrefetched = true;
}
}
}

View File

@@ -54,7 +54,7 @@ namespace Orchard.Localization.Services {
.Where(x => x != null)
.OrderByDescending(x => x.Priority);
if ( requestCulture.Count() < 1 )
if ( !requestCulture.Any() )
return String.Empty;
foreach (var culture in requestCulture) {

View File

@@ -153,7 +153,6 @@
<Compile Include="ContentManagement\Aspects\ILocalizableAspect.cs" />
<Compile Include="ContentManagement\ContentIdentity.cs" />
<Compile Include="ContentManagement\DefaultHqlQuery.cs" />
<Compile Include="ContentManagement\Handlers\RegisterIdentityResolverContext.cs" />
<Compile Include="ContentManagement\Handlers\UpdateContentContext.cs" />
<Compile Include="ContentManagement\IHqlExpression.cs" />
<Compile Include="ContentManagement\IHqlQuery.cs" />
@@ -166,6 +165,7 @@
<Compile Include="ContentManagement\Handlers\BuildShapeContext.cs" />
<Compile Include="ContentManagement\Handlers\ExportContentContext.cs" />
<Compile Include="ContentManagement\Handlers\ImportContentContext.cs" />
<Compile Include="ContentManagement\IIdentityResolverSelector.cs" />
<Compile Include="ContentManagement\ImportContentSession.cs" />
<Compile Include="ContentManagement\MetaData\Services\ISettingsFormatter.cs" />
<Compile Include="ContentManagement\QueryHints.cs" />