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 am...@apache.org on 2017/10/18 09:41:47 UTC

svn commit: r1812483 - in /jackrabbit/oak/trunk/oak-blob-plugins/src: main/java/org/apache/jackrabbit/oak/plugins/blob/datastore/ test/java/org/apache/jackrabbit/oak/plugins/blob/datastore/

Author: amitj
Date: Wed Oct 18 09:41:47 2017
New Revision: 1812483

URL: http://svn.apache.org/viewvc?rev=1812483&view=rev
Log:
OAK-6827: Consistency check fails with active deletions

Active deleted tracker to keep track of deleted files and provides methods to filter and reconcile

Added:
    jackrabbit/oak/trunk/oak-blob-plugins/src/test/java/org/apache/jackrabbit/oak/plugins/blob/datastore/ActiveDeletionTrackerStoreTest.java   (with props)
Modified:
    jackrabbit/oak/trunk/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/datastore/BlobIdTracker.java
    jackrabbit/oak/trunk/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/datastore/BlobTracker.java

Modified: jackrabbit/oak/trunk/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/datastore/BlobIdTracker.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/datastore/BlobIdTracker.java?rev=1812483&r1=1812482&r2=1812483&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/datastore/BlobIdTracker.java (original)
+++ jackrabbit/oak/trunk/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/datastore/BlobIdTracker.java Wed Oct 18 09:41:47 2017
@@ -29,13 +29,14 @@ import java.util.concurrent.ScheduledExe
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.ReentrantLock;
 
+import javax.annotation.Nullable;
+
 import com.google.common.base.Function;
 import com.google.common.base.Predicate;
 import com.google.common.base.Stopwatch;
 import com.google.common.collect.Iterators;
 import com.google.common.collect.Lists;
 import com.google.common.io.Files;
-import org.apache.commons.io.FileUtils;
 import org.apache.jackrabbit.core.data.DataRecord;
 import org.apache.jackrabbit.oak.commons.FileIOUtils.FileLineDifferenceIterator;
 import org.apache.jackrabbit.oak.commons.concurrent.ExecutorCloser;
@@ -62,6 +63,7 @@ import static org.apache.commons.io.File
 import static org.apache.commons.io.FileUtils.forceDelete;
 import static org.apache.commons.io.FileUtils.forceMkdir;
 import static org.apache.commons.io.FileUtils.lineIterator;
+import static org.apache.commons.io.FileUtils.touch;
 import static org.apache.commons.io.FilenameUtils.concat;
 import static org.apache.commons.io.FilenameUtils.removeExtension;
 import static org.apache.commons.io.IOUtils.closeQuietly;
@@ -84,9 +86,9 @@ public class BlobIdTracker implements Cl
 
     /**
      * System property to skip tracker. If set will skip:
-     *  * Snapshots (No-op)
-     *  * Retrieve (return empty)
-     *  * Add (No-op)
+     * * Snapshots (No-op)
+     * * Retrieve (return empty)
+     * * Add (No-op)
      */
     private final boolean SKIP_TRACKER = Boolean.getBoolean("oak.datastore.skipTracker");
 
@@ -100,6 +102,7 @@ public class BlobIdTracker implements Cl
 
     private final SharedDataStore datastore;
     private final long snapshotInterval;
+    private final ActiveDeletionTracker deleteTracker;
 
     protected BlobIdStore store;
 
@@ -109,15 +112,14 @@ public class BlobIdTracker implements Cl
 
     private File rootDir;
 
