Advertisement
Guest User

Untitled

a guest
Jul 22nd, 2017
56
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 13.35 KB | None | 0 0
  1. using System;
  2. using System.Collections;
  3. using System.Dynamic;
  4. using System.Linq;
  5. using System.Linq.Expressions;
  6. using MongoDB.Bson.IO;
  7. using MongoDB.Bson.Serialization;
  8. using MongoDB.Bson.Serialization.Serializers;
  9. using MongoDB.Bson;
  10. using MongoDB.Bson.Serialization.IdGenerators;
  11. using System.Collections.Generic;
  12. using log4net;
  13.  
  14. namespace MongoData
  15. {
  16. public class MongoDynamicBsonSerializer : SerializerBase<MongoDynamic>, IBsonIdProvider //, IBsonDocumentSerializer//, IBsonArraySerializer
  17. {
  18. private static readonly ILog log= LogManager.GetLogger("DynMongoSer");
  19.  
  20. public static MongoDynamicBsonSerializer Instance { get; } = new MongoDynamicBsonSerializer();
  21.  
  22. private static readonly IBsonSerializer<string> stringSerializer = new StringSerializer();
  23.  
  24. public override MongoDynamic Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
  25. {
  26. log.Debug($"Deserializing ... {args.NominalType}");
  27. var bsonReader = context.Reader;
  28. Type nominalType = args.NominalType;
  29.  
  30. var bsonType = bsonReader.GetCurrentBsonType();
  31. if (bsonType == BsonType.Null)
  32. {
  33. bsonReader.ReadNull();
  34. return null;
  35. }
  36. else if (bsonType == BsonType.Document)
  37. {
  38. MongoDynamic md = new MongoDynamic();
  39. bsonReader.ReadStartDocument();
  40.  
  41. // Scan document first to find interfaces and id fields
  42. Dictionary<string, Type> typeMap = ScanToLoadTypeMapFromInterfaces(context, bsonReader, md, nominalType);
  43.  
  44. while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
  45. {
  46. var name = bsonReader.ReadName();
  47. log.Debug($"Reading field {name}");
  48.  
  49. if (name == "_id")
  50. {
  51. md[name] = bsonReader.ReadObjectId();
  52. log.Debug($" {name} = {md[name]}");
  53. }
  54. else if (name == MongoDynamic.InterfacesField)
  55. {
  56. // Read it and ignore it, we already have it
  57. bsonReader.SkipValue();
  58. log.Debug(" Skipping int field");
  59. }
  60. else if (name == "_t")
  61. {
  62. log.Debug(" Skipping _t field, don't need that for interfaces");
  63. bsonReader.SkipValue();
  64. }
  65. else if (name == "Entity")
  66. {
  67. log.Debug(" Skipping Entity field");
  68. // Read it and ignore it, this was a broken earlier attempt at serialization
  69. bsonReader.SkipValue();
  70. }
  71. else if (bsonReader.CurrentBsonType == BsonType.Null)
  72. {
  73. bsonReader.ReadNull();
  74. md[name] = null;
  75. log.Debug($" {name} = null");
  76. }
  77. else
  78. {
  79. if (typeMap == null)
  80. {
  81. throw new FormatException("No interfaces defined for this dynamic object - can't deserialize [" + md["_id"] + "]");
  82. }
  83.  
  84. // lookup the type for this element according to the interfaces
  85. Type elementType;
  86. if (typeMap.TryGetValue(name, out elementType))
  87. {
  88. if (elementType.IsArray)
  89. {
  90. var subElementType = elementType.GetElementType();
  91.  
  92. if (subElementType.IsInterface)
  93. {
  94. bsonReader.ReadStartArray();
  95. var listType = typeof (List<>).MakeGenericType(subElementType);
  96. var elements = (IList) Activator.CreateInstance(listType);
  97.  
  98. while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
  99. {
  100. var subargs = new BsonDeserializationArgs {NominalType = subElementType};
  101. MongoDynamic o = this.Deserialize(context, subargs);
  102. elements.Add(o.ActLikeAllInterfacesPresent());
  103. }
  104. bsonReader.ReadEndArray();
  105.  
  106. var array = Array.CreateInstance(subElementType, elements.Count);
  107. for (int i = 0; i < array.Length; i++)
  108. {
  109. try
  110. {
  111. array.SetValue(elements[i], i);
  112. }
  113. catch (InvalidCastException)
  114. {
  115. log.Error(
  116. $"Cannot cast {elements[i].GetType().Name} to {subElementType}");
  117. }
  118. }
  119. md[name] = array;
  120. log.Debug($" {name} = {md[name]}");
  121. }
  122. else
  123. {
  124. md[name] = BsonSerializer.LookupSerializer(elementType).Deserialize(context, args);
  125. log.Debug($" {name} = {md[name]}");
  126. }
  127. }
  128. else if (elementType.IsInterface)
  129. {
  130. //log.Debug("Recursing on an interface " + elementType.Name);
  131. var subargs = new BsonDeserializationArgs {NominalType = elementType};
  132. var value = this.Deserialize(context, subargs);
  133. md[name] = value.ActLikeAllInterfacesPresent();
  134. log.Debug($" {name} = {md[name]}");
  135. }
  136. else
  137. {
  138. // Not an interface type, call the normal deserializer
  139. try
  140. {
  141. IBsonSerializer serializer = BsonSerializer.LookupSerializer(elementType);
  142. var value = serializer.Deserialize(context);
  143. md[name] = value;
  144. }
  145. catch (FormatException fex)
  146. {
  147. log.Error($"Unrecoverable error reading a {elementType}", fex);
  148. log.Error(md.ToStringValues());
  149. throw;
  150. }
  151. catch (Exception ex)
  152. {
  153. log.Error($"Could not Deserialize a {elementType}", ex);
  154. log.Error(md.ToStringValues());
  155. }
  156.  
  157. log.Debug($" {name} = {md[name]}");
  158. }
  159. }
  160. else
  161. {
  162. log.Debug("Trying to deserialize field " + name + " which was not found in the type map");
  163. log.Debug("TypeMap contains " + string.Join(", ", typeMap.Select(x => x.Key + "=" + x.Value)));
  164. try
  165. {
  166. // This is a value that is no longer in the interface, maybe a column you removed
  167. // not really much we can do with it ... but we need to read it and move on
  168. var value = BsonSerializer.Deserialize(bsonReader, typeof (object));
  169. md[name] = value;
  170. log.Debug($" {name} = {md[name]}");
  171. }
  172. catch (Exception ex)
  173. {
  174. ex.Data.Add("Explanation",
  175. "As with all databases, removing elements from the schema is going to cause problems");
  176. throw;
  177. }
  178. }
  179. }
  180. }
  181. bsonReader.ReadEndDocument();
  182. log.Debug($"--------");
  183. return md;
  184. }
  185. else
  186. {
  187. var message = $"Can't deserialize a {nominalType.FullName} from BsonType {bsonType}.";
  188. throw new FormatException(message);
  189. }
  190. }
  191.  
  192.  
  193. static Dictionary<string, Type> ScanToLoadTypeMapFromInterfaces(BsonDeserializationContext context, IBsonReader bsonReader,
  194. MongoDynamic md, Type nominalType)
  195. {
  196. Dictionary<string, Type> typeMap = null;
  197. var bookMark = bsonReader.GetBookmark();
  198. if (bsonReader.FindElement(MongoDynamic.InterfacesField))
  199. {
  200. bsonReader.ReadStartArray();
  201. var innerList = new List<string>();
  202. while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
  203. {
  204. innerList.Add(stringSerializer.Deserialize(context));
  205. }
  206. bsonReader.ReadEndArray();
  207. md[MongoDynamic.InterfacesField] = innerList;
  208. typeMap = md.GetTypeMap();
  209. log.Debug($" Interfaces = {md.InterfacesAsText}");
  210. log.Debug($" Properties = {string.Join(", ", typeMap.Keys)}");
  211. }
  212. else
  213. {
  214. log.Debug($"No 'int' field on this dynamic object - switching to use nominal type {nominalType}");
  215. var interfaces = nominalType.GetInterfaces();
  216. List<string> interfaceNames = new List<string>();
  217. if (nominalType.IsInterface) interfaceNames.Add(nominalType.FullName);
  218. interfaceNames.AddRange(interfaces.Select(i => i.FullName));
  219. // What about concrete types? Well, they are deserialized by normal MongoDB deserializer
  220. md[MongoDynamic.InterfacesField] = interfaceNames;
  221. typeMap = md.GetTypeMap();
  222. }
  223. bsonReader.ReturnToBookmark(bookMark);
  224. return typeMap;
  225. }
  226.  
  227. public bool GetDocumentId(object document, out object id, out Type idNominalType, out IIdGenerator idGenerator)
  228. {
  229. log.Debug("GetDocumentId");
  230. MongoDynamic x = (MongoDynamic)document;
  231. id = x._id;
  232. idNominalType = typeof(ObjectId);
  233. idGenerator = new ObjectIdGenerator();
  234. return true;
  235. }
  236.  
  237. public void SetDocumentId(object document, object id)
  238. {
  239. MongoDynamic x = (MongoDynamic)document;
  240. x._id = (ObjectId)id;
  241. }
  242.  
  243. public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args,
  244. MongoDynamic value)
  245. {
  246. var bsonWriter = context.Writer;
  247. log.Debug("Serialize " + value);
  248. if (value == null)
  249. {
  250. bsonWriter.WriteNull();
  251. return;
  252. }
  253.  
  254. // ------------------- NEW WAY --- WE KNOW IT'S A MONGODYNAMIC, NO NEED TO GO ALL IDynamicMetaObjectProvider ON IT
  255. MongoDynamic mongoDynamic = value as MongoDynamic;
  256.  
  257. bsonWriter.WriteStartDocument();
  258.  
  259. // And now write the fields according to the interface map
  260. var typeMap = mongoDynamic.GetTypeMap();
  261.  
  262. var metaObject = ((IDynamicMetaObjectProvider)value).GetMetaObject(Expression.Constant(value));
  263. var memberNames = metaObject.GetDynamicMemberNames().ToList();
  264. if (memberNames.Count == 0)
  265. {
  266. bsonWriter.WriteNull();
  267. return;
  268. }
  269.  
  270. foreach (var memberName in memberNames)
  271. {
  272. log.Debug("Examining " + memberName);
  273. // Get the value
  274. object memberValue;
  275. Type memberType;
  276.  
  277. if (memberName == "_id")
  278. {
  279. memberValue = mongoDynamic._id;
  280. memberType = typeof(ObjectId);
  281. }
  282. else if (memberName == "int")
  283. {
  284. memberValue = mongoDynamic.@int;
  285. memberType = memberValue.GetType(); // A Hashset<string>
  286. }
  287. else
  288. {
  289. memberValue = mongoDynamic[memberName]; /// Could also use ... Impromptu.InvokeGet(value, memberName);
  290. // Lookup the intended type for that field because it
  291. // may be different from the typeof(memberValue) which
  292. // is likely a Proxy type. If we can't find it, go ahead
  293. // and use the type of the object - for some reason it's not in the interfaces
  294. if (!typeMap.TryGetValue(memberName, out memberType))
  295. memberType = memberValue?.GetType();
  296. }
  297.  
  298. bsonWriter.WriteName(memberName);
  299. WriteValue(context, args, memberValue, bsonWriter, memberType);
  300.  
  301. if (memberName == "Name" && memberType == typeof(string))
  302. {
  303. string cleanedName = ITagExtensions.RemoveDiacritics(((string)memberValue).ToLowerInvariant());
  304. // Normalize the name field, lower case, replace special characters (TODO)
  305. bsonWriter.WriteName("name");
  306. WriteValue(context, args, cleanedName, bsonWriter, memberType);
  307. }
  308. }
  309.  
  310. bsonWriter.WriteEndDocument();
  311.  
  312. return;
  313. }
  314.  
  315. private void WriteValue(BsonSerializationContext context, BsonSerializationArgs args, object memberValue,
  316. IBsonWriter bsonWriter, Type memberType)
  317. {
  318. if (memberValue == null)
  319. bsonWriter.WriteNull();
  320. else
  321. {
  322. if (memberType.IsArray)
  323. {
  324. context.Writer.WriteStartArray();
  325.  
  326. memberType = memberType.GetElementType();
  327. foreach (var item in memberValue as IEnumerable)
  328. {
  329. // It could be a normal type here, or it could be an interface
  330. var iid = item as IId;
  331. var type = item.GetType();
  332. if (iid != null)
  333. {
  334. var mdInner = iid.Entity;
  335. this.Serialize(context, mdInner);
  336. }
  337. else
  338. {
  339. var serializer = BsonSerializer.LookupSerializer(memberType);
  340. serializer.Serialize(context, item);
  341. }
  342. }
  343. context.Writer.WriteEndArray();
  344. }
  345. else if (memberType.IsInterface)
  346. {
  347. // log.Debug("Serializing " + memberName + " : " + memberValue.GetType().Name + " a " + memberType.Name + " from a " + args.NominalType.Name + " by using another MongoDynamic!");
  348.  
  349. // Make it into a MongoDynamic object so we can read it back using only its interface
  350. MongoDynamic expanded = new MongoDynamic(memberType, memberValue);
  351. // Recursively call ourself to serialize this embedded interface which will again embed another interfaces field
  352. // to identify the interface type to load back in
  353. Serialize(context, args, expanded);
  354. }
  355. else
  356. {
  357. // log.Debug("Serializing " + memberName + " : " + memberValue.GetType() + " a " + memberType.Name + " from a " + args.NominalType.Name + " using normal lookup");
  358. var serializer = BsonSerializer.LookupSerializer(memberType);
  359. serializer.Serialize(context, memberValue);
  360. }
  361. }
  362. }
  363. }
  364. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement