You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by st...@apache.org on 2012/07/27 14:35:11 UTC
svn commit: r1366367 - in /jackrabbit/oak/trunk/oak-mk/src:
main/java/org/apache/jackrabbit/mk/core/
main/java/org/apache/jackrabbit/mk/json/
main/java/org/apache/jackrabbit/mk/model/
main/java/org/apache/jackrabbit/mk/store/ test/java/org/apache/jackr...
Author: stefan
Date: Fri Jul 27 12:35:11 2012
New Revision: 1366367
URL: http://svn.apache.org/viewvc?rev=1366367&view=rev
Log:
OAK-210: persistence granularity (WIP)
Added:
jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/json/JsonObject.java
jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/StagedNodeTree.java
Modified:
jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/core/MicroKernelImpl.java
jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/CommitBuilder.java
jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/MutableNode.java
jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/store/DefaultRevisionStore.java
jackrabbit/oak/trunk/oak-mk/src/test/java/org/apache/jackrabbit/mk/persistence/GCPersistenceTest.java
Modified: jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/core/MicroKernelImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/core/MicroKernelImpl.java?rev=1366367&r1=1366366&r2=1366367&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/core/MicroKernelImpl.java (original)
+++ jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/core/MicroKernelImpl.java Fri Jul 27 12:35:11 2012
@@ -23,8 +23,8 @@ import org.apache.jackrabbit.mk.json.Jso
import org.apache.jackrabbit.mk.json.JsopTokenizer;
import org.apache.jackrabbit.mk.model.Commit;
import org.apache.jackrabbit.mk.model.CommitBuilder;
-import org.apache.jackrabbit.mk.model.CommitBuilder.NodeTree;
import org.apache.jackrabbit.mk.model.Id;
+import org.apache.jackrabbit.mk.json.JsonObject;
import org.apache.jackrabbit.mk.model.StoredCommit;
import org.apache.jackrabbit.mk.model.tree.ChildNode;
import org.apache.jackrabbit.mk.model.tree.DiffBuilder;
@@ -380,7 +380,7 @@ public class MicroKernelImpl implements
}
String parentPath = PathUtils.getParentPath(nodePath);
String nodeName = PathUtils.getName(nodePath);
- cb.addNode(parentPath, nodeName, parseNode(t));
+ cb.addNode(parentPath, nodeName, JsonObject.create(t));
} else {
String value;
if (t.matches(JsopReader.NULL)) {
@@ -630,23 +630,6 @@ public class MicroKernelImpl implements
}
}
- NodeTree parseNode(JsopTokenizer t) throws Exception {
- NodeTree node = new NodeTree();
- if (!t.matches('}')) {
- do {
- String key = t.readString();
- t.read(':');
- if (t.matches('{')) {
- node.nodes.put(key, parseNode(t));
- } else {
- node.props.put(key, t.readRawValue().trim());
- }
- } while (t.matches(','));
- t.read('}');
- }
- return node;
- }
-
//-------------------------------------------------------< inner classes >
static class NodeFilter {
Added: jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/json/JsonObject.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/json/JsonObject.java?rev=1366367&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/json/JsonObject.java (added)
+++ jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/json/JsonObject.java Fri Jul 27 12:35:11 2012
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.mk.json;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Simple JSON Object representation
+ */
+public class JsonObject {
+
+ private Map<String, String> props = new HashMap<String, String>();
+ private Map<String, JsonObject> children = new HashMap<String, JsonObject>();
+
+ public static JsonObject create(JsopTokenizer t) {
+ JsonObject obj = new JsonObject();
+ if (!t.matches('}')) {
+ do {
+ String key = t.readString();
+ t.read(':');
+ if (t.matches('{')) {
+ obj.children.put(key, create(t));
+ } else {
+ obj.props.put(key, t.readRawValue().trim());
+ }
+ } while (t.matches(','));
+ t.read('}');
+ }
+ return obj;
+ }
+
+ public void toJson(JsopBuilder buf) {
+ toJson(buf, this);
+ }
+
+ public Map<String, String> getProperties() {
+ return props;
+ }
+
+ public Map<String, JsonObject> getChildren() {
+ return children;
+ }
+
+ private static void toJson(JsopBuilder buf, JsonObject obj) {
+ buf.object();
+ for (String name : obj.props.keySet()) {
+ buf.key(name).encodedValue(obj.props.get(name));
+ }
+ for (String name : obj.children.keySet()) {
+ buf.key(name);
+ toJson(buf, obj.children.get(name));
+ }
+ buf.endObject();
+ }
+}
\ No newline at end of file
Modified: jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/CommitBuilder.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/CommitBuilder.java?rev=1366367&r1=1366366&r2=1366367&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/CommitBuilder.java (original)
+++ jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/CommitBuilder.java Fri Jul 27 12:35:11 2012
@@ -16,20 +16,15 @@
*/
package org.apache.jackrabbit.mk.model;
+import org.apache.jackrabbit.mk.json.JsonObject;
import org.apache.jackrabbit.mk.json.JsopBuilder;
import org.apache.jackrabbit.mk.model.tree.DiffBuilder;
-import org.apache.jackrabbit.mk.model.tree.NodeDelta;
import org.apache.jackrabbit.mk.store.NotFoundException;
import org.apache.jackrabbit.mk.store.RevisionStore;
import org.apache.jackrabbit.oak.commons.PathUtils;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.Iterator;
import java.util.List;
-import java.util.Map;
/**
*
@@ -43,8 +38,9 @@ public class CommitBuilder {
private final RevisionStore store;
- // key is a path
- private final Map<String, MutableNode> staged = new HashMap<String, MutableNode>();
+ // staging area
+ private final StagedNodeTree stagedTree;
+
// change log
private final List<Change> changeLog = new ArrayList<Change>();
@@ -52,9 +48,10 @@ public class CommitBuilder {
this.baseRevId = baseRevId;
this.msg = msg;
this.store = store;
+ stagedTree = new StagedNodeTree(store, baseRevId);
}
- public void addNode(String parentNodePath, String nodeName, NodeTree node) throws Exception {
+ public void addNode(String parentNodePath, String nodeName, JsonObject node) throws Exception {
Change change = new AddNode(parentNodePath, nodeName, node);
change.apply();
// update change log
@@ -94,7 +91,7 @@ public class CommitBuilder {
}
public Id /* new revId */ doCommit(boolean createBranch) throws Exception {
- if (staged.isEmpty() && !createBranch) {
+ if (stagedTree.isEmpty() && !createBranch) {
// nothing to commit
return baseRevId;
}
@@ -106,15 +103,14 @@ public class CommitBuilder {
boolean privateCommit = createBranch || baseCommit.getBranchRootId() != null;
-
if (!privateCommit) {
Id currentHead = store.getHeadCommitId();
if (!currentHead.equals(baseRevId)) {
// todo gracefully handle certain conflicts (e.g. changes on moved sub-trees, competing deletes etc)
- // update base revision to new head
+ // update base revision to more recent current head
baseRevId = currentHead;
- // clear staging area
- staged.clear();
+ // reset staging area
+ stagedTree.reset(baseRevId);
// replay change log on new base revision
for (Change change : changeLog) {
change.apply();
@@ -124,7 +120,7 @@ public class CommitBuilder {
RevisionStore.PutToken token = store.createPutToken();
Id rootNodeId =
- changeLog.isEmpty() ? baseCommit.getRootNodeId() : persistStagedNodes(token);
+ changeLog.isEmpty() ? baseCommit.getRootNodeId() : stagedTree.persist(token);
Id newRevId;
@@ -133,12 +129,10 @@ public class CommitBuilder {
try {
Id currentHead = store.getHeadCommitId();
if (!currentHead.equals(baseRevId)) {
- StoredNode baseRoot = store.getRootNode(baseRevId);
- StoredNode theirRoot = store.getRootNode(currentHead);
- StoredNode ourRoot = store.getNode(rootNodeId);
-
- rootNodeId = mergeTree(baseRoot, ourRoot, theirRoot, token);
-
+ // there's a more recent head revision
+ // perform a three-way merge
+ rootNodeId = stagedTree.merge(store.getNode(rootNodeId), currentHead, baseRevId, token);
+ // update base revision to more recent current head
baseRevId = currentHead;
}
@@ -147,6 +141,7 @@ public class CommitBuilder {
// no need to create new commit object/update head revision
return currentHead;
}
+ // persist new commit
MutableCommit newCommit = new MutableCommit();
newCommit.setParentId(baseRevId);
newCommit.setCommitTS(System.currentTimeMillis());
@@ -189,7 +184,7 @@ public class CommitBuilder {
}
// reset instance
- staged.clear();
+ stagedTree.reset(newRevId);
changeLog.clear();
return newRevId;
@@ -204,7 +199,7 @@ public class CommitBuilder {
RevisionStore.PutToken token = store.createPutToken();
Id rootNodeId =
- changeLog.isEmpty() ? branchCommit.getRootNodeId() : persistStagedNodes(token);
+ changeLog.isEmpty() ? branchCommit.getRootNodeId() : stagedTree.persist(token);
Id newRevId;
@@ -212,11 +207,9 @@ public class CommitBuilder {
try {
Id currentHead = store.getHeadCommitId();
- StoredNode baseRoot = store.getRootNode(branchRootId);
- StoredNode theirRoot = store.getRootNode(currentHead);
StoredNode ourRoot = store.getNode(rootNodeId);
- rootNodeId = mergeTree(baseRoot, ourRoot, theirRoot, token);
+ rootNodeId = stagedTree.merge(ourRoot, currentHead, branchRootId, token);
if (store.getCommit(currentHead).getRootNodeId().equals(rootNodeId)) {
// the merge didn't cause any changes,
@@ -229,7 +222,7 @@ public class CommitBuilder {
newCommit.setMsg(msg);
// dynamically build diff of merged commit
String diff = new DiffBuilder(
- store.getNodeState(theirRoot),
+ store.getNodeState(store.getRootNode(currentHead)),
store.getNodeState(store.getNode(rootNodeId)),
"/", store, "").build();
newCommit.setChanges(diff);
@@ -241,231 +234,14 @@ public class CommitBuilder {
}
// reset instance
- staged.clear();
+ stagedTree.reset(newRevId);
changeLog.clear();
return newRevId;
}
- //-------------------------------------------------------< implementation >
-
- MutableNode getOrCreateStagedNode(String nodePath) throws Exception {
- MutableNode node = staged.get(nodePath);
- if (node == null) {
- MutableNode parent = staged.get("/");
- if (parent == null) {
- parent = new MutableNode(store.getRootNode(baseRevId), store, "/");
- staged.put("/", parent);
- }
- node = parent;
-
- int nth = PathUtils.getDepth(nodePath) - 1;
- for (String name : PathUtils.elements(nodePath)) {
- String path = PathUtils.getAncestorPath(nodePath, nth--);
- node = staged.get(path);
- if (node == null) {
- // not yet staged, resolve id using staged parent
- // to allow for staged move operations
- ChildNodeEntry cne = parent.getChildNodeEntry(name);
- if (cne == null) {
- throw new NotFoundException(nodePath);
- }
- node = new MutableNode(store.getNode(cne.getId()), store, path);
- staged.put(path, node);
- }
- parent = node;
- }
- }
- return node;
- }
-
- void moveStagedNodes(String srcPath, String destPath) throws Exception {
- MutableNode node = staged.get(srcPath);
- if (node != null) {
- staged.remove(srcPath);
- staged.put(destPath, node);
- for (Iterator<String> it = node.getChildNodeNames(0, -1); it.hasNext(); ) {
- String childName = it.next();
- moveStagedNodes(PathUtils.concat(srcPath, childName), PathUtils.concat(destPath, childName));
- }
- }
- }
-
- void copyStagedNodes(String srcPath, String destPath, String breakAtSrcPath) throws Exception {
- if (srcPath.equals(breakAtSrcPath)) {
- // OAK-83: prevent infinite recursion when copying to descendant path
- return;
- }
-
- MutableNode node = staged.get(srcPath);
- if (node != null) {
- staged.put(destPath, new MutableNode(node, store, destPath));
- for (Iterator<String> it = node.getChildNodeNames(0, -1); it.hasNext(); ) {
- String childName = it.next();
- copyStagedNodes(PathUtils.concat(srcPath, childName), PathUtils.concat(destPath, childName), breakAtSrcPath);
- }
- }
- }
-
- void removeStagedNodes(String nodePath) throws Exception {
- MutableNode node = staged.get(nodePath);
- if (node != null) {
- staged.remove(nodePath);
- for (Iterator<String> it = node.getChildNodeNames(0, -1); it.hasNext(); ) {
- String childName = it.next();
- removeStagedNodes(PathUtils.concat(nodePath, childName));
- }
- }
- }
-
- Id /* new id of root node */ persistStagedNodes(RevisionStore.PutToken token) throws Exception {
- // sort paths in in depth-descending order
- ArrayList<String> orderedPaths = new ArrayList<String>(staged.keySet());
- Collections.sort(orderedPaths, new Comparator<String>() {
- public int compare(String path1, String path2) {
- // paths should be ordered by depth, descending
- int result = getDepth(path2) - getDepth(path1);
- if (result != 0) {
- return result;
- } else {
- return path2.compareTo(path1);
- }
- }
-
- int getDepth(String path) {
- return PathUtils.getDepth(path);
- }
- });
- // iterate over staged entries in depth-descending order
- Id rootNodeId = null;
- for (String path : orderedPaths) {
- // persist node
- Id id = store.putNode(token, staged.get(path));
- if (PathUtils.denotesRoot(path)) {
- rootNodeId = id;
- } else {
- staged.get(PathUtils.getParentPath(path)).add(new ChildNodeEntry(PathUtils.getName(path), id));
- }
- }
- if (rootNodeId == null) {
- throw new Exception("internal error: inconsistent staging area content");
- }
- return rootNodeId;
- }
-
- /**
- * Performs a three-way merge of the trees rooted at {@code ourRoot},
- * {@code theirRoot}, using the tree at {@code baseRoot} as reference.
- *
- * @param baseRoot
- * @param ourRoot
- * @param theirRoot
- * @return id of merged root node
- * @throws Exception
- */
- Id /* id of merged root node */ mergeTree(StoredNode baseRoot, StoredNode ourRoot, StoredNode theirRoot,
- RevisionStore.PutToken token) throws Exception {
-
- // as we're going to use the staging area for the merge process,
- // we need to clear it first
- staged.clear();
-
- // recursively merge 'our' changes with 'their' changes...
- mergeNode(baseRoot, ourRoot, theirRoot, "/");
-
- return persistStagedNodes(token);
- }
-
- void mergeNode(StoredNode baseNode, StoredNode ourNode, StoredNode theirNode, String path) throws Exception {
- NodeDelta theirChanges = new NodeDelta(
- store, store.getNodeState(baseNode), store.getNodeState(theirNode));
- NodeDelta ourChanges = new NodeDelta(
- store, store.getNodeState(baseNode), store.getNodeState(ourNode));
-
- // merge non-conflicting changes
- MutableNode mergedNode = new MutableNode(theirNode, store, path);
- staged.put(path, mergedNode);
-
- mergedNode.getProperties().putAll(ourChanges.getAddedProperties());
- mergedNode.getProperties().putAll(ourChanges.getChangedProperties());
- for (String name : ourChanges.getRemovedProperties().keySet()) {
- mergedNode.getProperties().remove(name);
- }
-
- for (Map.Entry<String, Id> entry : ourChanges.getAddedChildNodes ().entrySet()) {
- mergedNode.add(new ChildNodeEntry(entry.getKey(), entry.getValue()));
- }
- for (Map.Entry<String, Id> entry : ourChanges.getChangedChildNodes ().entrySet()) {
- mergedNode.add(new ChildNodeEntry(entry.getKey(), entry.getValue()));
- }
- for (String name : ourChanges.getRemovedChildNodes().keySet()) {
- mergedNode.remove(name);
- }
-
- List<NodeDelta.Conflict> conflicts = theirChanges.listConflicts(ourChanges);
- // resolve/report merge conflicts
- for (NodeDelta.Conflict conflict : conflicts) {
- String conflictName = conflict.getName();
- String conflictPath = PathUtils.concat(path, conflictName);
- switch (conflict.getType()) {
- case PROPERTY_VALUE_CONFLICT:
- throw new Exception(
- "concurrent modification of property " + conflictPath
- + " with conflicting values: \""
- + ourNode.getProperties().get(conflictName)
- + "\", \""
- + theirNode.getProperties().get(conflictName));
-
- case NODE_CONTENT_CONFLICT: {
- if (ourChanges.getChangedChildNodes().containsKey(conflictName)) {
- // modified subtrees
- StoredNode baseChild = store.getNode(baseNode.getChildNodeEntry(conflictName).getId());
- StoredNode ourChild = store.getNode(ourNode.getChildNodeEntry(conflictName).getId());
- StoredNode theirChild = store.getNode(theirNode.getChildNodeEntry(conflictName).getId());
- // merge the dirty subtrees recursively
- mergeNode(baseChild, ourChild, theirChild, PathUtils.concat(path, conflictName));
- } else {
- // todo handle/merge colliding node creation
- throw new Exception("colliding concurrent node creation: " + conflictPath);
- }
- break;
- }
-
- case REMOVED_DIRTY_PROPERTY_CONFLICT:
- mergedNode.getProperties().remove(conflictName);
- break;
-
- case REMOVED_DIRTY_NODE_CONFLICT:
- mergedNode.remove(conflictName);
- break;
- }
-
- }
- }
-
//--------------------------------------------------------< inner classes >
- public static class NodeTree {
- public Map<String, String> props = new HashMap<String, String>();
- public Map<String, NodeTree> nodes = new HashMap<String, NodeTree>();
-
- void toJson(JsopBuilder buf) {
- toJson(buf, this);
- }
-
- private static void toJson(JsopBuilder buf, NodeTree node) {
- buf.object();
- for (String name : node.props.keySet()) {
- buf.key(name).encodedValue(node.props.get(name));
- }
- for (String name : node.nodes.keySet()) {
- buf.key(name);
- toJson(buf, node.nodes.get(name));
- }
- buf.endObject();
- }
- }
-
abstract class Change {
abstract void apply() throws Exception;
abstract String asDiff();
@@ -474,9 +250,9 @@ public class CommitBuilder {
class AddNode extends Change {
String parentNodePath;
String nodeName;
- NodeTree node;
+ JsonObject node;
- AddNode(String parentNodePath, String nodeName, NodeTree node) {
+ AddNode(String parentNodePath, String nodeName, JsonObject node) {
this.parentNodePath = parentNodePath;
this.nodeName = nodeName;
this.node = node;
@@ -484,7 +260,7 @@ public class CommitBuilder {
@Override
void apply() throws Exception {
- recursiveAddNode(parentNodePath, nodeName, node);
+ stagedTree.add(parentNodePath, nodeName, node);
}
@Override
@@ -494,24 +270,6 @@ public class CommitBuilder {
node.toJson(diff);
return diff.toString();
}
-
- private void recursiveAddNode(String parentPath, String name, NodeTree node) throws Exception {
- MutableNode modParent = getOrCreateStagedNode(parentPath);
- if (modParent.getChildNodeEntry(name) != null) {
- throw new Exception("there's already a child node with name '" + name + "'");
- }
- String newPath = PathUtils.concat(parentPath, name);
- MutableNode newChild = new MutableNode(store, newPath);
- newChild.getProperties().putAll(node.props);
-
- // id will be computed on commit
- modParent.add(new ChildNodeEntry(name, null));
- staged.put(newPath, newChild);
-
- for (String childName : node.nodes.keySet()) {
- recursiveAddNode(PathUtils.concat(parentPath, name), childName, node.nodes.get(childName));
- }
- }
}
class RemoveNode extends Change {
@@ -523,16 +281,7 @@ public class CommitBuilder {
@Override
void apply() throws Exception {
- String parentPath = PathUtils.getParentPath(nodePath);
- String nodeName = PathUtils.getName(nodePath);
-
- MutableNode parent = getOrCreateStagedNode(parentPath);
- if (parent.remove(nodeName) == null) {
- throw new NotFoundException(nodePath);
- }
-
- // update staging area
- removeStagedNodes(nodePath);
+ stagedTree.remove(nodePath);
}
@Override
@@ -554,39 +303,7 @@ public class CommitBuilder {
@Override
void apply() throws Exception {
- if (PathUtils.isAncestor(srcPath, destPath)) {
- throw new Exception("target path cannot be descendant of source path: " + destPath);
- }
-
- String srcParentPath = PathUtils.getParentPath(srcPath);
- String srcNodeName = PathUtils.getName(srcPath);
-
- String destParentPath = PathUtils.getParentPath(destPath);
- String destNodeName = PathUtils.getName(destPath);
-
- MutableNode srcParent = getOrCreateStagedNode(srcParentPath);
- if (srcParentPath.equals(destParentPath)) {
- if (srcParent.getChildNodeEntry(destNodeName) != null) {
- throw new Exception("node already exists at move destination path: " + destPath);
- }
- if (srcParent.rename(srcNodeName, destNodeName) == null) {
- throw new NotFoundException(srcPath);
- }
- } else {
- ChildNodeEntry srcCNE = srcParent.remove(srcNodeName);
- if (srcCNE == null) {
- throw new NotFoundException(srcPath);
- }
-
- MutableNode destParent = getOrCreateStagedNode(destParentPath);
- if (destParent.getChildNodeEntry(destNodeName) != null) {
- throw new Exception("node already exists at move destination path: " + destPath);
- }
- destParent.add(new ChildNodeEntry(destNodeName, srcCNE.getId()));
- }
-
- // update staging area
- moveStagedNodes(srcPath, destPath);
+ stagedTree.move(srcPath, destPath);
}
@Override
@@ -608,28 +325,7 @@ public class CommitBuilder {
@Override
void apply() throws Exception {
- String srcParentPath = PathUtils.getParentPath(srcPath);
- String srcNodeName = PathUtils.getName(srcPath);
-
- String destParentPath = PathUtils.getParentPath(destPath);
- String destNodeName = PathUtils.getName(destPath);
-
- MutableNode srcParent = getOrCreateStagedNode(srcParentPath);
- ChildNodeEntry srcCNE = srcParent.getChildNodeEntry(srcNodeName);
- if (srcCNE == null) {
- throw new NotFoundException(srcPath);
- }
-
- if (staged.containsKey(srcPath)) {
- // the copied subtree is modified
-
- // update staging area
- copyStagedNodes(srcPath, destPath, destPath);
- }
-
- MutableNode destParent = getOrCreateStagedNode(destParentPath);
- destParent.add(new ChildNodeEntry(destNodeName, srcCNE.getId()));
-
+ stagedTree.copy(srcPath, destPath);
}
@Override
@@ -638,7 +334,6 @@ public class CommitBuilder {
diff.tag('*').key(srcPath).value(destPath);
return diff.toString();
}
-
}
class SetProperty extends Change {
@@ -654,14 +349,7 @@ public class CommitBuilder {
@Override
void apply() throws Exception {
- MutableNode node = getOrCreateStagedNode(nodePath);
-
- Map<String, String> properties = node.getProperties();
- if (propValue == null) {
- properties.remove(propName);
- } else {
- properties.put(propName, propValue);
- }
+ stagedTree.setProperty(nodePath, propName, propValue);
}
@Override
@@ -676,5 +364,4 @@ public class CommitBuilder {
return diff.toString();
}
}
-
}
Modified: jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/MutableNode.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/MutableNode.java?rev=1366367&r1=1366366&r2=1366367&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/MutableNode.java (original)
+++ jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/MutableNode.java Fri Jul 27 12:35:11 2012
@@ -27,21 +27,12 @@ import java.util.Iterator;
*/
public class MutableNode extends AbstractNode implements PersistHook {
- /**
- * Node path, for informational purpose only.
- */
- private final transient String path;
-
- public MutableNode(RevisionProvider provider, String path) {
+ public MutableNode(RevisionProvider provider) {
super(provider);
-
- this.path = path;
}
- public MutableNode(Node other, RevisionProvider provider, String path) {
+ public MutableNode(Node other, RevisionProvider provider) {
super(other, provider);
-
- this.path = path;
}
public ChildNodeEntry add(ChildNodeEntry newEntry) {
@@ -80,9 +71,4 @@ public class MutableNode extends Abstrac
public void postPersist(RevisionStore store, RevisionStore.PutToken token) throws Exception {
// there's nothing to do
}
-
- @Override
- public String toString() {
- return path;
- }
}
Added: jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/StagedNodeTree.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/StagedNodeTree.java?rev=1366367&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/StagedNodeTree.java (added)
+++ jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/StagedNodeTree.java Fri Jul 27 12:35:11 2012
@@ -0,0 +1,482 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.mk.model;
+
+import org.apache.jackrabbit.mk.json.JsonObject;
+import org.apache.jackrabbit.mk.model.tree.NodeDelta;
+import org.apache.jackrabbit.mk.store.NotFoundException;
+import org.apache.jackrabbit.mk.store.RevisionStore;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A {@code StagedNodeTree} provides methods to manipulate a specific revision
+ * of the tree. The changes are recorded and can be persisted by calling
+ {@link #persist(RevisionStore.PutToken)}.
+ */
+public class StagedNodeTree {
+
+ private final RevisionStore store;
+
+ private StagedNode root;
+ private Id baseRevisionId;
+
+ /**
+ * Creates a new {@code StagedNodeTree} instance.
+ *
+ * @param store revision store used to read from and persist changes
+ * @param baseRevisionId id of revision the changes should be based upon
+ */
+ public StagedNodeTree(RevisionStore store, Id baseRevisionId) {
+ this.store = store;
+ this.baseRevisionId = baseRevisionId;
+ }
+
+ /**
+ * Discards all staged changes and resets the base revision to the
+ * specified new revision id.
+ *
+ * @param newBaseRevisionId id of revision the changes should be based upon
+ */
+ public void reset(Id newBaseRevisionId) {
+ root = null;
+ baseRevisionId = newBaseRevisionId;
+ }
+
+ /**
+ * Returns {@code true} if there are no staged changes, otherwise returns {@code false}.
+ * @return {@code true} if there are no staged changes, otherwise returns {@code false}.
+ */
+ public boolean isEmpty() {
+ return root == null;
+ }
+
+ /**
+ * Persists the staged nodes and returns the {@code Id} of new root node.
+ *
+ * @param token
+ * @return {@code Id} of new root node
+ * @throws Exception
+ */
+ public Id /* new id of root node */ persist(RevisionStore.PutToken token) throws Exception {
+ return root != null ? root.persist(token) : null;
+ }
+
+ /**
+ * Performs a three-way merge merging <i>our</i> tree (rooted at {@code ourRoot})
+ * and <i>their</i> tree (identified by {@code newBaseRevisionId}),
+ * using the common ancestor revision {@code commonAncestorRevisionId} as
+ * base reference.
+ * <p/>
+ * This instance will be initially reset to {@code newBaseRevisionId}, discarding
+ * all currently staged changes.
+ *
+ * @param ourRoot
+ * @param newBaseRevisionId
+ * @param commonAncestorRevisionId
+ * @param token
+ * @return {@code Id} of new root node
+ * @throws Exception
+ */
+ public Id /* new id of merged root node */ merge(StoredNode ourRoot,
+ Id newBaseRevisionId,
+ Id commonAncestorRevisionId,
+ RevisionStore.PutToken token) throws Exception {
+ // reset staging area to new base revision
+ reset(newBaseRevisionId);
+
+ StoredNode baseRoot = store.getRootNode(commonAncestorRevisionId);
+ StoredNode theirRoot = store.getRootNode(newBaseRevisionId);
+
+ // recursively merge 'our' changes with 'their' changes...
+ mergeNode(baseRoot, ourRoot, theirRoot, "/");
+
+ // persist staged nodes
+ return persist(token);
+ }
+
+ //-----------------------------------------< tree manipulation operations >
+
+ public void add(String parentNodePath, String nodeName, JsonObject node) throws Exception {
+ StagedNode parent = getStagedNode(parentNodePath, true);
+ if (parent.getChildNodeEntry(nodeName) != null) {
+ throw new Exception("there's already a child node with name '" + nodeName + "'");
+ }
+ parent.add(nodeName, node);
+ }
+
+ public void remove(String nodePath) throws Exception {
+ String parentPath = PathUtils.getParentPath(nodePath);
+ String nodeName = PathUtils.getName(nodePath);
+
+ StagedNode parent = getStagedNode(parentPath, true);
+ if (parent.remove(nodeName) == null) {
+ throw new NotFoundException(nodePath);
+ }
+
+ // discard any staged changes at nodePath
+ unstageNode(nodePath);
+ }
+
+ public void setProperty(String nodePath, String propName, String propValue) throws Exception {
+ StagedNode node = getStagedNode(nodePath, true);
+
+ Map<String, String> properties = node.getProperties();
+ if (propValue == null) {
+ properties.remove(propName);
+ } else {
+ properties.put(propName, propValue);
+ }
+ }
+
+ public void move(String srcPath, String destPath) throws Exception {
+ if (PathUtils.isAncestor(srcPath, destPath)) {
+ throw new Exception("target path cannot be descendant of source path: " + destPath);
+ }
+
+ String srcParentPath = PathUtils.getParentPath(srcPath);
+ String srcNodeName = PathUtils.getName(srcPath);
+
+ String destParentPath = PathUtils.getParentPath(destPath);
+ String destNodeName = PathUtils.getName(destPath);
+
+ StagedNode srcParent = getStagedNode(srcParentPath, true);
+ if (srcParent.getChildNodeEntry(srcNodeName) == null) {
+ throw new NotFoundException(srcPath);
+ }
+ StagedNode destParent = getStagedNode(destParentPath, true);
+ if (destParent.getChildNodeEntry(destNodeName) != null) {
+ throw new Exception("node already exists at move destination path: " + destPath);
+ }
+
+ if (srcParentPath.equals(destParentPath)) {
+ // rename
+ srcParent.rename(srcNodeName, destNodeName);
+ } else {
+ // move
+ srcParent.move(srcNodeName, destPath);
+ }
+ }
+
+ public void copy(String srcPath, String destPath) throws Exception {
+ String srcParentPath = PathUtils.getParentPath(srcPath);
+ String srcNodeName = PathUtils.getName(srcPath);
+
+ String destParentPath = PathUtils.getParentPath(destPath);
+ String destNodeName = PathUtils.getName(destPath);
+
+ StagedNode srcParent = getStagedNode(srcParentPath, false);
+ if (srcParent == null) {
+ // the subtree to be copied has not been modified
+ ChildNodeEntry entry = getStoredNode(srcParentPath).getChildNodeEntry(srcNodeName);
+ if (entry == null) {
+ throw new NotFoundException(srcPath);
+ }
+ StagedNode destParent = getStagedNode(destParentPath, true);
+ if (destParent.getChildNodeEntry(destNodeName) != null) {
+ throw new Exception("node already exists at copy destination path: " + destPath);
+ }
+ destParent.add(new ChildNodeEntry(destNodeName, entry.getId()));
+ return;
+ }
+
+ ChildNodeEntry srcEntry = srcParent.getChildNodeEntry(srcNodeName);
+ if (srcEntry == null) {
+ throw new NotFoundException(srcPath);
+ }
+
+ StagedNode destParent = getStagedNode(destParentPath, true);
+ StagedNode srcNode = getStagedNode(srcPath, false);
+ if (srcNode != null) {
+ // copy the modified subtree
+ destParent.add(destNodeName, srcNode.copy());
+ } else {
+ destParent.add(new ChildNodeEntry(destNodeName, srcEntry.getId()));
+ }
+ }
+
+ //-------------------------------------------------------< implementation >
+
+ private StagedNode getStagedNode(String path, boolean createIfNotStaged) throws Exception {
+ assert PathUtils.isAbsolute(path);
+
+ if (root == null) {
+ if (!createIfNotStaged) {
+ return null;
+ }
+ root = new StagedNode(store.getRootNode(baseRevisionId), store);
+ }
+
+ if (PathUtils.denotesRoot(path)) {
+ return root;
+ }
+
+ StagedNode parent = root, node = null;
+ for (String name : PathUtils.elements(path)) {
+ node = parent.getStagedChildNode(name, createIfNotStaged);
+ if (node == null) {
+ return null;
+ }
+ parent = node;
+ }
+ return node;
+ }
+
+ private StagedNode unstageNode(String path) throws Exception {
+ assert PathUtils.isAbsolute(path);
+
+ if (PathUtils.denotesRoot(path)) {
+ StagedNode unstaged = root;
+ root = null;
+ return unstaged;
+ }
+
+ String parentPath = PathUtils.getParentPath(path);
+ String name = PathUtils.getName(path);
+
+ StagedNode parent = getStagedNode(parentPath, false);
+ if (parent == null) {
+ return null;
+ }
+
+ return parent.unstageChildNode(name);
+ }
+
+ private StoredNode getStoredNode(String path) throws Exception {
+ assert PathUtils.isAbsolute(path);
+
+ if (PathUtils.denotesRoot(path)) {
+ return store.getRootNode(baseRevisionId);
+ }
+
+ StoredNode parent = store.getRootNode(baseRevisionId), node = null;
+ for (String name : PathUtils.elements(path)) {
+ ChildNodeEntry entry = parent.getChildNodeEntry(name);
+ if (entry == null) {
+ throw new NotFoundException(path);
+ }
+ node = store.getNode(entry.getId());
+ if (node == null) {
+ throw new NotFoundException(path);
+ }
+ parent = node;
+ }
+ return node;
+ }
+
+ /**
+ * Performs a three-way merge of the trees rooted at {@code ourRoot},
+ * {@code theirRoot}, using the tree at {@code baseRoot} as reference.
+ */
+ private void mergeNode(StoredNode baseNode, StoredNode ourNode, StoredNode theirNode, String path) throws Exception {
+ NodeDelta theirChanges = new NodeDelta(
+ store, store.getNodeState(baseNode), store.getNodeState(theirNode));
+ NodeDelta ourChanges = new NodeDelta(
+ store, store.getNodeState(baseNode), store.getNodeState(ourNode));
+
+ StagedNode stagedNode = getStagedNode(path, true);
+
+ // merge non-conflicting changes
+ stagedNode.getProperties().putAll(ourChanges.getAddedProperties());
+ stagedNode.getProperties().putAll(ourChanges.getChangedProperties());
+ for (String name : ourChanges.getRemovedProperties().keySet()) {
+ stagedNode.getProperties().remove(name);
+ }
+
+ for (Map.Entry<String, Id> entry : ourChanges.getAddedChildNodes ().entrySet()) {
+ stagedNode.add(new ChildNodeEntry(entry.getKey(), entry.getValue()));
+ }
+ for (Map.Entry<String, Id> entry : ourChanges.getChangedChildNodes ().entrySet()) {
+ stagedNode.add(new ChildNodeEntry(entry.getKey(), entry.getValue()));
+ }
+ for (String name : ourChanges.getRemovedChildNodes().keySet()) {
+ stagedNode.remove(name);
+ }
+
+ List<NodeDelta.Conflict> conflicts = theirChanges.listConflicts(ourChanges);
+ // resolve/report merge conflicts
+ for (NodeDelta.Conflict conflict : conflicts) {
+ String conflictName = conflict.getName();
+ String conflictPath = PathUtils.concat(path, conflictName);
+ switch (conflict.getType()) {
+ case PROPERTY_VALUE_CONFLICT:
+ throw new Exception(
+ "concurrent modification of property " + conflictPath
+ + " with conflicting values: \""
+ + ourNode.getProperties().get(conflictName)
+ + "\", \""
+ + theirNode.getProperties().get(conflictName));
+
+ case NODE_CONTENT_CONFLICT: {
+ if (ourChanges.getChangedChildNodes().containsKey(conflictName)) {
+ // modified subtrees
+ StoredNode baseChild = store.getNode(baseNode.getChildNodeEntry(conflictName).getId());
+ StoredNode ourChild = store.getNode(ourNode.getChildNodeEntry(conflictName).getId());
+ StoredNode theirChild = store.getNode(theirNode.getChildNodeEntry(conflictName).getId());
+ // merge the dirty subtrees recursively
+ mergeNode(baseChild, ourChild, theirChild, PathUtils.concat(path, conflictName));
+ } else {
+ // todo handle/merge colliding node creation
+ throw new Exception("colliding concurrent node creation: " + conflictPath);
+ }
+ break;
+ }
+
+ case REMOVED_DIRTY_PROPERTY_CONFLICT:
+ stagedNode.getProperties().remove(conflictName);
+ break;
+
+ case REMOVED_DIRTY_NODE_CONFLICT:
+ stagedNode.remove(conflictName);
+ break;
+ }
+
+ }
+ }
+
+ //--------------------------------------------------------< inner classes >
+
+ private class StagedNode extends MutableNode {
+
+ private final Map<String, StagedNode> stagedChildNodes = new HashMap<String, StagedNode>();
+
+ private StagedNode(RevisionStore store) {
+ super(store);
+ }
+
+ private StagedNode(Node base, RevisionStore store) {
+ super(base, store);
+ }
+
+ /**
+ * Returns a {@code StagedNode} representation of the given child node.
+ * If a {@code StagedNode} representation doesn't exist yet a new
+ * {@code StagedNode} instance will be returned if {@code createIfNotStaged == true},
+ * otherwise {@code null} will be returned.
+ * <p/>
+ * A {@code NotFoundException} will be thrown if there's no child node
+ * with the given name.
+ *
+ * @param name child node name
+ * @param createIfNotStaged flag controlling whether a new {@code StagedNode}
+ * instance should be created on demand
+ * @return a {@code StagedNode} instance or null if there's no {@code StagedNode}
+ * representation of the given child node and {@code createIfNotStaged == false}
+ * @throws NotFoundException if there's no child node with the given name
+ * @throws Exception if another error occurs
+ */
+ StagedNode getStagedChildNode(String name, boolean createIfNotStaged) throws Exception {
+ StagedNode child = stagedChildNodes.get(name);
+ if (child == null) {
+ ChildNodeEntry entry = getChildNodeEntry(name);
+ if (entry != null) {
+ if (createIfNotStaged) {
+ child = new StagedNode(store.getNode(entry.getId()), store);
+ stagedChildNodes.put(name, child);
+ }
+ } else {
+ throw new NotFoundException(name);
+ }
+ }
+ return child;
+ }
+
+ StagedNode unstageChildNode(String name) {
+ return stagedChildNodes.remove(name);
+ }
+
+ StagedNode add(String name, StagedNode node) {
+ stagedChildNodes.put(name, node);
+ // child id will be computed on persist
+ add(new ChildNodeEntry(name, null));
+ return node;
+ }
+
+ StagedNode copy() {
+ StagedNode copy = new StagedNode(this, store);
+ // recursively copy staged child nodes
+ for (Map.Entry<String, StagedNode> entry : stagedChildNodes.entrySet()) {
+ copy.add(entry.getKey(), entry.getValue().copy());
+ }
+ return copy;
+ }
+
+ StagedNode add(String name, JsonObject obj) {
+ StagedNode node = new StagedNode(store);
+ node.getProperties().putAll(obj.getProperties());
+ for (Map.Entry<String, JsonObject> entry : obj.getChildren().entrySet()) {
+ node.add(entry.getKey(), entry.getValue());
+ }
+ stagedChildNodes.put(name, node);
+ // child id will be computed on persist
+ add(new ChildNodeEntry(name, null));
+ return node;
+ }
+
+ void move(String name, String destPath) throws Exception {
+ ChildNodeEntry srcEntry = getChildNodeEntry(name);
+ assert srcEntry != null;
+
+ String destParentPath = PathUtils.getParentPath(destPath);
+ String destName = PathUtils.getName(destPath);
+
+ StagedNode destParent = getStagedNode(destParentPath, true);
+
+ StagedNode target = stagedChildNodes.get(name);
+
+ remove(name);
+ destParent.add(new ChildNodeEntry(destName, srcEntry.getId()));
+
+ if (target != null) {
+ // move staged child node
+ destParent.add(destName, target);
+ }
+ }
+
+ @Override
+ public ChildNodeEntry remove(String name) {
+ stagedChildNodes.remove(name);
+ return super.remove(name);
+ }
+
+ @Override
+ public ChildNodeEntry rename(String oldName, String newName) {
+ StagedNode child = stagedChildNodes.remove(oldName);
+ if (child != null) {
+ stagedChildNodes.put(newName, child);
+ }
+ return super.rename(oldName, newName);
+ }
+
+ Id persist(RevisionStore.PutToken token) throws Exception {
+ // recursively persist staged nodes
+ for (Map.Entry<String, StagedNode> entry : stagedChildNodes.entrySet()) {
+ String name = entry.getKey();
+ StagedNode childNode = entry.getValue();
+ // todo decide whether to inline/store child node separately based on some filter criteria
+ Id id = childNode.persist(token);
+ // update child node entry
+ add(new ChildNodeEntry(name, id));
+ }
+ // persist this node
+ return store.putNode(token, this);
+ }
+ }
+}
Modified: jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/store/DefaultRevisionStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/store/DefaultRevisionStore.java?rev=1366367&r1=1366366&r2=1366367&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/store/DefaultRevisionStore.java (original)
+++ jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/store/DefaultRevisionStore.java Fri Jul 27 12:35:11 2012
@@ -135,7 +135,7 @@ public class DefaultRevisionStore extend
.getBytes();
head = new Id(rawHead);
- Id rootNodeId = pm.writeNode(new MutableNode(this, "/"));
+ Id rootNodeId = pm.writeNode(new MutableNode(this));
MutableCommit initialCommit = new MutableCommit();
initialCommit.setCommitTS(System.currentTimeMillis());
initialCommit.setRootNodeId(rootNodeId);
Modified: jackrabbit/oak/trunk/oak-mk/src/test/java/org/apache/jackrabbit/mk/persistence/GCPersistenceTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk/src/test/java/org/apache/jackrabbit/mk/persistence/GCPersistenceTest.java?rev=1366367&r1=1366366&r2=1366367&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mk/src/test/java/org/apache/jackrabbit/mk/persistence/GCPersistenceTest.java (original)
+++ jackrabbit/oak/trunk/oak-mk/src/test/java/org/apache/jackrabbit/mk/persistence/GCPersistenceTest.java Fri Jul 27 12:35:11 2012
@@ -78,7 +78,7 @@ public class GCPersistenceTest {
@Test
public void testOldNodeIsSwept() throws Exception {
- MutableNode node = new MutableNode(null, "/");
+ MutableNode node = new MutableNode(null);
Id id = pm.writeNode(node);
Thread.sleep(1);
@@ -95,7 +95,7 @@ public class GCPersistenceTest {
@Test
public void testMarkedNodeIsNotSwept() throws Exception {
- MutableNode node = new MutableNode(null, "/");
+ MutableNode node = new MutableNode(null);
Id id = pm.writeNode(node);
// small delay needed
@@ -114,7 +114,7 @@ public class GCPersistenceTest {
public void testNewNodeIsNotSwept() throws Exception {
pm.start();
- MutableNode node = new MutableNode(null, "/");
+ MutableNode node = new MutableNode(null);
Id id = pm.writeNode(node);
// new node must already be marked