Author: mreutegg
Date: Tue Jul 21 10:25:04 2015
New Revision: 1692079

URL: http://svn.apache.org/r1692079
Log:
OAK-3112: Performance degradation of UnsavedModifications on MapDB

Added:
    
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/HybridMapFactory.java
   (with props)
Modified:
    jackrabbit/oak/branches/1.0/RELEASE-NOTES.txt
    
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/MapDBMapFactory.java
    
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/MapFactory.java
    
jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/UnsavedModificationsTest.java
    
jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/util/MapDBMapFactoryTest.java

Modified: jackrabbit/oak/branches/1.0/RELEASE-NOTES.txt
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/RELEASE-NOTES.txt?rev=1692079&r1=1692078&r2=1692079&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/RELEASE-NOTES.txt (original)
+++ jackrabbit/oak/branches/1.0/RELEASE-NOTES.txt Tue Jul 21 10:25:04 2015
@@ -21,6 +21,10 @@ New configuration options in Oak 1.0.18
 Support for pre extracting text to speed up reindexing has been added in 
OAK-2892
 For more details refer to 
http://jackrabbit.apache.org/oak/docs/query/lucene.html#text-extraction
 
+For DocumentNodeStore based deployments a new MapFactory implementation has
+been introduced and can be enabled with -Doak.useHybridMapFactory=true
+See OAK-3112 for details.
+
 Changes in Oak 1.0.18
 ---------------------
 

Added: 
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/HybridMapFactory.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/HybridMapFactory.java?rev=1692079&view=auto
==============================================================================
--- 
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/HybridMapFactory.java
 (added)
+++ 
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/HybridMapFactory.java
 Tue Jul 21 10:25:04 2015
