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
            }));
        }
예제 #3
0
        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);
        }