This is an automated email from the ASF dual-hosted git repository.

miroslav pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 575019f9a1 Issue/oak 10636 (#1296)
575019f9a1 is described below

commit 575019f9a1f00136eb5d571e6f2e7263b46f86db
Author: Miroslav Smiljanic <[email protected]>
AuthorDate: Fri Feb 23 15:08:13 2024 +0100

    Issue/oak 10636 (#1296)
    
    * OAK-10636 test and fist iteration for the solution
    
    * OAK-10636 modify TarFiles builder
    
    * OAK-10636 ignore .DS_Store
    
    * OAK-10636 ignore move segment map outside of the loop
    
    * OAK-10636 avoid code duplication
    
    * OAK-10636 check if TarFiles is initialised
    
    * OAK-10636 remove logger
    
    ---------
    
    Co-authored-by: Miroslav Smiljanic <[email protected]>
---
 .gitignore                                         |   1 +
 .../apache/jackrabbit/oak/segment/SegmentBlob.java |  31 ++++++-
 .../oak/segment/file/AbstractFileStore.java        |   6 +-
 .../jackrabbit/oak/segment/file/FileStore.java     |   8 +-
 .../oak/segment/file/ReadOnlyFileStore.java        |   3 +-
 .../oak/segment/file/tar/EntryRecovery.java        |   9 ++
 .../jackrabbit/oak/segment/file/tar/TarFiles.java  |  56 ++++++++++--
 .../jackrabbit/oak/segment/file/tar/TarReader.java |  18 +++-
 .../jackrabbit/oak/segment/IdMappingBlobStore.java |  88 ++++++++++++++++++
 .../oak/segment/LongIdMappingBlobStore.java}       |  16 ++--
 .../jackrabbit/oak/segment/file/FileStoreTest.java | 100 ++++++++++++++++++---
 .../oak/segment/file/tar/TarFilesTest.java         |  55 ++++++++++--
 12 files changed, 347 insertions(+), 44 deletions(-)

diff --git a/.gitignore b/.gitignore
index d13284858c..c700561aa7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,4 @@ atlassian-ide-plugin.xml
 derby.log
 .java-version
 oak-shaded-guava/dependency-reduced-pom.xml
+.DS_Store
\ No newline at end of file
diff --git 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentBlob.java
 
b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentBlob.java
index 9ab2ffea8d..05cca5caa3 100644
--- 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentBlob.java
+++ 
b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentBlob.java
@@ -27,7 +27,9 @@ import static 
org.apache.jackrabbit.oak.segment.SegmentStream.BLOCK_SIZE;
 
 import java.io.InputStream;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import java.util.function.Function;
 
 import org.apache.jackrabbit.oak.api.Blob;
 import org.apache.jackrabbit.oak.commons.properties.SystemPropertySupplier;
@@ -164,19 +166,28 @@ public class SegmentBlob extends Record implements Blob {
     }
 
     @Nullable
-    public static String readBlobId(@NotNull Segment segment, int 
recordNumber) {
+    private static String readBlobId(@NotNull Segment segment, int 
recordNumber, Function<Segment, String> readLongBlobIdFunction) {
         byte head = segment.readByte(recordNumber);
         if ((head & 0xf0) == 0xe0) {
             // 1110 xxxx: external value, small blob ID
             return readShortBlobId(segment, recordNumber, head);
         } else if ((head & 0xf8) == 0xf0) {
             // 1111 0xxx: external value, long blob ID
-            return readLongBlobId(segment, recordNumber);
+            return readLongBlobIdFunction.apply(segment);
         } else {
             return null;
         }
     }
 
+    @Nullable
+    public static String readBlobId(@NotNull Segment segment, int 
recordNumber) {
+        return readBlobId(segment, recordNumber, s -> readLongBlobId(s, 
recordNumber));
+    }
+
+    @Nullable
+    public static String readBlobId(@NotNull Segment segment, int 
recordNumber, Map<SegmentId, Segment> recoveredSegments) {
+        return readBlobId(segment, recordNumber, s -> readLongBlobId(s, 
recordNumber, recoveredSegments));
+    }
     //------------------------------------------------------------< Object >--
 
     @Override
@@ -230,10 +241,22 @@ public class SegmentBlob extends Record implements Blob {
         return segment.readBytes(recordNumber, 2, 
length).decode(UTF_8).toString();
     }
 
-    private static String readLongBlobId(Segment segment, int recordNumber) {
+    private static String readLongBlobId(Segment segment, int recordNumber, 
Function<RecordId, Segment> getSegmentFunction) {
         RecordId blobId = segment.readRecordId(recordNumber, 1);
+        Segment blobIdSegment = getSegmentFunction.apply(blobId);
+
+        return blobIdSegment.readString(blobId.getRecordNumber());
+    }
+
+    private static String readLongBlobId(Segment segment, int recordNumber) {
+        return readLongBlobId(segment, recordNumber, RecordId::getSegment);
+    }
 
-        return blobId.getSegment().readString(blobId.getRecordNumber());
+    private static String readLongBlobId(Segment segment, int recordNumber, 
Map<SegmentId, Segment> recoveredSegments) {
+        return readLongBlobId(segment, recordNumber, recordId -> {
+            Segment blobIdSegment = 
recoveredSegments.get(recordId.getSegmentId());
+            return blobIdSegment != null ? blobIdSegment : 
recordId.getSegment();
+        });
     }
 
     private List<RecordId> getBulkRecordIds() {
diff --git 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/AbstractFileStore.java
 
b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/AbstractFileStore.java
index 61cea135d1..9f5e168653 100644
--- 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/AbstractFileStore.java
+++ 
b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/AbstractFileStore.java
@@ -232,7 +232,9 @@ public abstract class AbstractFileStore implements 
SegmentStore, Closeable {
                 : GCGeneration.NULL;
         w.recoverEntry(msb, lsb, data, 0, data.length, generation);
         if (SegmentId.isDataSegmentId(lsb)) {
-            Segment segment = new Segment(tracker, segmentReader, 
tracker.newSegmentId(msb, lsb), buffer);
+            SegmentId segmentId = tracker.newSegmentId(msb, lsb);
+            Segment segment = new Segment(tracker, segmentReader, segmentId, 
buffer);
+            w.addSegment(segment);
             populateTarGraph(segment, w);
             populateTarBinaryReferences(segment, w);
         }
@@ -250,7 +252,7 @@ public abstract class AbstractFileStore implements 
SegmentStore, Closeable {
         final UUID id = segment.getSegmentId().asUUID();
         segment.forEachRecord((number, type, offset) -> {
             if (type == RecordType.BLOB_ID) {
-                w.recoverBinaryReference(generation, id, 
SegmentBlob.readBlobId(segment, number));
+                w.recoverBinaryReference(generation, id, 
SegmentBlob.readBlobId(segment, number, w.getRecoveredSegments()));
             }
         });
     }
diff --git 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FileStore.java
 
b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FileStore.java
index 57fb74379b..6381e1129c 100644
--- 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FileStore.java
+++ 
b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FileStore.java
@@ -155,6 +155,8 @@ public class FileStore extends AbstractFileStore {
 
         this.stats = new FileStoreStats(statsProvider, this, 0);
 
+        this.snfeListener = builder.getSnfeListener();
+
         CounterStats readerCountStats = 
statsProvider.getCounterStats(TAR_READER_COUNT, DEFAULT);
         CounterStats segmentCountStats = 
statsProvider.getCounterStats(SEGMENT_COUNT, DEFAULT);
         TarFiles.Builder tarFilesBuilder = TarFiles.builder()
@@ -167,9 +169,12 @@ public class FileStore extends AbstractFileStore {
                 .withMaxFileSize(builder.getMaxFileSize() * MB)
                 .withPersistence(builder.getPersistence())
                 .withReaderCountStats(readerCountStats)
-                .withSegmentCountStats(segmentCountStats);
+                .withSegmentCountStats(segmentCountStats)
+                .withInitialisedReadersAndWriters(false);
 
         this.tarFiles = tarFilesBuilder.build();
+        this.tarFiles.init();
+
         long size = this.tarFiles.size();
         this.stats.init(size);
 
@@ -202,7 +207,6 @@ public class FileStore extends AbstractFileStore {
                     .build(this)
         );
 
-        this.snfeListener = builder.getSnfeListener();
         this.eagerSegmentCaching = builder.getEagerSegmentCaching();
 
         TimerStats flushTimer = statsProvider.getTimer("oak.segment.flush", 
METRICS_ONLY);
diff --git 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/ReadOnlyFileStore.java
 
b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/ReadOnlyFileStore.java
index bf25f32b74..97b2dc7ef7 100644
--- 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/ReadOnlyFileStore.java
+++ 
b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/ReadOnlyFileStore.java
@@ -77,8 +77,9 @@ public class ReadOnlyFileStore extends AbstractFileStore {
                 .withMemoryMapping(memoryMapping)
                 .withReadOnly()
                 .withPersistence(builder.getPersistence())
+                .withInitialisedReadersAndWriters(false)
                 .build();
-
+        tarFiles.init();
         writer = 
defaultSegmentWriterBuilder("read-only").withoutCache().build(this);
         gcRetainedGenerations = 
builder.getGcOptions().getRetainedGenerations();
 
diff --git 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/EntryRecovery.java
 
b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/EntryRecovery.java
index 34bef73a9c..f32342581c 100644
--- 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/EntryRecovery.java
+++ 
b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/EntryRecovery.java
@@ -17,7 +17,11 @@
 
 package org.apache.jackrabbit.oak.segment.file.tar;
 
+import org.apache.jackrabbit.oak.segment.Segment;
+import org.apache.jackrabbit.oak.segment.SegmentId;
+
 import java.io.IOException;
+import java.util.Map;
 import java.util.UUID;
 
 public interface EntryRecovery {
@@ -28,4 +32,9 @@ public interface EntryRecovery {
 
     void recoverBinaryReference(GCGeneration generation, UUID segmentId, 
String reference);
 
+    Segment getSegment(SegmentId id);
+
+    void addSegment(Segment segment);
+
+    Map<SegmentId, Segment> getRecoveredSegments();
 }
diff --git 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/TarFiles.java
 
b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/TarFiles.java
index b3cb9fd06e..fbd48e15fc 100644
--- 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/TarFiles.java
+++ 
b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/TarFiles.java
@@ -49,6 +49,7 @@ import java.util.regex.Pattern;
 import org.apache.jackrabbit.guava.common.base.Predicate;
 import org.apache.jackrabbit.guava.common.collect.Iterables;
 
+import org.apache.jackrabbit.oak.api.IllegalRepositoryStateException;
 import org.apache.jackrabbit.oak.commons.Buffer;
 import org.apache.jackrabbit.oak.segment.file.FileReaper;
 import org.apache.jackrabbit.oak.segment.spi.monitor.FileStoreMonitor;
@@ -136,6 +137,8 @@ public class TarFiles implements Closeable {
 
         private CounterStats segmentCountStats = NoopStats.INSTANCE;
 
+        private boolean initialiseReadersAndWriters = true;
+
         private Builder() {
             // Prevent external instantiation.
         }
@@ -201,6 +204,11 @@ public class TarFiles implements Closeable {
             return this;
         }
 
+        public Builder withInitialisedReadersAndWriters(boolean 
initialiseReadersAndWriters) {
+            this.initialiseReadersAndWriters = initialiseReadersAndWriters;
+            return this;
+        }
+
         public TarFiles build() throws IOException {
             checkState(directory != null, "Directory not specified");
             checkState(tarRecovery != null, "TAR recovery strategy not 
specified");
@@ -369,6 +377,15 @@ public class TarFiles implements Closeable {
      */
     private final CounterStats segmentCount;
 
+    private final boolean readOnly;
+
+    private final TarRecovery tarRecovery;
+
+    /**
+     * If {@code true}, the readers and writers are initialised.
+     */
+    private boolean initialised;
+
     private static int getSegmentCount(TarReader reader) {
         return reader.getEntries().length;
     }
@@ -378,7 +395,15 @@ public class TarFiles implements Closeable {
         archiveManager = builder.buildArchiveManager();
         readerCount = builder.readerCountStats;
         segmentCount = builder.segmentCountStats;
+        readOnly = builder.readOnly;
+        tarRecovery = builder.tarRecovery;
 
+        if (builder.initialiseReadersAndWriters) {
+            init();
+        }
+    }
+
+    public void init() throws IOException {
         Map<Integer, Map<Character, String>> map = 
collectFiles(archiveManager);
         Integer[] indices = map.keySet().toArray(new Integer[map.size()]);
         Arrays.sort(indices);
@@ -390,23 +415,30 @@ public class TarFiles implements Closeable {
 
         for (Integer index : indices) {
             TarReader r;
-            if (builder.readOnly) {
-                r = TarReader.openRO(map.get(index), builder.tarRecovery, 
archiveManager);
+            if (readOnly) {
+                r = TarReader.openRO(map.get(index), tarRecovery, 
archiveManager);
             } else {
-                r = TarReader.open(map.get(index), builder.tarRecovery, 
archiveManager);
+                r = TarReader.open(map.get(index), tarRecovery, 
archiveManager);
             }
             segmentCount.inc(getSegmentCount(r));
             readers = new Node(r, readers);
             readerCount.inc();
         }
-        if (builder.readOnly) {
-            return;
+        if (!readOnly) {
+            int writeNumber = 0;
+            if (indices.length > 0) {
+                writeNumber = indices[indices.length - 1] + 1;
+            }
+            writer = new TarWriter(archiveManager, writeNumber, segmentCount);
         }
-        int writeNumber = 0;
-        if (indices.length > 0) {
-            writeNumber = indices[indices.length - 1] + 1;
+
+        initialised = true;
+    }
+
+    private void checkInitialised() {
+        if (!initialised) {
+            throw new IllegalRepositoryStateException("TarFiles not 
initialised");
         }
-        writer = new TarWriter(archiveManager, writeNumber, segmentCount);
     }
 
     @Override
@@ -530,6 +562,7 @@ public class TarFiles implements Closeable {
     }
 
     public void flush() throws IOException {
+        checkInitialised();
         lock.readLock().lock();
         try {
             writer.flush();
@@ -592,6 +625,7 @@ public class TarFiles implements Closeable {
     }
 
     public void writeSegment(UUID id, byte[] buffer, int offset, int length, 
GCGeneration generation, Set<UUID> references, Set<String> binaryReferences) 
throws IOException {
+        checkInitialised();
         lock.writeLock().lock();
         try {
             long size = writer.writeEntry(
@@ -643,6 +677,7 @@ public class TarFiles implements Closeable {
     }
 
     void newWriter() throws IOException {
+        checkInitialised();
         lock.writeLock().lock();
         try {
             internalNewWriter();
@@ -652,6 +687,7 @@ public class TarFiles implements Closeable {
     }
 
     public CleanupResult cleanup(CleanupContext context) throws IOException {
+        checkInitialised();
         CleanupResult result = new CleanupResult();
         result.removableFiles = new ArrayList<>();
         result.reclaimedSegmentIds = new HashSet<>();
@@ -803,6 +839,7 @@ public class TarFiles implements Closeable {
     }
 
     public void collectBlobReferences(Consumer<String> collector, 
Predicate<GCGeneration> reclaim) throws IOException {
+        checkInitialised();
         Node head;
         lock.writeLock().lock();
         try {
@@ -889,6 +926,7 @@ public class TarFiles implements Closeable {
     }
 
     public FileReaper createFileReaper() {
+        checkInitialised();
         return new FileReaper(archiveManager);
     }
 }
diff --git 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/TarReader.java
 
b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/TarReader.java
index 47f080370d..3a812f83f8 100644
--- 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/TarReader.java
+++ 
b/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/TarReader.java
@@ -30,6 +30,7 @@ import java.io.Closeable;
 import java.io.File;
 import java.io.IOException;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -44,6 +45,8 @@ import java.util.stream.Collectors;
 import org.apache.jackrabbit.guava.common.base.Predicate;
 
 import org.apache.jackrabbit.oak.commons.Buffer;
+import org.apache.jackrabbit.oak.segment.Segment;
+import org.apache.jackrabbit.oak.segment.SegmentId;
 import 
org.apache.jackrabbit.oak.segment.file.tar.binaries.BinaryReferencesIndex;
 import 
org.apache.jackrabbit.oak.segment.file.tar.binaries.BinaryReferencesIndexLoader;
 import 
org.apache.jackrabbit.oak.segment.file.tar.binaries.InvalidBinaryReferencesIndexException;
@@ -178,10 +181,11 @@ public class TarReader implements Closeable {
         log.info("Regenerating tar file {}", file);
 
         try (TarWriter writer = new TarWriter(archiveManager, file)) {
+            Map<SegmentId, Segment> segmentMap = new HashMap<>(entries.size());
+
             for (Entry<UUID, byte[]> entry : entries.entrySet()) {
                 try {
                     recovery.recoverEntry(entry.getKey(), entry.getValue(), 
new EntryRecovery() {
-
                         @Override
                         public void recoverEntry(long msb, long lsb, byte[] 
data, int offset, int size, GCGeneration generation) throws IOException {
                             writer.writeEntry(msb, lsb, data, offset, size, 
generation);
@@ -197,6 +201,18 @@ public class TarReader implements Closeable {
                             writer.addBinaryReference(generation, segmentId, 
reference);
                         }
 
+                        public Segment getSegment(SegmentId id) {
+                            return segmentMap.get(id);
+                        }
+
+                        public void addSegment(Segment segment) {
+                            segmentMap.put(segment.getSegmentId(), segment);
+                        }
+
+                        @Override
+                        public Map<SegmentId, Segment> getRecoveredSegments() {
+                            return segmentMap;
+                        }
                     });
                 } catch (IOException e) {
                     throw new IOException(String.format("Unable to recover 
entry %s for file %s", entry.getKey(), file), e);
diff --git 
a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/IdMappingBlobStore.java
 
b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/IdMappingBlobStore.java
new file mode 100644
index 0000000000..032b24d0de
--- /dev/null
+++ 
b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/IdMappingBlobStore.java
@@ -0,0 +1,88 @@
+/*
+ * 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.segment;
+
+import org.apache.jackrabbit.oak.spi.blob.BlobOptions;
+import org.apache.jackrabbit.oak.spi.blob.BlobStore;
+import org.apache.jackrabbit.oak.spi.blob.MemoryBlobStore;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+public abstract class IdMappingBlobStore implements BlobStore {
+
+    private final MemoryBlobStore bs = new MemoryBlobStore();
+
+    private final Map<String, String> ids = new HashMap<>();
+
+    @Override
+    public String writeBlob(InputStream inputStream) throws IOException {
+        String in = bs.writeBlob(inputStream);
+        String out = generateId();
+        ids.put(out, in);
+        return out;
+    }
+
+    @Override
+    public String writeBlob(InputStream inputStream, BlobOptions options) 
throws IOException {
+        return writeBlob(inputStream);
+    }
+
+    @Override
+    public int readBlob(String s, long l, byte[] bytes, int i, int i1) throws 
IOException {
+        return bs.readBlob(mapId(s), l, bytes, i, i1);
+    }
+
+    @Override
+    public long getBlobLength(String s) throws IOException {
+        return bs.getBlobLength(mapId(s));
+    }
+
+    @Override
+    public InputStream getInputStream(String s) throws IOException {
+        return bs.getInputStream(mapId(s));
+    }
+
+    @Override
+    public String getBlobId(@NotNull String s) {
+        return bs.getBlobId(s);
+    }
+
+    @Override
+    public String getReference(@NotNull String s) {
+        return bs.getBlobId(mapId(s));
+    }
+
+    @Override
+    public void close() throws Exception {
+        bs.close();
+    }
+
+    private String mapId(String in) {
+        String out = ids.get(in);
+        if (out == null) {
+            throw new IllegalArgumentException("in");
+        }
+        return out;
+    }
+
+    protected abstract String generateId();
+}
diff --git 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/EntryRecovery.java
 
b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/LongIdMappingBlobStore.java
similarity index 67%
copy from 
oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/EntryRecovery.java
copy to 
oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/LongIdMappingBlobStore.java
index 34bef73a9c..2b51647bc7 100644
--- 
a/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tar/EntryRecovery.java
+++ 
b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/LongIdMappingBlobStore.java
@@ -15,17 +15,17 @@
  * limitations under the License.
  */
 
-package org.apache.jackrabbit.oak.segment.file.tar;
+package org.apache.jackrabbit.oak.segment;
 
-import java.io.IOException;
-import java.util.UUID;
+import org.apache.jackrabbit.guava.common.base.Strings;
 
-public interface EntryRecovery {
+public class LongIdMappingBlobStore extends IdMappingBlobStore {
 
-    void recoverEntry(long msb, long lsb, byte[] data, int offset, int size, 
GCGeneration generation) throws IOException;
+    private static int next;
 
-    void recoverGraphEdge(UUID from, UUID to);
-
-    void recoverBinaryReference(GCGeneration generation, UUID segmentId, 
String reference);
+    @Override
+    protected String generateId() {
+        return Strings.repeat("0", Segment.BLOB_ID_SMALL_LIMIT) + next++;
+    }
 
 }
diff --git 
a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/file/FileStoreTest.java
 
b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/file/FileStoreTest.java
index 9043799682..d2e6f0d355 100644
--- 
a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/file/FileStoreTest.java
+++ 
b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/file/FileStoreTest.java
@@ -19,20 +19,15 @@
 
 package org.apache.jackrabbit.oak.segment.file;
 
-import static 
org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
-import static 
org.apache.jackrabbit.oak.segment.file.FileStoreBuilder.fileStoreBuilder;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThrows;
-import static org.junit.Assert.fail;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-
 import org.apache.jackrabbit.oak.api.Blob;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.segment.LongIdMappingBlobStore;
 import org.apache.jackrabbit.oak.segment.SegmentId;
 import org.apache.jackrabbit.oak.segment.SegmentNodeBuilder;
 import org.apache.jackrabbit.oak.segment.SegmentNodeState;
+import org.apache.jackrabbit.oak.segment.SegmentNodeStore;
+import org.apache.jackrabbit.oak.segment.SegmentNodeStoreBuilders;
 import org.apache.jackrabbit.oak.segment.file.tar.SegmentTarManager;
 import org.apache.jackrabbit.oak.segment.file.tar.SegmentTarWriter;
 import org.apache.jackrabbit.oak.segment.file.tar.TarPersistence;
@@ -41,20 +36,34 @@ import 
org.apache.jackrabbit.oak.segment.spi.monitor.IOMonitor;
 import org.apache.jackrabbit.oak.segment.spi.monitor.RemoteStoreMonitor;
 import org.apache.jackrabbit.oak.segment.spi.persistence.SegmentArchiveManager;
 import org.apache.jackrabbit.oak.segment.spi.persistence.SegmentArchiveWriter;
+import org.apache.jackrabbit.oak.spi.blob.BlobStore;
+import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
+import org.apache.jackrabbit.oak.spi.commit.EmptyHook;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 import org.jetbrains.annotations.NotNull;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
 
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+
+import static 
org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
+import static 
org.apache.jackrabbit.oak.segment.file.FileStoreBuilder.fileStoreBuilder;
+import static org.junit.Assert.*;
+
 public class FileStoreTest {
     private  static final String FAILED_TO_WRITE_ON_CLOSE = "Failed to write 
to the archive on closing";
 
     @Rule
     public TemporaryFolder folder = new TemporaryFolder(new File("target"));
 
-    private File getFileStoreFolder() {
-        return folder.getRoot();
+    private File getFileStoreFolder() throws IOException {
+        return folder.newFolder("segmentstore");
     }
 
     @Test
@@ -140,6 +149,73 @@ public class FileStoreTest {
         assertEquals("already shut down", closeEx.getMessage());
     }
 
+    @Test
+    public void testRecovery_FileStore_withExternalBlobStore() throws 
InvalidFileStoreVersionException, IOException, CommitFailedException {
+
+        File segmentStore = getFileStoreFolder();
+        BlobStore blobStore = new LongIdMappingBlobStore();
+        FileStore fileStore = createFileStore(segmentStore, blobStore);
+
+        SegmentNodeStore segmentNodeStore = 
SegmentNodeStoreBuilders.builder(fileStore).build();
+
+        NodeBuilder builder = segmentNodeStore.getRoot().builder();
+        builder.setProperty("foo", "bar");
+
+        Blob blob = builder.createBlob(new ZeroStream(100));
+
+
+        segmentNodeStore.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+        fileStore.flush();
+
+        // create another segment
+
+        builder.setProperty("binaryProperty", blob);
+
+        builder.setProperty("foo1", "bar1");
+        segmentNodeStore.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+        fileStore.flush();
+
+        // copy all files form segmentStore to segmentStoreClone
+        // with this, tha last tar archive will not have index, graph and 
binary references
+        File segmentStoreClone = folder.newFolder("segmentstore-clone");
+        Files.walk(segmentStore.toPath())
+                .forEach(source -> {
+                    try {
+                            Path target = 
segmentStoreClone.toPath().resolve(segmentStore.toPath().relativize(source));
+                            Files.copy(source, target, 
StandardCopyOption.REPLACE_EXISTING);
+                    } catch (IOException e) {
+                        throw new RuntimeException(e.getMessage(), e);
+                    }
+                });
+
+
+        fileStore.close();
+
+        // Start new FileStore, which should be able to recover successfully 
despite now having the index, graph and binary references
+        try {
+        fileStore = createFileStore(segmentStoreClone, blobStore);
+        } catch (Exception e){
+            fail("Should not throw exception" + e);
+        }
+
+        segmentNodeStore = SegmentNodeStoreBuilders.builder(fileStore).build();
+
+
+        builder = segmentNodeStore.getRoot().builder();
+
+        assertNotNull(builder.getProperty("foo"));
+        assertNotNull(builder.getProperty("foo1"));
+        blob = builder.getProperty("binaryProperty").getValue(Type.BINARY);
+        assertNotNull(blob);
+    }
+
+    public FileStore createFileStore(File segmentStore, BlobStore blobStore) 
throws IOException, InvalidFileStoreVersionException {
+        return fileStoreBuilder(segmentStore)
+                .withBinariesInlineThreshold(0)
+                .withBlobStore(blobStore)
+                .build();
+    }
+
     private static int counter = 0;
 
     private static void writeData(FileStore fileStore, int size) throws 
IOException {
diff --git 
a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/file/tar/TarFilesTest.java
 
b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/file/tar/TarFilesTest.java
index 39deea6ea2..37693d6ede 100644
--- 
a/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/file/tar/TarFilesTest.java
+++ 
b/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/file/tar/TarFilesTest.java
@@ -24,11 +24,7 @@ import static java.util.Collections.singleton;
 import static java.util.Collections.singletonList;
 import static java.util.UUID.randomUUID;
 import static 
org.apache.jackrabbit.oak.segment.file.tar.GCGeneration.newGCGeneration;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
 
 import java.io.File;
 import java.io.IOException;
@@ -40,6 +36,7 @@ import java.util.Random;
 import java.util.Set;
 import java.util.UUID;
 
+import org.apache.jackrabbit.oak.api.IllegalRepositoryStateException;
 import org.apache.jackrabbit.oak.commons.Buffer;
 import org.apache.jackrabbit.oak.segment.file.tar.TarFiles.CleanupResult;
 import org.apache.jackrabbit.oak.segment.spi.monitor.FileStoreMonitorAdapter;
@@ -397,4 +394,52 @@ public class TarFilesTest {
         assertTrue(result.getReclaimedSegmentIds().isEmpty());
         assertEquals(0, result.getReclaimedSize());
     }
+
+    @Test
+    public void testUninitialisedTarFiles() throws IOException {
+        tarFiles = TarFiles.builder()
+                .withDirectory(folder.getRoot())
+                .withTarRecovery((id, data, recovery) -> {
+                    // Intentionally left blank
+                })
+                .withIOMonitor(new IOMonitorAdapter())
+                .withFileStoreMonitor(new FileStoreMonitorAdapter())
+                .withMaxFileSize(MAX_FILE_SIZE)
+                .withRemoteStoreMonitor(new RemoteStoreMonitorAdapter())
+                .withInitialisedReadersAndWriters(false)
+                .build();
+
+        assertEquals(0, tarFiles.readerCount());
+        assertEquals(0, tarFiles.segmentCount());
+        assertEquals(0, tarFiles.size());
+        assertFalse(tarFiles.containsSegment(0l, 0l));
+        assertNull(tarFiles.readSegment(0l, 0l));
+
+        tarFiles.toString();
+        assertThrows(IllegalRepositoryStateException.class, () -> 
tarFiles.flush());
+        assertThrows(IllegalRepositoryStateException.class, () -> 
writeSegment(randomUUID()));
+        assertThrows(IllegalRepositoryStateException.class, () -> 
tarFiles.cleanup(new CleanupContext() {
+            @Override
+            public Collection<UUID> initialReferences() {
+                return emptySet();
+            }
+
+            @Override
+            public boolean shouldReclaim(UUID id, GCGeneration generation, 
boolean referenced) {
+                return false;
+            }
+
+            @Override
+            public boolean shouldFollow(UUID from, UUID to) {
+                return false;
+            }
+        }));
+
+        assertThrows(IllegalRepositoryStateException.class, () -> 
tarFiles.collectBlobReferences(r -> {}, g -> false));
+        assertFalse(tarFiles.getSegmentIds().iterator().hasNext());
+        assertTrue(tarFiles.getIndices().isEmpty());
+        assertTrue(tarFiles.getGraph("").isEmpty());
+
+        assertThrows(IllegalRepositoryStateException.class, () -> 
tarFiles.createFileReaper());
+    }
 }

Reply via email to