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 th...@apache.org on 2014/10/21 11:04:38 UTC

svn commit: r1633319 [1/2] - in /jackrabbit/oak/branches/1.0: oak-auth-external/ oak-core/ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/ oak-core/src/main/jav...

Author: thomasm
Date: Tue Oct 21 09:04:38 2014
New Revision: 1633319

URL: http://svn.apache.org/r1633319
Log:
OAK-2191 Persistent cache for the DocumentNodeStore

Added:
    jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/
    jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/BlobCache.java
    jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/CacheType.java
    jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/KeyDataType.java
    jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/MultiGenerationMap.java
    jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/NodeCache.java
    jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/PersistentCache.java
    jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/ValueDataType.java
    jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/
    jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/CacheTest.java
Modified:
    jackrabbit/oak/branches/1.0/oak-auth-external/pom.xml
    jackrabbit/oak/branches/1.0/oak-core/pom.xml
    jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java
    jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java
    jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
    jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/MemoryDiffCache.java
    jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocument.java
    jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/PathRev.java
    jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Revision.java
    jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/StableRevisionComparator.java
    jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentStore.java
    jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStore.java
    jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/StringValue.java
    jackrabbit/oak/branches/1.0/oak-jcr/pom.xml
    jackrabbit/oak/branches/1.0/oak-mk/pom.xml
    jackrabbit/oak/branches/1.0/oak-run/pom.xml
    jackrabbit/oak/branches/1.0/oak-upgrade/pom.xml

Modified: jackrabbit/oak/branches/1.0/oak-auth-external/pom.xml
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-auth-external/pom.xml?rev=1633319&r1=1633318&r2=1633319&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-auth-external/pom.xml (original)
+++ jackrabbit/oak/branches/1.0/oak-auth-external/pom.xml Tue Oct 21 09:04:38 2014
@@ -161,7 +161,7 @@
         <dependency>
             <groupId>com.h2database</groupId>
             <artifactId>h2</artifactId>
-            <version>1.3.175</version>
+            <version>1.4.182</version>
             <scope>test</scope>
         </dependency>
         <dependency>

Modified: jackrabbit/oak/branches/1.0/oak-core/pom.xml
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/pom.xml?rev=1633319&r1=1633318&r2=1633319&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-core/pom.xml (original)
+++ jackrabbit/oak/branches/1.0/oak-core/pom.xml Tue Oct 21 09:04:38 2014
@@ -289,7 +289,7 @@
     <dependency>
       <groupId>com.h2database</groupId>
       <artifactId>h2</artifactId>
-      <version>1.3.175</version>
+      <version>1.4.182</version>
       <optional>true</optional>
     </dependency>
     <dependency>

Modified: jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java?rev=1633319&r1=1633318&r2=1633319&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java (original)
+++ jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java Tue Oct 21 09:04:38 2014
@@ -33,6 +33,7 @@ import com.mongodb.DB;
 import org.apache.jackrabbit.mk.api.MicroKernel;
 import org.apache.jackrabbit.mk.api.MicroKernelException;
 import org.apache.jackrabbit.oak.spi.blob.BlobStore;
+import org.apache.jackrabbit.oak.spi.blob.GarbageCollectableBlobStore;
 import org.apache.jackrabbit.oak.spi.blob.MemoryBlobStore;
 import org.apache.jackrabbit.oak.commons.json.JsopReader;
 import org.apache.jackrabbit.oak.commons.json.JsopStream;
@@ -46,14 +47,27 @@ import org.apache.jackrabbit.oak.plugins
 import org.apache.jackrabbit.oak.plugins.document.mongo.MongoBlobStore;
 import org.apache.jackrabbit.oak.plugins.document.memory.MemoryDocumentStore;
 import org.apache.jackrabbit.oak.plugins.document.mongo.MongoDocumentStore;
+import org.apache.jackrabbit.oak.plugins.document.persistentCache.CacheType;
+import org.apache.jackrabbit.oak.plugins.document.persistentCache.PersistentCache;
 import org.apache.jackrabbit.oak.plugins.document.rdb.RDBBlobStore;
 import org.apache.jackrabbit.oak.plugins.document.rdb.RDBDocumentStore;
+import org.apache.jackrabbit.oak.plugins.document.util.StringValue;
 import org.apache.jackrabbit.oak.stats.Clock;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * A MicroKernel implementation that stores the data in a {@link DocumentStore}.
  */
 public class DocumentMK implements MicroKernel {
+    
+    static final Logger LOG = LoggerFactory.getLogger(DocumentMK.class);
+    
+    /**
+     * The path where the persistent cache is stored.
+     */
+    static final String PERSISTENT_CACHE = 
+            System.getProperty("oak.documentMK.persCache");
 
     /**
      * The threshold where special handling for many child node starts.
@@ -468,6 +482,7 @@ public class DocumentMK implements Micro
         private boolean disableBranches;
         private Clock clock = Clock.SIMPLE;
         private Executor executor;
+        private PersistentCache persistentCache;
 
         public Builder() {
             memoryCacheSize(DEFAULT_MEMORY_CACHE_SIZE);
@@ -488,7 +503,12 @@ public class DocumentMK implements Micro
                 }
 
                 if (this.blobStore == null) {
-                    this.blobStore = new MongoBlobStore(db, blobCacheSizeMB * 1024 * 1024L);
+                    GarbageCollectableBlobStore s = new MongoBlobStore(db, blobCacheSizeMB * 1024 * 1024L);
+                    PersistentCache p = getPersistentCache();
+                    if (p != null) {
+                        s = p.wrapBlobStore(s);
+                    }
+                    this.blobStore = s;
                 }
 
                 if (this.diffCache == null) {
@@ -759,9 +779,62 @@ public class DocumentMK implements Micro
         public DocumentMK open() {
             return new DocumentMK(this);
         }
-
-        public <K extends CacheValue, V extends CacheValue> Cache<K, V> buildCache(long maxWeight) {
-            if (LIRS_CACHE) {
+        
+        public Cache<PathRev, DocumentNodeState> buildNodeCache(DocumentNodeStore store) {
+            return buildCache(CacheType.NODE, getNodeCacheSize(), store, null);
+        }
+        
+        public Cache<PathRev, DocumentNodeState.Children> buildChildrenCache() {
+            return buildCache(CacheType.CHILDREN, getChildrenCacheSize(), null, null);            
+        }
+        
+        public Cache<StringValue, NodeDocument.Children> buildDocChildrenCache() {
+            return buildCache(CacheType.DOC_CHILDREN, getDocChildrenCacheSize(), null, null);
+        }
+        
+        public Cache<PathRev, StringValue> buildDiffCache() {
+            return buildCache(CacheType.DIFF, getDiffCacheSize(), null, null);
+        }
+
+        public Cache<CacheValue, NodeDocument> buildDocumentCache(DocumentStore docStore) {
+            return buildCache(CacheType.DOCUMENT, getDocumentCacheSize(), null, docStore);
+        }
+
+        private <K extends CacheValue, V extends CacheValue> Cache<K, V> buildCache(
+                CacheType cacheType,
+                long maxWeight,
+                DocumentNodeStore docNodeStore,
+                DocumentStore docStore
+                ) {
+            Cache<K, V> cache = buildCache(maxWeight);
+            PersistentCache p = getPersistentCache();
+            if (p != null) {
+                if (docNodeStore != null) {
+                    docNodeStore.setPersistentCache(p);
+                }
+                cache = p.wrap(docNodeStore, docStore, cache, cacheType);
+            }
+            return cache;
+        }
+        
+        private PersistentCache getPersistentCache() {
+            if (PERSISTENT_CACHE == null) {
+                return null;
+            }
+            if (persistentCache == null) {
+                try {
+                    persistentCache = new PersistentCache(PERSISTENT_CACHE);
+                } catch (Throwable e) {
+                    LOG.warn("Persistent cache not available; please disable the configuration", e);
+                    throw new IllegalArgumentException(e);
+                }
+            }
+            return persistentCache;
+        }
+        
+        private <K extends CacheValue, V extends CacheValue> Cache<K, V> buildCache(
+                long maxWeight) {
+            if (LIRS_CACHE || PERSISTENT_CACHE != null) {
                 return CacheLIRS.newBuilder().
                         weigher(weigher).
                         averageWeight(2000).
@@ -776,6 +849,7 @@ public class DocumentMK implements Micro
                     recordStats().
                     build();
         }
-    }
 
+    }
+    
 }

Modified: jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java?rev=1633319&r1=1633318&r2=1633319&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java (original)
+++ jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java Tue Oct 21 09:04:38 2014
@@ -18,8 +18,10 @@ package org.apache.jackrabbit.oak.plugin
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.NoSuchElementException;
 import java.util.Set;
 
@@ -59,7 +61,7 @@ import static org.apache.jackrabbit.oak.
 /**
  * A {@link NodeState} implementation for the {@link DocumentNodeStore}.
  */
-class DocumentNodeState extends AbstractNodeState implements CacheValue {
+public class DocumentNodeState extends AbstractNodeState implements CacheValue {
 
     public static final Children NO_CHILDREN = new Children();
 
@@ -106,11 +108,15 @@ class DocumentNodeState extends Abstract
             return true;
         } else if (that instanceof DocumentNodeState) {
             DocumentNodeState other = (DocumentNodeState) that;
-            if (getPath().equals(other.getPath())) {
-                if (revisionEquals(other)) {
-                    return true;
-                }
+            if (!getPath().equals(other.getPath())) {
+                // path does not match: not equals
+                // (even if the properties are equal)
+                return false;
             }
+            if (revisionEquals(other)) {
+                return true;
+            }
+            // revision does not match: might still be equals
         } else if (that instanceof ModifiedNodeState) {
             ModifiedNodeState modified = (ModifiedNodeState) that;
             if (modified.getBaseState() == this) {
@@ -119,9 +125,8 @@ class DocumentNodeState extends Abstract
         }
         if (that instanceof NodeState) {
             return AbstractNodeState.equals(this, (NodeState) that);
-        } else {
-            return false;
         }
+        return false;
     }
 
     @Override
@@ -480,6 +485,71 @@ class DocumentNodeState extends Abstract
         });
     }
 
