Skip to content

Changed TypeLocator to use ConcurrentDictionary for its cache #700

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,4 @@ Else {
Write-Output "RUNNING dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts --version-suffix=alpha1-$revision"
dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts --version-suffix=alpha1-$revision --include-symbols
CheckLastExitCode
}
}
35 changes: 35 additions & 0 deletions src/JsonApiDotNetCore/Graph/IdentifiableTypeCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using JsonApiDotNetCore.Models;

namespace JsonApiDotNetCore.Graph
{
/// <summary>
/// Used to cache and locate types, to facilitate auto-resource discovery
/// </summary>
internal sealed class IdentifiableTypeCache
{
private readonly ConcurrentDictionary<Assembly, List<ResourceDescriptor>> _typeCache = new ConcurrentDictionary<Assembly, List<ResourceDescriptor>>();

/// <summary>
/// Get all implementations of <see cref="IIdentifiable"/> in the assembly
/// </summary>
public IEnumerable<ResourceDescriptor> GetIdentifiableTypes(Assembly assembly)
{
return _typeCache.GetOrAdd(assembly, asm => FindIdentifiableTypes(asm).ToList());
}

private static IEnumerable<ResourceDescriptor> FindIdentifiableTypes(Assembly assembly)
{
foreach (var type in assembly.GetTypes())
{
if (TypeLocator.TryGetResourceDescriptor(type, out var descriptor))
{
yield return descriptor;
}
}
}
}
}
2 changes: 1 addition & 1 deletion src/JsonApiDotNetCore/Graph/ResourceDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ public ResourceDescriptor(Type resourceType, Type idType)
public Type ResourceType { get; }
public Type IdType { get; }

internal static ResourceDescriptor Empty => new ResourceDescriptor(null, null);
internal static ResourceDescriptor Empty { get; } = new ResourceDescriptor(null, null);
}
}
16 changes: 8 additions & 8 deletions src/JsonApiDotNetCore/Graph/ServiceDiscoveryFacade.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public class ServiceDiscoveryFacade : IServiceDiscoveryFacade
};
private readonly IServiceCollection _services;
private readonly IResourceGraphBuilder _resourceGraphBuilder;
private readonly IdentifiableTypeCache _typeCache = new IdentifiableTypeCache();

