diff --git a/src/main/java/org/jabref/logic/citation/SearchCitationsRelationsService.java b/src/main/java/org/jabref/logic/citation/SearchCitationsRelationsService.java index 11d041abb7f..7a51b686077 100644 --- a/src/main/java/org/jabref/logic/citation/SearchCitationsRelationsService.java +++ b/src/main/java/org/jabref/logic/citation/SearchCitationsRelationsService.java @@ -26,7 +26,8 @@ public SearchCitationsRelationsService( } public List searchReferences(BibEntry referencer, boolean forceUpdate) { - if (forceUpdate || !this.relationsRepository.containsReferences(referencer)) { + if ((forceUpdate && this.relationsRepository.isReferencesUpdatable(referencer)) + || !this.relationsRepository.containsReferences(referencer)) { try { var references = this.citationFetcher.searchCiting(referencer); if (!references.isEmpty()) { @@ -40,7 +41,8 @@ public List searchReferences(BibEntry referencer, boolean forceUpdate) } public List searchCitations(BibEntry cited, boolean forceUpdate) { - if (forceUpdate || !this.relationsRepository.containsCitations(cited)) { + if ((forceUpdate && this.relationsRepository.isCitationsUpdatable(cited)) + || !this.relationsRepository.containsCitations(cited)) { try { var citations = this.citationFetcher.searchCitedBy(cited); if (!citations.isEmpty()) { diff --git a/src/main/java/org/jabref/logic/citation/repository/BibEntryRelationDAO.java b/src/main/java/org/jabref/logic/citation/repository/BibEntryRelationDAO.java index e56b08f4d3f..883a4566a3c 100644 --- a/src/main/java/org/jabref/logic/citation/repository/BibEntryRelationDAO.java +++ b/src/main/java/org/jabref/logic/citation/repository/BibEntryRelationDAO.java @@ -11,4 +11,8 @@ public interface BibEntryRelationDAO { void cacheOrMergeRelations(BibEntry entry, List relations); boolean containsKey(BibEntry entry); + + default boolean isUpdatable(BibEntry entry) { + return true; + } } diff --git a/src/main/java/org/jabref/logic/citation/repository/ChainBibEntryRelationDAO.java b/src/main/java/org/jabref/logic/citation/repository/BibEntryRelationDAOChain.java similarity index 74% rename from src/main/java/org/jabref/logic/citation/repository/ChainBibEntryRelationDAO.java rename to src/main/java/org/jabref/logic/citation/repository/BibEntryRelationDAOChain.java index 3b802f7cada..2445e13a6fc 100644 --- a/src/main/java/org/jabref/logic/citation/repository/ChainBibEntryRelationDAO.java +++ b/src/main/java/org/jabref/logic/citation/repository/BibEntryRelationDAOChain.java @@ -4,14 +4,14 @@ import org.jabref.model.entry.BibEntry; -public class ChainBibEntryRelationDAO implements BibEntryRelationDAO { +public class BibEntryRelationDAOChain implements BibEntryRelationDAO { - private static final BibEntryRelationDAO EMPTY = new ChainBibEntryRelationDAO(null, null); + private static final BibEntryRelationDAO EMPTY = new BibEntryRelationDAOChain(null, null); private final BibEntryRelationDAO current; private final BibEntryRelationDAO next; - ChainBibEntryRelationDAO(BibEntryRelationDAO current, BibEntryRelationDAO next) { + BibEntryRelationDAOChain(BibEntryRelationDAO current, BibEntryRelationDAO next) { this.current = current; this.next = next; } @@ -44,10 +44,16 @@ public boolean containsKey(BibEntry entry) { || (this.next != EMPTY && this.next.containsKey(entry)); } + @Override + public boolean isUpdatable(BibEntry entry) { + return this.current.isUpdatable(entry) + && (this.next == EMPTY || this.next.isUpdatable(entry)); + } + public static BibEntryRelationDAO of(BibEntryRelationDAO... dao) { return List.of(dao) .reversed() .stream() - .reduce(EMPTY, (acc, current) -> new ChainBibEntryRelationDAO(current, acc)); + .reduce(EMPTY, (acc, current) -> new BibEntryRelationDAOChain(current, acc)); } } diff --git a/src/main/java/org/jabref/logic/citation/repository/BibEntryRelationsRepository.java b/src/main/java/org/jabref/logic/citation/repository/BibEntryRelationsRepository.java index 84b4c73a1d7..cb99b791cad 100644 --- a/src/main/java/org/jabref/logic/citation/repository/BibEntryRelationsRepository.java +++ b/src/main/java/org/jabref/logic/citation/repository/BibEntryRelationsRepository.java @@ -12,9 +12,13 @@ public interface BibEntryRelationsRepository { boolean containsCitations(BibEntry entry); + boolean isCitationsUpdatable(BibEntry entry); + void insertReferences(BibEntry entry, List citations); List readReferences(BibEntry entry); boolean containsReferences(BibEntry entry); + + boolean isReferencesUpdatable(BibEntry entry); } diff --git a/src/main/java/org/jabref/logic/citation/repository/ChainBibEntryRelationsRepository.java b/src/main/java/org/jabref/logic/citation/repository/ChainBibEntryRelationsRepository.java index 289b08edb8f..14d26308d98 100644 --- a/src/main/java/org/jabref/logic/citation/repository/ChainBibEntryRelationsRepository.java +++ b/src/main/java/org/jabref/logic/citation/repository/ChainBibEntryRelationsRepository.java @@ -12,10 +12,10 @@ public class ChainBibEntryRelationsRepository implements BibEntryRelationsReposi private final BibEntryRelationDAO referencesDao; public ChainBibEntryRelationsRepository(Path citationsStore, Path relationsStore) { - this.citationsDao = ChainBibEntryRelationDAO.of( + this.citationsDao = BibEntryRelationDAOChain.of( LRUCacheBibEntryRelationsDAO.CITATIONS, new MVStoreBibEntryRelationDAO(citationsStore, "citations") ); - this.referencesDao = ChainBibEntryRelationDAO.of( + this.referencesDao = BibEntryRelationDAOChain.of( LRUCacheBibEntryRelationsDAO.REFERENCES, new MVStoreBibEntryRelationDAO(relationsStore, "relations") ); } @@ -37,6 +37,11 @@ public boolean containsCitations(BibEntry entry) { return citationsDao.containsKey(entry); } + @Override + public boolean isCitationsUpdatable(BibEntry entry) { + return citationsDao.isUpdatable(entry); + } + @Override public void insertReferences(BibEntry entry, List references) { referencesDao.cacheOrMergeRelations( @@ -53,4 +58,9 @@ public List readReferences(BibEntry entry) { public boolean containsReferences(BibEntry entry) { return referencesDao.containsKey(entry); } + + @Override + public boolean isReferencesUpdatable(BibEntry entry) { + return referencesDao.isUpdatable(entry); + } } diff --git a/src/main/java/org/jabref/logic/citation/repository/LRUBibEntryRelationsCache.java b/src/main/java/org/jabref/logic/citation/repository/LRUBibEntryRelationsCache.java deleted file mode 100644 index f87455fc033..00000000000 --- a/src/main/java/org/jabref/logic/citation/repository/LRUBibEntryRelationsCache.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.jabref.logic.citation.repository; - -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.identifier.DOI; - -import org.eclipse.jgit.util.LRUMap; - -public class LRUBibEntryRelationsCache { - private static final Integer MAX_CACHED_ENTRIES = 100; - private static final Map> CITATIONS_MAP = new LRUMap<>(MAX_CACHED_ENTRIES, MAX_CACHED_ENTRIES); - private static final Map> REFERENCES_MAP = new LRUMap<>(MAX_CACHED_ENTRIES, MAX_CACHED_ENTRIES); - - public List getCitations(BibEntry entry) { - return entry - .getDOI() - .stream() - .flatMap(doi -> CITATIONS_MAP.getOrDefault(doi, Set.of()).stream()) - .toList(); - } - - public List getReferences(BibEntry entry) { - return entry - .getDOI() - .stream() - .flatMap(doi -> REFERENCES_MAP.getOrDefault(doi, Set.of()).stream()) - .toList(); - } - - public void cacheOrMergeCitations(BibEntry entry, List citations) { - entry.getDOI().ifPresent(doi -> { - var cachedRelations = CITATIONS_MAP.getOrDefault(doi, new LinkedHashSet<>()); - cachedRelations.addAll(citations); - CITATIONS_MAP.put(doi, cachedRelations); - }); - } - - public void cacheOrMergeReferences(BibEntry entry, List references) { - entry.getDOI().ifPresent(doi -> { - var cachedRelations = REFERENCES_MAP.getOrDefault(doi, new LinkedHashSet<>()); - cachedRelations.addAll(references); - REFERENCES_MAP.put(doi, cachedRelations); - }); - } - - public boolean citationsCached(BibEntry entry) { - return entry.getDOI().map(CITATIONS_MAP::containsKey).orElse(false); - } - - public boolean referencesCached(BibEntry entry) { - return entry.getDOI().map(REFERENCES_MAP::containsKey).orElse(false); - } -} diff --git a/src/main/java/org/jabref/logic/citation/repository/MVStoreBibEntryRelationDAO.java b/src/main/java/org/jabref/logic/citation/repository/MVStoreBibEntryRelationDAO.java index fe0ee41be64..8bf4f0473da 100644 --- a/src/main/java/org/jabref/logic/citation/repository/MVStoreBibEntryRelationDAO.java +++ b/src/main/java/org/jabref/logic/citation/repository/MVStoreBibEntryRelationDAO.java @@ -3,18 +3,22 @@ import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.nio.file.Path; +import java.time.Clock; +import java.time.ZoneId; import java.util.LinkedHashSet; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.IntStream; +import java.time.LocalDateTime; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.identifier.DOI; import org.jabref.model.entry.types.StandardEntryType; +import com.google.common.annotations.VisibleForTesting; import org.h2.mvstore.MVMap; import org.h2.mvstore.MVStore; import org.h2.mvstore.WriteBuffer; @@ -22,13 +26,17 @@ public class MVStoreBibEntryRelationDAO implements BibEntryRelationDAO { + private final static ZoneId TIME_STAMP_ZONE_ID = ZoneId.of("UTC"); + private final String mapName; + private final String insertionTimeStampMapName; private final MVStore.Builder storeConfiguration; private final MVMap.Builder> mapConfiguration = new MVMap.Builder>().valueType(new BibEntryHashSetSerializer()); MVStoreBibEntryRelationDAO(Path path, String mapName) { this.mapName = mapName; + this.insertionTimeStampMapName = mapName + "-insertion-timestamp"; this.storeConfiguration = new MVStore.Builder().autoCommitDisabled().fileName(path.toAbsolutePath().toString()); } @@ -47,19 +55,30 @@ public List getRelations(BibEntry entry) { @Override synchronized public void cacheOrMergeRelations(BibEntry entry, List relations) { + if (relations.isEmpty()) { + return; + } entry.getDOI().ifPresent(doi -> { try (var store = this.storeConfiguration.open()) { + // Save the relations MVMap> relationsMap = store.openMap(mapName, mapConfiguration); var relationsAlreadyStored = relationsMap.getOrDefault(doi.getDOI(), new LinkedHashSet<>()); relationsAlreadyStored.addAll(relations); relationsMap.put(doi.getDOI(), relationsAlreadyStored); + + // Save insertion timestamp + var insertionTime = LocalDateTime.now(TIME_STAMP_ZONE_ID); + MVMap insertionTimeStampMap = store.openMap(insertionTimeStampMapName); + insertionTimeStampMap.put(doi.getDOI(), insertionTime); + + // Commit store.commit(); } }); } @Override - public boolean containsKey(BibEntry entry) { + synchronized public boolean containsKey(BibEntry entry) { return entry .getDOI() .map(doi -> { @@ -71,6 +90,29 @@ public boolean containsKey(BibEntry entry) { .orElse(false); } + @Override + synchronized public boolean isUpdatable(BibEntry entry) { + var clock = Clock.system(TIME_STAMP_ZONE_ID); + return this.isUpdatable(entry, clock); + } + + @VisibleForTesting + boolean isUpdatable(final BibEntry entry, final Clock clock) { + final var executionTime = LocalDateTime.now(clock); + return entry + .getDOI() + .map(doi -> { + try (var store = this.storeConfiguration.open()) { + MVMap insertionTimeStampMap = store.openMap(insertionTimeStampMapName); + return insertionTimeStampMap.getOrDefault(doi.getDOI(), executionTime); + } + }) + .map(lastExecutionTime -> + lastExecutionTime.equals(executionTime) || lastExecutionTime.isBefore(executionTime.minusWeeks(1)) + ) + .orElse(true); + } + private static class BibEntrySerializer extends BasicDataType { private final static String FIELD_SEPARATOR = "--"; diff --git a/src/test/java/org/jabref/logic/citation/SearchCitationsRelationsServiceTest.java b/src/test/java/org/jabref/logic/citation/SearchCitationsRelationsServiceTest.java index 25097ed4b87..1d1f1fbbf8f 100644 --- a/src/test/java/org/jabref/logic/citation/SearchCitationsRelationsServiceTest.java +++ b/src/test/java/org/jabref/logic/citation/SearchCitationsRelationsServiceTest.java @@ -1,9 +1,8 @@ -package org.jabref.logic.citation.service; +package org.jabref.logic.citation; import java.util.HashMap; import java.util.List; -import org.jabref.logic.citation.SearchCitationsRelationsService; import org.jabref.logic.citation.repository.BibEntryRelationsRepositoryHelpersForTest; import org.jabref.logic.importer.fetcher.CitationFetcherHelpersForTest; import org.jabref.model.entry.BibEntry; diff --git a/src/test/java/org/jabref/logic/citation/repository/ChainBibEntryRelationDAOTest.java b/src/test/java/org/jabref/logic/citation/repository/BibEntryRelationDAOChainTest.java similarity index 79% rename from src/test/java/org/jabref/logic/citation/repository/ChainBibEntryRelationDAOTest.java rename to src/test/java/org/jabref/logic/citation/repository/BibEntryRelationDAOChainTest.java index 6507c9b1d80..d83156f0745 100644 --- a/src/test/java/org/jabref/logic/citation/repository/ChainBibEntryRelationDAOTest.java +++ b/src/test/java/org/jabref/logic/citation/repository/BibEntryRelationDAOChainTest.java @@ -19,12 +19,12 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -class ChainBibEntryRelationDAOTest { +class BibEntryRelationDAOChainTest { private static Stream createBibEntries() { return IntStream .range(0, 150) - .mapToObj(ChainBibEntryRelationDAOTest::createBibEntry); + .mapToObj(BibEntryRelationDAOChainTest::createBibEntry); } private static BibEntry createBibEntry(int i) { @@ -37,6 +37,7 @@ private static BibEntry createBibEntry(int i) { * Create a fake list of relations for a bibEntry based on the {@link PaperDetails#toBibEntry()} logic * that corresponds to this use case: we want to make sure that relations coming from SemanticScholar * and mapped as BibEntry will be serializable by the MVStore. + * * @param entry should not be null * @return never empty */ @@ -68,7 +69,7 @@ private static Stream createCacheAndBibEntry() { }); } - private class DaoMock implements BibEntryRelationDAO { + private static class DaoMock implements BibEntryRelationDAO { Map> table = new HashMap<>(); @@ -86,6 +87,11 @@ public void cacheOrMergeRelations(BibEntry entry, List relations) { public boolean containsKey(BibEntry entry) { return this.table.containsKey(entry); } + + @Override + public boolean isUpdatable(BibEntry entry) { + return !this.containsKey(entry); + } } @ParameterizedTest @@ -95,7 +101,7 @@ void theChainShouldReadFromFirstNode(BibEntryRelationDAO dao, BibEntry entry) { var relations = createRelations(entry); dao.cacheOrMergeRelations(entry, relations); var secondDao = new DaoMock(); - var doaChain = ChainBibEntryRelationDAO.of(dao, secondDao); + var doaChain = BibEntryRelationDAOChain.of(dao, secondDao); // WHEN var relationsFromChain = doaChain.getRelations(entry); @@ -112,7 +118,7 @@ void theChainShouldReadFromSecondNode(BibEntryRelationDAO dao, BibEntry entry) { var relations = createRelations(entry); dao.cacheOrMergeRelations(entry, relations); var firstDao = new DaoMock(); - var doaChain = ChainBibEntryRelationDAO.of(firstDao, dao); + var doaChain = BibEntryRelationDAOChain.of(firstDao, dao); // WHEN var relationsFromChain = doaChain.getRelations(entry); @@ -128,7 +134,7 @@ void theChainShouldReadFromSecondNodeAndRecopyToFirstNode(BibEntryRelationDAO da // GIVEN var relations = createRelations(entry); var firstDao = new DaoMock(); - var doaChain = ChainBibEntryRelationDAO.of(firstDao, dao); + var doaChain = BibEntryRelationDAOChain.of(firstDao, dao); // WHEN doaChain.cacheOrMergeRelations(entry, relations); @@ -142,18 +148,34 @@ void theChainShouldReadFromSecondNodeAndRecopyToFirstNode(BibEntryRelationDAO da @ParameterizedTest @MethodSource("createCacheAndBibEntry") - void theChainShouldContainAKeyEvenIfItWasOnlyInsertedInLastNode(BibEntryRelationDAO dao, BibEntry entry) { + void theChainShouldContainAKeyEvenIfItWasOnlyInsertedInLastNode(BibEntryRelationDAO secondDao, BibEntry entry) { // GIVEN var relations = createRelations(entry); var firstDao = new DaoMock(); - var doaChain = ChainBibEntryRelationDAO.of(firstDao, dao); + var doaChain = BibEntryRelationDAOChain.of(firstDao, secondDao); // WHEN - dao.cacheOrMergeRelations(entry, relations); + secondDao.cacheOrMergeRelations(entry, relations); + + // THEN Assertions.assertFalse(firstDao.containsKey(entry)); - boolean doesChainContainsTheKey = doaChain.containsKey(entry); + Assertions.assertTrue(doaChain.containsKey(entry)); + } + + @ParameterizedTest + @MethodSource("createCacheAndBibEntry") + void theChainShouldNotBeUpdatableBeforeInsertionAndNotAfterAnInsertion(BibEntryRelationDAO dao, BibEntry entry) { + // GIVEN + var relations = createRelations(entry); + var lastDao = new DaoMock(); + var daoChain = BibEntryRelationDAOChain.of(dao, lastDao); + Assertions.assertTrue(daoChain.isUpdatable(entry)); + + // WHEN + daoChain.cacheOrMergeRelations(entry, relations); // THEN - Assertions.assertTrue(doesChainContainsTheKey); + Assertions.assertTrue(daoChain.containsKey(entry)); + Assertions.assertFalse(daoChain.isUpdatable(entry)); } } diff --git a/src/test/java/org/jabref/logic/citation/repository/BibEntryRelationsRepositoryHelpersForTest.java b/src/test/java/org/jabref/logic/citation/repository/BibEntryRelationsRepositoryHelpersForTest.java index f2305b38b33..c829ed188b7 100644 --- a/src/test/java/org/jabref/logic/citation/repository/BibEntryRelationsRepositoryHelpersForTest.java +++ b/src/test/java/org/jabref/logic/citation/repository/BibEntryRelationsRepositoryHelpersForTest.java @@ -31,6 +31,11 @@ public boolean containsCitations(BibEntry entry) { return true; } + @Override + public boolean isCitationsUpdatable(BibEntry entry) { + return false; + } + @Override public void insertReferences(BibEntry entry, List citations) { insertReferences.accept(entry, citations); @@ -45,6 +50,11 @@ public List readReferences(BibEntry entry) { public boolean containsReferences(BibEntry entry) { return true; } + + @Override + public boolean isReferencesUpdatable(BibEntry entry) { + return false; + } }; } @@ -67,6 +77,11 @@ public boolean containsCitations(BibEntry entry) { return citationsDB.containsKey(entry); } + @Override + public boolean isCitationsUpdatable(BibEntry entry) { + return false; + } + @Override public void insertReferences(BibEntry entry, List citations) { referencesDB.put(entry, citations); @@ -81,6 +96,11 @@ public List readReferences(BibEntry entry) { public boolean containsReferences(BibEntry entry) { return referencesDB.containsKey(entry); } + + @Override + public boolean isReferencesUpdatable(BibEntry entry) { + return false; + } }; } } diff --git a/src/test/java/org/jabref/logic/citation/repository/MVStoreBibEntryRelationsRepositoryDAOTest.java b/src/test/java/org/jabref/logic/citation/repository/MVStoreBibEntryRelationsRepositoryDAOTest.java index ad6a175cfaf..1816ed10096 100644 --- a/src/test/java/org/jabref/logic/citation/repository/MVStoreBibEntryRelationsRepositoryDAOTest.java +++ b/src/test/java/org/jabref/logic/citation/repository/MVStoreBibEntryRelationsRepositoryDAOTest.java @@ -3,6 +3,11 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.time.Clock; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; import java.util.List; import java.util.random.RandomGenerator; import java.util.stream.Collectors; @@ -26,6 +31,9 @@ class MVStoreBibEntryRelationsRepositoryDAOTest { + private final static String TEMPORARY_FOLDER_NAME = "bib_entry_relations_test_not_contains_store"; + private final static String MAP_NAME = "test-relations"; + @TempDir Path temporaryFolder; private static Stream createBibEntries() { @@ -44,6 +52,7 @@ private static BibEntry createBibEntry(int i) { * Create a fake list of relations for a bibEntry based on the {@link PaperDetails#toBibEntry()} logic * that corresponds to this use case: we want to make sure that relations coming from SemanticScholar * and mapped as BibEntry will be serializable by the MVStore. + * * @param entry should not be null * @return never empty */ @@ -70,8 +79,8 @@ private static List createRelations(BibEntry entry) { @MethodSource("createBibEntries") void DAOShouldMergeRelationsWhenInserting(BibEntry bibEntry) throws IOException { // GIVEN - var file = Files.createFile(temporaryFolder.resolve("bib_entry_relations_test_store")); - var dao = new MVStoreBibEntryRelationDAO(file.toAbsolutePath(), "test-relations"); + var file = Files.createFile(temporaryFolder.resolve(TEMPORARY_FOLDER_NAME)); + var dao = new MVStoreBibEntryRelationDAO(file.toAbsolutePath(), MAP_NAME); Assertions.assertFalse(dao.containsKey(bibEntry)); var firstRelations = createRelations(bibEntry); var secondRelations = createRelations(bibEntry); @@ -95,8 +104,8 @@ void DAOShouldMergeRelationsWhenInserting(BibEntry bibEntry) throws IOException @MethodSource("createBibEntries") void containsKeyShouldReturnFalseIfNothingWasInserted(BibEntry entry) throws IOException { // GIVEN - var file = Files.createFile(temporaryFolder.resolve("bib_entry_relations_test_not_contains_store")); - var dao = new MVStoreBibEntryRelationDAO(file.toAbsolutePath(), "test-relations"); + var file = Files.createFile(temporaryFolder.resolve(TEMPORARY_FOLDER_NAME)); + var dao = new MVStoreBibEntryRelationDAO(file.toAbsolutePath(), MAP_NAME); // THEN Assertions.assertFalse(dao.containsKey(entry)); @@ -106,8 +115,8 @@ void containsKeyShouldReturnFalseIfNothingWasInserted(BibEntry entry) throws IOE @MethodSource("createBibEntries") void containsKeyShouldReturnTrueIfRelationsWereInserted(BibEntry entry) throws IOException { // GIVEN - var file = Files.createFile(temporaryFolder.resolve("bib_entry_relations_test_contains_store")); - var dao = new MVStoreBibEntryRelationDAO(file.toAbsolutePath(), "test-relations"); + var file = Files.createFile(temporaryFolder.resolve(TEMPORARY_FOLDER_NAME)); + var dao = new MVStoreBibEntryRelationDAO(file.toAbsolutePath(), MAP_NAME); var relations = createRelations(entry); // WHEN @@ -116,4 +125,42 @@ void containsKeyShouldReturnTrueIfRelationsWereInserted(BibEntry entry) throws I // THEN Assertions.assertTrue(dao.containsKey(entry)); } + + @ParameterizedTest + @MethodSource("createBibEntries") + void isUpdatableShouldReturnTrueBeforeInsertionsAndFalseAfterInsertions(BibEntry entry) throws IOException { + // GIVEN + var file = Files.createFile(temporaryFolder.resolve(TEMPORARY_FOLDER_NAME)); + var dao = new MVStoreBibEntryRelationDAO(file.toAbsolutePath(), MAP_NAME); + var relations = createRelations(entry); + Assertions.assertTrue(dao.isUpdatable(entry)); + + // WHEN + dao.cacheOrMergeRelations(entry, relations); + + // THEN + Assertions.assertFalse(dao.isUpdatable(entry)); + } + + @ParameterizedTest + @MethodSource("createBibEntries") + void isUpdatableShouldReturnTrueAfterOneWeek(BibEntry entry) throws IOException { + // GIVEN + var file = Files.createFile(temporaryFolder.resolve(TEMPORARY_FOLDER_NAME)); + var dao = new MVStoreBibEntryRelationDAO(file.toAbsolutePath(), MAP_NAME); + var relations = createRelations(entry); + var clock = Clock.fixed(Instant.now(), ZoneId.of("UTC")); + Assertions.assertTrue(dao.isUpdatable(entry, clock)); + + // WHEN + dao.cacheOrMergeRelations(entry, relations); + + // THEN + Assertions.assertFalse(dao.isUpdatable(entry, clock)); + var clockOneWeekAfter = Clock.fixed( + LocalDateTime.now(ZoneId.of("UTC")).plusWeeks(1).toInstant(ZoneOffset.UTC), + ZoneId.of("UTC") + ); + Assertions.assertTrue(dao.isUpdatable(entry, clockOneWeekAfter)); + } }