You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by st...@apache.org on 2011/10/25 15:46:50 UTC

svn commit: r1188658 - in /jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk: ./ api/ client/ mem/ server/ store/ wrapper/

Author: stefan
Date: Tue Oct 25 13:46:49 2011
New Revision: 1188658

URL: http://svn.apache.org/viewvc?rev=1188658&view=rev
Log:
new MicroKernel API method: diff

allows to diff arbitrary revisions; unlike getJournal which does report the diffs per commit, the diff method provides a consolidated diff of 2 revisions

Added:
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/store/NotFoundException.java
Modified:
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/MicroKernelImpl.java
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/Repository.java
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/api/MicroKernel.java
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/client/Client.java
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/mem/MemoryKernelImpl.java
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/MicroKernelServlet.java
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/store/BDbRevisionStore.java
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/store/CommitBuilder.java
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/store/H2RevisionStore.java
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/store/PersistenceManager.java
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/store/RevisionStore.java
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/store/SimpleRevisionStore.java
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/wrapper/LogWrapper.java
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/wrapper/SecurityWrapper.java

Modified: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/MicroKernelImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/MicroKernelImpl.java?rev=1188658&r1=1188657&r2=1188658&view=diff
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/MicroKernelImpl.java (original)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/MicroKernelImpl.java Tue Oct 25 13:46:49 2011
@@ -25,6 +25,7 @@ import org.apache.jackrabbit.mk.store.Co
 import org.apache.jackrabbit.mk.store.Node;
 import org.apache.jackrabbit.mk.store.NodeDiffHandler;
 import org.apache.jackrabbit.mk.store.NodeUtils;
+import org.apache.jackrabbit.mk.store.NotFoundException;
 import org.apache.jackrabbit.mk.util.CommitGate;
 import org.apache.jackrabbit.mk.util.PathUtils;
 import org.apache.jackrabbit.mk.util.SimpleLRUCache;