public ServiceDiscoveryFacade(IServiceCollection services, IResourceGraphBuilder resourceGraphBuilder)
{
Expand All @@ -67,7 +68,7 @@ public ServiceDiscoveryFacade AddAssembly(Assembly assembly)
{
AddDbContextResolvers(assembly);

var resourceDescriptors = TypeLocator.GetIdentifiableTypes(assembly);
var resourceDescriptors = _typeCache.GetIdentifiableTypes(assembly);
foreach (var resourceDescriptor in resourceDescriptors)
{
AddResource(assembly, resourceDescriptor);
Expand All @@ -77,7 +78,6 @@ public ServiceDiscoveryFacade AddAssembly(Assembly assembly)
return this;
}


public IEnumerable<Type> FindDerivedTypes(Type baseType)
{
return baseType.Assembly.GetTypes().Where(t =>
Expand Down Expand Up @@ -108,9 +108,9 @@ private void AddDbContextResolvers(Assembly assembly)
/// <param name="assembly">The assembly to search for resources in.</param>
public ServiceDiscoveryFacade AddResources(Assembly assembly)
{
var identifiables = TypeLocator.GetIdentifiableTypes(assembly);
foreach (var identifiable in identifiables)
AddResource(assembly, identifiable);
var resourceDescriptors = _typeCache.GetIdentifiableTypes(assembly);
foreach (var resourceDescriptor in resourceDescriptors)
AddResource(assembly, resourceDescriptor);

return this;
}
Expand Down Expand Up @@ -148,7 +148,7 @@ private void AddResourceToGraph(ResourceDescriptor identifiable)
/// <param name="assembly">The assembly to search for resources in.</param>
public ServiceDiscoveryFacade AddServices(Assembly assembly)
{
var resourceDescriptors = TypeLocator.GetIdentifiableTypes(assembly);
var resourceDescriptors = _typeCache.GetIdentifiableTypes(assembly);
foreach (var resourceDescriptor in resourceDescriptors)
{
AddServices(assembly, resourceDescriptor);
Expand All @@ -170,7 +170,7 @@ private void AddServices(Assembly assembly, ResourceDescriptor resourceDescripto
/// <param name="assembly">The assembly to search for resources in.</param>
public ServiceDiscoveryFacade AddRepositories(Assembly assembly)
{
var resourceDescriptors = TypeLocator.GetIdentifiableTypes(assembly);
var resourceDescriptors = _typeCache.GetIdentifiableTypes(assembly);
foreach (var resourceDescriptor in resourceDescriptors)
{
AddRepositories(assembly, resourceDescriptor);
Expand All @@ -186,7 +186,7 @@ private void AddRepositories(Assembly assembly, ResourceDescriptor resourceDescr
RegisterServiceImplementations(assembly, serviceInterface, resourceDescriptor);
}
}

private void RegisterServiceImplementations(Assembly assembly, Type interfaceType, ResourceDescriptor resourceDescriptor)
{
if (resourceDescriptor.IdType == typeof(Guid) && interfaceType.GetTypeInfo().GenericTypeParameters.Length == 1)
Expand Down
27 changes: 1 addition & 26 deletions src/JsonApiDotNetCore/Graph/TypeLocator.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using JsonApiDotNetCore.Extensions;
using JsonApiDotNetCore.Extensions;
using JsonApiDotNetCore.Models;
using System;
using System.Collections.Generic;
Expand All @@ -12,8 +12,6 @@ namespace JsonApiDotNetCore.Graph
/// </summary>
internal static class TypeLocator
{
private static readonly Dictionary<Assembly, List<ResourceDescriptor>> _identifiableTypeCache = new Dictionary<Assembly, List<ResourceDescriptor>>();

/// <summary>
/// Determine whether or not this is a json:api resource by checking if it implements <see cref="IIdentifiable"/>.
/// Returns the status and the resultant id type, either `(true, Type)` OR `(false, null)`
Expand All @@ -24,29 +22,6 @@ public static Type GetIdType(Type resourceType)
return identifiableInterface?.GetGenericArguments()[0];
}

/// <summary>
/// Get all implementations of <see cref="IIdentifiable"/> in the assembly
/// </summary>
public static IEnumerable<ResourceDescriptor> GetIdentifiableTypes(Assembly assembly)
=> (_identifiableTypeCache.TryGetValue(assembly, out _) == false)
? FindIdentifiableTypes(assembly)
: _identifiableTypeCache[assembly];

private static IEnumerable<ResourceDescriptor> FindIdentifiableTypes(Assembly assembly)
{
var descriptors = new List<ResourceDescriptor>();
_identifiableTypeCache[assembly] = descriptors;

foreach (var type in assembly.GetTypes())
{
if (TryGetResourceDescriptor(type, out var descriptor))
{
descriptors.Add(descriptor);
yield return descriptor;
}
}
}

/// <summary>
/// Attempts to get a descriptor of the resource type.
/// </summary>
Expand Down
39 changes: 39 additions & 0 deletions test/UnitTests/Graph/IdentifiableTypeCacheTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using JsonApiDotNetCore.Graph;
using JsonApiDotNetCore.Models;
using UnitTests.Internal;
using Xunit;

namespace UnitTests.Graph
{
public class IdentifiableTypeCacheTests
{
[Fact]
public void GetIdentifiableTypes_Locates_Identifiable_Resource()
{
// Arrange
var resourceType = typeof(Model);
var typeCache = new IdentifiableTypeCache();

// Act
var results = typeCache.GetIdentifiableTypes(resourceType.Assembly);

// Assert
Assert.Contains(results, r => r.ResourceType == resourceType);
}

[Fact]
public void GetIdentifiableTypes_Only_Contains_IIdentifiable_Types()
{
// Arrange
var resourceType = typeof(Model);
var typeCache = new IdentifiableTypeCache();

// Act
var resourceDescriptors = typeCache.GetIdentifiableTypes(resourceType.Assembly);

// Assert
foreach(var resourceDescriptor in resourceDescriptors)
Assert.True(typeof(IIdentifiable).IsAssignableFrom(resourceDescriptor.ResourceType));
}
}
}
27 changes: 0 additions & 27 deletions test/UnitTests/Graph/TypeLocator_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,33 +81,6 @@ public void GetIdType_Correctly_Identifies_NonJsonApiResource()
Assert.Equal(expectedIdType, idType);
}

[Fact]
public void GetIdentifiableTypes_Locates_Identifiable_Resource()
{
// Arrange
var resourceType = typeof(Model);

// Act
var results = TypeLocator.GetIdentifiableTypes(resourceType.Assembly);

// Assert
Assert.Contains(results, r => r.ResourceType == resourceType);
}

[Fact]
public void GetIdentifiableTypes_Only_Contains_IIdentifiable_Types()
{
// Arrange
var resourceType = typeof(Model);

// Act
var resourceDescriptors = TypeLocator.GetIdentifiableTypes(resourceType.Assembly);

// Assert
foreach(var resourceDescriptor in resourceDescriptors)
Assert.True(typeof(IIdentifiable).IsAssignableFrom(resourceDescriptor.ResourceType));
}

[Fact]
public void TryGetResourceDescriptor_Returns_True_If_Type_Is_IIdentifiable()
{
Expand Down