You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by md...@apache.org on 2012/02/10 19:54:22 UTC
svn commit: r1242885 - in /jackrabbit/sandbox/jackrabbit-microkernel/src:
main/java/org/apache/jackrabbit/state/ test/java/org/apache/jackrabbit/state/
Author: mduerig
Date: Fri Feb 10 18:54:21 2012
New Revision: 1242885
URL: http://svn.apache.org/viewvc?rev=1242885&view=rev
Log:
Microkernel based prototype of JCR implementation (WIP)
- refactored transient space implementation based on change log consolidation
Added:
jackrabbit/sandbox/jackrabbit-microkernel/src/test/java/org/apache/jackrabbit/state/TransientSpaceFuzzTest.java
Modified:
jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/state/ChangeTree.java
jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/state/NodeState.java
jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/state/TransientSpace.java
jackrabbit/sandbox/jackrabbit-microkernel/src/test/java/org/apache/jackrabbit/state/TransientSpaceTest.java
Modified: jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/state/ChangeTree.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/state/ChangeTree.java?rev=1242885&r1=1242884&r2=1242885&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/state/ChangeTree.java (original)
+++ jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/state/ChangeTree.java Fri Feb 10 18:54:21 2012
@@ -24,39 +24,21 @@ import org.apache.jackrabbit.Path;
import org.apache.jackrabbit.json.JsonValue;
import org.apache.jackrabbit.spi.commons.iterator.Iterators;
import org.apache.jackrabbit.spi.commons.iterator.Predicate;
-import org.apache.jackrabbit.utils.Predicates;
import javax.jcr.ItemExistsException;
import javax.jcr.ItemNotFoundException;
import javax.jcr.PathNotFoundException;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
-import java.util.Set;
import static org.apache.commons.collections.map.ReferenceMap.HARD;
import static org.apache.commons.collections.map.ReferenceMap.WEAK;
import static org.apache.jackrabbit.utils.Unchecked.cast;
/**
- * A change tree records changes to a tree of nodes and properties and it provides
- * a consolidated view of these changes. Changes are described by operations for
- * adding nodes, removing nodes, moving nodes and setting properties. <p/>
- *
- * A consolidate change list can be obtained from change trees. A change list is
- * a list of operations. A consolidated changes list is a change list which is
- * equivalent to the change list consisting of all operations applied to the change
- * tree and which is minimal amongst all change lists which are equivalent to the
- * former one. <p/>
- *
- * Two change lists are equivalent iff their effect on a tree of nodes and properties
- * is the same. That is, when applied to a tree both result in the same final state
- * of that tree. <p/>
- *
- * A change list is minimal amongst a set of change lists, if no change list in that
- * set contains more operations. <p/>
+ * A change tree records changes to a tree of nodes and properties. <p/>
*
* Internally a change tree is a tree of node deltas. A node delta describes whether
* a node has been added, removed, moved or whether its properties have been changed.
@@ -66,40 +48,38 @@ import static org.apache.jackrabbit.util
* removed or moved. <p/>
*
* A move operation is conceptually handled as a remove operation followed by an add
- * operation of the respective sub tree. In order to keep track of the move operation
- * the individual remove and add operations are marked as belonging to that move
- * operation. <p/>
- *
- * Since a remove node operation cancels a preceding add node operation for the same
- * node and because a move operation resembles a remove followed by an add operation
- * following equivalencies apply for move operations:
- *
- * <ul>
- * <li>Removing a node after it has been moved is equivalent to removing the original
- * node.</li>
- * <li>Moving a node after it has been added is equivalent to adding the node at the
- * target of the move.</li>
- * <li>Moving a moved node is equivalent to directly moving from the original source
- * to the eventual target.</li>
- * </ul>
+ * operation of the respective sub tree. <p/>
*/
public class ChangeTree {
private final NodeDelta root;
private final Predicate<Path> nodeExists;
+ private final Listener listener;
/** Keep Existing instances at least as long as referenced by a client */
private final Map<Path, Existing> existing = cast(new ReferenceMap(HARD, WEAK));
/**
+ * Listener for modifications in the hierarchy
+ */
+ public interface Listener {
+ void added(NodeDelta nodeDelta);
+ void removed(NodeDelta nodeDelta);
+ void moved(Path source, NodeDelta nodeDelta);
+ void setValue(NodeDelta parent, String name, JsonValue value);
+ }
+
+ /**
* Create a new change tree rooted at {@code rootPath}.
* @param rootPath root path for this change tree
+ * @param listener listener for changes in the hierarchy
* @param nodeExists predicate which determines whether a path exists on the
* persistent layer.
*/
- public ChangeTree(final Path rootPath, Predicate<Path> nodeExists) {
+ public ChangeTree(final Path rootPath, Listener listener, Predicate<Path> nodeExists) {
this.nodeExists = nodeExists;
-
- root = new Existing(null, "") {
+ this.listener = listener;
+
+ root = new Existing(null, "", rootPath) {
@Override
public Path getPath() {
return rootPath;
@@ -132,14 +112,6 @@ public class ChangeTree {
}
/**
- * Collect all transient changes made to this change tree.
- * @param listener
- */
- public void collectChanges(Listener listener) {
- root.accept(new ChangeVisitor(listener));
- }
-
- /**
* Clear all transient changes made to this change tree.
*/
public void clear() {
@@ -150,161 +122,11 @@ public class ChangeTree {
/**
* @return {@code true} iff this change tree has transient changes.
*/
- public boolean hasChanges() {
+ public boolean hasChanges() {
return root.hasChanges();
}
/**
- * @return A {@code Existing} instance for the given {@code parent} and {@code name}.
- * Returns a previously allocated instance if not yet garbage collected.
- * <em>Note:</em> returning fresh instances while previously allocated ones are still
- * referenced in client code results in schizophrenia: same node multiple states.
- */
- private Existing existing(NodeDelta parent, String name) {
- Path path = parent.getPath().concat(name);
- Existing e = existing.get(path);
- if (e == null) {
- e = new Existing(parent, name);
- existing.put(path, e);
- }
- return e;
- }
-
- /**
- * Visitor for traversing a change tree.
- */
- public interface Visitor {
- void visit(Existing delta);
- void visit(Added delta);
- void visit(Removed delta);
- void visit(Moved delta);
- }
-
- /**
- * Listener for collecting changes in a change tree.
- * @see ChangeTree#collectChanges(org.apache.jackrabbit.state.ChangeTree.Listener)
- */
- public interface Listener {
-
- /**
- * Record addition of a node at {@code path}.
- * @param path
- */
- void addNode(Path path);
-
- /**
- * Record removal of a node at {@code path}.
- * @param path
- */
- void removeNode(Path path);
-
- /**
- * Record a move from {@code sourcePath} to {@code destPath}.
- * @param sourcePath
- * @param destinationPath
- */
- void moveNode(Path sourcePath, Path destinationPath);
-
- /**
- * Record setting a property value at {@code path}.
- * @param path
- * @param value
- */
- void setProperty(Path path, JsonValue value);
-
- /**
- * Record removal of a property at {@code path}.
- * @param path
- */
- void removeProperty(Path path);
- }
-
- private class ChangeVisitor implements Visitor {
- private final Set<NodeDelta> visited = new HashSet<NodeDelta>();
- private final Listener listener;
-
- public ChangeVisitor(Listener listener) {
- this.listener = listener;
- }
-
- @Override
- public void visit(Existing delta) {
- if (!visited.contains(delta)) {
- visited.add(delta);
- handleProperties(delta);
- visitNodes(delta);
- }
- }
-
- @Override
- public void visit(Added delta) {
- if (!visited.contains(delta)) {
- listener.addNode(delta.getPath());
- visited.add(delta);
- handleProperties(delta);
- visitNodes(delta);
- }
- }
-
- private void visitParent(NodeDelta delta) {
- if (!visited.contains(delta.parent)) {
- visitParent(delta.parent);
- delta.parent.accept(this);
- }
- }
-
- @Override
- public void visit(Removed delta) {
- if (!visited.contains(delta)) {
- if (delta.isMoved()) {
- // Visit parents of target before visiting target
- visitParent(delta.movedTo);
- visited.add(delta);
- delta.movedTo.accept(this);
- }
- else {
- // Visit parents of removed before visiting remove
- visitParent(delta.removed);
- delta.removed.accept(this);
- listener.removeNode(delta.getPath());
- visited.add(delta);
- }
- }
- }
-
- @Override
- public void visit(Moved delta) {
- if (!visited.contains(delta)) {
- delta.source.accept(this);
- listener.moveNode(delta.getSourcePath(), delta.getPath());
- visited.add(delta);
- handleProperties(delta);
- visitNodes(delta);
- }
- }
-
- private void handleProperties(NodeDelta delta) {
- Path path = delta.getPath();
- for (Entry<String, JsonValue> property : delta.properties.entrySet()) {
- String name = property.getKey();
- JsonValue value = property.getValue();
- if (value == null) {
- listener.removeProperty(path.concat(name));
- }
- else {
- listener.setProperty(path.concat(name), value);
- }
- }
- }
-
- private void visitNodes(NodeDelta delta) {
- for (NodeDelta node : delta.childNodes()) {
- node.accept(this);
- }
- }
- }
-
- /**
* {@code NodeDelta} instances record changes to a node. {@code NodeDelta}'s
* subclasses correspond to these changes:
*
@@ -313,8 +135,6 @@ public class ChangeTree {
* added node.</li>
* <li>{@link org.apache.jackrabbit.state.ChangeTree.Removed} represents a transiently
* removed node.</li>
- * <li>{@link org.apache.jackrabbit.state.ChangeTree.Moved} represents a transiently
- * moved node.</li>
* <li>{@link org.apache.jackrabbit.state.ChangeTree.Existing} represents a node which
* is otherwise touched. That is, which either has property modifications or a has a
* child node which is touched. </li>
@@ -346,35 +166,25 @@ public class ChangeTree {
}
/**
- * @return persistent path to this node or {@code null} if this node is neither
- * an {@link org.apache.jackrabbit.state.ChangeTree.Existing existing} node nor a
- * {@link org.apache.jackrabbit.state.ChangeTree.Moved moved} node and this does
- * not have a persistent path.
+ * @return persistent path to this node or {@code null} if this node is not
+ * an {@link org.apache.jackrabbit.state.ChangeTree.Existing existing} node.
*/
public Path getPersistentPath() {
return null;
}
/**
- * @return persistent name to this node or {@code null} if this node is neither
- * an {@link org.apache.jackrabbit.state.ChangeTree.Existing existing} node nor a
- * {@link org.apache.jackrabbit.state.ChangeTree.Moved moved} node and this does
- * not have a persistent name.
+ * @return {@code true} iff this node has been transiently removed.
*/
- public String getPersistedName() {
- return null;
- }
+ public abstract boolean isRemoved();
/**
- * @return {@code true} iff this is node has been transiently removed. This is
- * either the result of a remove or a move operation.
+ * @return {@code true} iff this node has been transiently added.
*/
- public abstract boolean isRemoved();
+ public abstract boolean isAdded();
/**
- * @return {@code true} iff this node is moved. If at the same time {@link #isRemoved()}
- * is {@code true}, this is the source node of the move operation. Otherwise this is
- * the target node of the move operation.
+ * @return {@code true} idd this node has been transiently moved.
*/
public abstract boolean isMoved();
@@ -383,21 +193,12 @@ public class ChangeTree {
*/
public abstract boolean isTransient();
- public abstract void accept(Visitor visitor);
-
/**
- * @return {@code true} iff this node has either changed child nodes or changed
- * properties.
+ * @return {@code true} iff this node has changes. A node has changes
+ * iff it either has changed properties or one of its child nodes has changes.
*/
public boolean hasChanges() {
- return !properties.isEmpty()
- || Predicates.exists(childNodes.values(), new Predicate<NodeDelta>() {
-
- @Override
- public boolean evaluate(NodeDelta delta) {
- return delta.isTransient();
- }
- });
+ return !properties.isEmpty() || !childNodes.isEmpty();
}
/**
@@ -415,7 +216,7 @@ public class ChangeTree {
public abstract NodeDelta getNode(String name);
/**
- * @return Iterator of all added nodes and all nodes moved here.
+ * @return Iterator of all added nodes
*/
public Iterator<NodeDelta> getNodes() {
return Iterators.filterIterator(childNodes().iterator(), new Predicate<NodeDelta>() {
@@ -468,7 +269,9 @@ public class ChangeTree {
throw new ItemExistsException(name);
}
- return addChild(new Added(this, name));
+ NodeDelta added = addChild(new Added(this, name));
+ notifyAdded(added);
+ return added;
}
/**
@@ -483,7 +286,9 @@ public class ChangeTree {
throw new ItemNotFoundException(name);
}
- return delta.remove();
+ NodeDelta removed = delta.remove();
+ notifyRemoved(removed);
+ return removed;
}
/**
@@ -511,7 +316,9 @@ public class ChangeTree {
throw new PathNotFoundException(destParentPath.toJcrPath());
}
- source.moveTo(destParentPath, destination.getName());
+ Path sourcePath = source.getPath();
+ NodeDelta moved = source.moveTo(destParentPath, destination.getName());
+ notifyMoved(sourcePath, moved);
}
/**
@@ -520,7 +327,7 @@ public class ChangeTree {
* @param name
* @param value
*/
- public void setValue(String name, JsonValue value) {
+ public void setValue(String name, JsonValue value) { // fixme: make this JsonAtom!?
if (value == null && properties.containsKey(name) && properties.get(name) != null) {
properties.remove(name);
}
@@ -528,14 +335,23 @@ public class ChangeTree {
properties.put(name, value);
touch();
}
+ notifySetValue(this, name, value);
}
//------------------------------------------< internal >---
void touch() { }
- abstract NodeDelta remove() throws ItemNotFoundException;
- abstract void moveTo(Path parentPath, String name);
+ NodeDelta remove() {
+ return parent.addChild(new Removed(parent, name));
+ }
+
+ NodeDelta moveTo(Path parentPath, String name) {
+ remove();
+ this.name = name;
+ NodeDelta parent = ChangeTree.this.getNode(parentPath);
+ return parent.addChild(this);
+ }
final void clear() {
childNodes.clear();
@@ -568,24 +384,64 @@ public class ChangeTree {
}
return delta;
}
+
+ private void notifyAdded(NodeDelta added) {
+ if (listener != null) {
+ listener.added(added);
+ }
+ }
+
+ private void notifyRemoved(NodeDelta removed) {
+ if (listener != null) {
+ listener.removed(removed);
+ }
+ }
+
+ private void notifyMoved(Path sourcePath, NodeDelta moved) {
+ if (listener != null) {
+ listener.moved(sourcePath, moved);
+ }
+ }
+
+ private void notifySetValue(NodeDelta parent, String name, JsonValue value) {
+ if (listener != null) {
+ listener.setValue(parent, name, value);
+ }
+ }
+ }
+
+ //------------------------------------------< private/internal >---
+
+ /**
+ * @return A {@code Existing} instance for the given {@code parent} and {@code name}.
+ * Returns a previously allocated instance if not yet garbage collected.
+ * <em>Note:</em> returning fresh instances while previously allocated ones are still
+ * referenced in client code results in schizophrenia: same node multiple states.
+ */
+ private Existing existing(NodeDelta parent, String name, Path persistentPath) {
+ Existing e = existing.get(persistentPath);
+ if (e == null) {
+ e = new Existing(parent, name, persistentPath);
+ existing.put(persistentPath, e);
+ }
+ return e;
}
/**
* Represents an existing node. That is, a node which exists on the persistence layer.
*/
- public class Existing extends NodeDelta {
- Existing(NodeDelta parent, String name) {
+ private class Existing extends NodeDelta {
+ private final Path persistentPath;
+ private boolean isMoved;
+
+ Existing(NodeDelta parent, String name, Path persistentPath) {
super(parent, name);
+ this.persistentPath = persistentPath;
}
@Override
public Path getPersistentPath() {
- return getPath();
- }
-
- @Override
- public String getPersistedName() {
- return getName();
+ return persistentPath;
}
@Override
@@ -594,26 +450,27 @@ public class ChangeTree {
}
@Override
- public boolean isMoved() {
+ public boolean isAdded() {
return false;
}
@Override
- public boolean isTransient() {
- return false;
+ public boolean isMoved() {
+ return isMoved;
}
@Override
- public void accept(Visitor visitor) {
- visitor.visit(this);
+ public boolean isTransient() {
+ return !isMoved;
}
@Override
public NodeDelta getNode(String name) {
NodeDelta delta = getChild(name);
if (delta == null) {
- return nodeExists.evaluate(getPath().concat(name))
- ? existing(this, name)
+ Path path = persistentPath.concat(name);
+ return nodeExists.evaluate(path)
+ ? existing(this, name, path)
: null;
}
else {
@@ -633,25 +490,19 @@ public class ChangeTree {
return "Existing[" + getPath() + ']';
}
- @Override
- NodeDelta remove() throws ItemNotFoundException {
- return parent.addChild(new Removed(parent, name, this, null));
- }
+ //------------------------------------------< internal >---
@Override
- void moveTo(Path parentPath, String name) {
- Moved movedTo = new Moved(parent, name, this);
- Removed removed = new Removed(parent, this.name, this, movedTo);
- parent.addChild(removed);
- NodeDelta parent = ChangeTree.this.getNode(parentPath);
- parent.addChild(movedTo);
+ NodeDelta moveTo(Path parentPath, String name) {
+ isMoved = true;
+ return super.moveTo(parentPath, name);
}
}
/**
* Represents a transiently added node.
*/
- public class Added extends NodeDelta {
+ private class Added extends NodeDelta {
Added(NodeDelta parent, String name) {
super(parent, name);
}
@@ -662,80 +513,13 @@ public class ChangeTree {
}
@Override
- public boolean isMoved() {
- return false;
- }
-
- @Override
- public boolean isTransient() {
+ public boolean isAdded() {
return true;
}
@Override
- public void accept(Visitor visitor) {
- visitor.visit(this);
- }
-
- @Override
- public NodeDelta getNode(String name) {
- return getChild(name);
- }
-
- @Override
- public String toString() {
- return "Added[" + getPath() + ']';
- }
-
- @Override
- NodeDelta remove() {
- return parent.removeChild(name);
- }
-
- @Override
- void moveTo(Path parentPath, String name) {
- parent.removeChild(this.name);
- NodeDelta destParent = ChangeTree.this.getNode(parentPath);
- this.name = name;
- destParent.addChild(this);
- }
- }
-
- /**
- * Represents a moved node.
- */
- public class Moved extends NodeDelta {
- final NodeDelta source;
-
- Moved(NodeDelta parent, String name, NodeDelta source) {
- super(parent, name);
- this.source = source;
- }
-
- /**
- * @return path to the source node of the move operation.
- */
- public Path getSourcePath() {
- return source.getPath();
- }
-
- @Override
- public Path getPersistentPath() {
- return source.getPersistentPath();
- }
-
- @Override
- public String getPersistedName() {
- return source.getPersistedName();
- }
-
- @Override
- public boolean isRemoved() {
- return false;
- }
-
- @Override
public boolean isMoved() {
- return true;
+ return false;
}
@Override
@@ -744,54 +528,23 @@ public class ChangeTree {
}
@Override
- public void accept(Visitor visitor) {
- visitor.visit(this);
- }
-
- @Override
public NodeDelta getNode(String name) {
- NodeDelta delta = source.getNode(name);
- if (delta == null) {
- delta = getChild(name);
- }
- return delta;
+ NodeDelta delta = getChild(name);
+ return delta == null || delta.isRemoved() ? null : delta;
}
@Override
public String toString() {
- return "Moved[" + getPath() + "] from " + source;
- }
-
- @Override
- NodeDelta remove() throws ItemNotFoundException {
- // convert source from moved away to removed
- if (source.parent.childNodes.remove(source.name) != null) {
- source.parent.removeNode(source.name);
- }
-
- return parent.removeChild(name);
- }
-
- @Override
- void moveTo(Path parentPath, String name) {
- parent.removeChild(this.name);
- NodeDelta destParent = ChangeTree.this.getNode(parentPath);
- this.name = name;
- destParent.addChild(this);
+ return "Added[" + getPath() + ']';
}
}
/**
* Represents a transiently removed node.
*/
- public class Removed extends NodeDelta {
- private final Existing removed;
- private final Moved movedTo;
-
- Removed(NodeDelta parent, String name, Existing removed, Moved movedTo) {
+ private class Removed extends NodeDelta {
+ Removed(NodeDelta parent, String name) {
super(parent, name);
- this.removed = removed;
- this.movedTo = movedTo;
}
@Override
@@ -800,18 +553,18 @@ public class ChangeTree {
}
@Override
- public boolean isMoved() {
- return movedTo != null;
+ public boolean isAdded() {
+ return false;
}
@Override
- public boolean isTransient() {
- return true;
+ public boolean isMoved() {
+ return false;
}
@Override
- public void accept(Visitor visitor) {
- visitor.visit(this);
+ public boolean isTransient() {
+ return true;
}
@Override
@@ -840,23 +593,18 @@ public class ChangeTree {
}
@Override
- NodeDelta remove() throws ItemNotFoundException {
+ NodeDelta remove() {
throw new IllegalStateException("Removed");
}
@Override
- void moveTo(Path parentPath, String name) {
+ NodeDelta moveTo(Path parentPath, String name) {
throw new IllegalStateException("Removed");
}
@Override
public String toString() {
- if (isMoved()) {
- return "Moved[" + getPath() + "] to " + movedTo.getPath();
- }
- else {
- return "Removed[" + getPath() + ']';
- }
+ return "Removed[" + getPath() + ']';
}
}
Modified: jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/state/NodeState.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/state/NodeState.java?rev=1242885&r1=1242884&r2=1242885&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/state/NodeState.java (original)
+++ jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/state/NodeState.java Fri Feb 10 18:54:21 2012
@@ -265,7 +265,7 @@ public class NodeState {
jsonObject = JsonObject.EMPTY;
}
else {
- String json = getMicrokernel().getNodes(nodeDelta.getPersistentPath().toMkPath(), revision);
+ String json = getMicrokernel().getNodes(path.toMkPath(), revision);
jsonObject = FullJsonParser.parseObject(new UnescapingJsonTokenizer(json));
}
}
Modified: jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/state/TransientSpace.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/state/TransientSpace.java?rev=1242885&r1=1242884&r2=1242885&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/state/TransientSpace.java (original)
+++ jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/state/TransientSpace.java Fri Feb 10 18:54:21 2012
@@ -38,6 +38,8 @@ import javax.jcr.RepositoryException;
public class TransientSpace {
private final MicroKernel microkernel;
private final ChangeTree changeTree;
+ private final ChangeLog changeLog = new ChangeLog();
+
private String revision;
/**
@@ -51,12 +53,36 @@ public class TransientSpace {
this.microkernel = microkernel;
this.revision = revision;
- changeTree = new ChangeTree(Path.create(workspace), new Predicate<Path>() {
+ Listener changeTreeListener = new Listener() {
+ @Override
+ public void added(NodeDelta nodeDelta) {
+ changeLog.addNode(nodeDelta.getPath());
+ }
+
+ @Override
+ public void removed(NodeDelta nodeDelta) {
+ changeLog.removeNode(nodeDelta.getPath());
+ }
+
+ @Override
+ public void moved(Path source, NodeDelta nodeDelta) {
+ changeLog.moveNode(source, nodeDelta.getPath());
+ }
+
+ @Override
+ public void setValue(NodeDelta parent, String name, JsonValue value) {
+ changeLog.setProperty(parent.getPath(), name, value == null ? null : value.asAtom()); // fixme: JsonValue should be JsonAtom
+ }
+ };
+
+ Predicate<Path> nodeExists = new Predicate<Path>() {
@Override
public boolean evaluate(Path path) {
return microkernel.nodeExists(path.toMkPath(), getRevision());
}
- });
+ };
+
+ changeTree = new ChangeTree(Path.create(workspace), changeTreeListener, nodeExists);
}
/**
@@ -76,50 +102,8 @@ public class TransientSpace {
*/
public String save() throws RepositoryException {
try {
- final StringBuilder jsop = new StringBuilder();
-
- changeTree.collectChanges(new Listener() {
- @Override
- public void addNode(Path path) {
- jsop.append("+\"")
- .append(path.toMkPath())
- .append("\":{}\n");
- }
-
- @Override
- public void removeNode(Path path) {
- jsop.append("-\"")
- .append(path.toMkPath())
- .append("\"\n");
- }
-
- @Override
- public void moveNode(Path sourcePath, Path destinationPath) {
- jsop.append(">\"")
- .append(sourcePath.toMkPath())
- .append("\":\"")
- .append(destinationPath.toMkPath())
- .append("\"\n");
- }
-
- @Override
- public void setProperty(Path path, JsonValue value) {
- jsop.append("^\"")
- .append(path.toMkPath())
- .append("\":")
- .append(value.toJson())
- .append('\n');
- }
-
- @Override
- public void removeProperty(Path path) {
- jsop.append("^\"")
- .append(path.toMkPath())
- .append("\":null\n");
- }
- });
-
- revision = microkernel.commit("", jsop.toString(), revision, "");
+ revision = microkernel.commit("", changeLog.toJsop(), revision, "");
+ changeLog.clear();
changeTree.clear();
return revision;
}
Added: jackrabbit/sandbox/jackrabbit-microkernel/src/test/java/org/apache/jackrabbit/state/TransientSpaceFuzzTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-microkernel/src/test/java/org/apache/jackrabbit/state/TransientSpaceFuzzTest.java?rev=1242885&view=auto
==============================================================================
--- jackrabbit/sandbox/jackrabbit-microkernel/src/test/java/org/apache/jackrabbit/state/TransientSpaceFuzzTest.java (added)
+++ jackrabbit/sandbox/jackrabbit-microkernel/src/test/java/org/apache/jackrabbit/state/TransientSpaceFuzzTest.java Fri Feb 10 18:54:21 2012
@@ -0,0 +1,456 @@
+/*
+ * 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.state;
+
+import org.apache.jackrabbit.Path;
+import org.apache.jackrabbit.json.JsonHandler;
+import org.apache.jackrabbit.json.JsonParser;
+import org.apache.jackrabbit.json.JsonTokenizer;
+import org.apache.jackrabbit.json.JsonValue.JsonAtom;
+import org.apache.jackrabbit.json.Token;
+import org.apache.jackrabbit.json.UnescapingJsonTokenizer;
+import org.apache.jackrabbit.mk.MicroKernelFactory;
+import org.apache.jackrabbit.mk.api.MicroKernel;
+import org.apache.jackrabbit.state.TransientSpaceFuzzTest.Operation.AddNode;
+import org.apache.jackrabbit.state.TransientSpaceFuzzTest.Operation.MoveNode;
+import org.apache.jackrabbit.state.TransientSpaceFuzzTest.Operation.RemoveNode;
+import org.apache.jackrabbit.state.TransientSpaceFuzzTest.Operation.Save;
+import org.apache.jackrabbit.state.TransientSpaceFuzzTest.Operation.SetProperty;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.ItemExistsException;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.RepositoryException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(Parameterized.class)
+public class TransientSpaceFuzzTest {
+ static final Logger log = LoggerFactory.getLogger(TransientSpaceFuzzTest.class);
+
+ private static final Path ROOT = Path.create("root");
+ private static final int OP_COUNT = 50000;
+
+ private final Random random;
+
+ private MicroKernel mk1;
+ private MicroKernel mk2;
+ private TransientSpace transientSpace;
+
+ @Parameters
+ public static List<Object[]> seeds() {
+ return Arrays.asList(new Object[][] {
+ {0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9},
+ {10}, {11}, {12}, {13}, {14}, {15}, {16}, {17}, {18}, {19},
+ {20}, {21}, {22}, {23}, {24}, {25}, {26}, {27}, {28}, {29},
+ {30}, {31}, {32}, {33}, {34}, {35}, {36}, {37}, {38}, {39},
+ });
+ }
+
+ public TransientSpaceFuzzTest(int seed) {
+ log.info("Seed = {}", seed);
+ random = new Random(seed);
+ }
+
+ @Before
+ public void setup() {
+ mk1 = MicroKernelFactory.getInstance("fs:target/repository-test/microkernel1;clean");
+ mk1.commit("", "+\"/root\":{}", mk1.getHeadRevision(), "");
+
+ mk2 = MicroKernelFactory.getInstance("fs:target/repository-test/microkernel2;clean");
+ mk2.commit("", "+\"/root\":{}", mk2.getHeadRevision(), "");
+ transientSpace = new TransientSpace("root", mk2, mk2.getHeadRevision());
+ }
+
+ @After
+ public void tearDown() {
+ mk2.dispose();
+ mk1.dispose();
+ }
+
+ @Test
+ public void fuzzTest() throws Exception {
+ for (Operation op : operations(OP_COUNT)) {
+ log.info("{}", op);
+ op.apply(mk1);
+ op.apply(transientSpace);
+ if (op instanceof Save) {
+ checkEqual(ROOT);
+ }
+ }
+ }
+
+ private Iterable<Operation> operations(final int count) {
+ return new Iterable<Operation>() {
+ int k = count;
+
+ @Override
+ public Iterator<Operation> iterator() {
+ return new Iterator<Operation>() {
+ @Override
+ public boolean hasNext() {
+ return k-- > 0;
+ }
+
+ @Override
+ public Operation next() {
+ return createOperation();
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+ };
+ }
+
+ abstract static class Operation {
+ abstract void apply(MicroKernel mk);
+ abstract void apply(TransientSpace transientSpace) throws RepositoryException;
+
+ static class AddNode extends Operation {
+ private final Path parent;
+ private final String name;
+
+ AddNode(Path parent, String name) {
+ this.parent = parent;
+ this.name = name;
+ }
+
+ @Override
+ void apply(MicroKernel mk) {
+ mk.commit("", "+\"" + parent.concat(name).toMkPath() + "\":{}",
+ mk.getHeadRevision(), "");
+ }
+
+ @Override
+ void apply(TransientSpace transientSpace) throws ItemExistsException {
+ transientSpace.getNode(parent).addNode(name);
+ }
+
+ @Override
+ public String toString() {
+ return '+' + parent.concat(name).toMkPath() + ":{}";
+ }
+ }
+
+ static class RemoveNode extends Operation {
+ private final Path path;
+
+ RemoveNode(Path path) {
+ this.path = path;
+ }
+
+ @Override
+ void apply(MicroKernel mk) {
+ mk.commit("", "-\"" + path.toMkPath() + '"', mk.getHeadRevision(), "");
+ }
+
+ @Override
+ void apply(TransientSpace transientSpace) throws ItemNotFoundException {
+ transientSpace.getNode(path.getParent()).removeNode(path.getName());
+ }
+
+ @Override
+ public String toString() {
+ return '-' + path.toMkPath();
+ }
+ }
+
+ static class MoveNode extends Operation {
+ private final Path source;
+ private final Path destination;
+
+ MoveNode(Path source, Path destParent, String destName) {
+ this.source = source;
+ destination = destParent.concat(destName);
+ }
+
+ @Override
+ void apply(MicroKernel mk) {
+ mk.commit("", ">\"" + source.toMkPath() + "\":\"" + destination.toMkPath() + '"',
+ mk.getHeadRevision(), "");
+ }
+
+ @Override
+ void apply(TransientSpace transientSpace) throws RepositoryException {
+ transientSpace.getNode(source.getParent()).moveNode(source.getName(), destination);
+ }
+
+ @Override
+ public String toString() {
+ return '>' + source.toMkPath() + ':' + destination.toMkPath();
+ }
+ }
+
+ static class SetProperty extends Operation {
+ private final Path parent;
+ private final String name;
+ private final JsonAtom value;
+
+ SetProperty(Path parent, String name, JsonAtom value) {
+ this.parent = parent;
+ this.name = name;
+ this.value = value;
+ }
+
+ @Override
+ void apply(MicroKernel mk) {
+ mk.commit("", "^\"" + parent.concat(name).toMkPath() + "\":" + value.toJson(),
+ mk.getHeadRevision(), "");
+ }
+
+ @Override
+ void apply(TransientSpace transientSpace) throws RepositoryException {
+ transientSpace.getNode(parent).setValue(name, value);
+ }
+
+ @Override
+ public String toString() {
+ return '^' + parent.concat(name).toMkPath() + ':' + value.toJson();
+ }
+ }
+
+ static class Save extends Operation {
+ @Override
+ void apply(MicroKernel mk) {
+ // empty
+ }
+
+ @Override
+ void apply(TransientSpace transientSpace) throws RepositoryException {
+ transientSpace.save();
+ }
+
+ @Override
+ public String toString() {
+ return "save";
+ }
+ }
+ }
+
+ private Operation createOperation() {
+ Operation op;
+ do {
+ switch (random.nextInt(9)) {
+ case 0:
+ case 1:
+ case 2:
+ op = createAddNode();
+ break;
+ case 3:
+ op = createRemoveNode();
+ break;
+ case 4:
+ op = createMoveNode();
+ break;
+ case 5:
+ op = createAddProperty();
+ break;
+ case 6:
+ op = createSetProperty();
+ break;
+ case 7:
+ op = createRemoveProperty();
+ break;
+ case 8:
+ op = new Save();
+ break;
+ default:
+ throw new IllegalStateException();
+ }
+ } while (op == null);
+ return op;
+ }
+
+ private Operation createAddNode() {
+ Path parent = chooseNodePath();
+ String name = createNodeName();
+ return new AddNode(parent, name);
+ }
+
+ private Operation createRemoveNode() {
+ Path path = chooseNodePath();
+ return ROOT.equals(path) ? null : new RemoveNode(path);
+ }
+
+ private Operation createMoveNode() {
+ Path source = chooseNodePath();
+ Path destParent = chooseNodePath();
+ String destName = createNodeName();
+ return ROOT.equals(source) || destParent.toJcrPath().startsWith(source.toJcrPath())
+ ? null
+ : new MoveNode(source, destParent, destName);
+ }
+
+ private Operation createAddProperty() {
+ Path parent = chooseNodePath();
+ String name = createPropertyName();
+ JsonAtom value = createValue();
+ return new SetProperty(parent, name, value);
+ }
+
+ private Operation createSetProperty() {
+ Path path = choosePropertyPath();
+ if (path == null) {
+ return null;
+ }
+ JsonAtom value = createValue();
+ return new SetProperty(path.getParent(), path.getName(), value);
+ }
+
+ private Operation createRemoveProperty() {
+ Path path = choosePropertyPath();
+ if (path == null) {
+ return null;
+ }
+ return new SetProperty(path.getParent(), path.getName(), JsonAtom.NULL);
+ }
+
+ private int counter;
+
+ private String createNodeName() {
+ return "N" + counter++;
+ }
+
+ private String createPropertyName() {
+ return "P" + counter++;
+ }
+
+ private Path chooseNodePath() {
+ Path path = ROOT;
+
+ Path next;
+ while ((next = chooseNode(path)) != null) {
+ path = next;
+ }
+
+ return path;
+ }
+
+ private Path choosePropertyPath() {
+ return chooseProperty(chooseNodePath());
+ }
+
+ private Path chooseNode(final Path parent) {
+ final List<Path> nodes = new ArrayList<Path>();
+ String json = mk1.getNodes(parent.toMkPath(), mk1.getHeadRevision(), 0, 0, -1);
+ new JsonParser(new JsonHandler(){
+ @Override
+ public void object(JsonParser parser, Token key, JsonTokenizer tokenizer) {
+ new JsonParser(JsonHandler.INSTANCE).parseObject(tokenizer);
+ nodes.add(parent.concat(key.text()));
+ }
+ }).parseObject(new UnescapingJsonTokenizer(json));
+
+ int k = random.nextInt(nodes.size() + 1);
+ if (k < nodes.size()) {
+ return nodes.get(k);
+ }
+ else {
+ return null;
+ }
+ }
+
+ private Path chooseProperty(final Path parent) {
+ final List<Path> properties = new ArrayList<Path>();
+ String json = mk1.getNodes(parent.toMkPath(), mk1.getHeadRevision(), 0, 0, -1);
+ new JsonParser(new JsonHandler(){
+ @Override
+ public void atom(Token key, Token value) {
+ if (!key.text().startsWith(":")) {
+ properties.add(parent.concat(key.text()));
+ }
+ }
+ }).parseObject(new UnescapingJsonTokenizer(json));
+
+ int k = random.nextInt(properties.size() + 1);
+ if (k < properties.size()) {
+ return properties.get(k);
+ }
+ else {
+ return null;
+ }
+ }
+
+ private JsonAtom createValue() {
+ return JsonAtom.string("V" + counter++);
+ }
+
+ private void checkEqual(Path path) {
+ assertTrue(path.toString(), mk1.nodeExists(path.toMkPath(), mk1.getHeadRevision()));
+ assertTrue(path.toString(), mk2.nodeExists(path.toMkPath(), mk2.getHeadRevision()));
+
+
+ final Set<String> nodeNames1 = new HashSet<String>();
+ final Map<String, JsonAtom> properties1 = new HashMap<String, JsonAtom>();
+ collectItems(mk1, path, nodeNames1, properties1);
+
+ final Set<String> nodeNames2 = new HashSet<String>();
+ final Map<String, JsonAtom> properties2 = new HashMap<String, JsonAtom>();
+ collectItems(mk2, path, nodeNames2, properties2);
+
+ assertEquals(path.toString(), nodeNames1, nodeNames2);
+ assertEquals(path.toString(), properties1, properties2);
+
+ for (String name : nodeNames1) {
+ checkEqual(path.concat(name));
+ }
+ }
+
+ private static void collectItems(MicroKernel microKernel, Path path, final Set<String> nodeNames,
+ final Map<String, JsonAtom> properties) {
+
+ String json = microKernel.getNodes(path.toMkPath(), microKernel.getHeadRevision(), 0, 0, -1);
+ new JsonParser(new JsonHandler(){
+ @Override
+ public void object(JsonParser parser, Token key, JsonTokenizer tokenizer) {
+ new JsonParser(JsonHandler.INSTANCE).parseObject(tokenizer);
+ nodeNames.add(key.text());
+ }
+
+ @Override
+ public void atom(Token key, Token value) {
+ super.atom(key, value);
+ properties.put(key.text(), new JsonAtom(value));
+ }
+ }).parseObject(new UnescapingJsonTokenizer(json));
+ }
+
+}
Modified: jackrabbit/sandbox/jackrabbit-microkernel/src/test/java/org/apache/jackrabbit/state/TransientSpaceTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-microkernel/src/test/java/org/apache/jackrabbit/state/TransientSpaceTest.java?rev=1242885&r1=1242884&r2=1242885&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-microkernel/src/test/java/org/apache/jackrabbit/state/TransientSpaceTest.java (original)
+++ jackrabbit/sandbox/jackrabbit-microkernel/src/test/java/org/apache/jackrabbit/state/TransientSpaceTest.java Fri Feb 10 18:54:21 2012
@@ -11,18 +11,12 @@ import org.apache.jackrabbit.mk.MicroKer
import org.apache.jackrabbit.mk.api.MicroKernel;
import org.apache.jackrabbit.mk.api.MicroKernelException;
import org.apache.jackrabbit.mk.store.NotFoundException;
-import org.apache.jackrabbit.state.ChangeTree.Added;
-import org.apache.jackrabbit.state.ChangeTree.Existing;
-import org.apache.jackrabbit.state.ChangeTree.Moved;
import org.apache.jackrabbit.state.ChangeTree.NodeDelta;
-import org.apache.jackrabbit.state.ChangeTree.Removed;
-import org.apache.jackrabbit.state.ChangeTree.Visitor;
import org.hamcrest.CoreMatchers;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.internal.matchers.TypeSafeMatcher;
@@ -150,7 +144,7 @@ public class TransientSpaceTest {
added.removeNode("added2");
}
- @Test @Ignore("WIP")
+ @Test
public void removeAndAddAgain() throws RepositoryException {
NodeDelta root = transientSpace.getNode(ROOT);
root.addNode("a").addNode("b");
@@ -175,7 +169,7 @@ public class TransientSpaceTest {
assertFalse(root.getNode("a").hasNode("b"));
}
- @Test @Ignore("WIP")
+ @Test
public void moveAndAddAgain() throws RepositoryException {
NodeDelta root = transientSpace.getNode(ROOT);
root.addNode("a").addNode("b");
@@ -1123,46 +1117,4 @@ public class TransientSpaceTest {
}
}
- /* Debugging */
- @SuppressWarnings("UseOfSystemOutOrSystemErr")
- private static void dump(NodeDelta delta) {
- delta.accept(new Visitor() {
- private String ident = "";
-
-
- @Override
- public void visit(Existing delta) {
- System.out.println(ident + delta.getName() + ":E");
- visitChildren(delta);
- }
-
- @Override
- public void visit(Added delta) {
- System.out.println(ident + delta.getName() + ":A");
- visitChildren(delta);
- }
-
- @Override
- public void visit(Removed delta) {
- System.out.println(ident + delta.getName() + (delta.isMoved() ? ":M-" : ":R"));
- visitChildren(delta);
- }
-
- @Override
- public void visit(Moved delta) {
- System.out.println(ident + delta.getName() + ":M+");
- visitChildren(delta.source);
- visitChildren(delta);
- }
-
- private void visitChildren(NodeDelta delta) {
- ident = ident + " ";
- for (NodeDelta child : delta.childNodes()) {
- child.accept(this);
- }
- ident = ident.substring(0, ident.length() - 2);
- }
-
- });
- }
}