@@ -175,151 +176,182 @@ public class MicroKernelImpl implements 
                     key("msg").value(commit.getMsg());
             String diff = diffCache.get(commit.getId());
             if (diff == null) {
-                final JsopBuilder buff = new JsopBuilder();
-                // maps (key: id of target node, value: path/to/target)
-                // for tracking added/removed nodes; this allows us
-                // to detect 'move' operations
-                final HashMap<String, String> addedNodes = new HashMap<String, String>();
-                final HashMap<String, String> removedNodes = new HashMap<String, String>();
-                try {
-                    String path = "/";
-                    Node node1 = rep.getNode(commit.getParentId(), path);
-                    Node node2 = rep.getNode(commit.getId(), path);
-
-                    NodeUtils.diff(path, node1, node2, true, rep.getPersistenceManager(), new NodeDiffHandler() {
-                        public void propAdded(String nodePath, String propName, String value) {
-                            buff.appendTag("+ ").
-                                    key(PathUtils.concat(nodePath, propName)).
-                                    encodedValue(value).
-                                    newline();
-                        }
+                diff = diff(fromRevisionId, toRevisionId, "/");
+                diffCache.put(commit.getId(), diff);
+            }
+            commitBuff.key("changes").value(diff).endObject();
+        }
+        return commitBuff.endArray().toString();
+    }
 
-                        public void propChanged(String nodePath, String propName, String oldValue, String newValue) {
-                            buff.appendTag("^ ").
-                                    key(PathUtils.concat(nodePath, propName)).
-                                    encodedValue(newValue).
-                                    newline();
-                        }
+    public String diff(String fromRevisionId, String toRevisionId, String path) throws MicroKernelException {
+        if (path == null) {
+            path = "/";
+        }
 
-                        public void propDeleted(String nodePath, String propName, String value) {
-                            // since property and node deletions can't be distinguished
-                            // using the "- <path>" notation we're representing
-                            // property deletions as "^ <path>:null"
-                            buff.appendTag("^ ").
-                                    key(PathUtils.concat(nodePath, propName)).
-                                    encodedValue("null").
-                                    newline();
-                        }
+        try {
+            final JsopBuilder buff = new JsopBuilder();
+            // maps (key: id of target node, value: path/to/target)
+            // for tracking added/removed nodes; this allows us
+            // to detect 'move' operations
+            final HashMap<String, String> addedNodes = new HashMap<String, String>();
+            final HashMap<String, String> removedNodes = new HashMap<String, String>();
+            Node node1, node2;
+            try {
+                node1 = rep.getNode(fromRevisionId, path);
+            } catch (NotFoundException e) {
+                node1 = null;
+            }
+            try {
+                node2 = rep.getNode(toRevisionId, path);
+            } catch (NotFoundException e) {
+                node2 = null;
+            }
 
-                        public void childNodeAdded(String nodePath, String childName, String id) {
-                            addedNodes.put(id, PathUtils.concat(nodePath, childName));
-                            buff.appendTag("+ ").
-                                    key(PathUtils.concat(nodePath, childName)).object();
-                            try {
-                                toJson(buff, rep.getPersistenceManager().getNode(id), childName, Integer.MAX_VALUE, 0, -1);
-                            } catch (Exception e) {
-                                buff.value("ERROR: failed to retrieve node " + id);
-                            }
-                            buff.endObject().newline();
-                        }
+            if (node1 == null) {
+                if (node2 != null) {
+                    buff.appendTag("+ ").key(path).object();
+                    toJson(buff, node2, Integer.MAX_VALUE, 0, -1);
+                    return buff.endObject().newline().toString();
+                } else {
+                    throw new MicroKernelException("path doesn't exist in the specified revisions: " + path);
+                }
+            } else if (node2 == null) {
+                buff.appendTag("- ");
+                buff.value(path);
+                return buff.newline().toString();
+            }
 
-                        public void childNodeDeleted(String nodePath, String childName, String id) {
-                            removedNodes.put(id, PathUtils.concat(nodePath, childName));
-                            buff.appendTag("- ");
-                            buff.value(PathUtils.concat(nodePath, childName));
-                            buff.newline();
-                        }
+            NodeUtils.diff(path, node1, node2, true, rep.getPersistenceManager(), new NodeDiffHandler() {
+                public void propAdded(String nodePath, String propName, String value) {
+                    buff.appendTag("+ ").
+                            key(PathUtils.concat(nodePath, propName)).
+                            encodedValue(value).
+                            newline();
+                }
+
+                public void propChanged(String nodePath, String propName, String oldValue, String newValue) {
+                    buff.appendTag("^ ").
+                            key(PathUtils.concat(nodePath, propName)).
+                            encodedValue(newValue).
+                            newline();
+                }
+
+                public void propDeleted(String nodePath, String propName, String value) {
+                    // since property and node deletions can't be distinguished
+                    // using the "- <path>" notation we're representing
+                    // property deletions as "^ <path>:null"
+                    buff.appendTag("^ ").
+                            key(PathUtils.concat(nodePath, propName)).
+                            encodedValue("null").
+                            newline();
+                }
+
+                public void childNodeAdded(String nodePath, String childName, String id) {
+                    addedNodes.put(id, PathUtils.concat(nodePath, childName));
+                    buff.appendTag("+ ").
+                            key(PathUtils.concat(nodePath, childName)).object();
+                    try {
+                        toJson(buff, rep.getPersistenceManager().getNode(id), Integer.MAX_VALUE, 0, -1);
+                    } catch (Exception e) {
+                        buff.value("ERROR: failed to retrieve node " + id);
+                    }
+                    buff.endObject().newline();
+                }
+
+                public void childNodeDeleted(String nodePath, String childName, String id) {
+                    removedNodes.put(id, PathUtils.concat(nodePath, childName));
+                    buff.appendTag("- ");
+                    buff.value(PathUtils.concat(nodePath, childName));
+                    buff.newline();
+                }
+
+                public void childNodeChanged(String nodePath, String childName, String oldId, String newId) {
+                    // we're not interested
+                }
+            });
+
+            // check if this commit includes 'move' operations
+            // by building intersection of added and removed nodes
+            addedNodes.keySet().retainAll(removedNodes.keySet());
+            if (!addedNodes.isEmpty()) {
+                // this commit includes 'move' operations
+                removedNodes.keySet().retainAll(addedNodes.keySet());
+                // addedNodes & removedNodes now only contain information about moved nodes
+
+                // re-build the diff in a 2nd pass, this time representing moves correctly
+                buff.reset();
+
+                // TODO refactor code, avoid duplication
+                NodeUtils.diff(path, node1, node2, true, rep.getPersistenceManager(), new NodeDiffHandler() {
+                    public void propAdded(String nodePath, String propName, String value) {
+                        buff.appendTag("+ ").
+                                key(PathUtils.concat(nodePath, propName)).
+                                encodedValue(value).
+                                appendTag("\n");
+                    }
+
+                    public void propChanged(String nodePath, String propName, String oldValue, String newValue) {
+                        buff.appendTag("^ ").
+                                key(PathUtils.concat(nodePath, propName)).
+                                encodedValue(newValue).
+                                appendTag("\n");
+                    }
 
-                        public void childNodeChanged(String nodePath, String childName, String oldId, String newId) {
-                            // we're not interested
+                    public void propDeleted(String nodePath, String propName, String value) {
+                        // since property and node deletions can't be distinguished
+                        // using the "- <path>" notation we're representing
+                        // property deletions as "^ <path>:null"
+                        buff.appendTag("^ ").
+                                key(PathUtils.concat(nodePath, propName)).
+                                encodedValue("null").
+                                appendTag("\n");
+                    }
+
+                    public void childNodeAdded(String nodePath, String childName, String id) {
+                        if (addedNodes.containsKey(id)) {
+                            // moved node, will be processed separately
+                            return;
+                        }
+                        buff.appendTag("+ ").
+                                key(PathUtils.concat(nodePath, childName)).object();
+                        try {
+                            toJson(buff, rep.getPersistenceManager().getNode(id), Integer.MAX_VALUE, 0, -1);
+                        } catch (Exception e) {
+                            buff.value("ERROR: failed to retrieve node " + id);
                         }
-                    });
+                        buff.endObject().appendTag("\n");
+                    }
 
-                    // check if this commit includes 'move' operations
-                    // by building intersection of added and removed nodes
-                    addedNodes.keySet().retainAll(removedNodes.keySet());
-                    if (!addedNodes.isEmpty()) {
-                        // this commit includes 'move' operations
-                        removedNodes.keySet().retainAll(addedNodes.keySet());
-                        // addedNodes & removedNodes now only contain information about moved nodes
-
-                        // re-build the diff in a 2nd pass, this time representing moves correctly
-                        buff.reset();
-
-                        // TODO refactor code, avoid duplication
-                        NodeUtils.diff(path, node1, node2, true, rep.getPersistenceManager(), new NodeDiffHandler() {
-                            public void propAdded(String nodePath, String propName, String value) {
-                                buff.appendTag("+ ").
-                                        key(PathUtils.concat(nodePath, propName)).
-                                        encodedValue(value).
-                                        appendTag("\n");
-                            }
-
-                            public void propChanged(String nodePath, String propName, String oldValue, String newValue) {
-                                buff.appendTag("^ ").
-                                        key(PathUtils.concat(nodePath, propName)).
-                                        encodedValue(newValue).
-                                        appendTag("\n");
-                            }
-
-                            public void propDeleted(String nodePath, String propName, String value) {
-                                // since property and node deletions can't be distinguished
-                                // using the "- <path>" notation we're representing
-                                // property deletions as "^ <path>:null"
-                                buff.appendTag("^ ").
-                                        key(PathUtils.concat(nodePath, propName)).
-                                        encodedValue("null").
-                                        appendTag("\n");
-                            }
-
-                            public void childNodeAdded(String nodePath, String childName, String id) {
-                                if (addedNodes.containsKey(id)) {
-                                    // moved node, will be processed separately
-                                    return;
-                                }
-                                buff.appendTag("+ ").
-                                        key(PathUtils.concat(nodePath, childName)).object();
-                                try {
-                                    toJson(buff, rep.getPersistenceManager().getNode(id), childName, Integer.MAX_VALUE, 0, -1);
-                                } catch (Exception e) {
-                                    buff.value("ERROR: failed to retrieve node " + id);
-                                }
-                                buff.endObject().appendTag("\n");
-                            }
-
-                            public void childNodeDeleted(String nodePath, String childName, String id) {
-                                if (addedNodes.containsKey(id)) {
-                                    // moved node, will be processed separately
-                                    return;
-                                }
-                                buff.appendTag("- ");
-                                buff.value(PathUtils.concat(nodePath, childName));
-                                buff.appendTag("\n");
-                            }
-
-                            public void childNodeChanged(String nodePath, String childName, String oldId, String newId) {
-                                // we're not interested
-                            }
-                        });
-                        // finally process moved nodes
-                        for (Map.Entry<String, String> entry : addedNodes.entrySet()) {
-                            buff.appendTag("> ").
-                                    // path/to/deleted/node
-                                            key(removedNodes.get(entry.getKey())).
-                                    // path/to/added/node
-                                            value(entry.getValue()).
-                                    newline();
+                    public void childNodeDeleted(String nodePath, String childName, String id) {
+                        if (addedNodes.containsKey(id)) {
+                            // moved node, will be processed separately
+                            return;
                         }
+                        buff.appendTag("- ");
+                        buff.value(PathUtils.concat(nodePath, childName));
+                        buff.appendTag("\n");
+                    }
+
+                    public void childNodeChanged(String nodePath, String childName, String oldId, String newId) {
+                        // we're not interested
                     }
-                    diff = buff.toString();
-                    diffCache.put(commit.getId(), diff);
-                } catch (Exception e) {
-                    throw new MicroKernelException(e);
+                });
+                // finally process moved nodes
+                for (Map.Entry<String, String> entry : addedNodes.entrySet()) {
+                    buff.appendTag("> ").
+                            // path/to/deleted/node
+                                    key(removedNodes.get(entry.getKey())).
+                            // path/to/added/node
+                                    value(entry.getValue()).
+                            newline();
                 }
             }
-            commitBuff.key("changes").value(diff).endObject();
+            return buff.toString();
+
+        } catch (Exception e) {
+            throw new MicroKernelException(e);
         }
-        return commitBuff.endArray().toString();
     }
 
     public boolean nodeExists(String path, String revisionId) throws MicroKernelException {
@@ -336,7 +368,7 @@ public class MicroKernelImpl implements 
     public String getNodes(String path, String revisionId, int depth, long offset, int count) throws MicroKernelException {
         try {
             JsopBuilder buf = new JsopBuilder().object();
-            toJson(buf, rep.getNode(revisionId, path), PathUtils.getName(path), depth, offset, count);
+            toJson(buf, rep.getNode(revisionId, path), depth, offset, count);
             return buf.endObject().toString();
         } catch (Exception e) {
             throw new MicroKernelException(e);
@@ -453,7 +485,7 @@ public class MicroKernelImpl implements 
 
     //-------------------------------------------------------< implementation >
 
-    void toJson(JsopBuilder builder, Node node, String name, int depth, long offset, int count) throws Exception {
+    void toJson(JsopBuilder builder, Node node, int depth, long offset, int count) throws Exception {
         for (Map.Entry<String, String> prop : node.getProperties().entrySet()) {
             builder.key(prop.getKey()).encodedValue(prop.getValue());
         }
@@ -470,7 +502,7 @@ public class MicroKernelImpl implements 
                     builder.key(childName).object();
                     if (depth > 0) {
                         String childId = child.getValue();
-                        toJson(builder, rep.getPersistenceManager().getNode(childId), childName, depth - 1, 0, -1);
+                        toJson(builder, rep.getPersistenceManager().getNode(childId), depth - 1, 0, -1);
                     }
                     builder.endObject();
                 }

Modified: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/Repository.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/Repository.java?rev=1188658&r1=1188657&r2=1188658&view=diff
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/Repository.java (original)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/Repository.java Tue Oct 25 13:46:49 2011
@@ -23,6 +23,7 @@ import org.apache.jackrabbit.mk.store.Co
 import org.apache.jackrabbit.mk.store.CommitBuilder;
 import org.apache.jackrabbit.mk.store.H2RevisionStore;
 import org.apache.jackrabbit.mk.store.Node;
+import org.apache.jackrabbit.mk.store.NotFoundException;
 import org.apache.jackrabbit.mk.store.PersistenceManager;
 import org.apache.jackrabbit.mk.store.RevisionStore;
 import org.apache.jackrabbit.mk.util.PathUtils;
@@ -104,14 +105,22 @@ public class Repository {
         return pm.getHeadCommit();
     }
 
-    public Commit getCommit(String id) throws Exception {
+    public Commit getCommit(String id) throws NotFoundException, Exception {
         if (!initialized) {
             throw new IllegalStateException("not initialized");
         }
         return pm.getCommit(id);
     }
 
-    public Node getNode(String revId, String path) throws Exception {
+    /**
+     *
+     * @param revId
+     * @param path
+     * @return
+     * @throws NotFoundException if either path or revision doesn't exist
+     * @throws Exception if another error occurs
+     */
+    public Node getNode(String revId, String path) throws NotFoundException, Exception {
         if (!initialized) {
             throw new IllegalStateException("not initialized");
         }
@@ -145,23 +154,31 @@ public class Repository {
         }
     }
 
-
     public CommitBuilder getCommitBuilder(String revId, String msg) throws Exception {
         return new CommitBuilder(revId, msg, this);
 
     }
 
-    String[] /* array of node id's */ resolvePath(String revId, String path) throws Exception {
-        if (!PathUtils.isAbsolute(path)) {
+    /**
+     *
+     * @param revId
+     * @param nodePath
+     * @return
+     * @throws IllegalArgumentException if the specified path is not absolute
+     * @throws NotFoundException if either path or revision doesn't exist
+     * @throws Exception if another error occurs
+     */
+    String[] /* array of node id's */ resolvePath(String revId, String nodePath) throws Exception {
+        if (!PathUtils.isAbsolute(nodePath)) {
             throw new IllegalArgumentException("illegal path");
         }
 
         Commit commit = pm.getCommit(revId);
 
-        if (PathUtils.denotesRoot(path)) {
+        if (PathUtils.denotesRoot(nodePath)) {
             return new String[]{commit.getRootNodeId()};
         }
-        String[] names = PathUtils.split(path);
+        String[] names = PathUtils.split(nodePath);
         String[] ids = new String[names.length + 1];
 
         // get root node
@@ -171,7 +188,7 @@ public class Repository {
         for (int i = 0; i < names.length; i++) {
             String id = parent.getChildNodeEntries().get(names[i]);
             if (id == null) {
-                throw new Exception("path not found: " + path);
+                throw new NotFoundException(nodePath);
             }
             ids[i + 1] = id;
             parent = pm.getNode(id);

Modified: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/api/MicroKernel.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/api/MicroKernel.java?rev=1188658&r1=1188657&r2=1188658&view=diff
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/api/MicroKernel.java (original)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/api/MicroKernel.java Tue Oct 25 13:46:49 2011
@@ -148,7 +148,7 @@ public interface MicroKernel {
      * <p/>
      * Format:
      * <pre>
-     * [ { "id" : "<revisionId>", "ts" : <revisionTimestamp>, "msg" : <commitMessage>, "changes" : "<JSON diff>" }, ... ]
+     * [ { "id" : "&lt;revisionId&gt;", "ts" : "&lt;revisionTimestamp&gt;", "msg" : "&lt;commitMessage&gt;", "changes" : "&lt;JSON diff&gt;" }, ... ]
      * </pre>
      *
      * @param fromRevisionId first revision to be returned in journal
@@ -159,6 +159,26 @@ public interface MicroKernel {
     String /* jsonArray */ getJournal(String fromRevisionId, String toRevisionId)
             throws MicroKernelException;
 
+    /**
+     * Returns the JSON diff representation of the changes between the specified
+     * revisions. The changes will be consolidated if the specified range
+     * covers intermediary revisions.
+     * <p/>
+     * Format:
+     * <pre>
+     * [ { "id" : "&lt;revisionId&gt;", "ts" : "&lt;revisionTimestamp&gt;", "msg" : "&lt;commitMessage&gt;", "changes" : "&lt;JSON diff&gt;" }, ... ]
+     * </pre>
+     *
+     * @param fromRevisionId
+     * @param toRevisionId
+     * @param path consider only changes that affected the specified subtree at <code>path</code>;
+     *        if null the default "/" is assumed
+     * @return JSON diff representation of the changes
+     * @throws MicroKernelException if an error occurs
+     */
+    String /* JSON diff */ diff(String fromRevisionId, String toRevisionId,
+                                String path)
+            throws MicroKernelException;
 
     //-------------------------------------------------------------< READ ops >
 
@@ -196,10 +216,10 @@ public interface MicroKernel {
      * <p/>
      * Remarks:
      * <ul>
-     * <li>If the property <code>:childNodeCount</code> is 0, then the
+     * <li>If the property <code>:childNodeCount</code> equals 0 the
      * node does not have child nodes.
      * <li>If the value of <code>:childNodeCount</code> is larger than the list
-     * of returned child nodes, then node does have more child nodes than those
+     * of returned child nodes the node does have more child nodes than those
      * included in the tree. Large number of child nodes can be retrieved in
      * chunks using {@link #getNodes(String, String, int, long, int)}</li>
      * </ul>

Modified: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/client/Client.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/client/Client.java?rev=1188658&r1=1188657&r2=1188658&view=diff
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/client/Client.java (original)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/client/Client.java Tue Oct 25 13:46:49 2011
@@ -125,6 +125,24 @@ public class Client implements MicroKern
         }
     }
 
+    public String diff(String fromRevisionId, String toRevisionId,
+                       String path)
+            throws MicroKernelException {
+        Request request = null;
+
+        try {
+            request = createRequest("diff");
+            request.addParameter("from_revision_id", fromRevisionId);
+            request.addParameter("to_revision_id", toRevisionId);
+            request.addParameter("path", path);
+            return request.getString();
+        } catch (IOException e) {
+            throw toMicroKernelException(e);
+        } finally {
+            IOUtils.closeQuietly(request);
+        }
+    }
+
     public boolean nodeExists(String path, String revisionId)
             throws MicroKernelException {
 

Modified: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/mem/MemoryKernelImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/mem/MemoryKernelImpl.java?rev=1188658&r1=1188657&r2=1188658&view=diff
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/mem/MemoryKernelImpl.java (original)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/mem/MemoryKernelImpl.java Tue Oct 25 13:46:49 2011
@@ -416,6 +416,28 @@ public class MemoryKernelImpl implements
     }
 
     /**
+     * Returns the JSON diff representation of the changes between the specified
+     * revisions. The changes will be consolidated if the specified range
+     * covers intermediary revisions.
+     * <p/>
+     * Format:
+     * <pre>
+     * [ { "id" : "&lt;revisionId&gt;", "ts" : "&lt;revisionTimestamp&gt;", "msg" : "&lt;commitMessage&gt;", "changes" : "&lt;JSON diff&gt;" }, ... ]
+     * </pre>
+     *
+     * @param fromRevisionId
+     * @param toRevisionId
+     * @param path consider only changes that affected the specified subtree at <code>path</code>;
+     *        if null the default "/" is assumed
+     * @return JSON diff representation of the changes
+     */
+    public String /* JSON diff */ diff(String fromRevisionId, String toRevisionId,
+                                String path) {
+        // todo implement
+        return "";
+    }
+
+    /**
      * Get the nodes. The following prefixes are supported:
      * <ul><li>:pretty - beautify (format) the result
      * </li><li>:root - get the root node (including all old revisions)

Modified: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/MicroKernelServlet.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/MicroKernelServlet.java?rev=1188658&r1=1188657&r2=1188658&view=diff
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/MicroKernelServlet.java (original)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/MicroKernelServlet.java Tue Oct 25 13:46:49 2011
@@ -154,6 +154,26 @@ class MicroKernelServlet {
         }        
     }
 
+    private static class Diff implements Command {
+
+        public void execute(MicroKernel mk, Request request, Response response)
+                throws IOException, MicroKernelException {
+
+            String headRevision = mk.getHeadRevision();
+
+            String fromRevisionId = request.getParameter("from_revision_id", headRevision);
+            String toRevisionId = request.getParameter("to_revision_id", headRevision);
+            String path = request.getParameter("path", "/");
+
+            response.setContentType("application/json");
+            String json = mk.diff(fromRevisionId, toRevisionId, path);
+            if (request.getHeaders().containsKey("User-Agent")) {
+                json = JsopBuilder.prettyPrint(json);
+            }
+            response.write(json);
+        }
+    }
+
     private static class NodeExists implements Command {
 
         public void execute(MicroKernel mk, Request request, Response response)

Modified: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/store/BDbRevisionStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/store/BDbRevisionStore.java?rev=1188658&r1=1188657&r2=1188658&view=diff
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/store/BDbRevisionStore.java (original)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/store/BDbRevisionStore.java Tue Oct 25 13:46:49 2011
@@ -117,7 +117,7 @@ public class BDbRevisionStore extends Ab
         if (db.get(null, key, data, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
             return new ByteArrayInputStream(data.getData());
         } else {
-            throw new Exception("not found: " + id);
+            throw new NotFoundException(id);
         }
     }
 

Modified: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/store/CommitBuilder.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/store/CommitBuilder.java?rev=1188658&r1=1188657&r2=1188658&view=diff
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/store/CommitBuilder.java (original)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/store/CommitBuilder.java Tue Oct 25 13:46:49 2011
@@ -71,13 +71,13 @@ public class CommitBuilder {
         changeLog.add(new AddNode(parentNodePath, nodeName, properties));
     }
 
-    public void removeNode(String nodePath) throws Exception {
+    public void removeNode(String nodePath) throws NotFoundException, Exception {
         String parentPath = PathUtils.getParentPath(nodePath);
         String nodeName = PathUtils.getName(nodePath);
 
         MutableNode parent = getOrCreateStagedNode(parentPath);
         if (!parent.getChildNodeEntries().containsKey(nodeName)) {
-            throw new Exception("node not found");
+            throw new NotFoundException(nodePath);
         }
 
         parent.getChildNodeEntries().remove(nodeName);
@@ -89,7 +89,7 @@ public class CommitBuilder {
         changeLog.add(new RemoveNode(nodePath));
     }
 
-    public void moveNode(String srcPath, String destPath) throws Exception {
+    public void moveNode(String srcPath, String destPath) throws NotFoundException, Exception {
         String srcParentPath = PathUtils.getParentPath(srcPath);
         String srcNodeName = PathUtils.getName(srcPath);
 
@@ -99,7 +99,7 @@ public class CommitBuilder {
         MutableNode srcParent = getOrCreateStagedNode(srcParentPath);
         String targetId = srcParent.getChildNodeEntries().get(srcNodeName);
         if (targetId == null) {
-            throw new Exception("node not found: " + srcPath);
+            throw new NotFoundException(srcPath);
         }
         srcParent.getChildNodeEntries().remove(srcNodeName);
 
@@ -113,7 +113,7 @@ public class CommitBuilder {
         changeLog.add(new MoveNode(srcPath, destPath));
     }
 
-    public void copyNode(String srcPath, String destPath) throws Exception {
+    public void copyNode(String srcPath, String destPath) throws NotFoundException, Exception {
         String srcParentPath = PathUtils.getParentPath(srcPath);
         String srcNodeName = PathUtils.getName(srcPath);
 
@@ -123,7 +123,7 @@ public class CommitBuilder {
         MutableNode srcParent = getOrCreateStagedNode(srcParentPath);
         String targetId = srcParent.getChildNodeEntries().get(srcNodeName);
         if (targetId == null) {
-            throw new Exception("node not found: " + srcPath);
+            throw new NotFoundException(srcPath);
         }
 
         MutableNode destParent = getOrCreateStagedNode(destParentPath);

Modified: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/store/H2RevisionStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/store/H2RevisionStore.java?rev=1188658&r1=1188657&r2=1188658&view=diff
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/store/H2RevisionStore.java (original)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/store/H2RevisionStore.java Tue Oct 25 13:46:49 2011
@@ -161,7 +161,7 @@ public class H2RevisionStore implements 
                 if (rs.next()) {
                     return new ByteArrayInputStream(rs.getBytes(1));
                 } else {
-                    throw new Exception("not found: " + id);
+                    throw new NotFoundException(id);
                 }
             } finally {
                 stmt.close();

Added: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/store/NotFoundException.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/store/NotFoundException.java?rev=1188658&view=auto
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/store/NotFoundException.java (added)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/store/NotFoundException.java Tue Oct 25 13:46:49 2011
@@ -0,0 +1,41 @@
+/*
+ * 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.store;
+
+/**
+ *
+ */
+public class NotFoundException extends Exception {
+
+    private static final long serialVersionUID = 267748774351258035L;
+
+    public NotFoundException() {
+        super();
+    }
+
+    public NotFoundException(String message) {
+        super(message);
+    }
+
+    public NotFoundException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public NotFoundException(Throwable cause) {
+        super(cause);
+    }
+}

Modified: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/store/PersistenceManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/store/PersistenceManager.java?rev=1188658&r1=1188657&r2=1188658&view=diff
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/store/PersistenceManager.java (original)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/store/PersistenceManager.java Tue Oct 25 13:46:49 2011
@@ -71,7 +71,7 @@ public class PersistenceManager {
         initialized = false;
     }
 
-    public Node getNode(String id) throws Exception {
+    public Node getNode(String id) throws NotFoundException, Exception {
         if (!initialized) {
             throw new IllegalStateException("not initialized");
         }
@@ -95,7 +95,7 @@ public class PersistenceManager {
         return revStore.put(node.toBytes());
     }
 
-    public Commit getCommit(String id) throws Exception {
+    public Commit getCommit(String id) throws NotFoundException, Exception {
         if (!initialized) {
             throw new IllegalStateException("not initialized");
         }

Modified: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/store/RevisionStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/store/RevisionStore.java?rev=1188658&r1=1188657&r2=1188658&view=diff
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/store/RevisionStore.java (original)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/store/RevisionStore.java Tue Oct 25 13:46:49 2011
@@ -29,7 +29,7 @@ public interface RevisionStore {
 
     void close();
 
-    InputStream get(String id) throws Exception;
+    InputStream get(String id) throws NotFoundException, Exception;
 
     String /* id */ put(InputStream in) throws Exception;
 

Modified: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/store/SimpleRevisionStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/store/SimpleRevisionStore.java?rev=1188658&r1=1188657&r2=1188658&view=diff
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/store/SimpleRevisionStore.java (original)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/store/SimpleRevisionStore.java Tue Oct 25 13:46:49 2011
@@ -66,7 +66,12 @@ public class SimpleRevisionStore extends
         if (!initialized) {
             throw new IllegalStateException("not initialized");
         }
-        return new BufferedInputStream(new FileInputStream(getFile(id)));
+        File f = getFile(id);
+        if (f.exists()) {
+            return new BufferedInputStream(new FileInputStream(f));
+        } else {
+            throw new NotFoundException(id);
+        }
     }
 
     public String put(InputStream in) throws Exception {

Modified: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/wrapper/LogWrapper.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/wrapper/LogWrapper.java?rev=1188658&r1=1188657&r2=1188658&view=diff
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/wrapper/LogWrapper.java (original)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/wrapper/LogWrapper.java Tue Oct 25 13:46:49 2011
@@ -93,6 +93,18 @@ public class LogWrapper implements Micro
         }
     }
 
+    public String diff(String fromRevisionId, String toRevisionId, String path) {
+        try {
+            logMethod("diff", fromRevisionId, toRevisionId, path);
+            String result = mk.diff(fromRevisionId, toRevisionId, path);
+            logResult(result);
+            return result;
+        } catch (Exception e) {
+            logException(e);
+            throw convert(e);
+        }
+    }
+
     public long getLength(String blobId) {
         try {
             logMethod("getLength", blobId);

Modified: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/wrapper/SecurityWrapper.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/wrapper/SecurityWrapper.java?rev=1188658&r1=1188657&r2=1188658&view=diff
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/wrapper/SecurityWrapper.java (original)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/wrapper/SecurityWrapper.java Tue Oct 25 13:46:49 2011
@@ -158,6 +158,15 @@ public class SecurityWrapper implements 
         return buff.toString();
     }
 
+    public String diff(String fromRevisionId, String toRevisionId, String path) {
+        rightsRevision = getHeadRevision();
+        String diff = mk.diff(fromRevisionId, toRevisionId, path);
+        if (admin) {
+            return diff;
+        }
+        return filterDiff(diff, toRevisionId);
+    }
+
     private String filterDiff(String jsonDiff, String revisionId) {
         JsopBuilder buff = new JsopBuilder();
         verifyDiff(jsonDiff, revisionId, null, buff);