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 2013/02/13 09:53:48 UTC

svn commit: r1445508 - in /jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype: DocumentStore.java MemoryDocumentStore.java MongoMK.java Node.java UpdateOp.java Utils.java

Author: thomasm
Date: Wed Feb 13 08:53:48 2013
New Revision: 1445508

URL: http://svn.apache.org/r1445508
Log:
OAK-619 Lock-free MongoMK implementation

Added:
    jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/DocumentStore.java
    jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MemoryDocumentStore.java
    jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MongoMK.java
    jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/Node.java
    jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/UpdateOp.java
    jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/Utils.java

Added: jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/DocumentStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/DocumentStore.java?rev=1445508&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/DocumentStore.java (added)
+++ jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/DocumentStore.java Wed Feb 13 08:53:48 2013
@@ -0,0 +1,34 @@
+/*
+ * 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.mongomk.prototype;
+
+import java.util.Map;
+
+/**
+ * The interface for the backend storage for documents.
+ */
+public interface DocumentStore {
+    
+    enum Collection { NODES }
+
+    Map<String, Object> find(Collection collection, String key);
+
+    void remove(Collection collection, String key);
+    
+    Map<String, Object> createOrUpdate(Collection collection, UpdateOp update);
+
+}

Added: jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MemoryDocumentStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MemoryDocumentStore.java?rev=1445508&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MemoryDocumentStore.java (added)
+++ jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MemoryDocumentStore.java Wed Feb 13 08:53:48 2013
@@ -0,0 +1,183 @@
+/*
+ * 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.mongomk.prototype;
+
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.ConcurrentSkipListMap;
+
+import org.apache.jackrabbit.mongomk.prototype.UpdateOp.Operation;
+
+/**
+ * Emulates a MongoDB store (possibly consisting of multiple shards and
+ * replicas).
+ */
+public class MemoryDocumentStore implements DocumentStore {
+    
+    /**
+     * The 'nodes' collection. It contains all the node data, with one document
+     * per node, and the path as the primary key. Each document possibly
+     * contains multiple revisions.
+     * <p>
+     * Key: the path, value: the node data (possibly multiple revisions)
+     * <p>
+     * Old revisions are removed after some time, either by the process that
+     * removed or updated the node, lazily when reading, or in a background
+     * process.
+     */
+    private ConcurrentSkipListMap<String, Map<String, Object>> nodes = 
+            new ConcurrentSkipListMap<String, Map<String, Object>>();
+    
+    /**
+     * Get a document. The returned map is a clone (the caller
+     * can modify it without affecting the stored version).
+     * 
+     * @param collection the collection
+     * @param path the path
+     * @return the map, or null if not found
+     */
+    public Map<String, Object> find(Collection collection, String path) {
+        ConcurrentSkipListMap<String, Map<String, Object>> map = getMap(collection);
+        Map<String, Object> n = map.get(path);
+        if (n == null) {
+            return null;
+        }
+        Map<String, Object> copy = Utils.newMap();
+        synchronized (n) {
+            copy.putAll(n);
+            return copy;
+        }
+    }
+    
+    /**
+     * Remove a document.
+     * 
+     * @param collection the collection
+     * @param path the path
+     */
+    public void remove(Collection collection, String path) {
+        getMap(collection).remove(path);
+    }
+    
+    /**
+     * Get the in-memory map for this collection.
+     * 
+     * @param collection the collection
+     * @return the map
+     */
+    private ConcurrentSkipListMap<String, Map<String, Object>> getMap(Collection collection) {
+        switch (collection) {
+        case NODES:
+            return nodes;
+        default:
+            throw new IllegalArgumentException(collection.name());
+        }
+    }
+    
+    /**
+     * Create or update a document. For MongoDb, this is using "findAndModify" with
+     * the "upsert" flag (insert or update).
+     * 
+     * @param collection the collection
+     * @param update the update operation
+     * @return the old document, or null if there was no
+     */
+    public Map<String, Object> createOrUpdate(Collection collection, UpdateOp update) {
+        ConcurrentSkipListMap<String, Map<String, Object>> map = getMap(collection);
+        Map<String, Object> n;
+        Map<String, Object> oldNode;
+        
+        // get the node if it's there
+        oldNode = n = map.get(update.key);
+
+        if (n == null) {
+            // for a new node, add it (without synchronization)
+            n = Utils.newMap();
+            oldNode = map.putIfAbsent(update.key, n);
+            if (oldNode != null) {
+                // somebody else added it at the same time
+                n = oldNode;
+            }
+        }
+        if (oldNode != null) {
+            // clone the old node
+            // (document level operations are synchronized)
+            Map<String, Object> old = Utils.newMap();
+            synchronized (oldNode) {
+                old.putAll(oldNode);
+            }
+            oldNode = old;
+        }
+        // update the document 
+        // (document level operations are synchronized)
+        synchronized (n) {
+            for (Entry<String, Operation> e : update.changes.entrySet()) {
+                String k = e.getKey();
+                Object old = n.get(k);
+                Operation op = e.getValue();
+                switch (op.type) {
+                case SET: {
+                    n.put(k, op.value);
+                    break;
+                }
+                case INCREMENT: {
+                    Long x = (Long) op.value;
+                    if (old == null) {
+                        old = 0L;
+                    }
+                    n.put(k, ((Long) old) + x);
+                    break;
+                }
+                case ADD_MAP_ENTRY: {
+                    @SuppressWarnings("unchecked")
+                    Map<String, String> m = (Map<String, String>) old;
+                    if (m == null) {
+                        m = Utils.newMap();
+                        n.put(k, m);
+                    }
+                    m.put(op.subKey.toString(), op.value.toString());
+                    break;
+                }
+                case REMOVE_MAP_ENTRY: {
+                    @SuppressWarnings("unchecked")
+                    Map<String, String> m = (Map<String, String>) old;
+                    if (m != null) {
+                        m.remove(op.subKey.toString());
+                    }
+                    break;
+                }
+                }
+            }
+        }
+        return oldNode;
+    }
+    
+    public String toString() {
+        StringBuilder buff = new StringBuilder();
+        buff.append("Nodes:\n");
+        for(String p : nodes.keySet()) {
+            buff.append("Path: ").append(p).append('\n');
+            Map<String, Object> e = nodes.get(p);
+            for(String prop : e.keySet()) {
+                buff.append(prop).append('=').append(e.get(prop)).append('\n');
+            }
+            buff.append("\n");
+        }
+        return buff.toString();
+    }
+    
+}

