private void pickWinnerOfConflicts( DocumentRevisionTree objectTree, BasicDocumentRevision newLeaf, BasicDocumentRevision previousLeaf) { // We are having a conflict, and we are resolving it if (newLeaf.isDeleted() == previousLeaf.isDeleted()) { // If both leafs are deleted or not int previousLeafDepth = objectTree.depth(previousLeaf.getSequence()); int newLeafDepth = objectTree.depth(newLeaf.getSequence()); if (previousLeafDepth > newLeafDepth) { this.changeDocumentToBeNotCurrent(newLeaf.getSequence()); } else if (previousLeafDepth < newLeafDepth) { this.changeDocumentToBeNotCurrent(previousLeaf.getSequence()); } else { // Compare revision hash if both leafs has same depth String previousRevisionHash = previousLeaf.getRevision().substring(2); String newRevisionHash = newLeaf.getRevision().substring(2); if (previousRevisionHash.compareTo(newRevisionHash) > 0) { this.changeDocumentToBeNotCurrent(newLeaf.getSequence()); } else { this.changeDocumentToBeNotCurrent(previousLeaf.getSequence()); } } } else { // If only one of the leaf is not deleted, that is the winner if (newLeaf.isDeleted()) { this.changeDocumentToBeNotCurrent(newLeaf.getSequence()); } else { this.changeDocumentToBeNotCurrent(previousLeaf.getSequence()); } } }
@Override public void deleteDocument(String docId, String prevRevId) throws ConflictException { Preconditions.checkState(this.isOpen(), "Database is closed"); Preconditions.checkArgument( !Strings.isNullOrEmpty(docId), "Input document id can not be empty"); Preconditions.checkArgument( !Strings.isNullOrEmpty(prevRevId), "Input previous revision id can not be empty"); CouchUtils.validateRevisionId(prevRevId); DocumentDeleted documentDeleted = null; this.sqlDb.beginTransaction(); try { BasicDocumentRevision preRevision = this.getDocument(docId, prevRevId); if (preRevision == null) { throw new IllegalArgumentException("The document trying to update does not exist."); } DocumentRevisionTree revisionTree = this.getAllRevisionsOfDocument(docId); if (revisionTree == null) { throw new IllegalArgumentException("Document does not exist for id: " + docId); } else if (!revisionTree.leafRevisionIds().contains(prevRevId)) { throw new ConflictException("Revision to be deleted is not a leaf node:" + prevRevId); } if (!preRevision.isDeleted()) { this.checkOffPreviousWinnerRevisionStatus(preRevision); String newRevisionId = CouchUtils.generateNextRevisionId(preRevision.getRevision()); // Previous revision to be deleted could be winner revision ("current" == true), // or a non-winner leaf revision ("current" == false), the new inserted // revision must have the same flag as it previous revision. // Deletion of non-winner leaf revision is mainly used when resolving // conflicts. this.insertRevision( preRevision.getInternalNumericId(), newRevisionId, preRevision.getSequence(), true, preRevision.isCurrent(), JSONUtils.EMPTY_JSON, false); BasicDocumentRevision newRevision = this.getDocument(preRevision.getId(), newRevisionId); documentDeleted = new DocumentDeleted(preRevision, newRevision); } // Very tricky! Must call setTransactionSuccessful() even no change // to the db within this method. This is to allow this method to be // nested to other outer transaction, otherwise, the outer transaction // will rollback. this.sqlDb.setTransactionSuccessful(); } finally { this.sqlDb.endTransaction(); if (documentDeleted != null) { eventBus.post(documentDeleted); } } }
private long insertDocumentHistoryIntoExistingTree( DocumentRevision newRevision, List<String> revisions, Long docNumericID, DocumentRevisionTree localRevs) { DocumentRevision parent = localRevs.lookup(newRevision.getId(), revisions.get(0)); Preconditions.checkNotNull(parent, "Parent must not be null"); BasicDocumentRevision previousLeaf = (BasicDocumentRevision) localRevs.getCurrentRevision(); // Walk through the remote history in chronological order, matching each revision ID to // a local revision. When the list diverges, start creating blank local revisions to fill // in the local history int i; for (i = 1; i < revisions.size(); i++) { DocumentRevision nextNode = localRevs.lookupChildByRevId(parent, revisions.get(i)); if (nextNode == null) { break; } else { parent = nextNode; } } if (i >= revisions.size()) { Log.v(LOG_TAG, "All revision are in local sqlDatabase already, no new revision inserted."); return -1; } // Insert the new stub revisions for (; i < revisions.size() - 1; i++) { Log.v( LOG_TAG, "Inserting new stub revision, id: " + docNumericID + ", rev: " + revisions.get(i)); this.changeDocumentToBeNotCurrent(parent.getSequence()); insertStubRevision(docNumericID, revisions.get(i), parent.getSequence()); parent = getDocument(newRevision.getId(), revisions.get(i)); localRevs.add(parent); } // Insert the new leaf revision Log.v(LOG_TAG, "Inserting new revision, id: " + docNumericID + ", rev: " + revisions.get(i)); String newRevisionId = revisions.get(revisions.size() - 1); this.changeDocumentToBeNotCurrent(parent.getSequence()); long sequence = insertRevision( docNumericID, newRevisionId, parent.getSequence(), newRevision.isDeleted(), true, newRevision.asBytes(), true); BasicDocumentRevision newLeaf = getDocument(newRevision.getId(), newRevisionId); localRevs.add(newLeaf); // Refresh previous leaf in case it is changed in sqlDb but not in memory previousLeaf = getDocument(previousLeaf.getId(), previousLeaf.getRevision()); if (previousLeaf.isCurrent()) { // we have a conflicts, and we need to resolve it. pickWinnerOfConflicts(localRevs, newLeaf, previousLeaf); } return sequence; }