PageRenderTime 72ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

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

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