+    public String asString() {
+        JsopWriter json = new JsopBuilder();
+        json.key("path").value(path);
+        json.key("rev").value(rev.toString());
+        if (lastRevision != null) {
+            json.key("lastRev").value(lastRevision.toString());
+        }
+        if (hasChildren) {
+            json.key("hasChildren").value(hasChildren);
+        }
+        if (properties.size() > 0) {
+            json.key("prop").object();
+            for (String k : properties.keySet()) {
+                json.key(k).value(getPropertyAsString(k));
+            }
+            json.endObject();
+        }
+        return json.toString();
+    }
+    
+    public static DocumentNodeState fromString(DocumentNodeStore store, String s) {
+        JsopTokenizer json = new JsopTokenizer(s);
+        String path = null;
+        Revision rev = null;
+        Revision lastRev = null;
+        boolean hasChildren = false;
+        DocumentNodeState state = null;
+        HashMap<String, String> map = new HashMap<String, String>();
+        while (true) {
+            String k = json.readString();
+            json.read(':');
+            if ("path".equals(k)) {
+                path = json.readString();
+            } else if ("rev".equals(k)) {
+                rev = Revision.fromString(json.readString());
+            } else if ("lastRev".equals(k)) {
+                lastRev = Revision.fromString(json.readString());
+            } else if ("hasChildren".equals(k)) {
+                hasChildren = json.read() == JsopReader.TRUE;
+            } else if ("prop".equals(k)) {
+                json.read('{');
+                while (true) {
+                    if (json.matches('}')) {
+                        break;
+                    }
+                    k = json.readString();
+                    json.read(':');
+                    String v = json.readString();
+                    map.put(k, v);
+                    json.matches(',');
+                }
+            }
+            if (json.matches(JsopReader.END)) {
+                break;
+            }
+            json.read(',');
+        }
+        state = new DocumentNodeState(store, path, rev, hasChildren);
+        state.setLastRevision(lastRev);
+        for (Entry<String, String> e : map.entrySet()) {
+            state.setProperty(e.getKey(), e.getValue());
+        }
+        return state;
+    }
+
     /**
      * A list of children for a node.
      */
@@ -489,21 +559,71 @@ class DocumentNodeState extends Abstract
          * Ascending sorted list of names of child nodes.
          */
         final ArrayList<String> children = new ArrayList<String>();
