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);
        }
Example #2
0
        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
            });
        }
Example #3
0
        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
            }));
        }