@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); } } }
@Override public BasicDocumentRevision updateDocument( String docId, String prevRevId, final DocumentBody body) 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"); Preconditions.checkNotNull(body, "Input document body can not be null"); this.validateDBBody(body); CouchUtils.validateRevisionId(prevRevId); DocumentUpdated documentUpdated = 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."); } if (!preRevision.isCurrent()) { throw new ConflictException("Revision to be updated is not current revision."); } this.checkOffPreviousWinnerRevisionStatus(preRevision); String newRevisionId = this.insertNewWinnerRevision(body, preRevision); BasicDocumentRevision newRevision = this.getDocument(preRevision.getId(), newRevisionId); this.sqlDb.setTransactionSuccessful(); documentUpdated = new DocumentUpdated(preRevision, newRevision); return newRevision; } finally { this.sqlDb.endTransaction(); if (documentUpdated != null) { eventBus.post(documentUpdated); } } }
@Override public BasicDocumentRevision createDocument(String docId, final DocumentBody body) { Preconditions.checkState(this.isOpen(), "Database is closed"); CouchUtils.validateDocumentId(docId); Preconditions.checkNotNull(body, "Input document body can not be null"); this.validateDBBody(body); DocumentCreated documentCreated = null; this.sqlDb.beginTransaction(); try { long docNumericID = insertDocumentID(docId); if (docNumericID < 0) { throw new IllegalArgumentException( "Can not insert new doc, likely the docId exists already: " + docId); } String revisionId = CouchUtils.getFirstRevisionId(); long newSequence = insertRevision(docNumericID, revisionId, -1l, false, true, body.asBytes(), true); if (newSequence < 0) { throw new IllegalStateException("Error inserting data, please checking data."); } BasicDocumentRevision doc = getDocument(docId, revisionId); documentCreated = new DocumentCreated(doc); Log.d(LOG_TAG, "New document created: " + doc.toString()); this.sqlDb.setTransactionSuccessful(); return doc; } finally { this.sqlDb.endTransaction(); if (documentCreated != null) { eventBus.post(documentCreated); } } }
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; }
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()); } } }