private static void MergeReplicationHistories(string documentId, RavenJObject origin, RavenJObject external, ref RavenJObject result) { result = (RavenJObject)origin.CloneToken(); RavenJToken originHistory; RavenJToken externalHisotry; var originHasHistory = origin.TryGetValue(Constants.RavenReplicationHistory, out originHistory); var externalHasHistory = external.TryGetValue(Constants.RavenReplicationHistory, out externalHisotry); RavenJToken externalVersion; RavenJToken externalSource; //we are going to lose the external source and version if we don't add them here if (external.TryGetValue(Constants.RavenReplicationVersion, out externalVersion) && external.TryGetValue(Constants.RavenReplicationSource, out externalSource)) { if (externalHasHistory) { externalHisotry = externalHisotry.CloneToken(); } else { externalHisotry = new RavenJArray(); } var historyEntry = new RavenJObject(); historyEntry[Constants.RavenReplicationVersion] = externalVersion; historyEntry[Constants.RavenReplicationSource] = externalSource; ((RavenJArray)externalHisotry).Add(historyEntry); externalHasHistory = true; } RavenJArray mergedHistory = null; //need to merge histories if (originHasHistory) { mergedHistory = Historian.MergeReplicationHistories((RavenJArray)originHistory, (RavenJArray)externalHisotry, documentId); result[Constants.RavenReplicationMergedHistory] = true; } else if (externalHasHistory) { //this might be a snapshot if somehow there was an history but no version or source mergedHistory = (RavenJArray)(externalHisotry.IsSnapshot? externalHisotry.CloneToken(): externalHisotry); } //if the original has history and the external didn't we already cloned it. if (mergedHistory != null) { result[Constants.RavenReplicationHistory] = mergedHistory; } }
private void ReplicateDelete(string id, RavenJObject newMetadata, TExternal incoming) { TInternal existingItem; Etag existingEtag; bool deleted; var existingMetadata = TryGetExisting(id, out existingItem, out existingEtag, out deleted); if (existingMetadata == null) { if (log.IsDebugEnabled) { log.Debug("Replicating deleted item {0} from {1} that does not exist, ignoring.", id, Src); } return; } if (existingMetadata.ContainsKey(Constants.RavenEntityName)) { newMetadata[Constants.RavenEntityName] = existingMetadata[Constants.RavenEntityName]; } RavenJObject currentReplicationEntry = null; if (newMetadata.ContainsKey(Constants.RavenReplicationVersion) && newMetadata.ContainsKey(Constants.RavenReplicationSource)) { currentReplicationEntry = new RavenJObject { { Constants.RavenReplicationVersion, newMetadata[Constants.RavenReplicationVersion] }, { Constants.RavenReplicationSource, newMetadata[Constants.RavenReplicationSource] } }; } var existingHistory = ReplicationData.GetOrCreateHistory(existingMetadata); if (currentReplicationEntry != null && existingHistory.Any(x => RavenJTokenEqualityComparer.Default.Equals( ((RavenJObject)x)[Constants.RavenReplicationSource], currentReplicationEntry[Constants.RavenReplicationSource]) && ((RavenJObject)x)[Constants.RavenReplicationVersion].Value <long>() >= currentReplicationEntry[Constants.RavenReplicationVersion].Value <long>())) { if (log.IsDebugEnabled) { log.Debug("Replicated delete for {0} already exist in item history, ignoring", id); } return; } if (existingMetadata.Value <bool>(Constants.RavenDeleteMarker)) //deleted locally as well { if (log.IsDebugEnabled) { log.Debug("Replicating deleted item {0} from {1} that was deleted locally. Merging histories.", id, Src); } var newHistory = ReplicationData.GetOrCreateHistory(newMetadata); if (currentReplicationEntry != null) { newHistory.Add(currentReplicationEntry); } //Merge histories ReplicationData.SetHistory(newMetadata, Historian.MergeReplicationHistories(newHistory, existingHistory, id)); newMetadata[Constants.RavenReplicationMergedHistory] = true; MarkAsDeleted(id, newMetadata); return; } if (Historian.IsDirectChildOfCurrent(newMetadata, existingMetadata)) // not modified { if (log.IsDebugEnabled) { log.Debug("Delete of existing item {0} was replicated successfully from {1}", id, Src); } DeleteItem(id, existingEtag); MarkAsDeleted(id, newMetadata); return; } CreatedConflict createdConflict; if (existingMetadata.Value <bool>(Constants.RavenReplicationConflict)) // locally conflicted { if (log.IsDebugEnabled) { log.Debug("Replicating deleted item {0} from {1} that is already conflicted, adding to conflicts.", id, Src); } var savedConflictedItemId = SaveConflictedItem(id, newMetadata, incoming, existingEtag); createdConflict = AppendToCurrentItemConflicts(id, savedConflictedItemId, existingMetadata, existingItem); } else { RavenJObject resolvedMetadataToSave; TExternal resolvedItemToSave; if (TryResolveConflict(id, newMetadata, incoming, existingItem, out resolvedMetadataToSave, out resolvedItemToSave)) { if (resolvedMetadataToSave.ContainsKey("Raven-Delete-Marker") && resolvedMetadataToSave.Value <bool>("Raven-Delete-Marker")) { // the deleted document "wins" if (newMetadata.ContainsKey(Constants.RavenEntityName)) { resolvedMetadataToSave[Constants.RavenEntityName] = newMetadata[Constants.RavenEntityName]; } DeleteItem(id, null); MarkAsDeleted(id, resolvedMetadataToSave); } else { var etag = deleted == false ? existingEtag : null; AddWithoutConflict(id, etag, resolvedMetadataToSave, resolvedItemToSave); } return; } var newConflictId = SaveConflictedItem(id, newMetadata, incoming, existingEtag); if (log.IsDebugEnabled) { log.Debug("Existing item {0} is in conflict with replicated delete from {1}, marking item as conflicted", id, Src); } // we have a new conflict move the existing doc to a conflict and create a conflict document var existingDocumentConflictId = id + "/conflicts/" + GetReplicationIdentifierForCurrentDatabase(); createdConflict = CreateConflict(id, newConflictId, existingDocumentConflictId, existingItem, existingMetadata); } Database.TransactionalStorage.ExecuteImmediatelyOrRegisterForSynchronization(() => Database.Notifications.RaiseNotifications(new ReplicationConflictNotification() { Id = id, Etag = createdConflict.Etag, Conflicts = createdConflict.ConflictedIds, ItemType = ReplicationConflictTypes.DocumentReplicationConflict, OperationType = ReplicationOperationTypes.Delete })); }
public bool TryResolveConflict(string id, RavenJObject metadata, RavenJObject document, JsonDocument existingDoc, Func <string, JsonDocument> getDocument, out RavenJObject metadataToSave, out RavenJObject documentToSave) { var success = TryResolve(id, metadata, document, existingDoc, getDocument, out metadataToSave, out documentToSave); if (success == false) { return(false); } var history = ReplicationData.GetHistory(metadata); var existingHistory = ReplicationData.GetHistory(existingDoc.Metadata); ReplicationData.SetHistory(metadataToSave, Historian.MergeReplicationHistories(history, existingHistory)); metadataToSave[Constants.RavenReplicationMergedHistory] = true; // here we make sure that we keep a deleted document deleted, rather than "reviving" it. var ravenDeleteMarker = existingDoc.Metadata.Value <string>("Raven-Delete-Marker"); bool markerValue; if (ravenDeleteMarker != null && bool.TryParse(ravenDeleteMarker, out markerValue) && markerValue) { existingDoc.Metadata["Raven-Remove-Document-Marker"] = true; } var docToSave = documentToSave; var metaToSave = metadataToSave; if (log.IsDebugEnabled) { log.Debug(() => { var builder = new StringBuilder(); builder.AppendLine(string.Format("Conflict on document with key '{0}' resolved by '{1}'.", id, GetType().Name)); builder.AppendLine(string.Format("Existing document:")); if (existingDoc != null && existingDoc.DataAsJson != null) { builder.AppendLine(existingDoc.DataAsJson.ToString()); } builder.AppendLine(string.Format("Existing metadata:")); if (existingDoc != null && existingDoc.Metadata != null) { builder.AppendLine(existingDoc.Metadata.ToString()); } builder.AppendLine(string.Format("Incoming document:")); if (document != null) { builder.AppendLine(document.ToString()); } builder.AppendLine(string.Format("Incoming metadata:")); if (metadata != null) { builder.AppendLine(metadata.ToString()); } builder.AppendLine(string.Format("Output document:")); if (docToSave != null) { builder.AppendLine(docToSave.ToString()); } builder.AppendLine(string.Format("Output metadata:")); if (metaToSave != null) { builder.AppendLine(metaToSave.ToString()); } return(builder.ToString()); }); } return(true); }