+        int cachedMemory;
         boolean hasMore;
 
         @Override
         public int getMemory() {
-            int size = 114;
-            for (String c : children) {
-                size += c.length() * 2 + 56;
+            if (cachedMemory == 0) {
+                int size = 114;
+                for (String c : children) {
+                    size += c.length() * 2 + 56;
+                }
+                cachedMemory = size;
             }
-            return size;
+            return cachedMemory;
         }
 
         @Override
         public String toString() {
             return children.toString();
         }
+
+        public String asString() {
+            JsopWriter json = new JsopBuilder();
+            if (hasMore) {
+                json.key("hasMore").value(true);
+            }
+            if (children.size() > 0) {
+                json.key("children").array();
+                for (String c : children) {
+                    json.value(c);
+                }
+                json.endArray();
+            }
+            return json.toString();            
+        }
+        
+        public static Children fromString(String s) {
+            JsopTokenizer json = new JsopTokenizer(s);
+            Children children = new Children();
+            while (true) {
+                if (json.matches(JsopReader.END)) {
+                    break;
+                }
+                String k = json.readString();
+                json.read(':');
+                if ("hasMore".equals(k)) {
+                    children.hasMore = json.read() == JsopReader.TRUE;
+                } else if ("children".equals(k)) {
+                    json.read('[');
+                    while (true) {
+                        if (json.matches(']')) {
+                            break;
+                        }
+                        String value = json.readString();
+                        children.children.add(value);
+                        json.matches(',');
+                    }
+                }
+                if (json.matches(JsopReader.END)) {
+                    break;
+                }
+                json.read(',');
+            }
+            return children;            
+        }
+        
     }
 
     private class ChildNodeEntryIterator implements Iterator<ChildNodeEntry> {

Modified: jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java?rev=1633319&r1=1633318&r2=1633319&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java (original)
+++ jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java Tue Oct 21 09:04:38 2014
@@ -64,13 +64,13 @@ import org.apache.jackrabbit.oak.plugins
 import org.apache.jackrabbit.oak.plugins.blob.MarkSweepGarbageCollector;
 import org.apache.jackrabbit.oak.plugins.document.mongo.MongoBlobReferenceIterator;
 import org.apache.jackrabbit.oak.plugins.document.mongo.MongoDocumentStore;
+import org.apache.jackrabbit.oak.plugins.document.persistentCache.PersistentCache;
 import org.apache.jackrabbit.oak.spi.blob.BlobStore;
 import org.apache.jackrabbit.oak.commons.json.JsopStream;
 import org.apache.jackrabbit.oak.commons.json.JsopWriter;
 import org.apache.jackrabbit.oak.api.Blob;
 import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.cache.CacheStats;
-import org.apache.jackrabbit.oak.cache.CacheValue;
 import org.apache.jackrabbit.oak.commons.PathUtils;
 import org.apache.jackrabbit.oak.kernel.BlobSerializer;
 import org.apache.jackrabbit.oak.plugins.document.Branch.BranchCommit;
@@ -249,7 +249,7 @@ public final class DocumentNodeStore
      *
      * Key: PathRev, value: DocumentNodeState
      */
-    private final Cache<CacheValue, DocumentNodeState> nodeCache;
+    private final Cache<PathRev, DocumentNodeState> nodeCache;
     private final CacheStats nodeCacheStats;
 
     /**
@@ -257,7 +257,7 @@ public final class DocumentNodeStore
      *
      * Key: PathRev, value: Children
      */
-    private final Cache<CacheValue, DocumentNodeState.Children> nodeChildrenCache;
+    private final Cache<PathRev, DocumentNodeState.Children> nodeChildrenCache;
     private final CacheStats nodeChildrenCacheStats;
 
     /**
@@ -265,7 +265,7 @@ public final class DocumentNodeStore
      *
      * Key: StringValue, value: Children
      */
-    private final Cache<CacheValue, NodeDocument.Children> docChildrenCache;
+    private final Cache<StringValue, NodeDocument.Children> docChildrenCache;
     private final CacheStats docChildrenCacheStats;
 
     /**
@@ -319,6 +319,8 @@ public final class DocumentNodeStore
 
     private final boolean disableBranches;
 
+    private PersistentCache persistentCache;
+
     public DocumentNodeStore(DocumentMK.Builder builder) {
         this.blobStore = builder.getBlobStore();
         if (builder.isUseSimpleRevision()) {
@@ -361,15 +363,15 @@ public final class DocumentNodeStore
 
         //TODO Make stats collection configurable as it add slight overhead
 
-        nodeCache = builder.buildCache(builder.getNodeCacheSize());
+        nodeCache = builder.buildNodeCache(this);
         nodeCacheStats = new CacheStats(nodeCache, "Document-NodeState",
                 builder.getWeigher(), builder.getNodeCacheSize());
 
-        nodeChildrenCache = builder.buildCache(builder.getChildrenCacheSize());
+        nodeChildrenCache = builder.buildChildrenCache();
         nodeChildrenCacheStats = new CacheStats(nodeChildrenCache, "Document-NodeChildren",
                 builder.getWeigher(), builder.getChildrenCacheSize());
 
-        docChildrenCache = builder.buildCache(builder.getDocChildrenCacheSize());
+        docChildrenCache = builder.buildDocChildrenCache();
         docChildrenCacheStats = new CacheStats(docChildrenCache, "Document-DocChildren",
                 builder.getWeigher(), builder.getDocChildrenCacheSize());
 
@@ -465,6 +467,9 @@ public final class DocumentNodeStore
                 }
             }
         }
+        if (persistentCache != null) {
+            persistentCache.close();
+        }
     }
 
     @Nonnull
@@ -686,7 +691,7 @@ public final class DocumentNodeStore
                     return n;
                 }
             });
-            return node == missing ? null : node;
+            return node == missing || node.equals(missing) ? null : node;
         } catch (ExecutionException e) {
             throw new MicroKernelException(e);
         }
@@ -701,32 +706,28 @@ public final class DocumentNodeStore
         }
         final String path = checkNotNull(parent).getPath();
         final Revision readRevision = parent.getLastRevision();
-        PathRev key = childNodeCacheKey(path, readRevision, name);
-        DocumentNodeState.Children children;
-        for (;;) {
-            try {
-                children = nodeChildrenCache.get(key, new Callable<DocumentNodeState.Children>() {
-                    @Override
-                    public DocumentNodeState.Children call() throws Exception {
-                        return readChildren(parent, name, limit);
-                    }
-                });
-            } catch (ExecutionException e) {
-                throw new MicroKernelException(
-                        "Error occurred while fetching children for path "
-                                + path, e.getCause());
-            }
-            if (children.hasMore && limit > children.children.size()) {
-                // there are potentially more children and
-                // current cache entry contains less than requested limit
-                // -> need to refresh entry with current limit
-                nodeChildrenCache.invalidate(key);
-            } else {
-                // use this cache entry
-                break;
+        try {
+            PathRev key = childNodeCacheKey(path, readRevision, name);
+            DocumentNodeState.Children children = nodeChildrenCache.get(key, new Callable<DocumentNodeState.Children>() {
+                @Override
+                public DocumentNodeState.Children call() throws Exception {
+                    return readChildren(parent, name, limit);
+                }
+            });
+            if (children.children.size() < limit && children.hasMore) {
+                // not enough children loaded - load more,
+                // and put that in the cache
+                // (not using nodeChildrenCache.invalidate, because
+                // the generational persistent cache doesn't support that)
+                children = readChildren(parent, name, limit);
+                nodeChildrenCache.put(key, children);
             }
+            return children;
+        } catch (ExecutionException e) {
+            throw new MicroKernelException(
+                    "Error occurred while fetching children for path "
+                            + path, e.getCause());
         }
-        return children;
     }
 
     /**
@@ -812,7 +813,7 @@ public final class DocumentNodeStore
             // or more than 16k child docs are requested
             return store.query(Collection.NODES, from, to, limit);
         }
-        CacheValue key = new StringValue(path);
+        StringValue key = new StringValue(path);
         // check cache
         NodeDocument.Children c = docChildrenCache.getIfPresent(key);
         if (c == null) {
@@ -937,13 +938,13 @@ public final class DocumentNodeStore
             }
         }
         if (isNew) {
-            CacheValue key = childNodeCacheKey(path, rev, null);
             DocumentNodeState.Children c = new DocumentNodeState.Children();
             Set<String> set = Sets.newTreeSet();
             for (String p : added) {
                 set.add(Utils.unshareString(PathUtils.getName(p)));
             }
             c.children.addAll(set);
+            PathRev key = childNodeCacheKey(path, rev, null);
             nodeChildrenCache.put(key, c);
         }
 
@@ -962,7 +963,7 @@ public final class DocumentNodeStore
 
         // update docChildrenCache
         if (!added.isEmpty()) {
-            CacheValue docChildrenKey = new StringValue(path);
+            StringValue docChildrenKey = new StringValue(path);
             NodeDocument.Children docChildren = docChildrenCache.getIfPresent(docChildrenKey);
             if (docChildren != null) {
                 int currentSize = docChildren.childNames.size();
@@ -1628,7 +1629,11 @@ public final class DocumentNodeStore
                 if (toNode != null) {
                     // exists in both revisions
                     // check if different
-                    if (!fromNode.getLastRevision().equals(toNode.getLastRevision())) {
+                    Revision a = fromNode.getLastRevision();
+                    Revision b = toNode.getLastRevision();
+                    if (a == null && b == null) {
+                        // ok
+                    } else if (a == null || b == null || !a.equals(b)) {
                         w.tag('^').key(name).object().endObject().newline();
                     }
                 } else {
@@ -1693,7 +1698,8 @@ public final class DocumentNodeStore
     private static PathRev childNodeCacheKey(@Nonnull String path,
                                              @Nonnull Revision readRevision,
                                              @Nullable String name) {
-        return new PathRev((name == null ? "" : name) + path, readRevision);
+        String p = (name == null ? "" : name) + path;
+        return new PathRev(p, readRevision);
     }
 
     private static DocumentRootBuilder asDocumentRootBuilder(NodeBuilder builder)
@@ -1878,4 +1884,8 @@ public final class DocumentNodeStore
     public LastRevRecoveryAgent getLastRevRecoveryAgent() {
         return lastRevRecoveryAgent;
     }
+
+    public void setPersistentCache(PersistentCache persistentCache) {
+        this.persistentCache = persistentCache;
+    }
 }

Modified: jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/MemoryDiffCache.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/MemoryDiffCache.java?rev=1633319&r1=1633318&r2=1633319&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/MemoryDiffCache.java (original)
+++ jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/MemoryDiffCache.java Tue Oct 21 09:04:38 2014
@@ -20,7 +20,6 @@ import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
 
 import org.apache.jackrabbit.oak.cache.CacheStats;
-import org.apache.jackrabbit.oak.cache.CacheValue;
 import org.apache.jackrabbit.oak.plugins.document.util.StringValue;
 
 import com.google.common.cache.Cache;
@@ -37,12 +36,12 @@ class MemoryDiffCache implements DiffCac
      *
      * Key: PathRev, value: StringValue
      */
-    protected final Cache<CacheValue, StringValue> diffCache;
+    protected final Cache<PathRev, StringValue> diffCache;
     protected final CacheStats diffCacheStats;
 
 
     MemoryDiffCache(DocumentMK.Builder builder) {
-        diffCache = builder.buildCache(builder.getDiffCacheSize());
+        diffCache = builder.buildDiffCache();
         diffCacheStats = new CacheStats(diffCache, "Document-Diff",
                 builder.getWeigher(), builder.getDiffCacheSize());
     }

Modified: jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocument.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocument.java?rev=1633319&r1=1633318&r2=1633319&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocument.java (original)
+++ jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocument.java Tue Oct 21 09:04:38 2014
@@ -22,6 +22,7 @@ import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.NavigableMap;
 import java.util.Queue;
 import java.util.Set;
@@ -43,6 +44,10 @@ import com.google.common.collect.Iterato
 import com.google.common.collect.Queues;
 import org.apache.jackrabbit.oak.cache.CacheValue;
 import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.commons.json.JsopBuilder;
+import org.apache.jackrabbit.oak.commons.json.JsopReader;
+import org.apache.jackrabbit.oak.commons.json.JsopTokenizer;
+import org.apache.jackrabbit.oak.commons.json.JsopWriter;
 import org.apache.jackrabbit.oak.plugins.document.memory.MemoryDocumentStore;
 import org.apache.jackrabbit.oak.plugins.document.util.Utils;
 import org.slf4j.Logger;
@@ -1363,7 +1368,7 @@ public final class NodeDocument extends 
             return false;
         }
         int c1 = context.getRevisionComparator().compare(r1, r2);
