public override bool TryResolve(string id, RavenJObject metadata, RavenJObject document, JsonDocument existingDoc, Func <string, JsonDocument> getDocument) { var existingDocumentIsInConflict = existingDoc.Metadata[Constants.RavenReplicationConflict] != null; var existingDocumentIsDeleted = existingDoc.Metadata[Constants.RavenDeleteMarker] != null && existingDoc.Metadata[Constants.RavenDeleteMarker].Value <bool>(); if (existingDocumentIsInConflict && existingDocumentIsDeleted == false) { var conflictIds = existingDoc.DataAsJson.Value <RavenJArray>("Conflicts") .Select(x => x.Value <string>()) .ToArray(); if (conflictIds.Length == 0) { return(false); } return (conflictIds .Select(getDocument) .All(doc => Historian.IsDirectChildOfCurrent(metadata, doc.Metadata))); } return(false); }
public ConflictItem Check(string fileName, RavenJObject localMetadata, RavenJObject remoteMetadata, string remoteServerUrl) { if (Historian.IsDirectChildOfCurrent(localMetadata, remoteMetadata)) { return(null); } return (new ConflictItem { CurrentHistory = TransformToFullConflictHistory(localMetadata), RemoteHistory = TransformToFullConflictHistory(remoteMetadata), FileName = fileName, RemoteServerUrl = remoteServerUrl }); }
public override bool TryResolve(string id, RavenJObject metadata, byte[] data, Attachment existingAttachment, Func <string, Attachment> getAttachment, out RavenJObject metadataToSave, out byte[] dataToSave) { var existingAttachmentIsInConflict = existingAttachment.Metadata[Constants.RavenReplicationConflict] != null; var existingAttachmentIsDeleted = existingAttachment.Metadata[Constants.RavenDeleteMarker] != null && existingAttachment.Metadata[Constants.RavenDeleteMarker].Value <bool>(); metadataToSave = null; dataToSave = null; if (existingAttachmentIsInConflict && existingAttachmentIsDeleted == false) { var conflictIds = existingAttachment.Data().ToJObject().Value <RavenJArray>("Conflicts") .Select(x => x.Value <string>()) .ToArray(); if (conflictIds.Length == 0) { return(false); } if (conflictIds .Select(getAttachment) .All(doc => Historian.IsDirectChildOfCurrent(metadata, doc.Metadata)) == false) { return(false); } metadataToSave = metadata; dataToSave = data; return(true); } return(false); }
public void Replicate(string id, RavenJObject metadata, TExternal incoming) { if (metadata.Value <bool>(Constants.RavenDeleteMarker)) { ReplicateDelete(id, metadata, incoming); return; } TInternal existingItem; Guid existingEtag; var existingMetadata = TryGetExisting(id, out existingItem, out existingEtag); if (existingMetadata == null) { log.Debug("New item {0} replicated successfully from {1}", id, Src); AddWithoutConflict(id, null, metadata, incoming); return; } // we just got the same version from the same source - request playback again? // at any rate, not an error, moving on if (existingMetadata.Value <string>(Constants.RavenReplicationSource) == metadata.Value <string>(Constants.RavenReplicationSource) && existingMetadata.Value <long>(Constants.RavenReplicationVersion) == metadata.Value <long>(Constants.RavenReplicationVersion)) { return; } var existingDocumentIsInConflict = existingMetadata[Constants.RavenReplicationConflict] != null; var existingDocumentIsDeleted = existingMetadata[Constants.RavenDeleteMarker] != null && existingMetadata[Constants.RavenDeleteMarker].Value <bool>(); if (existingDocumentIsInConflict == false && // if the current document is not in conflict, we can continue without having to keep conflict semantics existingDocumentIsDeleted == false && (Historian.IsDirectChildOfCurrent(metadata, existingMetadata))) // this update is direct child of the existing doc, so we are fine with overwriting this { log.Debug("Existing item {0} replicated successfully from {1}", id, Src); AddWithoutConflict(id, existingEtag, metadata, incoming); return; } if (existingDocumentIsInConflict == false && // if the current document is not in conflict, we can continue without having to keep conflict semantics existingDocumentIsDeleted && (Historian.IsDirectChildOfCurrent(metadata, existingMetadata))) // this update is direct child of the existing doc, so we are fine with overwriting this { log.Debug("Existing item {0} replicated successfully from {1}", id, Src); AddWithoutConflict(id, null, metadata, incoming); return; } if (TryResolveConflict(id, metadata, incoming, existingItem)) { AddWithoutConflict(id, existingEtag, metadata, incoming); return; } Database.TransactionalStorage.ExecuteImmediatelyOrRegisterForSyncronization(() => Database.RaiseNotifications(new DocumentChangeNotification { Id = id, Type = ReplicationConflict })); var newDocumentConflictId = SaveConflictedItem(id, metadata, incoming, existingEtag); if (existingDocumentIsInConflict) // the existing document is in conflict { log.Debug("Conflicted item {0} has a new version from {1}, adding to conflicted documents", id, Src); AppendToCurrentItemConflicts(id, newDocumentConflictId, existingMetadata, existingItem); return; } log.Debug("Existing item {0} is in conflict with replicated version 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/" + HashReplicationIdentifier(existingEtag); CreateConflict(id, newDocumentConflictId, existingDocumentConflictId, existingItem, existingMetadata); }
private void ReplicateDelete(string id, RavenJObject metadata, TExternal incoming) { TInternal existingItem; Guid existingEtag; var existingMetadata = TryGetExisting(id, out existingItem, out existingEtag); if (existingMetadata == null) { log.Debug("Replicating deleted item {0} from {1} that does not exist, ignoring", id, Src); return; } if (existingMetadata.Value <bool>(Constants.RavenReplicationConflict)) // locally conflicted { log.Debug("Replicating deleted item {0} from {1} that is already conflicted, adding to conflicts.", id, Src); var savedConflictedItemId = SaveConflictedItem(id, metadata, incoming, existingEtag); AppendToCurrentItemConflicts(id, savedConflictedItemId, existingMetadata, existingItem); return; } if (existingMetadata.Value <bool>(Constants.RavenDeleteMarker)) //deleted locally as well { log.Debug("Replicating deleted item {0} from {1} that was deleted locally. Merging histories", id, Src); var existingHistory = existingMetadata.Value <RavenJArray>(Constants.RavenReplicationHistory) ?? new RavenJArray(); var newHistory = metadata.Value <RavenJArray>(Constants.RavenReplicationHistory) ?? new RavenJArray(); foreach (var item in newHistory) { existingHistory.Add(item); } if (metadata.ContainsKey(Constants.RavenReplicationVersion) && metadata.ContainsKey(Constants.RavenReplicationSource)) { existingHistory.Add(new RavenJObject { { Constants.RavenReplicationVersion, metadata[Constants.RavenReplicationVersion] }, { Constants.RavenReplicationSource, metadata[Constants.RavenReplicationSource] } }); } while (existingHistory.Length > Constants.ChangeHistoryLength) { existingHistory.RemoveAt(0); } MarkAsDeleted(id, metadata); return; } if (Historian.IsDirectChildOfCurrent(metadata, existingMetadata)) // not modified { log.Debug("Delete of existing item {0} was replicated successfully from {1}", id, Src); DeleteItem(id, existingEtag); MarkAsDeleted(id, metadata); return; } Database.TransactionalStorage.ExecuteImmediatelyOrRegisterForSyncronization(() => Database.RaiseNotifications(new DocumentChangeNotification { Id = id, Type = DocumentChangeTypes.ReplicationConflict })); var newConflictId = SaveConflictedItem(id, metadata, incoming, existingEtag); 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/" + HashReplicationIdentifier(existingEtag); CreateConflict(id, newConflictId, existingDocumentConflictId, existingItem, existingMetadata); }
public SynchronizationWorkItem DetermineWork(string file, RavenJObject localMetadata, RavenJObject destinationMetadata, string localServerUrl, out NoSyncReason reason) { reason = NoSyncReason.Unknown; if (localMetadata == null) { reason = NoSyncReason.SourceFileNotExist; return(null); } if (destinationMetadata != null && destinationMetadata[SynchronizationConstants.RavenSynchronizationConflict] != null && destinationMetadata[SynchronizationConstants.RavenSynchronizationConflictResolution] == null) { reason = NoSyncReason.DestinationFileConflicted; return(null); } if (localMetadata[SynchronizationConstants.RavenSynchronizationConflict] != null) { reason = NoSyncReason.SourceFileConflicted; return(null); } if (localMetadata[SynchronizationConstants.RavenDeleteMarker] != null) { if (localMetadata.ContainsKey(SynchronizationConstants.RavenRenameFile)) { var rename = localMetadata.Value <string>(SynchronizationConstants.RavenRenameFile); if (destinationMetadata != null) { return(new RenameWorkItem(file, rename, localServerUrl, storage)); } return(new ContentUpdateWorkItem(rename, localServerUrl, storage, sigGenerator)); // we have a rename tombstone but file does not exists on destination } return(new DeleteWorkItem(file, localServerUrl, storage)); } if (destinationMetadata != null && Historian.IsDirectChildOfCurrent(localMetadata, destinationMetadata)) { reason = NoSyncReason.ContainedInDestinationHistory; return(null); } // file exists on dest and has the same content if (destinationMetadata != null && localMetadata.Value <string>("Content-MD5") == destinationMetadata.Value <string>("Content-MD5")) { // check metadata to detect if any synchronization is needed if (localMetadata.Keys.Except(new[] { "ETag", "Last-Modified" }) .Any(key => !destinationMetadata.ContainsKey(key) || localMetadata[key] != destinationMetadata[key])) { return(new MetadataUpdateWorkItem(file, localServerUrl, destinationMetadata, storage)); } reason = NoSyncReason.SameContentAndMetadata; return(null); // the same content and metadata - no need to synchronize } return(new ContentUpdateWorkItem(file, localServerUrl, storage, sigGenerator)); }
public void Replicate(string id, RavenJObject metadata, TExternal incoming) { if (metadata.Value <bool>(Constants.RavenDeleteMarker)) { ReplicateDelete(id, metadata, incoming); return; } TInternal existingItem; Etag existingEtag; bool deleted; RavenJObject existingMetadata; try { existingMetadata = TryGetExisting(id, out existingItem, out existingEtag, out deleted); } catch (Exception e) { log.ErrorException(string.Format("Replication - fetching existing item failed. (key = {0})", id), e); throw new InvalidOperationException("Replication - fetching existing item failed. (key = " + id + ")", e); } if (existingMetadata == null) { AddWithoutConflict(id, null, metadata, incoming); if (log.IsDebugEnabled) { log.Debug("New item {0} replicated successfully from {1}", id, Src); } return; } // we just got the same version from the same source - request playback again? // at any rate, not an error, moving on if (existingMetadata.Value <string>(Constants.RavenReplicationSource) == metadata.Value <string>(Constants.RavenReplicationSource) && existingMetadata.Value <long>(Constants.RavenReplicationVersion) == metadata.Value <long>(Constants.RavenReplicationVersion)) { return; } var existingDocumentIsInConflict = existingMetadata[Constants.RavenReplicationConflict] != null; if (existingDocumentIsInConflict == false && // if the current document is not in conflict, we can continue without having to keep conflict semantics (Historian.IsDirectChildOfCurrent(metadata, existingMetadata))) // this update is direct child of the existing doc, so we are fine with overwriting this { var etag = deleted == false ? existingEtag : null; AddWithoutConflict(id, etag, metadata, incoming); if (log.IsDebugEnabled) { log.Debug("Existing item {0} replicated successfully from {1}", id, Src); } return; } // this is the case where the incoming metadata is an older version of the metadata we have. // this can happen when multiple sources send data with diffrent latencies if (existingDocumentIsInConflict == false && Historian.IsDirectChildOfCurrent(existingMetadata, metadata)) { return; } RavenJObject resolvedMetadataToSave; TExternal resolvedItemToSave; if (TryResolveConflict(id, metadata, incoming, existingItem, out resolvedMetadataToSave, out resolvedItemToSave)) { if (resolvedMetadataToSave.ContainsKey("Raven-Remove-Document-Marker") && resolvedMetadataToSave.Value <bool>("Raven-Remove-Document-Marker")) { if (metadata.ContainsKey(Constants.RavenEntityName)) { resolvedMetadataToSave[Constants.RavenEntityName] = metadata[Constants.RavenEntityName]; } DeleteItem(id, null); MarkAsDeleted(id, resolvedMetadataToSave); } else { var etag = deleted == false ? existingEtag : null; var resolvedItemJObject = resolvedItemToSave as RavenJObject; if (resolvedItemJObject != null) { ExecuteRemoveConflictOnPutTrigger(id, metadata, resolvedItemJObject); } AddWithoutConflict(id, etag, resolvedMetadataToSave, resolvedItemToSave); } return; } //this is expensive but worth trying if we can avoid conflicts if (TryResolveConflictByCheckingIfIdentical(id, metadata, incoming, existingItem, out resolvedMetadataToSave)) { //The metadata here is merged (changed), it needs to be pushed. AddWithoutConflict(id, null, resolvedMetadataToSave, incoming); return; } CreatedConflict createdConflict; var newDocumentConflictId = SaveConflictedItem(id, metadata, incoming, existingEtag); if (existingDocumentIsInConflict) // the existing document is in conflict { if (log.IsDebugEnabled) { log.Debug("Conflicted item {0} has a new version from {1}, adding to conflicted documents", id, Src); } createdConflict = AppendToCurrentItemConflicts(id, newDocumentConflictId, existingMetadata, existingItem); } else { if (log.IsDebugEnabled) { log.Debug("Existing item {0} is in conflict with replicated version 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, newDocumentConflictId, existingDocumentConflictId, existingItem, existingMetadata); } Database.TransactionalStorage.ExecuteImmediatelyOrRegisterForSynchronization(() => Database.Notifications.RaiseNotifications(new ReplicationConflictNotification() { Id = id, Etag = createdConflict.Etag, ItemType = ReplicationConflict, OperationType = ReplicationOperationTypes.Put, Conflicts = createdConflict.ConflictedIds })); }
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 })); }
private void ReplicateDelete(string id, RavenJObject metadata, TExternal incoming) { TInternal existingItem; Etag existingEtag; bool deleted; var existingMetadata = TryGetExisting(id, out existingItem, out existingEtag, out deleted); if (existingMetadata == null) { log.Debug("Replicating deleted item {0} from {1} that does not exist, ignoring", id, Src); return; } if (existingMetadata.Value <bool>(Constants.RavenDeleteMarker)) //deleted locally as well { log.Debug("Replicating deleted item {0} from {1} that was deleted locally. Merging histories", id, Src); var existingHistory = new RavenJArray(ReplicationData.GetHistory(existingMetadata)); var newHistory = new RavenJArray(ReplicationData.GetHistory(metadata)); foreach (var item in newHistory) { existingHistory.Add(item); } if (metadata.ContainsKey(Constants.RavenReplicationVersion) && metadata.ContainsKey(Constants.RavenReplicationSource)) { existingHistory.Add(new RavenJObject { { Constants.RavenReplicationVersion, metadata[Constants.RavenReplicationVersion] }, { Constants.RavenReplicationSource, metadata[Constants.RavenReplicationSource] } }); } while (existingHistory.Length > Constants.ChangeHistoryLength) { existingHistory.RemoveAt(0); } MarkAsDeleted(id, metadata); return; } if (Historian.IsDirectChildOfCurrent(metadata, existingMetadata)) // not modified { log.Debug("Delete of existing item {0} was replicated successfully from {1}", id, Src); DeleteItem(id, existingEtag); MarkAsDeleted(id, metadata); return; } CreatedConflict createdConflict; if (existingMetadata.Value <bool>(Constants.RavenReplicationConflict)) // locally conflicted { log.Debug("Replicating deleted item {0} from {1} that is already conflicted, adding to conflicts.", id, Src); var savedConflictedItemId = SaveConflictedItem(id, metadata, incoming, existingEtag); createdConflict = AppendToCurrentItemConflicts(id, savedConflictedItemId, existingMetadata, existingItem); } else { RavenJObject resolvedMetadataToSave; TExternal resolvedItemToSave; if (TryResolveConflict(id, metadata, incoming, existingItem, out resolvedMetadataToSave, out resolvedItemToSave)) { AddWithoutConflict(id, existingEtag, resolvedMetadataToSave, resolvedItemToSave); return; } var newConflictId = SaveConflictedItem(id, metadata, incoming, existingEtag); 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 void Replicate(string id, RavenJObject metadata, TExternal incoming) { if (metadata.Value <bool>(Constants.RavenDeleteMarker)) { ReplicateDelete(id, metadata, incoming); return; } TInternal existingItem; Etag existingEtag; bool deleted; RavenJObject existingMetadata; try { existingMetadata = TryGetExisting(id, out existingItem, out existingEtag, out deleted); } catch (Exception e) { log.Error("Replication - fetching existing item failed. (key = {0})", id); throw new InvalidOperationException("Replication - fetching existing item failed. (key = " + id + ")", e); } if (existingMetadata == null) { log.Debug("New item {0} replicated successfully from {1}", id, Src); AddWithoutConflict(id, null, metadata, incoming); return; } // we just got the same version from the same source - request playback again? // at any rate, not an error, moving on if (existingMetadata.Value <string>(Constants.RavenReplicationSource) == metadata.Value <string>(Constants.RavenReplicationSource) && existingMetadata.Value <long>(Constants.RavenReplicationVersion) == metadata.Value <long>(Constants.RavenReplicationVersion)) { return; } var existingDocumentIsInConflict = existingMetadata[Constants.RavenReplicationConflict] != null; if (existingDocumentIsInConflict == false && // if the current document is not in conflict, we can continue without having to keep conflict semantics (Historian.IsDirectChildOfCurrent(metadata, existingMetadata))) // this update is direct child of the existing doc, so we are fine with overwriting this { log.Debug("Existing item {0} replicated successfully from {1}", id, Src); var etag = deleted == false ? existingEtag : null; AddWithoutConflict(id, etag, metadata, incoming); return; } RavenJObject resolvedMetadataToSave; TExternal resolvedItemToSave; if (TryResolveConflict(id, metadata, incoming, existingItem, out resolvedMetadataToSave, out resolvedItemToSave)) { if (metadata.ContainsKey("Raven-Remove-Document-Marker") && metadata.Value <bool>("Raven-Remove-Document-Marker")) { DeleteItem(id, null); MarkAsDeleted(id, metadata); } else { var etag = deleted == false ? existingEtag : null; AddWithoutConflict(id, etag, resolvedMetadataToSave, resolvedItemToSave); } return; } CreatedConflict createdConflict; var newDocumentConflictId = SaveConflictedItem(id, metadata, incoming, existingEtag); if (existingDocumentIsInConflict) // the existing document is in conflict { log.Debug("Conflicted item {0} has a new version from {1}, adding to conflicted documents", id, Src); createdConflict = AppendToCurrentItemConflicts(id, newDocumentConflictId, existingMetadata, existingItem); } else { log.Debug("Existing item {0} is in conflict with replicated version 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/" + HashReplicationIdentifier(existingEtag); createdConflict = CreateConflict(id, newDocumentConflictId, existingDocumentConflictId, existingItem, existingMetadata); } Database.TransactionalStorage.ExecuteImmediatelyOrRegisterForSynchronization(() => Database.Notifications.RaiseNotifications(new ReplicationConflictNotification() { Id = id, Etag = createdConflict.Etag, ItemType = ReplicationConflict, OperationType = ReplicationOperationTypes.Put, Conflicts = createdConflict.ConflictedIds })); }