You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

348 lines
14 KiB

  1. /* Copyright 2010-2011 10gen 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.IO;
  18. using System.Linq;
  19. using System.Reflection;
  20. using System.Text;
  21. using System.Text.RegularExpressions;
  22. using MongoDB.Bson.IO;
  23. using MongoDB.Bson.Serialization.Options;
  24. namespace MongoDB.Bson.Serialization {
  25. /// <summary>
  26. /// Represents a serializer for a class map.
  27. /// </summary>
  28. public class BsonClassMapSerializer : IBsonSerializer {
  29. #region private static fields
  30. private static BsonClassMapSerializer instance = new BsonClassMapSerializer();
  31. #endregion
  32. #region constructors
  33. /// <summary>
  34. /// Initializes a new instance of the BsonClassMapSerializer class.
  35. /// </summary>
  36. public BsonClassMapSerializer() {
  37. }
  38. #endregion
  39. #region public static properties
  40. /// <summary>
  41. /// Gets an instance of the BsonClassMapSerializer class.
  42. /// </summary>
  43. public static BsonClassMapSerializer Instance {
  44. get { return instance; }
  45. }
  46. #endregion
  47. #region public methods
  48. /// <summary>
  49. /// Deserializes an object from a BsonReader.
  50. /// </summary>
  51. /// <param name="bsonReader">The BsonReader.</param>
  52. /// <param name="nominalType">The nominal type of the object.</param>
  53. /// <param name="options">The serialization options.</param>
  54. /// <returns>An object.</returns>
  55. public object Deserialize(
  56. BsonReader bsonReader,
  57. Type nominalType,
  58. IBsonSerializationOptions options
  59. ) {
  60. VerifyNominalType(nominalType);
  61. if (bsonReader.CurrentBsonType == Bson.BsonType.Null) {
  62. bsonReader.ReadNull();
  63. return null;
  64. } else {
  65. var discriminatorConvention = BsonDefaultSerializer.LookupDiscriminatorConvention(nominalType);
  66. var actualType = discriminatorConvention.GetActualType(bsonReader, nominalType);
  67. if (actualType != nominalType) {
  68. var serializer = BsonSerializer.LookupSerializer(actualType);
  69. if (serializer != this) {
  70. // in rare cases a concrete actualType might have a more specialized serializer
  71. return serializer.Deserialize(bsonReader, nominalType, actualType, options);
  72. }
  73. }
  74. return Deserialize(bsonReader, nominalType, actualType, options);
  75. }
  76. }
  77. /// <summary>
  78. /// Deserializes an object from a BsonReader.
  79. /// </summary>
  80. /// <param name="bsonReader">The BsonReader.</param>
  81. /// <param name="nominalType">The nominal type of the object.</param>
  82. /// <param name="actualType">The actual type of the object.</param>
  83. /// <param name="options">The serialization options.</param>
  84. /// <returns>An object.</returns>
  85. public object Deserialize(
  86. BsonReader bsonReader,
  87. Type nominalType,
  88. Type actualType,
  89. IBsonSerializationOptions options
  90. ) {
  91. VerifyNominalType(nominalType);
  92. if (bsonReader.CurrentBsonType == Bson.BsonType.Null) {
  93. bsonReader.ReadNull();
  94. return null;
  95. } else {
  96. var classMap = BsonClassMap.LookupClassMap(actualType);
  97. if (classMap.IsAnonymous) {
  98. throw new InvalidOperationException("Anonymous classes cannot be deserialized");
  99. }
  100. var obj = classMap.CreateInstance();
  101. bsonReader.ReadStartDocument();
  102. var missingElementMemberMaps = new HashSet<BsonMemberMap>(classMap.MemberMaps); // make a copy!
  103. var discriminatorConvention = BsonDefaultSerializer.LookupDiscriminatorConvention(nominalType);
  104. while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) {
  105. var elementName = bsonReader.ReadName();
  106. if (elementName == discriminatorConvention.ElementName) {
  107. bsonReader.SkipValue(); // skip over discriminator
  108. continue;
  109. }
  110. var memberMap = classMap.GetMemberMapForElement(elementName);
  111. if (memberMap != null && memberMap != classMap.ExtraElementsMemberMap) {
  112. DeserializeMember(bsonReader, obj, memberMap);
  113. missingElementMemberMaps.Remove(memberMap);
  114. } else {
  115. if (classMap.ExtraElementsMemberMap != null) {
  116. DeserializeExtraElement(bsonReader, obj, elementName, classMap.ExtraElementsMemberMap);
  117. } else if (classMap.IgnoreExtraElements) {
  118. bsonReader.SkipValue();
  119. } else {
  120. string message = string.Format("Unexpected element: {0}", elementName);
  121. throw new FileFormatException(message);
  122. }
  123. }
  124. }
  125. bsonReader.ReadEndDocument();
  126. foreach (var memberMap in missingElementMemberMaps) {
  127. if (memberMap.IsRequired) {
  128. var message = string.Format("Required element is missing: {0}", memberMap.ElementName);
  129. throw new FileFormatException(message);
  130. }
  131. if (memberMap.HasDefaultValue) {
  132. memberMap.ApplyDefaultValue(obj);
  133. }
  134. }
  135. return obj;
  136. }
  137. }
  138. /// <summary>
  139. /// Gets the document Id.
  140. /// </summary>
  141. /// <param name="document">The document.</param>
  142. /// <param name="id">The Id.</param>
  143. /// <param name="idNominalType">The nominal type of the Id.</param>
  144. /// <param name="idGenerator">The IdGenerator for the Id type.</param>
  145. /// <returns>True if the document has an Id.</returns>
  146. public bool GetDocumentId(
  147. object document,
  148. out object id,
  149. out Type idNominalType,
  150. out IIdGenerator idGenerator
  151. ) {
  152. var classMap = BsonClassMap.LookupClassMap(document.GetType());
  153. var idMemberMap = classMap.IdMemberMap;
  154. if (idMemberMap != null) {
  155. id = idMemberMap.Getter(document);
  156. idNominalType = idMemberMap.MemberType;
  157. idGenerator = idMemberMap.IdGenerator;
  158. return true;
  159. } else {
  160. id = null;
  161. idNominalType = null;
  162. idGenerator = null;
  163. return false;
  164. }
  165. }
  166. /// <summary>
  167. /// Serializes an object to a BsonWriter.
  168. /// </summary>
  169. /// <param name="bsonWriter">The BsonWriter.</param>
  170. /// <param name="nominalType">The nominal type.</param>
  171. /// <param name="value">The object.</param>
  172. /// <param name="options">The serialization options.</param>
  173. public void Serialize(
  174. BsonWriter bsonWriter,
  175. Type nominalType,
  176. object value,
  177. IBsonSerializationOptions options
  178. ) {
  179. if (value == null) {
  180. bsonWriter.WriteNull();
  181. } else {
  182. // Nullable types are weird because they get boxed as their underlying value type
  183. // we can best handle that by switching the nominalType to the underlying value type
  184. // (so VerifyNominalType doesn't fail and we don't get an unnecessary discriminator)
  185. if (nominalType.IsGenericType && nominalType.GetGenericTypeDefinition() == typeof(Nullable<>)) {
  186. nominalType = nominalType.GetGenericArguments()[0];
  187. }
  188. VerifyNominalType(nominalType);
  189. var actualType = (value == null) ? nominalType : value.GetType();
  190. var classMap = BsonClassMap.LookupClassMap(actualType);
  191. bsonWriter.WriteStartDocument();
  192. var documentOptions = (options == null) ? DocumentSerializationOptions.Defaults : (DocumentSerializationOptions) options;
  193. BsonMemberMap idMemberMap = null;
  194. if (documentOptions.SerializeIdFirst) {
  195. idMemberMap = classMap.IdMemberMap;
  196. if (idMemberMap != null) {
  197. SerializeMember(bsonWriter, value, idMemberMap);
  198. }
  199. }
  200. if (actualType != nominalType || classMap.DiscriminatorIsRequired || classMap.HasRootClass) {
  201. // never write out a discriminator for an anonymous class
  202. if (!classMap.IsAnonymous) {
  203. var discriminatorConvention = BsonDefaultSerializer.LookupDiscriminatorConvention(nominalType);
  204. var discriminator = discriminatorConvention.GetDiscriminator(nominalType, actualType);
  205. if (discriminator != null) {
  206. bsonWriter.WriteName(discriminatorConvention.ElementName);
  207. discriminator.WriteTo(bsonWriter);
  208. }
  209. }
  210. }
  211. foreach (var memberMap in classMap.MemberMaps) {
  212. // note: if serializeIdFirst is false then idMemberMap will be null (so no property will be skipped)
  213. if (memberMap != idMemberMap) {
  214. if (memberMap == classMap.ExtraElementsMemberMap) {
  215. SerializeExtraElements(bsonWriter, value, memberMap);
  216. } else {
  217. SerializeMember(bsonWriter, value, memberMap);
  218. }
  219. }
  220. }
  221. bsonWriter.WriteEndDocument();
  222. }
  223. }
  224. /// <summary>
  225. /// Sets the document Id.
  226. /// </summary>
  227. /// <param name="document">The document.</param>
  228. /// <param name="id">The Id.</param>
  229. public void SetDocumentId(
  230. object document,
  231. object id
  232. ) {
  233. var classMap = BsonClassMap.LookupClassMap(document.GetType());
  234. var idMemberMap = classMap.IdMemberMap;
  235. if (idMemberMap != null) {
  236. idMemberMap.Setter(document, id);
  237. } else {
  238. var message = string.Format("Class {0} has no Id member", document.GetType());
  239. throw new InvalidOperationException(message);
  240. }
  241. }
  242. #endregion
  243. #region private methods
  244. private void DeserializeExtraElement(
  245. BsonReader bsonReader,
  246. object obj,
  247. string elementName,
  248. BsonMemberMap extraElementsMemberMap
  249. ) {
  250. var extraElements = (BsonDocument) extraElementsMemberMap.Getter(obj);
  251. if (extraElements == null) {
  252. extraElements = new BsonDocument();
  253. extraElementsMemberMap.Setter(obj, extraElements);
  254. }
  255. var value = BsonValue.ReadFrom(bsonReader);
  256. extraElements[elementName] = value;
  257. }
  258. private void DeserializeMember(
  259. BsonReader bsonReader,
  260. object obj,
  261. BsonMemberMap memberMap
  262. ) {
  263. var nominalType = memberMap.MemberType;
  264. Type actualType;
  265. if (bsonReader.CurrentBsonType == BsonType.Null) {
  266. actualType = nominalType;
  267. } else {
  268. var discriminatorConvention = BsonDefaultSerializer.LookupDiscriminatorConvention(nominalType);
  269. actualType = discriminatorConvention.GetActualType(bsonReader, nominalType); // returns nominalType if no discriminator found
  270. }
  271. var serializer = memberMap.GetSerializer(actualType);
  272. var value = serializer.Deserialize(bsonReader, nominalType, actualType, memberMap.SerializationOptions);
  273. memberMap.Setter(obj, value);
  274. }
  275. private void SerializeExtraElements(
  276. BsonWriter bsonWriter,
  277. object obj,
  278. BsonMemberMap extraElementsMemberMap
  279. ) {
  280. var extraElements = (BsonDocument) extraElementsMemberMap.Getter(obj);
  281. if (extraElements != null) {
  282. foreach (var element in extraElements) {
  283. element.WriteTo(bsonWriter);
  284. }
  285. }
  286. }
  287. private void SerializeMember(
  288. BsonWriter bsonWriter,
  289. object obj,
  290. BsonMemberMap memberMap
  291. ) {
  292. var value = memberMap.Getter(obj);
  293. if (value == null && memberMap.IgnoreIfNull) {
  294. return; // don't serialize null value
  295. }
  296. if (memberMap.HasDefaultValue && !memberMap.SerializeDefaultValue && object.Equals(value, memberMap.DefaultValue)) {
  297. return; // don't serialize default value
  298. }
  299. if (!memberMap.ShouldSerializeValue(obj))
  300. {
  301. return;// the object determined that it does not need serialization.
  302. }
  303. bsonWriter.WriteName(memberMap.ElementName);
  304. var nominalType = memberMap.MemberType;
  305. var actualType = (value == null) ? nominalType : value.GetType();
  306. var serializer = memberMap.GetSerializer(actualType);
  307. serializer.Serialize(bsonWriter, nominalType, value, memberMap.SerializationOptions);
  308. }
  309. private void VerifyNominalType(
  310. Type nominalType
  311. ) {
  312. if (
  313. !(nominalType.IsClass || (nominalType.IsValueType && !nominalType.IsPrimitive) || nominalType.IsInterface) ||
  314. typeof(Array).IsAssignableFrom(nominalType)
  315. ) {
  316. string message = string.Format("BsonClassMapSerializer cannot be used with type: {0}", nominalType.FullName);
  317. throw new BsonSerializationException(message);
  318. }
  319. }
  320. #endregion
  321. }
  322. }