/src/Framework/N2/Persistence/Proxying/InterceptingProxyFactory.cs

https://github.com/lundbeck/n2cms · C# · 212 lines · 175 code · 32 blank · 5 comment · 47 complexity · 268d9fe1f468f7b623fdb057664d40d2 MD5 · raw file

  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Reflection;
  6. using Castle.DynamicProxy;
  7. using N2.Engine;
  8. using N2.Definitions;
  9. namespace N2.Persistence.Proxying
  10. {
  11. public class AttributedProperty
  12. {
  13. public PropertyInfo Property { get; set; }
  14. public IInterceptableProperty Attribute { get; set; }
  15. }
  16. /// <summary>
  17. /// Creates a proxy that rewires auto-generated properties to detail get/set.
  18. /// </summary>
  19. [Service(typeof(IProxyFactory))]
  20. public class InterceptingProxyFactory : EmptyProxyFactory
  21. {
  22. Logger<InterceptingProxyFactory> logger;
  23. class Tuple
  24. {
  25. public Type Type;
  26. public IInterceptorBuilder Builder;
  27. public Func<IInterceptableType, bool>[] UnproxiedSaveSetters;
  28. }
  29. private readonly Dictionary<string, Tuple> types = new Dictionary<string, Tuple>();
  30. private readonly ProxyGenerator generator = new ProxyGenerator();
  31. private readonly Type[] additionalInterfacesToProxy = new Type[] { typeof(IInterceptedType) };
  32. public InterceptingProxyFactory()
  33. {
  34. }
  35. public override void Initialize(IEnumerable<ItemDefinition> interceptedTypes)
  36. {
  37. foreach (var definition in interceptedTypes)
  38. {
  39. var type = definition.ItemType;
  40. var interceptableProperties = GetInterceptableProperties(definition).ToList();
  41. if (interceptableProperties.Count == 0)
  42. continue;
  43. var interceptor = new DetailPropertyInterceptor(type, interceptableProperties);
  44. types[type.FullName] = new Tuple
  45. {
  46. Type = type,
  47. Builder = interceptor,
  48. UnproxiedSaveSetters = GetSaveSetters(interceptableProperties).ToArray()
  49. };
  50. }
  51. }
  52. private IEnumerable<Func<IInterceptableType, bool>> GetSaveSetters(IEnumerable<AttributedProperty> interceptableProperties)
  53. {
  54. foreach (var property in interceptableProperties)
  55. {
  56. var pi = property.Property;
  57. Type propertyType = pi.PropertyType;
  58. string propertyName = pi.Name;
  59. MethodInfo getter = pi.GetGetMethod();
  60. MethodInfo setter = pi.GetSetMethod();
  61. if (property.Attribute.PersistAs == PropertyPersistenceLocation.DetailCollection)
  62. {
  63. if (!typeof(IEnumerable).IsAssignableFrom(propertyType))
  64. throw new InvalidOperationException("The property type of '" + propertyName + "' on '" + pi.DeclaringType + "' does not implement IEnumerable which is required for properties stored in a detail collection");
  65. yield return (interceptable) => StoreDetailCollectionValues(propertyName, getter, interceptable);
  66. }
  67. else if (property.Attribute.PersistAs == PropertyPersistenceLocation.Child)
  68. {
  69. if (typeof(ContentItem).IsAssignableFrom(propertyType))
  70. yield return (interceptable) => StoreChildValue(propertyName, getter, interceptable);
  71. else if (propertyType.IsContentItemEnumeration())
  72. yield return (interceptable) => StoreChildrenValue(propertyName, getter, interceptable);
  73. else
  74. logger.WarnFormat("{0} on {1}.{2} is not an allowed type for Child persistence location. Only property types deriving from ContentItem or IEnmerable<ContentItem> are allowed.", propertyType, pi.DeclaringType.Name, propertyName);
  75. }
  76. else if (property.Attribute.PersistAs == PropertyPersistenceLocation.ValueAccessor)
  77. {
  78. var accessor = property.Attribute as IValueAccessor;
  79. if (accessor == null)
  80. throw new InvalidOperationException("The property '" + propertyName + "' has an attribute '" + property.Attribute.GetType() + "' which specifices PropertyPersistenceLocation.ValueAccessor but the attribute doesn't implement IValueAccessor");
  81. yield return (interceptable) => StoreValueAccessorValue(pi, getter, setter, accessor, interceptable);
  82. }
  83. else
  84. {
  85. yield return (interceptable) => StoreDetailValue(propertyType, propertyName, getter, interceptable);
  86. }
  87. }
  88. }
  89. private bool StoreValueAccessorValue(PropertyInfo property, MethodInfo getter, MethodInfo setter, IValueAccessor accessor, IInterceptableType interceptable)
  90. {
  91. var value = getter.Invoke(interceptable, null);
  92. var context = new ValueAccessorContext
  93. {
  94. Instance = interceptable,
  95. Property = property,
  96. BackingPropertyGetter = () => { throw new NotSupportedException("Getting property not supported while setting"); },
  97. BackingPropertySetter = (v) => setter.Invoke(interceptable, new[] { v })
  98. };
  99. return accessor.SetValue(context, property.Name, value);
  100. }
  101. private static bool StoreChildrenValue(string propertyName, MethodInfo getter, IInterceptableType interceptable)
  102. {
  103. var newChildren = getter.Invoke(interceptable, null) as IEnumerable;
  104. var existingChildren = interceptable.GetChildren(propertyName);
  105. interceptable.SetChildren(propertyName, newChildren);
  106. return (newChildren == null && existingChildren == null)
  107. && !(newChildren == null && existingChildren != null)
  108. && !(newChildren != null && existingChildren == null)
  109. && Enumerable.SequenceEqual(newChildren.OfType<object>(), existingChildren.OfType<object>());
  110. }
  111. private static bool StoreChildValue(string propertyName, MethodInfo getter, IInterceptableType interceptable)
  112. {
  113. var newChild = getter.Invoke(interceptable, null);
  114. var existingChild = interceptable.GetChild(propertyName);
  115. interceptable.SetChild(propertyName, newChild);
  116. return newChild != existingChild;
  117. }
  118. private static bool StoreDetailValue(Type propertyType, string propertyName, MethodInfo getter, IInterceptableType interceptable)
  119. {
  120. object propertyValue = getter.Invoke(interceptable, null);
  121. object detailValue = interceptable.GetValue(propertyName);
  122. if (propertyValue == null && detailValue == null)
  123. return false;
  124. if (propertyValue != null && propertyValue.Equals(detailValue))
  125. return false;
  126. interceptable.SetValue(propertyName, propertyValue, propertyType);
  127. return true;
  128. }
  129. private static bool StoreDetailCollectionValues(string propertyName, MethodInfo getter, IInterceptableType interceptable)
  130. {
  131. IEnumerable propertyValue = (IEnumerable)getter.Invoke(interceptable, null);
  132. var collectionValues = interceptable.GetValues(propertyName);
  133. if (propertyValue == null && collectionValues == null)
  134. return false;
  135. interceptable.SetValues(propertyName, collectionValues);
  136. return true;
  137. }
  138. private IEnumerable<AttributedProperty> GetInterceptableProperties(ItemDefinition definition)
  139. {
  140. // Also include properties on base classes since properties are matched by reference and
  141. // and we want to intercept calls to properties on base classes with the same name
  142. for (Type t = definition.ItemType; t != null; t = t.BaseType)
  143. {
  144. foreach (var property in t.GetProperties())
  145. {
  146. IEnumerable<IInterceptableProperty> attributes;
  147. if (!definition.IsInterceptable(property, out attributes))
  148. continue;
  149. yield return new AttributedProperty { Property = property, Attribute = attributes.First() };
  150. }
  151. }
  152. }
  153. public override object Create(string typeName, object id)
  154. {
  155. Tuple tuple;
  156. if (!types.TryGetValue(typeName, out tuple))
  157. return null;
  158. return generator.CreateClassProxy(tuple.Type, additionalInterfacesToProxy, tuple.Builder.Interceptor);
  159. }
  160. public override bool OnSaving(object instance)
  161. {
  162. return ApplyToDetailsOnUnproxiedInstance(instance);
  163. }
  164. private bool ApplyToDetailsOnUnproxiedInstance(object instance)
  165. {
  166. if (instance is IInterceptedType)
  167. return false;
  168. Tuple tuple;
  169. if (!types.TryGetValue(instance.GetType().FullName, out tuple))
  170. return false;
  171. var interceptable = instance as IInterceptableType;
  172. bool altered = false;
  173. foreach (var setter in tuple.UnproxiedSaveSetters)
  174. {
  175. altered = setter(interceptable) || altered;
  176. }
  177. return altered;
  178. }
  179. }
  180. }