Added: jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MongoMK.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MongoMK.java?rev=1445508&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MongoMK.java (added)
+++ jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MongoMK.java Wed Feb 13 08:53:48 2013
@@ -0,0 +1,263 @@
+/*
+ * 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.mongomk.prototype;
+
+import java.io.InputStream;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+
+import org.apache.jackrabbit.mk.api.MicroKernel;
+import org.apache.jackrabbit.mk.api.MicroKernelException;
+
+public class MongoMK implements MicroKernel {
+    
+    /**
+     * The MongoDB store (might be used by multiple MongoMKs).
+     */
+    private final DocumentStore store;
+    
+    /**
+     * The unique cluster id, similar to the unique machine id in MongoDB.
+     */
+    private final int clusterId;
+    
+    /**
+     * The node cache.
+     * 
+     * Key: path@rev
+     * Value: node
+     */
+    // TODO: should be path@id
+    private final Map<String, Node> nodeCache = new Cache<String, Node>(1024);
+    
+    /**
+     * For revisions that are older than this many seconds, the MongoMK will
+     * assume the revision is valid. For more recent changes, the MongoMK needs
+     * to verify it first (by reading the revision root). The default is
+     * Integer.MAX_VALUE, meaning no revisions are trusted. Once the garbage
+     * collector removes old revisions, this value is changed.
+     */
+    private static final int trustedRevisionAge = Integer.MAX_VALUE;
+    
+    /**
+     * The set of known valid revision.
+     * The key is the revision id, the value is 1 (because a cache can't be a set).
+     */
+    private final Map<String, Long> revCache = new Cache<String, Long>(1024);
+
+    /**
+     * Create a new MongoMK.
+     * 
+     * @param store the store (might be shared)
+     * @param clusterId the cluster id (must be unique)
+     */
+    public MongoMK(MemoryDocumentStore store, int clusterId) {
+        this.store = store;
+        this.clusterId = clusterId;
+    }
+    
+    String getNewRevision() {
+        return Utils.createRevision(clusterId);
+    }
+    
+    /**
+     * Get the node for the given path and revision. The returned object might
+     * not be modified directly.
+     * 
+     * @param path
+     * @param rev
+     * @return the node
+     */
+    Node getNode(String path, String rev) {
+        String key = path + "@" + rev;
+        Node node = nodeCache.get(key);
+        if (node == null) {
+            node = readNode(path, rev);
+            if (node != null) {
+                nodeCache.put(key, node);
+            }
+        }
+        return node;
+    }
+    
+    /**
+     * Try to add a node.
+     * 
+     * @param rev the revision
+     * @param n the node
+     * @throw IllegalStateException if the node already existed
+     */
+    public void addNode(String rev, String commitRoot, Node n) {
+        UpdateOp node = new UpdateOp(n.path);
+        int depth = Utils.pathDepth(n.path);
+        node.set("_path", n.path);
+        node.set("_pathDepth", depth);
+        int commitDepth = depth - Utils.pathDepth(commitRoot);
+        node.addMapEntry("_commitDepth", rev, commitDepth);
+        
+        // the affected (direct) children of this revision
+        node.addMapEntry("_affectedChildren", rev, "");
+        
+        node.increment("_changeCount", 1L);
+//        setCommitRoot(path);
+
+
+        for (String p : n.properties.keySet()) {
+            node.addMapEntry(p, rev, n.properties.get(p));
+        }
+        Map<String, Object> map = store.createOrUpdate(DocumentStore.Collection.NODES, node);        
+        if (map != null) {
+            // TODO rollback changes
+            throw new IllegalStateException("Node already exists: " + n.path);
+        }
+    }
+    
+    private Node readNode(String path, String rev) {
+        Map<String, Object> map = store.find(DocumentStore.Collection.NODES, path);        
+        if (map == null) {
+            return null;
+        }
+        Node n = new Node(path, rev);
+        for(String key : map.keySet()) {
+            Object v = map.get(key);
+            
+        }
+        return n;
+    }
+    
+    @Override
+    public String getHeadRevision() throws MicroKernelException {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public String getRevisionHistory(long since, int maxEntries, String path)
+            throws MicroKernelException {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public String waitForCommit(String oldHeadRevisionId, long timeout)
+            throws MicroKernelException, InterruptedException {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public String getJournal(String fromRevisionId, String toRevisionId,
+            String path) throws MicroKernelException {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public String diff(String fromRevisionId, String toRevisionId, String path,
+            int depth) throws MicroKernelException {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public boolean nodeExists(String path, String revisionId)
+            throws MicroKernelException {
+        // TODO Auto-generated method stub
+        return false;
+    }
+
+    @Override
+    public long getChildNodeCount(String path, String revisionId)
+            throws MicroKernelException {
+        // TODO Auto-generated method stub
+        return 0;
+    }
+
+    @Override
+    public String getNodes(String path, String revisionId, int depth,
+            long offset, int maxChildNodes, String filter)
+            throws MicroKernelException {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public String commit(String path, String jsonDiff, String revisionId,
+            String message) throws MicroKernelException {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public String branch(String trunkRevisionId) throws MicroKernelException {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public String merge(String branchRevisionId, String message)
+            throws MicroKernelException {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    @Nonnull
+    public String rebase(@Nonnull String branchRevisionId, String newBaseRevisionId)
+            throws MicroKernelException {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public long getLength(String blobId) throws MicroKernelException {
+        // TODO Auto-generated method stub
+        return 0;
+    }
+
+    @Override
+    public int read(String blobId, long pos, byte[] buff, int off, int length)
+            throws MicroKernelException {
+        // TODO Auto-generated method stub
+        return 0;
+    }
+
+    @Override
+    public String write(InputStream in) throws MicroKernelException {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    static class Cache<K, V> extends LinkedHashMap<K, V> {
+
+        private static final long serialVersionUID = 1L;
+        private int size;
+
+        Cache(int size) {
+            super(size, (float) 0.75, true);
+            this.size = size;
+        }
+
+        protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
+            return size() > size;
+        }
+
+    }
+
+}

Added: jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/Node.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/Node.java?rev=1445508&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/Node.java (added)
+++ jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/Node.java Wed Feb 13 08:53:48 2013
@@ -0,0 +1,48 @@
+/*
+ * 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.mongomk.prototype;
+
+import java.util.Map;
+
+/**
+ * Represents a node held in memory (in the cache for example).
+ */
+public class Node {
+
+    final String path;
+    final String rev;
+    final Map<String, String> properties = Utils.newMap();
+    
+    Node(String path, String rev) {
+        this.path = path;
+        this.rev = rev;
+    }
+    
+    void setProperty(String propertyName, String value) {
+        properties.put(propertyName, value);
+    }
+    
+    public String toString() {
+        StringBuilder buff = new StringBuilder();
+        buff.append("path: ").append(path).append('\n');
+        buff.append("rev: ").append(rev).append('\n');
+        buff.append(properties);
+        buff.append('\n');
+        return buff.toString();
+    }
+    
+}

Added: jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/UpdateOp.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/UpdateOp.java?rev=1445508&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/UpdateOp.java (added)
+++ jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/UpdateOp.java Wed Feb 13 08:53:48 2013
@@ -0,0 +1,139 @@
+/*
+ * 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.mongomk.prototype;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * A MongoDB "update" operation for one node.
+ */
+public class UpdateOp {
+    
+    final String key;
+    
+    final Map<String, Operation> changes = new TreeMap<String, Operation>();
+    
+    /**
+     * Create an update operation for the given document. The commit root is assumed
+     * to be the path, unless this is changed later on.
+     * 
+     * @param path the path
+     * @param rev the revision
+     */
+    UpdateOp(String key) {
+        this.key = key;
+    }
+    
+    /**
+     * Add a new map entry for this revision.
+     * 
+     * @param property the property
+     * @param value the value
+     */
+    void addMapEntry(String property, String subKey, Object value) {
+        Operation op = new Operation();
+        op.type = Operation.Type.ADD_MAP_ENTRY;
+        op.subKey = subKey;
+        op.value = value;
+        changes.put(property, op);
+    }
+    
+    /**
+     * Set the property.
+     * 
+     * @param property the property name
+     * @param value the value
+     */
+    void set(String property, Object value) {
+        Operation op = new Operation();
+        op.type = Operation.Type.SET;
+        op.value = value;
+        changes.put(property, op);
+    }
+
+    /**
+     * Increment the value.
+     * 
+     * @param key the key
+     * @param value the increment
+     */
+    void increment(String property, long value) {
+        Operation op = new Operation();
+        op.type = Operation.Type.INCREMENT;
+        op.value = value;
+        changes.put(property, op);
+    }
+    
+    /**
+     * A MongoDB operation for a given key within a document. 
+     */
+    static class Operation {
+        
+        /**
+         * The MongoDB operation type.
+         */
+        enum Type { 
+            
+            /**
+             * Set the value. 
+             * The sub-key is not used.
+             */
+            SET,
+            
+            /**
+             * Increment the Long value with the provided Long value.
+             * The sub-key is not used.
+             */
+            INCREMENT, 
+            
+            /**
+             * Add the sub-key / value pair.
+             * The value in the stored node is a map.
+             */ 
+             ADD_MAP_ENTRY, 
+             
+             /**
+              * Remove the sub-key / value pair.
+              * The value in the stored node is a map.
+              */ 
+             REMOVE_MAP_ENTRY 
+         }
+             
+        
+        /**
+         * The operation type.
+         */
+        Type type;
+        
+        /**
+         * The sub-key.
+         */
+        Object subKey;
+        
+        /**
+         * The value, if any.
+         */
+        Object value;
+        
+        public String toString() {
+            return type + ": " + subKey + " = " + value;
+        }
+        
+    }
+
+}

Added: jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/Utils.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/Utils.java?rev=1445508&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/Utils.java (added)
+++ jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/Utils.java Wed Feb 13 08:53:48 2013
@@ -0,0 +1,49 @@
+package org.apache.jackrabbit.mongomk.prototype;
+
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class Utils {
+    
+    static long startTime = System.currentTimeMillis();
+    static AtomicInteger counter = new AtomicInteger();
+    
+    static int pathDepth(String path) {
+        return path.equals("/") ? 0 : path.replaceAll("[^/]", "").length();
+    }
+    
+    static <K, V> Map<K, V> newMap() {
+        return new TreeMap<K, V>();
+    }
+    
+    /**
+     * Create a simple revision id. It consists of 3 hex characters for the
+     * seconds since startup, 3 characters for the cluster id, and 3 characters
+     * for the counter. The format is similar to MongoDB ObjectId.
+     * 
+     * @param clusterId the unique machineId + processId
+     * @return the unique revision id
+     */
+    static String createRevision(int clusterId) {
+        int seconds = (int) ((System.currentTimeMillis() - startTime) / 1000);
+        int count = counter.getAndIncrement();
+        StringBuilder buff = new StringBuilder("r");
+        buff.append(Integer.toHexString(0x1000 + seconds).substring(1));
+        buff.append(Integer.toHexString(0x1000 + clusterId).substring(1));
+        buff.append(Integer.toHexString(0x1000 + count).substring(1));
+        return buff.toString();
+    }
+    
+    /**
+     * Get the age of a revision in seconds.
+     * 
+     * @param rev the revision
+     * @return the age in seconds
+     */
+    int getRevisionAge(String rev) {
+        String s = rev.substring(0, 3);
+        return Integer.parseInt(s, 16);
+    }
+    
+}