AttributeConventionPack.cs 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. /* Copyright 2010-present MongoDB Inc.
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. using System;
  16. using System.Collections.Generic;
  17. using System.Linq;
  18. using System.Reflection;
  19. using MongoDB.Bson.Serialization.Attributes;
  20. namespace MongoDB.Bson.Serialization.Conventions
  21. {
  22. /// <summary>
  23. /// Convention pack for applying attributes.
  24. /// </summary>
  25. public class AttributeConventionPack : IConventionPack
  26. {
  27. // private static fields
  28. private static readonly AttributeConventionPack __attributeConventionPack = new AttributeConventionPack();
  29. // private fields
  30. private readonly AttributeConvention _attributeConvention;
  31. // constructors
  32. /// <summary>
  33. /// Initializes a new instance of the <see cref="AttributeConventionPack" /> class.
  34. /// </summary>
  35. private AttributeConventionPack()
  36. {
  37. _attributeConvention = new AttributeConvention();
  38. }
  39. // public static properties
  40. /// <summary>
  41. /// Gets the instance.
  42. /// </summary>
  43. public static IConventionPack Instance
  44. {
  45. get { return __attributeConventionPack; }
  46. }
  47. // public properties
  48. /// <summary>
  49. /// Gets the conventions.
  50. /// </summary>
  51. public IEnumerable<IConvention> Conventions
  52. {
  53. get { yield return _attributeConvention; }
  54. }
  55. // nested classes
  56. private class AttributeConvention : ConventionBase, IClassMapConvention, ICreatorMapConvention, IMemberMapConvention, IPostProcessingConvention
  57. {
  58. // public methods
  59. public void Apply(BsonClassMap classMap)
  60. {
  61. foreach (var attribute in classMap.ClassType.GetTypeInfo().GetCustomAttributes(inherit: false).OfType<IBsonClassMapAttribute>())
  62. {
  63. attribute.Apply(classMap);
  64. }
  65. OptInMembersWithBsonMemberMapModifierAttribute(classMap);
  66. OptInMembersWithBsonCreatorMapModifierAttribute(classMap);
  67. IgnoreMembersWithBsonIgnoreAttribute(classMap);
  68. ThrowForDuplicateMemberMapAttributes(classMap);
  69. }
  70. public void Apply(BsonCreatorMap creatorMap)
  71. {
  72. if (creatorMap.MemberInfo != null)
  73. {
  74. foreach (var attribute in creatorMap.MemberInfo.GetCustomAttributes(inherit: false).OfType<IBsonCreatorMapAttribute>())
  75. {
  76. attribute.Apply(creatorMap);
  77. }
  78. }
  79. }
  80. public void Apply(BsonMemberMap memberMap)
  81. {
  82. var attributes = memberMap.MemberInfo.GetCustomAttributes(inherit: false).OfType<IBsonMemberMapAttribute>();
  83. var groupings = attributes.GroupBy(a => (a is BsonSerializerAttribute) ? 1 : 2);
  84. foreach (var grouping in groupings.OrderBy(g => g.Key))
  85. {
  86. foreach (var attribute in grouping)
  87. {
  88. attribute.Apply(memberMap);
  89. }
  90. }
  91. }
  92. public void PostProcess(BsonClassMap classMap)
  93. {
  94. foreach (var attribute in classMap.ClassType.GetTypeInfo().GetCustomAttributes(inherit: false).OfType<IBsonPostProcessingAttribute>())
  95. {
  96. attribute.PostProcess(classMap);
  97. }
  98. }
  99. // private methods
  100. private bool AllowsDuplicate(Type type)
  101. {
  102. var usageAttribute = type.GetTypeInfo().GetCustomAttributes(inherit: true)
  103. .OfType<BsonMemberMapAttributeUsageAttribute>()
  104. .SingleOrDefault();
  105. return usageAttribute == null || usageAttribute.AllowMultipleMembers;
  106. }
  107. private void OptInMembersWithBsonCreatorMapModifierAttribute(BsonClassMap classMap)
  108. {
  109. // let other constructors opt-in if they have any IBsonCreatorMapAttribute attributes
  110. foreach (var constructorInfo in classMap.ClassType.GetTypeInfo().GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly))
  111. {
  112. var hasAttribute = constructorInfo.GetCustomAttributes(inherit: false).OfType<IBsonCreatorMapAttribute>().Any();
  113. if (hasAttribute)
  114. {
  115. classMap.MapConstructor(constructorInfo);
  116. }
  117. }
  118. // let other static factory methods opt-in if they have any IBsonCreatorMapAttribute attributes
  119. foreach (var methodInfo in classMap.ClassType.GetTypeInfo().GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly))
  120. {
  121. var hasAttribute = methodInfo.GetCustomAttributes(inherit: false).OfType<IBsonCreatorMapAttribute>().Any();
  122. if (hasAttribute)
  123. {
  124. classMap.MapFactoryMethod(methodInfo);
  125. }
  126. }
  127. }
  128. private void OptInMembersWithBsonMemberMapModifierAttribute(BsonClassMap classMap)
  129. {
  130. // let other fields opt-in if they have any IBsonMemberMapAttribute attributes
  131. foreach (var fieldInfo in classMap.ClassType.GetTypeInfo().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly))
  132. {
  133. var hasAttribute = fieldInfo.GetCustomAttributes(inherit: false).OfType<IBsonMemberMapAttribute>().Any();
  134. if (hasAttribute)
  135. {
  136. classMap.MapMember(fieldInfo);
  137. }
  138. }
  139. // let other properties opt-in if they have any IBsonMemberMapAttribute attributes
  140. foreach (var propertyInfo in classMap.ClassType.GetTypeInfo().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly))
  141. {
  142. var hasAttribute = propertyInfo.GetCustomAttributes(inherit: false).OfType<IBsonMemberMapAttribute>().Any();
  143. if (hasAttribute)
  144. {
  145. classMap.MapMember(propertyInfo);
  146. }
  147. }
  148. }
  149. private void IgnoreMembersWithBsonIgnoreAttribute(BsonClassMap classMap)
  150. {
  151. foreach (var memberMap in classMap.DeclaredMemberMaps.ToList())
  152. {
  153. var ignoreAttribute = (BsonIgnoreAttribute)memberMap.MemberInfo.GetCustomAttributes(inherit: false).OfType<BsonIgnoreAttribute>().FirstOrDefault();
  154. if (ignoreAttribute != null)
  155. {
  156. classMap.UnmapMember(memberMap.MemberInfo);
  157. }
  158. }
  159. }
  160. private void ThrowForDuplicateMemberMapAttributes(BsonClassMap classMap)
  161. {
  162. var nonDuplicatesAlreadySeen = new List<Type>();
  163. foreach (var memberMap in classMap.DeclaredMemberMaps)
  164. {
  165. var attributes = memberMap.MemberInfo.GetCustomAttributes(inherit: false).OfType<IBsonMemberMapAttribute>();
  166. // combine them only if the modifier isn't already in the attributes list...
  167. var attributeTypes = attributes.Select(x => x.GetType());
  168. foreach (var attributeType in attributeTypes)
  169. {
  170. if (nonDuplicatesAlreadySeen.Contains(attributeType))
  171. {
  172. var message = string.Format("Attributes of type {0} can only be applied to a single member.", attributeType);
  173. throw new DuplicateBsonMemberMapAttributeException(message);
  174. }
  175. if (!AllowsDuplicate(attributeType))
  176. {
  177. nonDuplicatesAlreadySeen.Add(attributeType);
  178. }
  179. }
  180. }
  181. }
  182. }
  183. }
  184. }