-        int c2 = r1.compareRevisionTimeThenClusterId(r2);
+        int c2 = r1.compareTo(r2);
         if (c1 == 0) {
             return c2 == 0;
         } else if (c1 < 0) {
@@ -1661,12 +1666,89 @@ public final class NodeDocument extends 
     private Map<Revision, String> getDeleted() {
         return ValueMap.create(this, DELETED);
     }
-
+    
+    public String asString() {
+        JsopWriter json = new JsopBuilder();
+        toJson(json, data);
+        return json.toString();
+    }
+    
+    @SuppressWarnings("unchecked")
+    private static void toJson(JsopWriter json, Map<? extends Object, Object> map) {
+        for (Entry<? extends Object, Object>e : map.entrySet()) {
+            json.key(e.getKey().toString());
+            Object value = e.getValue();
+            if (value == null) {
+                json.value(null);
+            } else if (value instanceof Boolean) {
+                json.value((Boolean) value);
+            } else if (value instanceof Long) {
+                json.value((Long) value);
+            } else if (value instanceof Integer) {
+                json.value((Integer) value);
+            } else if (value instanceof Map) {
+                json.object();
+                toJson(json, (Map<Object, Object>) value);
+                json.endObject();
+            } else if (value instanceof Revision) {
+                json.value(value.toString());
+            } else {
+                json.value((String) value);
+            }        
+        }
+    }
+    
+    public static NodeDocument fromString(DocumentStore store, String s) {
+        JsopTokenizer json = new JsopTokenizer(s);
+        NodeDocument doc = new NodeDocument(store);
+        while (true) {
+            if (json.matches(JsopReader.END)) {
+                break;
+            }
+            String k = json.readString();
+            json.read(':');
+            if (json.matches(JsopReader.END)) {
+                break;
+            }
+            doc.put(k, fromJson(json));
+            json.matches(',');
+        }
+        return doc;
+    }
+    
+    private static Object fromJson(JsopTokenizer json) {
+        switch (json.read()) {
+        case JsopReader.NULL:
+            return null;
+        case JsopReader.TRUE:
+            return true;
+        case JsopReader.FALSE:
+            return false;
+        case JsopReader.NUMBER:
+            return Long.parseLong(json.getToken());
+        case JsopReader.STRING:
+            return json.getToken();
+        case '{':
+            TreeMap<Revision, Object> map = new TreeMap<Revision, Object>(StableRevisionComparator.REVERSE);
+            while (true) {
+                if (json.matches('}')) {
+                    break;
+                }
+                String k = json.readString();
+                json.read(':');
+                map.put(Revision.fromString(k), fromJson(json));
+                json.matches(',');
+            }
+            return map;
+        }
+        throw new IllegalArgumentException(json.readRawValue());
+    }
+    
     /**
      * The list of children for a node. The list might be complete or not, in
      * which case it only represents a block of children.
      */
-    static final class Children implements CacheValue, Cloneable {
+    public static final class Children implements CacheValue, Cloneable {
 
         /**
          * The child node names, ordered as stored in DocumentStore.
@@ -1699,6 +1781,52 @@ public final class NodeDocument extends 
                 throw new RuntimeException();
             }
         }
+
+        public String asString() {
+            JsopWriter json = new JsopBuilder();
+            if (isComplete) {
+                json.key("isComplete").value(true);
+            }
+            if (childNames.size() > 0) {
+                json.key("children").array();
+                for (String c : childNames) {
+                    json.value(c);
+                }
+                json.endArray();
+            }
+            return json.toString();            
+        }
+        
+        public static Children fromString(String s) {
+            JsopTokenizer json = new JsopTokenizer(s);
+            Children children = new Children();
+            while (true) {
+                if (json.matches(JsopReader.END)) {
+                    break;
+                }
+                String k = json.readString();
+                json.read(':');
+                if ("isComplete".equals(k)) {
+                    children.isComplete = json.read() == JsopReader.TRUE;
+                } else if ("children".equals(k)) {
+                    json.read('[');
+                    while (true) {
+                        if (json.matches(']')) {
+                            break;
+                        }
+                        String value = json.readString();
+                        children.childNames.add(value);
+                        json.matches(',');
+                    }
+                }
+                if (json.matches(JsopReader.END)) {
+                    break;
+                }
+                json.read(',');
+            }
+            return children;            
+        }
+        
     }
 
     /**

Modified: jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/PathRev.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/PathRev.java?rev=1633319&r1=1633318&r2=1633319&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/PathRev.java (original)
+++ jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/PathRev.java Tue Oct 21 09:04:38 2014
@@ -69,4 +69,25 @@ public final class PathRev implements Ca
     public String toString() {
         return path + "@" + revision;
     }
+
+    public String asString() {
+        return toString();
+    }
+
+    public static PathRev fromString(String s) {
+        int index = s.lastIndexOf('@');
+        return new PathRev(s.substring(0, index), Revision.fromString(s.substring(index + 1)));
+    }
+
+    public int compareTo(PathRev b) {
+        if (this == b) {
+            return 0;
+        }
+        int compare = path.compareTo(b.path);
+        if (compare == 0) {
+            compare = revision.compareTo(b.revision);
+        }
+        return compare;
+    }
+    
 }

Modified: jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Revision.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Revision.java?rev=1633319&r1=1633318&r2=1633319&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Revision.java (original)
+++ jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Revision.java Tue Oct 21 09:04:38 2014
@@ -136,6 +136,22 @@ public class Revision {
         }
         return comp;
     }
+    
+    /**
+     * Compare all components of two revisions.
+     * 
+     * @param other the other revision
+     * @return -1, 0, or 1
+     */
+    int compareTo(Revision other) {
+        int comp = compareRevisionTimeThenClusterId(other);
+        if (comp == 0) {
+            if (branch != other.branch) {
+                return branch ? -1 : 1;
+            }
+        }
+        return comp;
+    }
 
     /**
      * Compare the cluster node ids of both revisions.
@@ -526,17 +542,17 @@ public class Revision {
             Revision range1 = getRevisionSeen(o1);
             Revision range2 = getRevisionSeen(o2);
             if (range1 == FUTURE && range2 == FUTURE) {
-                return o1.compareRevisionTimeThenClusterId(o2);
+                return o1.compareTo(o2);
             }
             if (range1 == null && range2 == null) {
-                return o1.compareRevisionTimeThenClusterId(o2);
+                return o1.compareTo(o2);
             }
             if (range1 == null) {
                 return -1;
             } else if (range2 == null) {
                 return 1;
             }
-            int comp = range1.compareRevisionTimeThenClusterId(range2);
+            int comp = range1.compareTo(range2);
             if (comp != 0) {
                 return comp;
             }

Modified: jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/StableRevisionComparator.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/StableRevisionComparator.java?rev=1633319&r1=1633318&r2=1633319&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/StableRevisionComparator.java (original)
+++ jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/StableRevisionComparator.java Tue Oct 21 09:04:38 2014
@@ -42,6 +42,6 @@ public class StableRevisionComparator im
 
     @Override
     public int compare(Revision o1, Revision o2) {
-        return o1.compareRevisionTimeThenClusterId(o2);
+        return o1.compareTo(o2);
     }
 }

Modified: jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentStore.java?rev=1633319&r1=1633318&r2=1633319&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentStore.java (original)
+++ jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentStore.java Tue Oct 21 09:04:38 2014
@@ -195,7 +195,7 @@ public class MongoDocumentStore implemen
         if (builder.useOffHeapCache()) {
             nodesCache = createOffHeapCache(builder);
         } else {
-            nodesCache = builder.buildCache(builder.getDocumentCacheSize());
+            nodesCache = builder.buildDocumentCache(this);
         }
 
         cacheStats = new CacheStats(nodesCache, "Document-Documents", builder.getWeigher(),

Added: jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/BlobCache.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/BlobCache.java?rev=1633319&view=auto
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/BlobCache.java (added)
+++ jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/BlobCache.java Tue Oct 21 09:04:38 2014
@@ -0,0 +1,199 @@
+/*
+ * 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.oak.plugins.document.persistentCache;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+import org.apache.jackrabbit.oak.plugins.document.persistentCache.PersistentCache.GenerationCache;
+import org.apache.jackrabbit.oak.spi.blob.BlobStore;
+import org.apache.jackrabbit.oak.spi.blob.GarbageCollectableBlobStore;
+import org.h2.mvstore.MVMapConcurrent;
+import org.h2.mvstore.StreamStore;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A persistent blob cache. Only blobs that are smaller than 10% of the maximum
+ * cache size are stored.
+ */
+public class BlobCache implements BlobStore, GarbageCollectableBlobStore, GenerationCache {
+
+    static final Logger LOG = LoggerFactory.getLogger(BlobCache.class);
+
+    private final GarbageCollectableBlobStore base;
+    private final PersistentCache cache;
+    private final MultiGenerationMap<String, byte[]> meta;
+    private MultiGenerationMap<Long, byte[]> data;
+    private StreamStore streamStore;
+    private long maxEntrySize;
+    
+    public BlobCache(
+            PersistentCache cache, 
+            GarbageCollectableBlobStore base) {
+        this.cache = cache;
+        this.base = base;
+        data = new MultiGenerationMap<Long, byte[]>();
+        meta = new MultiGenerationMap<String, byte[]>();
+        maxEntrySize = cache.getMaxBinaryEntrySize();
+    }
+    
+    @Override
+    public void addGeneration(int generation, boolean readOnly) {
+        Map<Long, byte[]> d = cache.openMap(generation, "data", 
+                new MVMapConcurrent.Builder<Long, byte[]>());
+        data.addReadMap(generation, d);
+        Map<String, byte[]> m = cache.openMap(generation, "meta", 
+                new MVMapConcurrent.Builder<String, byte[]>());
+        meta.addReadMap(generation, m);
+        if (!readOnly) {
+            // the order is important:
+            // if we switch the data first,
+            // we could end up with the data in store 1
+            // but the metadata in store 2 - which could
+            // result in a data block not found if store 1
+            // is removed later on
+            meta.setWriteMap(m);
+            data.setWriteMap(d);
+        }
+        if (streamStore == null) {
+            streamStore = new StreamStore(data);
+        }
+    }    
+    
+    @Override
+    public void removeGeneration(int generation) {
+        data.removeReadMap(generation);
+        meta.removeReadMap(generation);
+    }
+    
+    @Override
+    public InputStream getInputStream(String blobId) throws IOException {
+        if (streamStore == null) {
+            return base.getInputStream(blobId);
+        }
+        cache.switchGenerationIfNeeded();
+        byte[] id = meta.get(blobId);
+        if (id == null) {
+            long length = base.getBlobLength(blobId);
+            InputStream in = base.getInputStream(blobId);
+            if (length < base.getBlockSizeMin()) {
+                // in-place
+                return in;
+            }
+            if (length > maxEntrySize) {
+                // too large, don't cache
+                return in;
+            }
+            id = streamStore.put(in);
+            in.close();
+            meta.put(blobId, id);
+        }
+        return streamStore.get(id);
+    }
+
+    @Override
+    public String writeBlob(InputStream in) throws IOException {
+        // TODO maybe copy the binary to the cache in a background thread
+        return base.writeBlob(in);
+    }
+
+    @Override
+    public int readBlob(String blobId, long pos, byte[] buff, int off,
+            int length) throws IOException {
+        InputStream in = getInputStream(blobId);
+        long remainingSkip = pos;
+        while (remainingSkip > 0) {
+            remainingSkip -= in.skip(remainingSkip);
+        }
+        return in.read(buff, off, length);
+    }
+
+    @Override
+    public long getBlobLength(String blobId) throws IOException {
+        return base.getBlobLength(blobId);
+    }
+
+    @Override
+    @CheckForNull
+    public String getBlobId(@Nonnull String reference) {
+        return base.getBlobId(reference);
+    }
+
+    @Override
+    @CheckForNull
+    public String getReference(@Nonnull String blobId) {
+        return base.getReference(blobId);
+    }
+
+    @Override
+    public void clearCache() {
+        base.clearCache();
+    }
+
+    @Override
+    public void clearInUse() {
+        base.clearInUse();
+    }
+
+    @Override
+    public boolean deleteChunks(List<String> arg0, long arg1) throws Exception {
+        return base.deleteChunks(arg0, arg1);
+    }
+
+    @Override
+    public Iterator<String> getAllChunkIds(long arg0) throws Exception {
+        return base.getAllChunkIds(arg0);
+    }
+
+    @Override
+    public long getBlockSizeMin() {
+        return base.getBlockSizeMin();
+    }
+
+    @Override
+    public Iterator<String> resolveChunks(String arg0) throws IOException {
+        return base.resolveChunks(arg0);
+    }
+
+    @Override
+    public void setBlockSize(int arg0) {
+        base.setBlockSize(arg0);
+    }
+
+    @Override
+    public void startMark() throws IOException {
+        base.startMark();
+    }
+
+    @Override
+    public int sweep() throws IOException {
+        return base.sweep();
+    }
+
+    @Override
+    public String writeBlob(String arg0) throws IOException {
+        return base.writeBlob(arg0);
+    }
+    
+}

Added: jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/CacheType.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/CacheType.java?rev=1633319&view=auto
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/CacheType.java (added)
+++ jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/CacheType.java Tue Oct 21 09:04:38 2014
@@ -0,0 +1,166 @@
+/*
+ * 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.oak.plugins.document.persistentCache;
+
+import org.apache.jackrabbit.oak.plugins.document.DocumentNodeState;
+import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore;
+import org.apache.jackrabbit.oak.plugins.document.DocumentStore;
+import org.apache.jackrabbit.oak.plugins.document.NodeDocument;
+import org.apache.jackrabbit.oak.plugins.document.PathRev;
+import org.apache.jackrabbit.oak.plugins.document.util.StringValue;
+
+public enum CacheType {
+    
+    NODE {
+        @Override
+        public <K> String keyToString(K key) {
+            return ((PathRev) key).asString();
+        }
+        @SuppressWarnings("unchecked")
+        @Override
+        public <K> K keyFromString(String key) {
+            return (K) PathRev.fromString(key);
+        }
+        @Override
+        public <K> int compareKeys(K a, K b) {
+            return ((PathRev) a).compareTo((PathRev) b);
+        }
+        @Override
+        public <V> String valueToString(V value) {
+            return ((DocumentNodeState) value).asString();
+        }
+        @SuppressWarnings("unchecked")
+        @Override
+        public <V> V valueFromString(
+                DocumentNodeStore store, DocumentStore docStore, String value) {
+            return (V) DocumentNodeState.fromString(store, value);
+        }
+    },
+    
+    CHILDREN {
+        @Override
+        public <K> String keyToString(K key) {
+            return ((PathRev) key).asString();
+        }
+        @SuppressWarnings("unchecked")
+        @Override
+        public <K> K keyFromString(String key) {
+            return (K) PathRev.fromString(key);
+        }
+        @Override
+        public <K> int compareKeys(K a, K b) {
+            return ((PathRev) a).compareTo((PathRev) b);
+        }
+        @Override
+        public <V> String valueToString(V value) {
+            return ((DocumentNodeState.Children) value).asString();
+        }
+        @SuppressWarnings("unchecked")
+        @Override
+        public <V> V valueFromString(
+                DocumentNodeStore store, DocumentStore docStore, String value) {
+            return (V) DocumentNodeState.Children.fromString(value);
+        }
+    }, 
+    
+    DIFF {
+        @Override
+        public <K> String keyToString(K key) {
+            return ((PathRev) key).asString();
+        }
+        @SuppressWarnings("unchecked")
+        @Override
+        public <K> K keyFromString(String key) {
+            return (K) PathRev.fromString(key);
+        }
+        @Override
+        public <K> int compareKeys(K a, K b) {
+            return ((PathRev) a).compareTo((PathRev) b);
+        }            
+        @Override
+        public <V> String valueToString(V value) {
+            return ((StringValue) value).asString();
+        }
+        @SuppressWarnings("unchecked")
+        @Override
+        public <V> V valueFromString(
+                DocumentNodeStore store, DocumentStore docStore, String value) {
+            return (V) StringValue.fromString(value);
+        }
+    },
+    
+    DOC_CHILDREN {
+        @Override
+        public <K> String keyToString(K key) {
+            return ((StringValue) key).asString();
+        }
+        @SuppressWarnings("unchecked")
+        @Override
+        public <K> K keyFromString(String key) {
+            return (K) StringValue.fromString(key);
+        }
+        @Override
+        public <K> int compareKeys(K a, K b) {
+            return ((StringValue) a).asString().compareTo(((StringValue) b).asString());
+        }            
+        @Override
+        public <V> String valueToString(V value) {
+            return ((NodeDocument.Children) value).asString();
+        }
+        @SuppressWarnings("unchecked")
+        @Override
+        public <V> V valueFromString(
+                DocumentNodeStore store, DocumentStore docStore, String value) {
+            return (V) NodeDocument.Children.fromString(value);
+        }
+    }, 
+    
+    DOCUMENT {
+        @Override
+        public <K> String keyToString(K key) {
+            return ((StringValue) key).asString();
+        }
+        @SuppressWarnings("unchecked")
+        @Override
+        public <K> K keyFromString(String key) {
+            return (K) StringValue.fromString(key);
+        }
+        @Override
+        public <K> int compareKeys(K a, K b) {
+            return ((StringValue) a).asString().compareTo(((StringValue) b).asString());
+        }            
+        @Override
+        public <V> String valueToString(V value) {
+            return ((NodeDocument) value).asString();
+        }
+        @SuppressWarnings("unchecked")
+        @Override
+        public <V> V valueFromString(
+                DocumentNodeStore store, DocumentStore docStore, String value) {
+            return (V) NodeDocument.fromString(docStore, value);
+        }
+    }; 
+    
+    public abstract <K> String keyToString(K key);
+    public abstract <K> K keyFromString(String key);
+    public abstract <K> int compareKeys(K a, K b);
+    public abstract <V> String valueToString(V value);
+    public abstract <V> V valueFromString(
+            DocumentNodeStore store, DocumentStore docStore, String value);
+
+}
+

Added: jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/KeyDataType.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/KeyDataType.java?rev=1633319&view=auto
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/KeyDataType.java (added)
+++ jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/KeyDataType.java Tue Oct 21 09:04:38 2014
@@ -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.oak.plugins.document.persistentCache;
+
+import java.nio.ByteBuffer;
+
+import org.apache.jackrabbit.oak.cache.CacheValue;
+import org.h2.mvstore.WriteBuffer;
+import org.h2.mvstore.type.DataType;
+import org.h2.mvstore.type.StringDataType;
+
+public class KeyDataType implements DataType {
+    
+    private final CacheType type;
+    
+    public KeyDataType(CacheType type) {
+        this.type = type;
+    }
+
+    @Override
+    public int compare(Object a, Object b) {
+        return type.compareKeys(a, b);
+    }
+
+    @Override
+    public int getMemory(Object obj) {
+        return ((CacheValue) obj).getMemory();
+    }
+
+    @Override
+    public void write(WriteBuffer buff, Object obj) {
+        String s = type.keyToString(obj);
+        StringDataType.INSTANCE.write(buff, s);
+    }
+
+    @Override
+    public Object read(ByteBuffer buff) {
+        String s = StringDataType.INSTANCE.read(buff);
+        return type.keyFromString(s);
+    }
+
+    @Override
+    public void write(WriteBuffer buff, Object[] obj, int len, boolean key) {
+        for (int i = 0; i < len; i++) {
+            write(buff, obj[i]);
+        }
+    }
+
+    @Override
+    public void read(ByteBuffer buff, Object[] obj, int len, boolean key) {
+        for (int i = 0; i < len; i++) {
+            obj[i] = read(buff);
+        }
+    }
+    
+}
\ No newline at end of file

Added: jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/MultiGenerationMap.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/MultiGenerationMap.java?rev=1633319&view=auto
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/MultiGenerationMap.java (added)
+++ jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/MultiGenerationMap.java Tue Oct 21 09:04:38 2014
@@ -0,0 +1,126 @@
+/*
+ * 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.oak.plugins.document.persistentCache;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentSkipListMap;
+
+public class MultiGenerationMap<K, V> implements Map<K, V> {
+    
+    private volatile Map<K, V> write;
+    private ConcurrentSkipListMap<Integer, Map<K, V>> read = 
+            new ConcurrentSkipListMap<Integer, Map<K, V>>();
+    
+    MultiGenerationMap() {
+    }
+    
+    public void setWriteMap(Map<K, V> m) {
+        write = m;
+    }
+
+    public void addReadMap(int generation, Map<K, V> m) {
+        read.put(generation, m);
+    }
+    
+    public void removeReadMap(int generation) {
+        read.remove(generation);
+    }
+    
+    @Override
+    public V put(K key, V value) {
+        return write.put(key, value);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public V get(Object key) {
+        for (int generation : read.descendingKeySet()) {
+            Map<K, V> m = read.get(generation);
+            if (m != null) {
+                V value = m.get(key);
+                if (value != null) {
+                    if (m != write) {
+                        put((K) key, value);
+                    }
+                    return value;
+                }
+            }
+        }
+        return null;
+    }
+    
+    @Override
+    public boolean containsKey(Object key) {
+        for (int generation : read.descendingKeySet()) {
+            Map<K, V> m = read.get(generation);
+            if (m != null) {
+                if (m.containsKey(key)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public V remove(Object key) {
+        return write.remove(key);
+    }
+
+    @Override
+    public void clear() {
+        write.clear();
+    }
+    
+    @Override
+    public int size() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isEmpty() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean containsValue(Object value) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void putAll(Map<? extends K, ? extends V> m) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Set<K> keySet() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Collection<V> values() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Set<java.util.Map.Entry<K, V>> entrySet() {
+        throw new UnsupportedOperationException();
+    }
+
+}

Added: jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/NodeCache.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/NodeCache.java?rev=1633319&view=auto
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/NodeCache.java (added)
+++ jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/NodeCache.java Tue Oct 21 09:04:38 2014
@@ -0,0 +1,176 @@
+/*
+ * 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.oak.plugins.document.persistentCache;
+
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutionException;
+
+import javax.annotation.Nullable;
+
+import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore;
+import org.apache.jackrabbit.oak.plugins.document.DocumentStore;
+import org.apache.jackrabbit.oak.plugins.document.persistentCache.PersistentCache.GenerationCache;
+import org.h2.mvstore.MVMapConcurrent;
+import org.h2.mvstore.type.DataType;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheStats;
+import com.google.common.collect.ImmutableMap;
+
+class NodeCache<K, V> implements Cache<K, V>, GenerationCache {
+    
+    private final PersistentCache cache;
+    private final Cache<K, V> memCache;
+    private final MultiGenerationMap<K, V> map;
+    private final CacheType type;
+    private final DocumentNodeStore docNodeStore;
+    private final DocumentStore docStore;
+    
+    NodeCache(
+            PersistentCache cache,
+            Cache<K, V> memCache,
+            DocumentNodeStore docNodeStore, 
+            DocumentStore docStore, CacheType type) {
+        this.cache = cache;
+        this.memCache = memCache;
+        this.type = type;
+        this.docNodeStore = docNodeStore;
+        this.docStore = docStore;
+        PersistentCache.LOG.info("wrap " + this.type);
+        map = new MultiGenerationMap<K, V>();
+    }
+    
+    @Override
+    public void addGeneration(int generation, boolean readOnly) {
+        DataType keyType = new KeyDataType(type);
+        DataType valueType = new ValueDataType(docNodeStore, docStore, type);
+        MVMapConcurrent.Builder<K, V> b = new MVMapConcurrent.Builder<K, V>().
+                keyType(keyType).valueType(valueType);
+        String mapName = type.name();
+        Map<K, V> m = cache.openMap(generation, mapName, b);
+        map.addReadMap(generation, m);
+        if (!readOnly) {
+            map.setWriteMap(m);
+        }
+    }
+    
+    @Override
+    public void removeGeneration(int generation) {
+        map.removeReadMap(generation);
+    }
+    
+    private V readIfPresent(K key) {
+        cache.switchGenerationIfNeeded();
+        V v = map.get(key);
+        return v;
+    }
+    
+    public void write(K key, V value) {
+        cache.switchGenerationIfNeeded();
+        if (value == null) {
+            map.remove(key);
+        } else {
+            map.put(key, value);
+        }
+    }
+    
+    @SuppressWarnings("unchecked")
+    @Override
+    @Nullable
+    public V getIfPresent(Object key) {
+        V value = memCache.getIfPresent(key);
+        if (value != null) {
+            return value;
+        }
+        value = readIfPresent((K) key);
+        if (value != null) {
+            memCache.put((K) key, value);
+        }
+        return value;
+    }
+
+    @Override
+    public V get(K key,
+            Callable<? extends V> valueLoader)
+            throws ExecutionException {
+        V value = getIfPresent(key);
+        if (value != null) {
+            return value;
+        }
+        value = memCache.get(key, valueLoader);
+        write(key, value);
+        return value;
+    }
+
+    @Override
+    public ImmutableMap<K, V> getAllPresent(
+            Iterable<?> keys) {
+        return memCache.getAllPresent(keys);
+    }
+
+    @Override
+    public void put(K key, V value) {
+        memCache.put(key, value);
+        write(key, value);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public void invalidate(Object key) {
+        memCache.invalidate(key);
+        write((K) key, (V) null);
+    }
+
+    @Override
+    public void putAll(Map<? extends K, ? extends V> m) {
+        memCache.putAll(m);
+    }
+
+    @Override
+    public void invalidateAll(Iterable<?> keys) {
+        memCache.invalidateAll(keys);
+    }
+
+    @Override
+    public void invalidateAll() {
+        memCache.invalidateAll();
+        map.clear();
+    }
+
+    @Override
+    public long size() {
+        return memCache.size();
+    }
+
+    @Override
+    public CacheStats stats() {
+        return memCache.stats();
+    }
+
+    @Override
+    public ConcurrentMap<K, V> asMap() {
+        return memCache.asMap();
+    }
+
+    @Override
+    public void cleanUp() {
+        memCache.cleanUp();
+    }
+
+}
\ No newline at end of file

Added: jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/PersistentCache.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/PersistentCache.java?rev=1633319&view=auto
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/PersistentCache.java (added)
+++ jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/PersistentCache.java Tue Oct 21 09:04:38 2014
@@ -0,0 +1,312 @@
+/*
+ * 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.oak.plugins.document.persistentCache;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.TreeSet;
+
+import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore;
+import org.apache.jackrabbit.oak.plugins.document.DocumentStore;
+import org.apache.jackrabbit.oak.spi.blob.GarbageCollectableBlobStore;
+import org.h2.mvstore.FileStore;
+import org.h2.mvstore.MVMapConcurrent;
+import org.h2.mvstore.MVStore;
+import org.h2.mvstore.MVStoreTool;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.cache.Cache;
+
+/**
+ * A persistent cache for the document store.
+ */
+public class PersistentCache {
+    
+    static final Logger LOG = LoggerFactory.getLogger(PersistentCache.class);
+   
+    private static final String FILE_PREFIX = "cache-";
+    private static final String FILE_SUFFIX = ".data";
+    
+    private boolean cacheNodes = true;
+    private boolean cacheChildren = true;
+    private boolean cacheDiff = true;
+    private boolean cacheDocs;
+    private boolean cacheDocChildren;
+    private boolean compactOnClose = true;
+    private boolean compress = true;
+    private ArrayList<GenerationCache> caches = 
+            new ArrayList<GenerationCache>();
+    
+    private final String directory;
+    private MVStore writeStore;
+    private MVStore readStore;
+    private int maxSizeMB = 1024;
+    private int readGeneration = -1;
+    private int writeGeneration = 0;
+    private long maxBinaryEntry = 1024 * 1024;
+    
+    public PersistentCache(String url) {
+        LOG.info("start version 1");
+        String[] parts = url.split(",");
+        String dir = parts[0];
+        for (String p : parts) {
+            if (p.equals("+docs")) {
+                cacheDocs = true;
+            } else if (p.equals("+docChildren")) {
+                cacheDocChildren = true;
+            } else if (p.equals("-nodes")) {
+                cacheNodes = false;
+            } else if (p.equals("-children")) {
+                cacheChildren = false;
+            } else if (p.equals("-diff")) {
+                cacheDiff = false;
+            } else if (p.equals("+all")) {
+                cacheDocs = true;
+                cacheDocChildren = true;
+            } else if (p.equals("-compact")) {
+                compactOnClose = false;
+            } else if (p.equals("-compress")) {
+                compress = false;
+            } else if (p.endsWith("time")) {
+                dir += "-" + System.currentTimeMillis();
+            } else if (p.startsWith("size=")) {
+                maxSizeMB = Integer.parseInt(p.split("=")[1]);
+            } else if (p.startsWith("binary=")) {
+                maxBinaryEntry = Long.parseLong(p.split("=")[1]);
+            }
+        }
+        this.directory = dir;
+        if (dir.length() == 0) {
+            readGeneration = -1;
+            writeGeneration = 0;
+            writeStore = openStore(writeGeneration, false);
+            return;
+        }
+        File dr = new File(dir);
+        if (!dr.exists()) {
+            dr.mkdirs();
+        }
+        if (dr.exists() && !dr.isDirectory()) {
+            throw new IllegalArgumentException("A file exists at cache directory " + dir);
+        }
+        File[] list = dr.listFiles();
+        TreeSet<Integer> generations = new TreeSet<Integer>();
+        if (list != null) {
+            for(File f : list) {
+                String fn = f.getName();
+                if (fn.startsWith(FILE_PREFIX) && fn.endsWith(FILE_SUFFIX)) {
+                    String g = fn.substring(FILE_PREFIX.length(), fn.indexOf(FILE_SUFFIX));
+                    try {
+                        int gen = Integer.parseInt(g);
+                        if (gen >= 0) {
+                            File f2 = new File(getFileName(gen));
+                            if (fn.equals(f2.getName())) {
+                                // ignore things like "cache-000.data"
+                                generations.add(gen);
+                            }
+                        }
+                    } catch (Exception e) {
+                        // ignore this file
+                    }
+                }
+            }
+        }
+        while (generations.size() > 2) {
+            generations.remove(generations.last());
+        }
+        readGeneration = generations.size() > 1 ? generations.first() : -1;
+        writeGeneration = generations.size() > 0 ? generations.last() : 0;
+        if (readGeneration >= 0) {
+            readStore = openStore(readGeneration, true);
+        }
+        writeStore = openStore(writeGeneration, false);
+    }
+    
+    private String getFileName(int generation) {
+        if (directory.length() == 0) {
+            return null;
+        }
+        return directory + "/" + FILE_PREFIX + generation + FILE_SUFFIX;
+    }
+    
+    private MVStore openStore(int generation, boolean readOnly) {
+        String fileName = getFileName(generation);
+        MVStore.Builder builder = new MVStore.Builder();
+        if (compress) {
+            builder.compress();
+        }
+        if (fileName != null) {
+            builder.fileName(fileName);
+        }
+        if (readOnly) {
+            builder.readOnly();
+        }
+        if (maxSizeMB < 10) {
+            builder.cacheSize(maxSizeMB);
+        }
+        builder.backgroundExceptionHandler(new Thread.UncaughtExceptionHandler() {
+            @Override
+            public void uncaughtException(Thread t, Throwable e) {
+                LOG.error("Error in persistent cache", e);
+            }
+        });
+        return builder.open();
+    }
+    
+    public void close() {
+        closeStore(writeStore, writeGeneration);
+        closeStore(readStore, readGeneration);
+    }
+    
+    private void closeStore(MVStore s, int generation) {
+        if (s == null) {
+            return;
+        }
+        String fileName = getFileName(generation);
+        boolean compact = compactOnClose;
+        if (s.getFileStore().isReadOnly()) {
+            compact = false;
+        }
+        s.close();
+        if (compact) {
+            MVStoreTool.compact(fileName, true);
+        }
+    }
+    
+    public synchronized GarbageCollectableBlobStore wrapBlobStore(
+            GarbageCollectableBlobStore base) {
+        BlobCache c = new BlobCache(this, base);
+        initGenerationCache(c);
+        return c;
+    }
+    
+    public synchronized <K, V> Cache<K, V> wrap(
+            DocumentNodeStore docNodeStore, 
+            DocumentStore docStore,
+            Cache<K, V> base, CacheType type) {
+        boolean wrap;
+        switch (type) {
+        case NODE:
+            wrap = cacheNodes;
+            break;
+        case CHILDREN:
+            wrap = cacheChildren;
+            break;
+        case DIFF:
+            wrap = cacheDiff;
+            break;
+        case DOC_CHILDREN:
+            wrap = cacheDocChildren;
+            break;
+        case DOCUMENT:
+            wrap = cacheDocs;
+            break;
+        default:  
+            wrap = false;
+            break;
+        }
+        if (wrap) {
+            NodeCache<K, V> c = new NodeCache<K, V>(this, base, docNodeStore, docStore, type);
+            initGenerationCache(c);
+            return c;
+        }
+        return base;
+    }
+    
+    private void initGenerationCache(GenerationCache c) {
+        caches.add(c);
+        if (readGeneration >= 0) {
+            c.addGeneration(readGeneration, true);
+        }
+        c.addGeneration(writeGeneration, false);
+    }
+    
+    synchronized <K, V> Map<K, V> openMap(int generation, String name, 
+            MVMapConcurrent.Builder<K, V> builder) {
+        MVStore s;
+        if (generation == readGeneration) {
+            s = readStore;
+        } else if (generation == writeGeneration) {
+            s = writeStore;
+        } else {
+            throw new IllegalArgumentException("Unknown generation: " + generation);
+        }
+        return s.openMap(name, builder);
+    }
+    
+    public void switchGenerationIfNeeded() {
+        if (!needSwitch()) {
+            return;
+        }
+        synchronized (this) {
+            // maybe another thread already switched,
+            // so we need to check again
+            if (!needSwitch()) {
+                return;
+            }
+            int oldReadGeneration = readGeneration;
+            MVStore oldRead = readStore;
+            readStore = writeStore;
+            readGeneration = writeGeneration;
+            MVStore w = openStore(writeGeneration + 1, false);
+            writeStore = w;
+            writeGeneration++;
+            for (GenerationCache c : caches) {
+                c.addGeneration(writeGeneration, false);
+                if (oldReadGeneration >= 0) {
+                    c.removeGeneration(oldReadGeneration);
+                }
+            }
+            if (oldRead != null) {
+                oldRead.close();
+                new File(getFileName(oldReadGeneration)).delete();
+            }
+        }
+    }
+    
+    private boolean needSwitch() {
+        FileStore fs = writeStore.getFileStore();
+        if (fs == null) {
+            return false;
+        }
+        long size = fs.size();
+        if (size / 1024 / 1024 <= maxSizeMB) {
+            return false;
+        }
+        return true;
+    }
+    
+    public int getMaxSize() {
+        return maxSizeMB;
+    }
+    
+    public long getMaxBinaryEntrySize() {
+        return maxBinaryEntry;
+    }
+
+    static interface GenerationCache {
+
+        void addGeneration(int writeGeneration, boolean b);
+
+        void removeGeneration(int oldReadGeneration);
+        
+        
+    }
+
+}

Added: jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/ValueDataType.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/ValueDataType.java?rev=1633319&view=auto
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/ValueDataType.java (added)
+++ jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/ValueDataType.java Tue Oct 21 09:04:38 2014
@@ -0,0 +1,78 @@
+/*
+ * 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.oak.plugins.document.persistentCache;
+
+import java.nio.ByteBuffer;
+
+import org.apache.jackrabbit.oak.cache.CacheValue;
+import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore;
+import org.apache.jackrabbit.oak.plugins.document.DocumentStore;
+import org.h2.mvstore.WriteBuffer;
+import org.h2.mvstore.type.DataType;
+import org.h2.mvstore.type.StringDataType;
+
+public class ValueDataType implements DataType {
+    
+    private final DocumentNodeStore docNodeStore;
+    private final DocumentStore docStore;
+    private final CacheType type;
+    
+    public ValueDataType(
+            DocumentNodeStore docNodeStore,
+            DocumentStore docStore, CacheType type) {
+        this.docNodeStore = docNodeStore;
+        this.docStore = docStore;
+        this.type = type;
+    }
+
+    @Override
+    public int compare(Object a, Object b) {
+        return 0;
+    }
+
+    @Override
+    public int getMemory(Object obj) {
+        return ((CacheValue) obj).getMemory();
+    }
+
+    @Override
+    public void write(WriteBuffer buff, Object obj) {
+        String s = type.valueToString(obj);
+        StringDataType.INSTANCE.write(buff, s);
+    }
+
+    @Override
+    public Object read(ByteBuffer buff) {
+        String s = StringDataType.INSTANCE.read(buff);
+        return type.valueFromString(docNodeStore, docStore, s);
+    }
+
+    @Override
+    public void write(WriteBuffer buff, Object[] obj, int len, boolean key) {
+        for (int i = 0; i < len; i++) {
+            write(buff, obj[i]);
+        }
+    }
+
+    @Override
+    public void read(ByteBuffer buff, Object[] obj, int len, boolean key) {
+        for (int i = 0; i < len; i++) {
+            obj[i] = read(buff);
+        }
+    }
+    
+}
\ No newline at end of file

Modified: jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStore.java?rev=1633319&r1=1633318&r2=1633319&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStore.java (original)
+++ jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStore.java Tue Oct 21 09:04:38 2014
@@ -242,7 +242,7 @@ public class RDBDocumentStore implements
         this.ds = ds;
         this.callStack = LOG.isDebugEnabled() ? new Exception("call stack of RDBDocumentStore creation") : null;
 
-        this.nodesCache = builder.buildCache(builder.getDocumentCacheSize());
+        this.nodesCache = builder.buildDocumentCache(this);
         this.cacheStats = new CacheStats(nodesCache, "Document-Documents", builder.getWeigher(), builder.getDocumentCacheSize());
 
         Connection con = ds.getConnection();

Modified: jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/StringValue.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/StringValue.java?rev=1633319&r1=1633318&r2=1633319&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/StringValue.java (original)
+++ jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/StringValue.java Tue Oct 21 09:04:38 2014
@@ -61,4 +61,13 @@ public final class StringValue implement
     public String toString() {
         return value;
     }
+
+    public String asString() {
+        return value;
+    }
+    
+    public static StringValue fromString(String value) {
+        return new StringValue(value);
+    }
+
 }

Added: jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/CacheTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/CacheTest.java?rev=1633319&view=auto
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/CacheTest.java (added)
+++ jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/CacheTest.java Tue Oct 21 09:04:38 2014
@@ -0,0 +1,46 @@
+/*
+ * 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.oak.plugins.document.persistentCache;
+
+import java.io.ByteArrayInputStream;
+import java.util.Random;
+
+import org.apache.jackrabbit.oak.spi.blob.BlobStore;
+import org.apache.jackrabbit.oak.spi.blob.MemoryBlobStore;
+import org.junit.Test;
+
+public class CacheTest {
+
+    @Test
+    public void test() throws Exception {
+        PersistentCache cache = new PersistentCache("target/cacheTest,size=1,-compress");
+        MemoryBlobStore mem = new MemoryBlobStore();
+        mem.setBlockSizeMin(100);
+        BlobStore b = cache.wrapBlobStore(mem);
+        Random r = new Random();
+        for(int i=0; i<10000; i++) {
+            byte[] data = new byte[100];
+            r.nextBytes(data);
+            String id = b.writeBlob(new ByteArrayInputStream(data));
+            b.readBlob(id, 0, new byte[1], 0, 1);
+        }
+        cache.close();
+    }
+    
+}

Modified: jackrabbit/oak/branches/1.0/oak-jcr/pom.xml
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-jcr/pom.xml?rev=1633319&r1=1633318&r2=1633319&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-jcr/pom.xml (original)
+++ jackrabbit/oak/branches/1.0/oak-jcr/pom.xml Tue Oct 21 09:04:38 2014
@@ -296,7 +296,7 @@
     <dependency>
       <groupId>com.h2database</groupId>
       <artifactId>h2</artifactId>
-      <version>1.3.175</version>
+      <version>1.4.182</version>
       <scope>test</scope>
     </dependency>
     <dependency>