Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Commit.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Commit.java (revision 1727356) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Commit.java (working copy) @@ -30,6 +30,7 @@ import javax.annotation.Nullable; import com.google.common.base.Function; +import com.google.common.collect.Iterables; import com.google.common.collect.Sets; import org.apache.jackrabbit.oak.api.PropertyState; @@ -314,17 +315,17 @@ boolean commitRootHasChanges = operations.containsKey(commitRootPath); // create a "root of the commit" if there is none UpdateOp commitRoot = getUpdateOperationForNode(commitRootPath); - for (String p : operations.keySet()) { - UpdateOp op = operations.get(p); + + if (!commitRoot.isNew() && commitRootHasChanges) { + // commit root already exists and this is an update + changedNodes.add(commitRoot); + } + + for (UpdateOp op : operations.values()) { if (op.isNew()) { NodeDocument.setDeleted(op, revision, false); } - if (op == commitRoot) { - if (!op.isNew() && commitRootHasChanges) { - // commit root already exists and this is an update - changedNodes.add(op); - } - } else { + if (op != commitRoot) { NodeDocument.setCommitRoot(op, revision, commitRootDepth); if (op.isNew()) { newNodes.add(op); @@ -333,7 +334,7 @@ } } } - if (changedNodes.size() == 0 && commitRoot.isNew()) { + if (changedNodes.isEmpty() && commitRoot.isNew()) { // no updates and root of commit is also new. that is, // it is the root of a subtree added in a commit. // so we try to add the root like all other nodes @@ -342,30 +343,32 @@ } boolean success = false; try { - if (newNodes.size() > 0) { + if (!newNodes.isEmpty()) { // set commit root on new nodes if (!store.create(NODES, newNodes)) { // some of the documents already exist: // try to apply all changes one by one - for (UpdateOp op : newNodes) { - if (op == commitRoot) { - // don't write the commit root just yet - // (because there might be a conflict) - NodeDocument.unsetRevision(commitRoot, revision); - } - changedNodes.add(op); - } + changedNodes.addAll(newNodes); newNodes.clear(); + + // don't write the commit root just yet + // (because there might be a conflict) + NodeDocument.unsetRevision(commitRoot, revision); } } + for (UpdateOp op : changedNodes) { // set commit root on changed nodes. this may even apply // to the commit root. the _commitRoot entry is removed // again when the _revisions entry is set at the end NodeDocument.setCommitRoot(op, revision, commitRootDepth); - opLog.add(op); - createOrUpdateNode(store, op); } + + opLog.addAll(changedNodes); + List oldDocs = store.createOrUpdate(NODES, changedNodes); + checkConflicts(oldDocs, changedNodes); + checkSplitCandidate(oldDocs); + // finally write the commit root, unless it was already written // with added nodes (the commit root might be written twice, // first to check if there was a conflict, and only then to commit @@ -490,6 +493,12 @@ checkSplitCandidate(doc); } + private void checkSplitCandidate(Iterable docs) { + for (NodeDocument doc : docs) { + checkSplitCandidate(doc); + } + } + private void checkSplitCandidate(@Nullable NodeDocument doc) { if (doc != null && doc.getMemory() > SPLIT_CANDIDATE_THRESHOLD) { nodeStore.addSplitCandidate(doc.getId()); @@ -587,6 +596,25 @@ } } + private void checkConflicts(List oldDocs, + List updates) { + int i = 0; + List exceptions = new ArrayList(); + Set revisions = new HashSet(); + for (NodeDocument doc : oldDocs) { + UpdateOp op = updates.get(i++); + try { + checkConflicts(op, doc); + } catch (ConflictException e) { + exceptions.add(e); + Iterables.addAll(revisions, e.getConflictRevisions()); + } + } + if (!exceptions.isEmpty()) { + throw new ConflictException("Following exceptions occurred during the bulk update operations: " + exceptions, revisions); + } + } + private String formatConflictRevision(Revision r) { if (nodeStore.getHeadRevision().isRevisionNewer(r)) { return r + " (not yet visible)";