@@ -0,0 +1,345 @@
+/*
+ * 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.util;
+
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.ForwardingConcurrentMap;
+import com.google.common.collect.Maps;
+
+import org.apache.jackrabbit.oak.plugins.document.Revision;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A hybrid MapFactory implementation switching from an in-memory map to a
+ * MapDB backed map when the size of the map reaches a threshold. Once the
+ * map shrinks again, the implementation switches back to an in-memory map.
+ *
+ * This factory implementation keeps track of maps created by {@link #create()}
+ * and {@link #create(Comparator)} with weak references and disposes the
+ * underlying {@link MapDBMapFactory} when a map created by that factory is not
+ * referenceable and not in use anymore.
+ */
+public class HybridMapFactory extends MapFactory {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(HybridMapFactory.class);
+
+    /**
+     * Keep at most this number of entries in memory, otherwise use a map
+     * implementation backed by MapDB.
+     */
+    static final int IN_MEMORY_SIZE_LIMIT = 100000;
+
+    /**
+     * Switch to an in-memory map when there are less than 10'000 entries
+     * in the map.
+     */
+    static final int IN_MEMORY_SIZE_LIMIT_LOW = 10000;
+
+    /**
+     * Reference queue for maps backed by MapDB.
+     */
+    private final ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
+
+    /**
+     * Maps {@link MapReference}s to their corresponding MapFactory.
+     */
+    private final Map<Reference, MapFactory> factories = 
Maps.newIdentityHashMap();
+
+    @Override
+    public ConcurrentMap<String, Revision> create() {
+        return create(null);
+    }
+
+    @Override
+    public ConcurrentMap<String, Revision> create(Comparator<String> 
comparator) {
+        return new MapImpl(comparator);
+    }
+
+    @Override
+    public void dispose() {
+        for (MapFactory f : factories.values()) {
+            dispose(f);
+        }
+        factories.clear();
+    }
+
+    //------------------------------< internal 
>--------------------------------
+
+    private synchronized void pollReferenceQueue() {
+        Reference ref;
+        while ((ref = queue.poll()) != null) {
+            dispose(factories.remove(ref));
+        }
+    }
+
+    private void dispose(MapFactory factory) {
+        try {
+            if (factory != null) {
+                Stopwatch sw = Stopwatch.createStarted();
+                factory.dispose();
+                sw.stop();
+                LOG.debug("Disposed MapDB map in {}", sw);
+            }
+        } catch (Exception e) {
+            LOG.warn("Failed to dispose MapFactory", e);
+        }
+    }
+
+    /**
+     * A map implementation, which forwards calls to either an in-memory or
+     * MapDB backed map implementation. Methods, which modify the underlying 
map
+     * are synchronized because of OAK-2888.
+     */
+    private final class MapImpl extends
+            ForwardingConcurrentMap<String, Revision> {
+
+        /**
+         * A comparator, if keys of the map are sorted, otherwise {@code null}.
+         */
+        private final Comparator<String> comparator;
+
+        /**
+         * The current map. This is either in-memory or a map backed by MapDB.
+         */
+        private volatile ConcurrentMap<String, Revision> map;
+
+        /**
+         * Maintain size of {@link #map} because the in-memory MapFactory
+         * variant uses a ConcurrentSkipListMap with a non-constant size() 
cost.
+         */
+        private long size;
+
+        /**
+         * Whether the current {@link #map} is in memory.
+         */
+        private boolean mapInMemory;
+
+        MapImpl(@Nullable Comparator<String> comparator) {
+            this.comparator = comparator;
+            this.map = createMap(true);
+            this.mapInMemory = true;
+        }
+
+        @Override
+        protected ConcurrentMap<String, Revision> delegate() {
+            return map;
+        }
+
+        @Override
+        public synchronized Revision putIfAbsent(@Nonnull String key,
+                                                 @Nonnull Revision value) {
+            Revision r = map.putIfAbsent(key, checkNotNull(value));
+            if (r == null) {
+                size++;
+                maybeSwitchToMapDB();
+            }
+            return r;
+        }
+
+        @Override
+        public synchronized Revision put(@Nonnull String key, @Nonnull 
Revision value) {
+            Revision r = map.put(key, checkNotNull(value));
+            if (r == null) {
+                size++;
+                maybeSwitchToMapDB();
+            }
+            return r;
+        }
+
+        @Override
+        public synchronized void putAll(@Nonnull Map<? extends String, ? 
extends Revision> map) {
+            for (Map.Entry<? extends String, ? extends Revision> entry : 
map.entrySet()) {
+                if (this.map.put(entry.getKey(), 
checkNotNull(entry.getValue())) == null) {
+                    size++;
+                    maybeSwitchToMapDB();
+                }
+            }
+        }
+
+        @Override
+        public synchronized boolean remove(@Nonnull Object key,
+                                           @Nonnull Object value) {
+            boolean remove = map.remove(key, value);
+            if (remove) {
+                size--;
+                maybeSwitchToInMemoryMap();
+            }
+            return remove;
+        }
+
+        @Override
+        public synchronized Revision remove(@Nonnull Object object) {
+            Revision r = map.remove(object);
+            if (r != null) {
+                size--;
+                maybeSwitchToInMemoryMap();
+            }
+            return r;
+        }
+
+        @Override
+        public synchronized Revision replace(@Nonnull String key,
+                                             @Nonnull Revision value) {
+            return map.replace(key, checkNotNull(value));
+        }
+
+        @Override
+        public synchronized boolean replace(@Nonnull String key,
+                                            @Nonnull Revision oldValue,
+                                            @Nonnull Revision newValue) {
+            return map.replace(key, checkNotNull(oldValue), 
checkNotNull(newValue));
+        }
+
+        @Override
+        public synchronized void clear() {
+            map.clear();
+            size = 0;
+        }
+
+        @Nonnull
+        @Override
+        public Collection<Revision> values() {
+            return Collections.unmodifiableCollection(map.values());
+        }
+
+        @Nonnull
+        @Override
+        public Set<Entry<String, Revision>> entrySet() {
+            return Collections.unmodifiableSet(map.entrySet());
+        }
+
+        @Nonnull
+        @Override
+        public Set<String> keySet() {
+            return Collections.unmodifiableSet(map.keySet());
+        }
+
+        private void maybeSwitchToMapDB() {
+            // switch map if
+            // - map is currently in-memory
+            // - size limit is reached
+            if (mapInMemory
+                    && size >= IN_MEMORY_SIZE_LIMIT) {
+                Stopwatch sw = Stopwatch.createStarted();
+                ConcurrentMap<String, Revision> tmp = createMap(false);
+                tmp.putAll(map);
+                map = tmp;
+                mapInMemory = false;
+                sw.stop();
+                LOG.debug("Switched to MapDB map in {}", sw);
+                pollReferenceQueue();
+            }
+        }
+
+        private void maybeSwitchToInMemoryMap() {
+            // only switch to in memory if not already in memory
+            // and size is less than lower limit
+            if (!mapInMemory && size < IN_MEMORY_SIZE_LIMIT_LOW) {
+                Stopwatch sw = Stopwatch.createStarted();
+                ConcurrentMap<String, Revision> tmp = createMap(true);
+                tmp.putAll(map);
+                map = tmp;
+                mapInMemory = true;
+                sw.stop();
+                LOG.debug("Switched to in-memory map in {}", sw);
+                pollReferenceQueue();
+            }
+        }
+
+        private ConcurrentMap<String, Revision> createMap(boolean inMemory) {
+            MapFactory f;
+            if (inMemory) {
+                f = MapFactory.DEFAULT;
+            } else {
+                f = new MapDBMapFactory();
+            }
+            ConcurrentMap<String, Revision> map;
+            if (comparator != null) {
+                map = f.create(comparator);
+            } else {
+                map = f.create();
+            }
+            if (!inMemory) {
+                map = new MapDBWrapper(map);
+                factories.put(new MapReference(map, queue), f);
+            }
+            return map;
+        }
+    }
+
+    /**
+     * A map wrapper implementation forwarding calls to a MapDB backed map
+     * implementation. Instances of this class are tracked with weak references
+     * and the corresponding {@link MapDBMapFactory} is disposed when there
+     * are no more references to a MapDBWrapper. This also includes collections
+     * obtained from this map, e.g. {@link #entrySet()}, {@link #values()} or
+     * {@link #keySet()}.
+     *
+     */
+    private static final class MapDBWrapper extends 
ForwardingConcurrentMap<String, Revision> {
+
+        private final ConcurrentMap<String, Revision> map;
+
+        protected MapDBWrapper(@Nonnull ConcurrentMap<String, Revision> map) {
+            this.map = checkNotNull(map);
+        }
+
+        @Override
+        protected ConcurrentMap<String, Revision> delegate() {
+            return map;
+        }
+
+        @Override
+        public Set<Entry<String, Revision>> entrySet() {
+            return Collections.unmodifiableSet(map.entrySet());
+        }
+
+        @Override
+        public Collection<Revision> values() {
+            return Collections.unmodifiableCollection(map.values());
+        }
+
+        @Override
+        public Set<String> keySet() {
+            return Collections.unmodifiableSet(map.keySet());
+        }
+    }
+
+    private static final class MapReference extends WeakReference<Object> {
+
+        public MapReference(@Nonnull Object referent,
+                            @Nonnull ReferenceQueue<Object> queue) {
+            super(checkNotNull(referent), checkNotNull(queue));
+        }
+    }
+}