-    public BlobIdTracker(String path, String repositoryId,
-        long snapshotIntervalSecs, SharedDataStore datastore) throws IOException {
-        this(path, repositoryId, newSingleThreadScheduledExecutor(),
-            snapshotIntervalSecs, snapshotIntervalSecs, datastore);
+    public BlobIdTracker(String path, String repositoryId, long snapshotIntervalSecs, SharedDataStore datastore)
+        throws IOException {
+        this(path, repositoryId, newSingleThreadScheduledExecutor(), snapshotIntervalSecs, snapshotIntervalSecs,
+            datastore);
     }
 
-    public BlobIdTracker(String path, String repositoryId, ScheduledExecutorService scheduler,
-            long snapshotDelaySecs, long snapshotIntervalSecs, SharedDataStore datastore)
-        throws IOException {
+    public BlobIdTracker(String path, String repositoryId, ScheduledExecutorService scheduler, long snapshotDelaySecs,
+        long snapshotIntervalSecs, SharedDataStore datastore) throws IOException {
         String root = concat(path, datastoreMeta);
         this.rootDir = new File(root);
         this.datastore = datastore;
@@ -127,9 +129,9 @@ public class BlobIdTracker implements Cl
             forceMkdir(rootDir);
             prefix = fileNamePrefix + "-" + repositoryId;
             this.store = new BlobIdStore(rootDir, prefix);
-            scheduler.scheduleAtFixedRate(new SnapshotJob(),
-                SECONDS.toMillis(snapshotDelaySecs),
+            scheduler.scheduleAtFixedRate(new SnapshotJob(), SECONDS.toMillis(snapshotDelaySecs),
                 SECONDS.toMillis(snapshotIntervalSecs), MILLISECONDS);
+            this.deleteTracker = new ActiveDeletionTracker(rootDir, prefix);
         } catch (IOException e) {
             LOG.error("Error initializing blob tracker", e);
             close();
@@ -137,34 +139,42 @@ public class BlobIdTracker implements Cl
         }
     }
 
-    @Override
-    public void remove(File recs) throws IOException {
+    public ActiveDeletionTracker getDeleteTracker() {
+        return deleteTracker;
+    }
+
+    @Override public void remove(File recs, Options options) throws IOException {
+        if (options == Options.ACTIVE_DELETION) {
+            get();
+            deleteTracker.track(recs);
+        }
+        store.removeRecords(recs);
+        snapshot(true);
+    }
+
+    @Override public void remove(File recs) throws IOException {
         store.removeRecords(recs);
         snapshot(true);
     }
 
-    @Override
-    public void remove(Iterator<String> recs) throws IOException {
+    @Override public void remove(Iterator<String> recs) throws IOException {
         store.removeRecords(recs);
         snapshot(true);
     }
 
-    @Override
-    public void add(String id) throws IOException {
+    @Override public void add(String id) throws IOException {
         if (!SKIP_TRACKER) {
             store.addRecord(id);
         }
     }
 
-    @Override
-    public void add(Iterator<String> recs) throws IOException {
+    @Override public void add(Iterator<String> recs) throws IOException {
         if (!SKIP_TRACKER) {
             store.addRecords(recs);
         }
     }
 
-    @Override
-    public void add(File recs) throws IOException {
+    @Override public void add(File recs) throws IOException {
         if (!SKIP_TRACKER) {
             store.addRecords(recs);
         }
@@ -175,14 +185,13 @@ public class BlobIdTracker implements Cl
      * them to the local store and then returns an iterator over it.
      * This way the ids returned are as recent as the snapshots taken on all
      * instances/repositories connected to the DataStore.
-     *
+     * <p>
      * The iterator returned ia a Closeable instance and should be closed by calling #close().
      *
      * @return iterator over all the blob ids available
      * @throws IOException
      */
-    @Override
-    public Iterator<String> get() throws IOException {
+    @Override public Iterator<String> get() throws IOException {
         try {
             if (!SKIP_TRACKER) {
                 globalMerge();
@@ -195,8 +204,7 @@ public class BlobIdTracker implements Cl
         }
     }
 
-    @Override
-    public File get(String path) throws IOException {
+    @Override public File get(String path) throws IOException {
         if (!SKIP_TRACKER) {
             globalMerge();
             return store.getRecords(path);
@@ -218,31 +226,26 @@ public class BlobIdTracker implements Cl
             Iterable<DataRecord> refRecords = datastore.getAllMetadataRecords(fileNamePrefix);
 
             // Download all the corresponding files for the records
-            List<File> refFiles = newArrayList(
-                transform(refRecords,
-                    new Function<DataRecord, File>() {
-                        @Override
-                        public File apply(DataRecord input) {
-                            InputStream inputStream = null;
-                            try {
-                                inputStream = input.getStream();
-                                return copy(inputStream);
-                            } catch (Exception e) {
-                                LOG.warn("Error copying data store file locally {}",
-                                    input.getIdentifier(), e);
-                            } finally {
-                                closeQuietly(inputStream);
-                            }
-                            return null;
-                        }
-                    }));
+            List<File> refFiles = newArrayList(transform(refRecords, new Function<DataRecord, File>() {
+                @Override public File apply(DataRecord input) {
+                    InputStream inputStream = null;
+                    try {
+                        inputStream = input.getStream();
+                        return copy(inputStream);
+                    } catch (Exception e) {
+                        LOG.warn("Error copying data store file locally {}", input.getIdentifier(), e);
+                    } finally {
+                        closeQuietly(inputStream);
+                    }
+                    return null;
+                }
+            }));
             LOG.info("Retrieved all blob id files in [{}]", watch.elapsed(TimeUnit.MILLISECONDS));
 
             // Merge all the downloaded files in to the local store
             watch = Stopwatch.createStarted();
             store.merge(refFiles, true);
-            LOG.info("Merged all retrieved blob id files in [{}]",
-                watch.elapsed(TimeUnit.MILLISECONDS));
+            LOG.info("Merged all retrieved blob id files in [{}]", watch.elapsed(TimeUnit.MILLISECONDS));
 
             // Remove all the data store records as they have been merged
             watch = Stopwatch.createStarted();
@@ -250,8 +253,7 @@ public class BlobIdTracker implements Cl
                 datastore.deleteMetadataRecord(rec.getIdentifier().toString());
                 LOG.debug("Deleted metadata record {}", rec.getIdentifier().toString());
             }
-            LOG.info("Deleted all blob id metadata files in [{}]",
-                watch.elapsed(TimeUnit.MILLISECONDS));
+            LOG.info("Deleted all blob id metadata files in [{}]", watch.elapsed(TimeUnit.MILLISECONDS));
         } catch (IOException e) {
             LOG.error("Error in merging blob records iterator from the data store", e);
             throw e;
@@ -280,17 +282,15 @@ public class BlobIdTracker implements Cl
 
                 watch = Stopwatch.createStarted();
                 File recs = store.getBlobRecordsFile();
-                datastore.addMetadataRecord(recs,
-                    (prefix + instanceId + System.currentTimeMillis() + mergedFileSuffix));
-                LOG.info("Added blob id metadata record in DataStore in [{}]",
-                    watch.elapsed(TimeUnit.MILLISECONDS));
+                datastore.addMetadataRecord(recs, (prefix + instanceId + System.currentTimeMillis() + mergedFileSuffix));
+                LOG.info("Added blob id metadata record in DataStore in [{}]", watch.elapsed(TimeUnit.MILLISECONDS));
 
                 try {
                     forceDelete(recs);
                     LOG.info("Deleted blob record file after snapshot and upload {}", recs);
 
                     // Update the timestamp for the snapshot marker
-                    FileUtils.touch(getSnapshotMarkerFile());
+                    touch(getSnapshotMarkerFile());
                     LOG.info("Updated snapshot marker");
                 } catch (IOException e) {
                     LOG.debug("Failed to in cleaning up {}", recs, e);
@@ -312,13 +312,122 @@ public class BlobIdTracker implements Cl
      *
      * @throws IOException
      */
-    @Override
-    public void close() throws IOException {
+    @Override public void close() throws IOException {
         store.close();
         new ExecutorCloser(scheduler).close();
     }
 
     /**
+     * Tracking any active deletions  store for managing the blob reference
+     */
+    public static class ActiveDeletionTracker {
+        /* Suffix for tracking file */
+        private static final String DEL_SUFFIX = ".del";
+
+        /* deletion tracking file */
+        private File delFile;
+
+        public static final String DELIM = ",";
+
+        /* Lock for operations on the active deletions file */
+        private final ReentrantLock lock;
+
+        private static final Function<String, String> transformer = new Function<String, String>() {
+            @Nullable
+            @Override
+            public String apply(@Nullable String input) {
+                if (input != null) {
+                    return input.split(DELIM)[0];
+                }
+                return "";
+            }};
+
+        ActiveDeletionTracker(File rootDir, String prefix) throws IOException {
+            delFile = new File(rootDir, prefix + DEL_SUFFIX);
+            touch(delFile);
+            lock = new ReentrantLock();
+        }
+
+        /**
+         * Adds the ids in the file provided to the tracked deletions.
+         * @param recs the deleted ids to track
+         */
+        public void track(File recs) throws IOException {
+            lock.lock();
+            try {
+                append(Lists.newArrayList(recs), delFile, false);
+                sort(delFile);
+            } finally {
+                lock.unlock();
+            }
+        }
+
+        public File retrieve(String path) throws IOException {
+            File copiedRecsFile = new File(path);
+            try {
+                copyFile(delFile, copiedRecsFile);
+                return copiedRecsFile;
+            } catch (IOException e) {
+                LOG.error("Error in retrieving active deletions file", e);
+                throw e;
+            }
+        }
+
+        /**
+         * Remove ids given by the file in parameter from the deletions being tracked.
+         *
+         * @param recs the sorted file containing ids to be removed from tracker
+         */
+        public void reconcile(File recs) throws IOException {
+            lock.lock();
+            try {
+                // Remove and spool the remaining ids into a temp file
+                File toBeRemoved = createTempFile("toBeRemoved", null);
+                File removed = createTempFile("removed", null);
+
+                FileLineDifferenceIterator toBeRemovedIterator = null;
+                FileLineDifferenceIterator removeIterator = null;
+                try {
+                    // Gather all records which are not required to be tracked anymore
+                    toBeRemovedIterator = new FileLineDifferenceIterator(recs, delFile, null);
+                    writeStrings(toBeRemovedIterator, toBeRemoved, false);
+
+                    // Remove records not to be tracked
+                    removeIterator = new FileLineDifferenceIterator(toBeRemoved, delFile, null);
+                    writeStrings(removeIterator, removed, false);
+                } finally {
+                    if (toBeRemovedIterator != null) {
+                        toBeRemovedIterator.close();
+                    }
+
+                    if (removeIterator != null) {
+                        removeIterator.close();
+                    }
+
+                    if (toBeRemoved != null) {
+                        toBeRemoved.delete();
+                    }
+                }
+
+                move(removed, delFile);
+                LOG.trace("removed active delete records");
+            } finally {
+                lock.unlock();
+            }
+        }
+
+        /**
+         * Return any ids not existing in the deletions being tracked from the ids in file parameter.
+         *
+         * @param recs the file to search for ids existing in the deletions here
+         * @return
+         */
+        public Iterator<String> filter(File recs) throws IOException {
+            return new FileLineDifferenceIterator(delFile, recs, transformer);
+        }
+    }
+
+    /**
      * Local store for managing the blob reference
      */
     static class BlobIdStore implements Closeable {

Modified: jackrabbit/oak/trunk/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/datastore/BlobTracker.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/datastore/BlobTracker.java?rev=1812483&r1=1812482&r2=1812483&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/datastore/BlobTracker.java (original)
+++ jackrabbit/oak/trunk/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/datastore/BlobTracker.java Wed Oct 18 09:41:47 2017
@@ -66,6 +66,13 @@ public interface BlobTracker extends Clo
     void remove(File recs) throws IOException;
 
     /**
+     * Remove the ids in the given file and deletes the file.
+     *
+     * @param recs
+     * @throws IOException
+     */
+    void remove(File recs, Options options) throws IOException;
+    /**
      * Fetches an iterator of records available.
      *
      * @return
@@ -81,4 +88,6 @@ public interface BlobTracker extends Clo
      * @throws IOException
      */
     File get(String path) throws IOException;
+
+    enum Options {DEFAULT, ACTIVE_DELETION}
 }

Added: jackrabbit/oak/trunk/oak-blob-plugins/src/test/java/org/apache/jackrabbit/oak/plugins/blob/datastore/ActiveDeletionTrackerStoreTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-blob-plugins/src/test/java/org/apache/jackrabbit/oak/plugins/blob/datastore/ActiveDeletionTrackerStoreTest.java?rev=1812483&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-blob-plugins/src/test/java/org/apache/jackrabbit/oak/plugins/blob/datastore/ActiveDeletionTrackerStoreTest.java (added)
+++ jackrabbit/oak/trunk/oak-blob-plugins/src/test/java/org/apache/jackrabbit/oak/plugins/blob/datastore/ActiveDeletionTrackerStoreTest.java Wed Oct 18 09:41:47 2017
@@ -0,0 +1,208 @@
+/*
+ * 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.blob.datastore;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import org.apache.jackrabbit.oak.commons.FileIOUtils;
+import org.apache.jackrabbit.oak.plugins.blob.SharedDataStore;
+import org.apache.jackrabbit.oak.plugins.blob.datastore.BlobIdTracker.ActiveDeletionTracker;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static java.lang.String.valueOf;
+import static java.util.UUID.randomUUID;
+import static org.apache.jackrabbit.oak.commons.FileIOUtils.readStringsAsSet;
+import static org.apache.jackrabbit.oak.plugins.blob.datastore.DataStoreUtils.getBlobStore;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeNoException;
+import static org.junit.Assume.assumeThat;
+
+/**
+ * Test for BlobIdTracker.ActiveDeletionTracker to test tracking removed blob ids.
+ */
+public class ActiveDeletionTrackerStoreTest {
+    private static final Logger log = LoggerFactory.getLogger(ActiveDeletionTrackerStoreTest.class);
+
+    File root;
+    SharedDataStore dataStore;
+    ActiveDeletionTracker tracker;
+
+    @Rule
+    public TemporaryFolder folder = new TemporaryFolder(new File("target"));
+    private String repoId;
+
+    @BeforeClass
+    public static void setUpBeforeClass() throws Exception {
+        try {
+            assumeThat(getBlobStore(), instanceOf(SharedDataStore.class));
+        } catch (Exception e) {
+            assumeNoException(e);
+        }
+    }
+
+    @Before
+    public void setup() throws Exception {
+        this.root = folder.newFolder();
+        if (dataStore == null) {
+            dataStore = getBlobStore(root);
+        }
+        this.repoId = randomUUID().toString();
+        this.tracker = initTracker();
+    }
+
+    private ActiveDeletionTracker initTracker() throws IOException {
+        return new ActiveDeletionTracker(root, repoId);
+    }
+
+    @After
+    public void tearDown() throws IOException {
+        folder.delete();
+    }
+
+    @Test
+    public void track() throws Exception {
+        Set<String> initAdd = add(tracker, range(0, 20), folder);
+        Set<String> retrieved = retrieve(tracker, folder);
+
+        assertEquals("Incorrect elements after add snapshot", initAdd, retrieved);
+    }
+
+    @Test
+    public void filter() throws Exception {
+        add(tracker, range(0, 20), folder);
+        File toFilter = create(range(7, 10), folder);
+        Iterator<String> filtered = tracker.filter(toFilter);
+
+        assertTrue("More elements after filtering", Lists.newArrayList(filtered).isEmpty());
+    }
+
+    @Test
+    public void noFilter() throws Exception {
+        add(tracker, range(5, 20), folder);
+        List<String> toFilter = combine(range(7, 10), range(0, 4));
+        File toFilterFile = create(toFilter, folder);
+        Iterator<String> filtered = tracker.filter(toFilterFile);
+
+        assertEquals("Incorrect elements after filtering", range(0, 4), Lists.newArrayList(filtered));
+    }
+
+    @Test
+    public void filterWithExtraElements() throws Exception {
+        add(tracker, range(5, 20), folder);
+        List<String> toFilter = combine(combine(range(7, 10), range(0, 4)), range(21, 25));
+        File toFilterFile = create(toFilter, folder);
+        Iterator<String> filtered = tracker.filter(toFilterFile);
+
+        assertEquals("Incorrect elements after filtering",
+            combine(range(0, 4), range(21, 25)), Lists.newArrayList(filtered));
+    }
+
+    @Test
+    public void reconcileNone() throws Exception {
+        Set<String> initAdd = add(tracker, range(0, 20), folder);
+        List toReconcile = Lists.newArrayList();
+
+        File toFilter = create(toReconcile, folder);
+
+        tracker.reconcile(toFilter);
+        Set<String> retrieved = retrieve(tracker, folder);
+
+        assertEquals("Incorrect elements with after reconciliation", Sets.newHashSet(toReconcile), retrieved);
+    }
+
+    @Test
+    public void reconcile() throws Exception {
+        Set<String> initAdd = add(tracker, range(0, 20), folder);
+        List<String> toReconcile = combine(range(7, 10), range(1, 4));
+
+        File toFilter = create(toReconcile, folder);
+
+        tracker.reconcile(toFilter);
+        Set<String> retrieved = retrieve(tracker, folder);
+
+        assertEquals("Incorrect elements with after reconciliation", Sets.newHashSet(toReconcile), retrieved);
+    }
+
+    @Test
+    public void addCloseRestart() throws IOException {
+        Set<String> initAdd = add(tracker, range(0, 10), folder);
+        this.tracker = initTracker();
+        Set<String> retrieved = retrieve(tracker, folder);
+        assertEquals("Incorrect elements after safe restart", initAdd, retrieved);
+    }
+
+    private static Set<String> add(ActiveDeletionTracker store, List<String> ints, TemporaryFolder folder) throws IOException {
+        File f = folder.newFile();
+        FileIOUtils.writeStrings(ints.iterator(), f, false);
+        store.track(f);
+        return Sets.newHashSet(ints);
+    }
+
+    private static File create(List<String> ints, TemporaryFolder folder) throws IOException {
+        File f = folder.newFile();
+        FileIOUtils.writeStrings(ints.iterator(), f, false);
+        return f;
+    }
+
+    private static Set<String> retrieve(ActiveDeletionTracker store, TemporaryFolder folder) throws IOException {
+        File f = folder.newFile();
+        Set<String> retrieved = readStringsAsSet(
+            new FileInputStream(store.retrieve(f.getAbsolutePath())), false);
+        return retrieved;
+    }
+
+    private static List<String> range(int min, int max) {
+        List<String> list = newArrayList();
+        for (int i = min; i <= max; i++) {
+            list.add(Strings.padStart(valueOf(i), 2, '0'));
+        }
+        return list;
+    }
+
+    private static List<String> combine(List<String> first, List<String> second) {
+        first.addAll(second);
+        Collections.sort(first, new Comparator<String>() {
+            @Override public int compare(String s1, String s2) {
+                return Integer.valueOf(s1).compareTo(Integer.valueOf(s2));
+            }
+        });
+        return first;
+    }
+}
+

Propchange: jackrabbit/oak/trunk/oak-blob-plugins/src/test/java/org/apache/jackrabbit/oak/plugins/blob/datastore/ActiveDeletionTrackerStoreTest.java
------------------------------------------------------------------------------
    svn:eol-style = native