Skip to content
This repository was archived by the owner on Dec 13, 2021. It is now read-only.

Commit 6c244d2

Browse files
authored
Merge pull request #235 from umco/dev/type-cache
Proposal of a "type-cache"
2 parents e6b9d57 + 26b4b03 commit 6c244d2

File tree

8 files changed

+412
-206
lines changed

8 files changed

+412
-206
lines changed
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Reflection;
5+
using Umbraco.Core.Models;
6+
7+
namespace Our.Umbraco.Ditto
8+
{
9+
internal sealed class DittoTypeInfo
10+
{
11+
public static DittoTypeInfo Create<T>()
12+
{
13+
return Create(typeof(T));
14+
}
15+
16+
public static DittoTypeInfo Create(Type type)
17+
{
18+
var config = new DittoTypeInfo
19+
{
20+
TargetType = type
21+
};
22+
23+
// constructor
24+
//
25+
// Check the validity of the mapped type constructor as early as possible.
26+
var constructorParams = type.GetConstructorParameters();
27+
if (constructorParams != null)
28+
{
29+
// Is it a PublishedContent or similar?
30+
if (constructorParams.Length == 1 && constructorParams[0].ParameterType == typeof(IPublishedContent))
31+
{
32+
config.ConstructorHasPublishedContentParameter = true;
33+
}
34+
35+
if (constructorParams.Length == 0 || config.ConstructorHasPublishedContentParameter)
36+
{
37+
config.ConstructorIsValid = true;
38+
}
39+
}
40+
41+
// No valid constructor, but see if the value can be cast to the type
42+
if (type.IsAssignableFrom(typeof(IPublishedContent)))
43+
{
44+
config.IsOfTypePublishedContent = true;
45+
config.ConstructorIsValid = true;
46+
}
47+
48+
// attributes
49+
//
50+
config.CustomAttributes = type.GetCustomAttributes();
51+
52+
// cacheable
53+
//
54+
var conversionHandlers = new List<DittoConversionHandler>();
55+
56+
foreach (var attr in config.CustomAttributes)
57+
{
58+
if (attr is DittoCacheAttribute)
59+
{
60+
config.IsCacheable = true;
61+
config.CacheInfo = (DittoCacheAttribute)attr;
62+
}
63+
64+
// Check for class level DittoConversionHandlerAttribute
65+
if (attr is DittoConversionHandlerAttribute)
66+
{
67+
conversionHandlers.Add(((DittoConversionHandlerAttribute)attr).HandlerType.GetInstance<DittoConversionHandler>());
68+
}
69+
}
70+
71+
// properties (lazy & eager)
72+
//
73+
var lazyProperties = new List<DittoTypePropertyInfo>();
74+
var lazyPropertyNames = new List<string>();
75+
var eagerProperties = new List<DittoTypePropertyInfo>();
76+
77+
// Collect all the properties of the given type and loop through writable ones.
78+
foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
79+
{
80+
if (property.CanWrite == false)
81+
continue;
82+
83+
if (property.GetSetMethod() == null)
84+
continue;
85+
86+
var attributes = new List<Attribute>(property.GetCustomAttributes());
87+
88+
if (attributes.Any(x => x is DittoIgnoreAttribute))
89+
continue;
90+
91+
var propertyType = property.PropertyType;
92+
93+
var propertyConfig = new DittoTypePropertyInfo
94+
{
95+
CustomAttributes = attributes,
96+
PropertyInfo = property,
97+
};
98+
99+
100+
// Check the property for any explicit processor attributes
101+
var processors = attributes.Where(x => x is DittoProcessorAttribute).Cast<DittoProcessorAttribute>().ToList();
102+
if (processors.Count == 0)
103+
{
104+
// Adds the default processor for this conversion
105+
var defaultProcessor = DittoProcessorRegistry.Instance.GetDefaultProcessorFor(type);
106+
// Forces the default processor to be the very first processor
107+
defaultProcessor.Order = -1;
108+
processors.Add(defaultProcessor);
109+
}
110+
111+
// Check for registered processors on the property's type
112+
processors.AddRange(propertyType.GetCustomAttributes<DittoProcessorAttribute>(true));
113+
114+
// Check any type arguments in generic enumerable types.
115+
// This should return false against typeof(string) etc also.
116+
if (propertyType.IsCastableEnumerableType())
117+
{
118+
propertyConfig.IsEnumerable = true;
119+
propertyConfig.EnumerableType = propertyType.GenericTypeArguments[0];
120+
121+
processors.AddRange(propertyConfig.EnumerableType.GetCustomAttributes<DittoProcessorAttribute>(true));
122+
}
123+
124+
// Sort the order of the processors
125+
processors.Sort((x, y) => x.Order.CompareTo(y.Order));
126+
127+
// Check for globally registered processors
128+
processors.AddRange(DittoProcessorRegistry.Instance.GetRegisteredProcessorAttributesFor(propertyType));
129+
130+
// Add any core processors onto the end
131+
processors.AddRange(DittoProcessorRegistry.Instance.GetPostProcessorAttributes());
132+
133+
propertyConfig.Processors = processors;
134+
135+
136+
var propertyCache = attributes.Where(x => x is DittoCacheAttribute).Cast<DittoCacheAttribute>().FirstOrDefault();
137+
if (propertyCache != null)
138+
{
139+
propertyConfig.IsCacheable = true;
140+
propertyConfig.CacheInfo = propertyCache;
141+
}
142+
143+
// detect if the property should be lazy-loaded
144+
if (property.ShouldAttemptLazyLoad())
145+
{
146+
lazyProperties.Add(propertyConfig);
147+
lazyPropertyNames.Add(property.Name);
148+
}
149+
else
150+
{
151+
eagerProperties.Add(propertyConfig);
152+
}
153+
}
154+
155+
if (lazyProperties.Count > 0)
156+
{
157+
config.ConstructorRequiresProxyType = true;
158+
config.HasLazyProperties = true;
159+
config.LazyPropertyNames = lazyPropertyNames; // lazyProperties.Select(x => x.PropertyInfo.Name);
160+
config.LazyProperties = lazyProperties;
161+
}
162+
163+
if (eagerProperties.Count > 0)
164+
{
165+
config.HasEagerProperties = true;
166+
config.EagerProperties = eagerProperties;
167+
}
168+
169+
170+
171+
// conversion handlers
172+
//
173+
174+
// Check for globaly registered handlers
175+
foreach (var handlerType in DittoConversionHandlerRegistry.Instance.GetRegisteredHandlerTypesFor(type))
176+
{
177+
conversionHandlers.Add(handlerType.GetInstance<DittoConversionHandler>());
178+
}
179+
180+
config.ConversionHandlers = conversionHandlers;
181+
182+
var before = new List<MethodInfo>();
183+
var after = new List<MethodInfo>();
184+
185+
// Check for method level DittoOnConvert[ing|ed]Attribute
186+
foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
187+
{
188+
var ing = method.GetCustomAttribute<DittoOnConvertingAttribute>();
189+
var ed = method.GetCustomAttribute<DittoOnConvertedAttribute>();
190+
191+
if (ing == null && ed == null)
192+
continue;
193+
194+
var p = method.GetParameters();
195+
if (p.Length == 1 && p[0].ParameterType == typeof(DittoConversionHandlerContext))
196+
{
197+
if (ing != null)
198+
before.Add(method);
199+
200+
if (ed != null)
201+
after.Add(method);
202+
}
203+
}
204+
205+
config.ConvertingMethods = before;
206+
config.ConvertedMethods = after;
207+
208+
209+
return config;
210+
}
211+
212+
public Type TargetType { get; set; }
213+
214+
public bool IsCacheable { get; set; }
215+
public DittoCacheAttribute CacheInfo { get; set; }
216+
217+
public bool ConstructorIsValid { get; set; }
218+
public bool ConstructorHasPublishedContentParameter { get; set; }
219+
public bool ConstructorRequiresProxyType { get; set; }
220+
221+
public bool IsOfTypePublishedContent { get; set; }
222+
223+
public IEnumerable<Attribute> CustomAttributes { get; set; }
224+
225+
public bool HasLazyProperties { get; set; }
226+
public IEnumerable<DittoTypePropertyInfo> LazyProperties { get; set; }
227+
public IEnumerable<string> LazyPropertyNames { get; set; }
228+
229+
public bool HasEagerProperties { get; set; }
230+
public IEnumerable<DittoTypePropertyInfo> EagerProperties { get; set; }
231+
232+
public IEnumerable<DittoConversionHandler> ConversionHandlers { get; set; }
233+
public IEnumerable<MethodInfo> ConvertingMethods { get; set; }
234+
public IEnumerable<MethodInfo> ConvertedMethods { get; set; }
235+
236+
internal sealed class DittoTypePropertyInfo
237+
{
238+
public bool IsCacheable { get; set; }
239+
public DittoCacheAttribute CacheInfo { get; set; }
240+
241+
public IEnumerable<Attribute> CustomAttributes { get; set; }
242+
243+
public IEnumerable<DittoProcessorAttribute> Processors { get; set; }
244+
public PropertyInfo PropertyInfo { get; set; }
245+
246+
public bool IsEnumerable { get; set; }
247+
public Type EnumerableType { get; set; }
248+
}
249+
}
250+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using System;
2+
using System.Collections.Concurrent;
3+
4+
namespace Our.Umbraco.Ditto
5+
{
6+
internal static class DittoTypeInfoCache
7+
{
8+
private static readonly ConcurrentDictionary<Type, DittoTypeInfo> _cache = new ConcurrentDictionary<Type, DittoTypeInfo>();
9+
10+
public static void Add<T>()
11+
{
12+
Add(typeof(T));
13+
}
14+
15+
public static void Add(Type type)
16+
{
17+
_cache.TryAdd(type, DittoTypeInfo.Create(type));
18+
}
19+
20+
public static void Clear<T>()
21+
{
22+
Clear(typeof(T));
23+
}
24+
25+
public static void Clear(Type type)
26+
{
27+
_cache.TryRemove(type, out DittoTypeInfo config);
28+
}
29+
30+
public static DittoTypeInfo GetOrAdd<T>()
31+
{
32+
return GetOrAdd(typeof(T));
33+
}
34+
35+
public static DittoTypeInfo GetOrAdd(Type type)
36+
{
37+
if (_cache.TryGetValue(type, out DittoTypeInfo config) == false)
38+
{
39+
config = DittoTypeInfo.Create(type);
40+
_cache.TryAdd(type, config);
41+
}
42+
43+
return config;
44+
}
45+
}
46+
}

src/Our.Umbraco.Ditto/Extensions/Internal/PropertyInfoExtensions.cs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,21 @@ public static bool IsMappable(this PropertyInfo source)
7272
/// <returns>True if a lazy load attempt should be make</returns>
7373
public static bool ShouldAttemptLazyLoad(this PropertyInfo source)
7474
{
75-
if (source.HasCustomAttribute<DittoIgnoreAttribute>())
75+
var hasPropertyAttribute = source.HasCustomAttribute<DittoLazyAttribute>();
76+
var hasClassAttribute = source.DeclaringType.HasCustomAttribute<DittoLazyAttribute>();
77+
var isVirtualAndOverridable = source.IsVirtualAndOverridable();
78+
79+
if (isVirtualAndOverridable && (hasPropertyAttribute || hasClassAttribute || Ditto.LazyLoadStrategy == LazyLoad.AllVirtuals))
7680
{
77-
return false;
81+
return true;
82+
}
83+
else if (isVirtualAndOverridable == false && hasPropertyAttribute)
84+
{
85+
// Ensure it's a virtual property (Only relevant to property level lazy loads)
86+
throw new InvalidOperationException($"Lazy property '{source.Name}' of type '{source.DeclaringType.AssemblyQualifiedName}' must be declared virtual in order to be lazy loadable.");
7887
}
7988

80-
return source.HasCustomAttribute<DittoLazyAttribute>() ||
81-
((source.DeclaringType.HasCustomAttribute<DittoLazyAttribute>() || Ditto.LazyLoadStrategy == LazyLoad.AllVirtuals)
82-
&& source.IsVirtualAndOverridable());
89+
return false;
8390
}
8491
}
8592
}

0 commit comments

Comments
 (0)