Propchange: 
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/HybridMapFactory.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: 
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/MapDBMapFactory.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/MapDBMapFactory.java?rev=1692079&r1=1692078&r2=1692079&view=diff
==============================================================================
--- 
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/MapDBMapFactory.java
 (original)
+++ 
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/MapDBMapFactory.java
 Tue Jul 21 10:25:04 2015
@@ -36,19 +36,22 @@ import org.mapdb.Serializer;
 public class MapDBMapFactory extends MapFactory {
 
     private final AtomicInteger counter = new AtomicInteger();
-    private final DB db;
+    private DB db;
 
-    public MapDBMapFactory() {
-        this.db = DBMaker.newTempFileDB()
-                .deleteFilesAfterClose()
-                .transactionDisable()
-                .asyncWriteEnable()
-                .make();
+    private synchronized DB getDB() {
+        if (db == null) {
+            this.db = DBMaker.newTempFileDB()
+                    .deleteFilesAfterClose()
+                    .transactionDisable()
+                    .asyncWriteEnable()
+                    .make();
+        }
+        return db;
     }
 
     @Override
     public BTreeMap<String, Revision> create() {
-        return db.createTreeMap(String.valueOf(counter.incrementAndGet()))
+        return getDB().createTreeMap(String.valueOf(counter.incrementAndGet()))
                 .valueSerializer(new RevisionSerializer())
                 .counterEnable()
                 .makeStringMap();
@@ -57,7 +60,7 @@ public class MapDBMapFactory extends Map
     @Override
     public synchronized BTreeMap<String, Revision> create(
             Comparator<String> comparator) {
-        return db.createTreeMap(String.valueOf(counter.incrementAndGet()))
+        return getDB().createTreeMap(String.valueOf(counter.incrementAndGet()))
                 .valueSerializer(new RevisionSerializer())
                 .keySerializer(new CustomKeySerializer(comparator))
                 .counterEnable()
@@ -65,8 +68,11 @@ public class MapDBMapFactory extends Map
     }
 
     @Override
-    public void dispose() {
-        db.close();
+    public synchronized void dispose() {
+        if (db != null) {
+            db.close();
+            db = null;
+        }
     }
 
     private static class CustomKeySerializer extends BTreeKeySerializer<String>

Modified: 
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/MapFactory.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/MapFactory.java?rev=1692079&r1=1692078&r2=1692079&view=diff
==============================================================================
--- 
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/MapFactory.java
 (original)
+++ 
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/MapFactory.java
 Tue Jul 21 10:25:04 2015
@@ -32,10 +32,15 @@ import org.apache.jackrabbit.oak.plugins
  */
 public abstract class MapFactory {
 
-    private static final boolean USE_MEMORY_MAP_FACTORY
-            = Boolean.getBoolean("oak.useMemoryMapFactory");
+    private static boolean useMemoryMapFactory() {
+        return Boolean.getBoolean("oak.useMemoryMapFactory");
+    }
+
+    private static boolean useHybridMapFactory() {
+        return Boolean.getBoolean("oak.useHybridMapFactory");
+    }
 
-    private static MapFactory DEFAULT = new MapFactory() {
+    public static final MapFactory DEFAULT = new MapFactory() {
         @Override
         public ConcurrentMap<String, Revision> create() {
             return new ConcurrentHashMap<String, Revision>();
@@ -69,8 +74,10 @@ public abstract class MapFactory {
     }
 
     public static MapFactory createFactory() {
-        if (USE_MEMORY_MAP_FACTORY) {
+        if (useMemoryMapFactory()) {
             return MapFactory.getInstance();
+        } else if (useHybridMapFactory()) {
+            return new HybridMapFactory();
         } else {
             return new MapDBMapFactory();
         }

Modified: 
jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/UnsavedModificationsTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/UnsavedModificationsTest.java?rev=1692079&r1=1692078&r2=1692079&view=diff
==============================================================================
--- 
jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/UnsavedModificationsTest.java
 (original)
+++ 
jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/UnsavedModificationsTest.java
 Tue Jul 21 10:25:04 2015
@@ -18,6 +18,7 @@ package org.apache.jackrabbit.oak.plugin
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Random;
 import java.util.Set;
@@ -28,11 +29,14 @@ import com.google.common.collect.Sets;
 
 import org.apache.jackrabbit.oak.commons.PathUtils;
 import org.apache.jackrabbit.oak.plugins.document.memory.MemoryDocumentStore;
+import org.apache.jackrabbit.oak.plugins.document.util.HybridMapFactory;
 import org.apache.jackrabbit.oak.plugins.document.util.MapDBMapFactory;
 import org.apache.jackrabbit.oak.plugins.document.util.MapFactory;
 import org.junit.Ignore;
 import org.junit.Test;
 
+import static org.junit.Assert.assertEquals;
+
 public class UnsavedModificationsTest {
 
     private static final String CHARS = "abcdefghijklmnopqrstuvwxyz";
@@ -123,7 +127,7 @@ public class UnsavedModificationsTest {
     }
 
     // OAK-3112
-    @Ignore
+    @Ignore("Long running performance test")
     @Test
     public void performance() throws Exception {
         DocumentStore store = new MemoryDocumentStore() {
@@ -137,8 +141,7 @@ public class UnsavedModificationsTest {
         final DocumentNodeStore ns = new 
DocumentMK.Builder().setDocumentStore(store)
                 .getNodeStore();
 
-        // MapFactory factory = MapFactory.getInstance();
-        MapFactory factory = new MapDBMapFactory();
+        MapFactory factory = new HybridMapFactory();
         final UnsavedModifications pending = new UnsavedModifications(factory);
 
         Thread t = new Thread(new Runnable() {
@@ -165,7 +168,7 @@ public class UnsavedModificationsTest {
                                 }
                                 paths.clear();
                             }
-                            if (num % 1000 == 0) {
+                            if (random.nextFloat() < 0.00005) {
                                 pending.persist(ns, new ReentrantLock());
                             }
                         }
@@ -189,6 +192,105 @@ public class UnsavedModificationsTest {
         ns.dispose();
     }
 
+    @Test
+    public void getPathsAfterPersist() throws Exception {
+        DocumentStore store = new MemoryDocumentStore() {
+            @Override
+            public <T extends Document> void update(Collection<T> collection,
+                                                    List<String> keys,
+                                                    UpdateOp updateOp) {
+                // ignore call
+            }
+        };
+        final DocumentNodeStore ns = new 
DocumentMK.Builder().setDocumentStore(store)
+                .getNodeStore();
+
+        MapFactory factory = new HybridMapFactory();
+        UnsavedModifications pending = new UnsavedModifications(factory);
+
+        Revision r = new Revision(1, 0, 1);
+        for (int i = 0; i <= UnsavedModifications.IN_MEMORY_SIZE_LIMIT; i++) {
+            pending.put("/node-" + i, r);
+        }
+
+        // start iterating over paths
+        Iterator<String> paths = pending.getPaths().iterator();
+        for (int i = 0; i < 1000; i++) {
+            paths.next();
+        }
+
+        // drain pending, this will force it back to in-memory
+        pending.persist(ns, new ReentrantLock());
+
+        // loop over remaining paths
+        while (paths.hasNext()) {
+            paths.next();
+        }
+        pending.close();
+    }
+
+    @Test
+    public void getPathsWithRevisionAfterPersist() throws Exception {
+        DocumentStore store = new MemoryDocumentStore() {
+            @Override
+            public <T extends Document> void update(Collection<T> collection,
+                                                    List<String> keys,
+                                                    UpdateOp updateOp) {
+                // ignore call
+            }
+        };
+        final DocumentNodeStore ns = new 
DocumentMK.Builder().setDocumentStore(store)
+                .getNodeStore();
+
+        MapFactory factory = new HybridMapFactory();
+        UnsavedModifications pending = new UnsavedModifications(factory);
+
+        Revision r = new Revision(1, 0, 1);
+        for (int i = 0; i <= UnsavedModifications.IN_MEMORY_SIZE_LIMIT; i++) {
+            pending.put("/node-" + i, r);
+        }
+
+        // start iterating over paths
+        Iterator<String> paths = pending.getPaths(r).iterator();
+        for (int i = 0; i < 1000; i++) {
+            paths.next();
+        }
+
+        // drain pending, this will force it back to in-memory
+        pending.persist(ns, new ReentrantLock());
+
+        // loop over remaining paths
+        while (paths.hasNext()) {
+            paths.next();
+        }
+        pending.close();
+    }
+
+    @Test
+    public void defaultMapFactoryCreate() {
+        MapFactory factory = MapFactory.createFactory();
+        assertEquals(MapDBMapFactory.class, factory.getClass());
+        factory.dispose();
+    }
+
+    @Test
+    public void useHybridMapFactory() {
+        System.setProperty("oak.useHybridMapFactory", "true");
+        MapFactory factory = MapFactory.createFactory();
+        assertEquals(HybridMapFactory.class, factory.getClass());
+        factory.dispose();
+        System.clearProperty("oak.useHybridMapFactory");
+    }
+
+    @Test
+    public void useMemoryMapFactory() {
+        System.setProperty("oak.useMemoryMapFactory", "true");
+        MapFactory factory = MapFactory.createFactory();
+        assertEquals(MapFactory.DEFAULT.getClass(), factory.getClass());
+        factory.dispose();
+        System.clearProperty("oak.useMemoryMapFactory");
+    }
+
     private static String randomName(Random r, int len) {
         StringBuilder sb = new StringBuilder(len);
         for (int i = 0; i < len; i++) {

Modified: 
jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/util/MapDBMapFactoryTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/util/MapDBMapFactoryTest.java?rev=1692079&r1=1692078&r2=1692079&view=diff
==============================================================================
--- 
jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/util/MapDBMapFactoryTest.java
 (original)
+++ 
jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/util/MapDBMapFactoryTest.java
 Tue Jul 21 10:25:04 2015
@@ -16,12 +16,17 @@
  */
 package org.apache.jackrabbit.oak.plugins.document.util;
 
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 
 import org.apache.jackrabbit.oak.plugins.document.PathComparator;
 import org.apache.jackrabbit.oak.plugins.document.Revision;
+import org.junit.After;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
 import com.google.common.collect.Lists;
 
@@ -30,12 +35,32 @@ import static org.junit.Assert.assertEqu
 /**
  * <code>MapDBMapFactoryTest</code>...
  */
+@RunWith(Parameterized.class)
 public class MapDBMapFactoryTest {
 
+    private MapFactory factory;
+
+    @Parameterized.Parameters
+    public static Collection<Object[]> factories() {
+        Object[][] factories = new Object[][] {
+                {new MapDBMapFactory()},
+                {new HybridMapFactory()},
+                {MapFactory.DEFAULT}
+        };
+        return Arrays.asList(factories);
+    }
+
+    public MapDBMapFactoryTest(MapFactory factory) {
+        this.factory = factory;
+    }
+
+    @After
+    public void dispose() {
+        factory.dispose();
+    }
+
     @Test
     public void comparator() {
-        MapFactory factory = new MapDBMapFactory();
-
         Revision r = new Revision(1, 0, 1);
         Map<String, Revision> map = factory.create(PathComparator.INSTANCE);
 
@@ -58,7 +83,5 @@ public class MapDBMapFactoryTest {
         List<String> actual = Lists.newArrayList(map.keySet());
 
         assertEquals(expected, actual);
-
-        factory.dispose();
     }
 }


Reply via email to