PageRenderTime 57ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/Raven.Database/Bundles/Replication/Responders/Behaviors/SingleItemReplicationBehavior.cs

https://github.com/kairogyn/ravendb
C# | 263 lines | 215 code | 43 blank | 5 comment | 25 complexity | aa69bbd965e0c51d993677a8ae1b0333 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, BSD-3-Clause, CC-BY-SA-3.0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Security.Cryptography;
  5. using System.Text;
  6. using Raven.Abstractions.Data;
  7. using Raven.Abstractions.Logging;
  8. using Raven.Database;
  9. using Raven.Database.Storage;
  10. using Raven.Imports.Newtonsoft.Json.Linq;
  11. using Raven.Json.Linq;
  12. namespace Raven.Bundles.Replication.Responders
  13. {
  14. using Raven.Database.Bundles.Replication.Impl;
  15. public abstract class SingleItemReplicationBehavior<TInternal, TExternal>
  16. {
  17. protected class CreatedConflict
  18. {
  19. public Etag Etag { get; set; }
  20. public string[] ConflictedIds { get; set; }
  21. }
  22. private readonly ILog log = LogManager.GetCurrentClassLogger();
  23. public DocumentDatabase Database { get; set; }
  24. public IStorageActionsAccessor Actions { get; set; }
  25. public string Src { get; set; }
  26. public void Replicate(string id, RavenJObject metadata, TExternal incoming)
  27. {
  28. if (metadata.Value<bool>(Constants.RavenDeleteMarker))
  29. {
  30. ReplicateDelete(id, metadata, incoming);
  31. return;
  32. }
  33. TInternal existingItem;
  34. Etag existingEtag;
  35. bool deleted;
  36. var existingMetadata = TryGetExisting(id, out existingItem, out existingEtag, out deleted);
  37. if (existingMetadata == null)
  38. {
  39. log.Debug("New item {0} replicated successfully from {1}", id, Src);
  40. AddWithoutConflict(id, null, metadata, incoming);
  41. return;
  42. }
  43. // we just got the same version from the same source - request playback again?
  44. // at any rate, not an error, moving on
  45. if (existingMetadata.Value<string>(Constants.RavenReplicationSource) == metadata.Value<string>(Constants.RavenReplicationSource)
  46. && existingMetadata.Value<long>(Constants.RavenReplicationVersion) == metadata.Value<long>(Constants.RavenReplicationVersion))
  47. {
  48. return;
  49. }
  50. var existingDocumentIsInConflict = existingMetadata[Constants.RavenReplicationConflict] != null;
  51. if (existingDocumentIsInConflict == false && // if the current document is not in conflict, we can continue without having to keep conflict semantics
  52. (Historian.IsDirectChildOfCurrent(metadata, existingMetadata))) // this update is direct child of the existing doc, so we are fine with overwriting this
  53. {
  54. log.Debug("Existing item {0} replicated successfully from {1}", id, Src);
  55. var etag = deleted == false ? existingEtag : null;
  56. AddWithoutConflict(id, etag, metadata, incoming);
  57. return;
  58. }
  59. if (TryResolveConflict(id, metadata, incoming, existingItem))
  60. {
  61. if (metadata.ContainsKey("Raven-Remove-Document-Marker") &&
  62. metadata.Value<bool>("Raven-Remove-Document-Marker"))
  63. {
  64. DeleteItem(id, null);
  65. MarkAsDeleted(id, metadata);
  66. }
  67. else
  68. {
  69. var etag = deleted == false ? existingEtag : null;
  70. AddWithoutConflict(id, etag, metadata, incoming);
  71. }
  72. return;
  73. }
  74. CreatedConflict createdConflict;
  75. var newDocumentConflictId = SaveConflictedItem(id, metadata, incoming, existingEtag);
  76. if (existingDocumentIsInConflict) // the existing document is in conflict
  77. {
  78. log.Debug("Conflicted item {0} has a new version from {1}, adding to conflicted documents", id, Src);
  79. createdConflict = AppendToCurrentItemConflicts(id, newDocumentConflictId, existingMetadata, existingItem);
  80. }
  81. else
  82. {
  83. log.Debug("Existing item {0} is in conflict with replicated version from {1}, marking item as conflicted", id, Src);
  84. // we have a new conflict
  85. // move the existing doc to a conflict and create a conflict document
  86. var existingDocumentConflictId = id + "/conflicts/" + HashReplicationIdentifier(existingEtag);
  87. createdConflict = CreateConflict(id, newDocumentConflictId, existingDocumentConflictId, existingItem,
  88. existingMetadata);
  89. }
  90. Database.TransactionalStorage.ExecuteImmediatelyOrRegisterForSynchronization(() =>
  91. Database.RaiseNotifications(new ReplicationConflictNotification()
  92. {
  93. Id = id,
  94. Etag = createdConflict.Etag,
  95. ItemType = ReplicationConflict,
  96. OperationType = ReplicationOperationTypes.Put,
  97. Conflicts = createdConflict.ConflictedIds
  98. }));
  99. }
  100. protected abstract ReplicationConflictTypes ReplicationConflict { get; }
  101. private string SaveConflictedItem(string id, RavenJObject metadata, TExternal incoming, Etag existingEtag)
  102. {
  103. metadata[Constants.RavenReplicationConflictDocument] = true;
  104. var newDocumentConflictId = id + "/conflicts/" + HashReplicationIdentifier(metadata);
  105. metadata.Add(Constants.RavenReplicationConflict, RavenJToken.FromObject(true));
  106. AddWithoutConflict(
  107. newDocumentConflictId,
  108. null, // we explicitly want to overwrite a document if it already exists, since it is known uniuque by the key
  109. metadata,
  110. incoming);
  111. return newDocumentConflictId;
  112. }
  113. private void ReplicateDelete(string id, RavenJObject metadata, TExternal incoming)
  114. {
  115. TInternal existingItem;
  116. Etag existingEtag;
  117. bool deleted;
  118. var existingMetadata = TryGetExisting(id, out existingItem, out existingEtag, out deleted);
  119. if (existingMetadata == null)
  120. {
  121. log.Debug("Replicating deleted item {0} from {1} that does not exist, ignoring", id, Src);
  122. return;
  123. }
  124. if (existingMetadata.Value<bool>(Constants.RavenDeleteMarker)) //deleted locally as well
  125. {
  126. log.Debug("Replicating deleted item {0} from {1} that was deleted locally. Merging histories", id, Src);
  127. var existingHistory = new RavenJArray(ReplicationData.GetHistory(existingMetadata));
  128. var newHistory = new RavenJArray(ReplicationData.GetHistory(metadata));
  129. foreach (var item in newHistory)
  130. {
  131. existingHistory.Add(item);
  132. }
  133. if (metadata.ContainsKey(Constants.RavenReplicationVersion) &&
  134. metadata.ContainsKey(Constants.RavenReplicationSource))
  135. {
  136. existingHistory.Add(new RavenJObject
  137. {
  138. {Constants.RavenReplicationVersion, metadata[Constants.RavenReplicationVersion]},
  139. {Constants.RavenReplicationSource, metadata[Constants.RavenReplicationSource]}
  140. });
  141. }
  142. while (existingHistory.Length > Constants.ChangeHistoryLength)
  143. {
  144. existingHistory.RemoveAt(0);
  145. }
  146. MarkAsDeleted(id, metadata);
  147. return;
  148. }
  149. if (Historian.IsDirectChildOfCurrent(metadata, existingMetadata))// not modified
  150. {
  151. log.Debug("Delete of existing item {0} was replicated successfully from {1}", id, Src);
  152. DeleteItem(id, existingEtag);
  153. MarkAsDeleted(id, metadata);
  154. return;
  155. }
  156. if (TryResolveConflict(id, metadata, incoming, existingItem))
  157. {
  158. if (metadata.ContainsKey("Raven-Remove-Document-Marker") &&
  159. metadata.Value<bool>("Raven-Remove-Document-Marker"))
  160. {
  161. DeleteItem(id, null);
  162. MarkAsDeleted(id, metadata);
  163. }
  164. else
  165. {
  166. AddWithoutConflict(id, existingEtag, metadata, incoming);
  167. }
  168. return;
  169. }
  170. CreatedConflict createdConflict;
  171. if (existingMetadata.Value<bool>(Constants.RavenReplicationConflict)) // locally conflicted
  172. {
  173. log.Debug("Replicating deleted item {0} from {1} that is already conflicted, adding to conflicts.", id, Src);
  174. var savedConflictedItemId = SaveConflictedItem(id, metadata, incoming, existingEtag);
  175. createdConflict = AppendToCurrentItemConflicts(id, savedConflictedItemId, existingMetadata, existingItem);
  176. }
  177. else
  178. {
  179. var newConflictId = SaveConflictedItem(id, metadata, incoming, existingEtag);
  180. log.Debug("Existing item {0} is in conflict with replicated delete from {1}, marking item as conflicted", id, Src);
  181. // we have a new conflict move the existing doc to a conflict and create a conflict document
  182. var existingDocumentConflictId = id + "/conflicts/" + HashReplicationIdentifier(existingEtag);
  183. createdConflict = CreateConflict(id, newConflictId, existingDocumentConflictId, existingItem, existingMetadata);
  184. }
  185. Database.TransactionalStorage.ExecuteImmediatelyOrRegisterForSynchronization(() =>
  186. Database.RaiseNotifications(new ReplicationConflictNotification()
  187. {
  188. Id = id,
  189. Etag = createdConflict.Etag,
  190. Conflicts = createdConflict.ConflictedIds,
  191. ItemType = ReplicationConflictTypes.DocumentReplicationConflict,
  192. OperationType = ReplicationOperationTypes.Delete
  193. }));
  194. }
  195. protected abstract void DeleteItem(string id, Etag etag);
  196. protected abstract void MarkAsDeleted(string id, RavenJObject metadata);
  197. protected abstract void AddWithoutConflict(string id, Etag etag, RavenJObject metadata, TExternal incoming);
  198. protected abstract CreatedConflict CreateConflict(string id, string newDocumentConflictId, string existingDocumentConflictId, TInternal existingItem, RavenJObject existingMetadata);
  199. protected abstract CreatedConflict AppendToCurrentItemConflicts(string id, string newConflictId, RavenJObject existingMetadata, TInternal existingItem);
  200. protected abstract RavenJObject TryGetExisting(string id, out TInternal existingItem, out Etag existingEtag, out bool deleted);
  201. protected abstract bool TryResolveConflict(string id, RavenJObject metadata, TExternal document,
  202. TInternal existing);
  203. private static string HashReplicationIdentifier(RavenJObject metadata)
  204. {
  205. using (var md5 = MD5.Create())
  206. {
  207. var bytes = Encoding.UTF8.GetBytes(metadata.Value<string>(Constants.RavenReplicationSource) + "/" + metadata.Value<string>("@etag"));
  208. return new Guid(md5.ComputeHash(bytes)).ToString();
  209. }
  210. }
  211. private string HashReplicationIdentifier(Etag existingEtag)
  212. {
  213. using (var md5 = MD5.Create())
  214. {
  215. var bytes = Encoding.UTF8.GetBytes(Database.TransactionalStorage.Id + "/" + existingEtag);
  216. return new Guid(md5.ComputeHash(bytes)).ToString();
  217. }
  218. }
  219. }
  220. }