@Override public BasicDocumentRevision updateLocalDocument( String docId, String prevRevId, final DocumentBody body) { 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"); CouchUtils.validateRevisionId(prevRevId); DocumentRevision preRevision = this.getLocalDocument(docId, prevRevId); this.sqlDb.beginTransaction(); try { String newRevId = CouchUtils.generateNextLocalRevisionId(prevRevId); ContentValues values = new ContentValues(); values.put("revid", newRevId); values.put("json", body.asBytes()); String[] whereArgs = new String[] {docId, prevRevId}; int rowsUpdated = this.sqlDb.update("localdocs", values, "docid=? AND revid=?", whereArgs); if (rowsUpdated == 1) { this.sqlDb.setTransactionSuccessful(); return this.getLocalDocument(docId, newRevId); } else { throw new IllegalStateException("Error updating local docs: " + preRevision); } } finally { this.sqlDb.endTransaction(); } }
@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 boolean checkRevisionIsInCorrectOrder(List<String> revisionHistory) { for (int i = 0; i < revisionHistory.size() - 1; i++) { CouchUtils.validateRevisionId(revisionHistory.get(i)); int l = CouchUtils.generationFromRevId(revisionHistory.get(i)); int m = CouchUtils.generationFromRevId(revisionHistory.get(i + 1)); if (l >= m) { return false; } } return true; }
@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 void forceInsert( DocumentRevision rev, List<String> revisionHistory, Map<String, Object> attachments) { Preconditions.checkState(this.isOpen(), "Database is closed"); Preconditions.checkNotNull(rev, "Input document revision can not be null"); Preconditions.checkNotNull(revisionHistory, "Input revision history must not be null"); Preconditions.checkArgument( revisionHistory.size() > 0, "Input revision history must not be empty"); Preconditions.checkArgument( checkCurrentRevisionIsInRevisionHistory(rev, revisionHistory), "Current revision must exist in revision history."); Preconditions.checkArgument( checkRevisionIsInCorrectOrder(revisionHistory), "Revision history must be in right order."); CouchUtils.validateDocumentId(rev.getId()); CouchUtils.validateRevisionId(rev.getRevision()); Log.v( LOG_TAG, "forceInsert(): " + rev.toString() + ",\n" + JSONUtils.toPrettyJson(revisionHistory)); DocumentCreated documentCreated = null; DocumentUpdated documentUpdated = null; boolean ok = true; this.sqlDb.beginTransaction(); try { long seq = 0; // sequence here is -1, but we need it to insert the attachment - also might be wanted by // subscribers if (this.containsDocument(rev.getId())) { seq = doForceInsertExistingDocumentWithHistory(rev, revisionHistory); rev.initialiseSequence(seq); // TODO fetch the parent doc? documentUpdated = new DocumentUpdated(null, rev); } else { seq = doForceInsertNewDocumentWithHistory(rev, revisionHistory); rev.initialiseSequence(seq); documentCreated = new DocumentCreated(rev); } // now deal with any attachments if (attachments != null) { for (String att : attachments.keySet()) { String data = (String) ((Map<String, Object>) attachments.get(att)).get("data"); InputStream is = new Base64InputStream(new ByteArrayInputStream(data.getBytes())); String type = (String) ((Map<String, Object>) attachments.get(att)).get("content_type"); // inline attachments are automatically decompressed, so we don't have to worry about that UnsavedStreamAttachment ufa = new UnsavedStreamAttachment(is, att, type); boolean result = false; try { PreparedAttachment preparedAttachment = new PreparedAttachment(ufa, this.attachmentManager.attachmentsDir); result = this.attachmentManager.addAttachment(preparedAttachment, rev); } catch (IOException e) { Log.e(LOG_TAG, "IOException when preparing attachment " + ufa + ": " + e); } if (!result) { Log.e( LOG_TAG, "There was a problem adding the attachment " + ufa + " to the datastore; not force inserting this document: " + rev); ok = false; break; } } } if (ok) { this.sqlDb.setTransactionSuccessful(); } } finally { this.sqlDb.endTransaction(); if (ok) { if (documentCreated != null) { eventBus.post(documentCreated); } else if (documentUpdated != null) { eventBus.post(documentUpdated); } } } }