Skip to content

Commit

Permalink
Refactor Citations Relations Service Layer (#11189)
Browse files Browse the repository at this point in the history
* Move logic from repository to service
* Refactor repositories
* Update tab configuration
  • Loading branch information
alexandre-cremieux committed Sep 29, 2024
1 parent 8048da8 commit 18db75e
Show file tree
Hide file tree
Showing 15 changed files with 552 additions and 371 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,16 @@
import org.jabref.gui.StateManager;
import org.jabref.gui.desktop.os.NativeDesktop;
import org.jabref.gui.entryeditor.EntryEditorTab;
import org.jabref.logic.citation.repository.BibEntryRelationsCache;
import org.jabref.logic.citation.repository.BibEntryRelationsRepository;
import org.jabref.logic.importer.fetcher.CitationFetcher;
import org.jabref.logic.importer.fetcher.SemanticScholarCitationFetcher;
import org.jabref.gui.icon.IconTheme;
import org.jabref.gui.preferences.GuiPreferences;
import org.jabref.gui.util.NoSelectionModel;
import org.jabref.gui.util.ViewModelListCellFactory;
import org.jabref.logic.citation.repository.LRUBibEntryRelationsCache;
import org.jabref.logic.citation.repository.LRUBibEntryRelationsRepository;
import org.jabref.logic.citation.service.SearchCitationsRelationsService;
import org.jabref.logic.database.DuplicateCheck;
import org.jabref.logic.importer.fetcher.CitationFetcher;
import org.jabref.logic.importer.fetcher.SemanticScholarCitationFetcher;
import org.jabref.logic.l10n.Localization;
import org.jabref.logic.util.BackgroundTask;
import org.jabref.logic.util.TaskExecutor;
Expand Down Expand Up @@ -73,7 +74,7 @@ public class CitationRelationsTab extends EntryEditorTab {
private final GuiPreferences preferences;
private final LibraryTab libraryTab;
private final TaskExecutor taskExecutor;
private final BibEntryRelationsRepository bibEntryRelationsRepository;
private final SearchCitationsRelationsService searchCitationsRelationsService;
private final CitationsRelationsTabViewModel citationsRelationsTabViewModel;
private final DuplicateCheck duplicateCheck;

Expand All @@ -94,11 +95,22 @@ public CitationRelationsTab(DialogService dialogService,
setTooltip(new Tooltip(Localization.lang("Show articles related by citation")));

this.duplicateCheck = new DuplicateCheck(new BibEntryTypesManager());
this.bibEntryRelationsRepository = new BibEntryRelationsRepository(
var bibEntryRelationsRepository = new LRUBibEntryRelationsRepository(
new LRUBibEntryRelationsCache()
);
this.searchCitationsRelationsService = new SearchCitationsRelationsService(
new SemanticScholarCitationFetcher(preferences.getImporterPreferences()),
new BibEntryRelationsCache()
bibEntryRelationsRepository
);
citationsRelationsTabViewModel = new CitationsRelationsTabViewModel(
databaseContext,
preferences,
undoManager,
stateManager,
dialogService,
fileUpdateMonitor,
taskExecutor
);
citationsRelationsTabViewModel = new CitationsRelationsTabViewModel(databaseContext, preferences, undoManager, stateManager, dialogService, fileUpdateMonitor, taskExecutor);
}

/**
Expand Down Expand Up @@ -347,41 +359,54 @@ private void searchForRelations(BibEntry entry, CheckListView<CitationRelationIt
citedByTask.cancel();
}

BackgroundTask<List<BibEntry>> task;

if (searchType == CitationFetcher.SearchType.CITES) {
task = BackgroundTask.wrap(() -> {
if (shouldRefresh) {
bibEntryRelationsRepository.forceRefreshReferences(entry);
}
return bibEntryRelationsRepository.getReferences(entry);
});
citingTask = task;
} else {
task = BackgroundTask.wrap(() -> {
if (shouldRefresh) {
bibEntryRelationsRepository.forceRefreshCitations(entry);
}
return bibEntryRelationsRepository.getCitations(entry);
});
citedByTask = task;
}

task.onRunning(() -> prepareToSearchForRelations(abortButton, refreshButton, importButton, progress, task))
.onSuccess(fetchedList -> onSearchForRelationsSucceed(entry, listView, abortButton, refreshButton,
searchType, importButton, progress, fetchedList, observableList))
this.createBackGroundTask(entry, searchType, shouldRefresh)
.consumeOnRunning(task -> prepareToSearchForRelations(
abortButton, refreshButton, importButton, progress, task
))
.onSuccess(fetchedList -> onSearchForRelationsSucceed(
entry,
listView,
abortButton,
refreshButton,
searchType,
importButton,
progress,
fetchedList,
observableList
))
.onFailure(exception -> {
LOGGER.error("Error while fetching citing Articles", exception);
hideNodes(abortButton, progress, importButton);
listView.setPlaceholder(new Label(Localization.lang("Error while fetching citing entries: %0",
exception.getMessage())));

listView.setPlaceholder(
new Label(Localization.lang(
"Error while fetching citing entries: %0", exception.getMessage())
)
);
refreshButton.setVisible(true);
dialogService.notify(exception.getMessage());
})
.executeWith(taskExecutor);
}

private BackgroundTask<List<BibEntry>> createBackGroundTask(
BibEntry entry, CitationFetcher.SearchType searchType, boolean shouldRefresh
) {
return switch (searchType) {
case CitationFetcher.SearchType.CITES -> {
citingTask = BackgroundTask.wrap(
() -> this.searchCitationsRelationsService.searchReferences(entry, shouldRefresh)
);
yield citingTask;
}
case CitationFetcher.SearchType.CITED_BY -> {
citedByTask = BackgroundTask.wrap(
() -> this.searchCitationsRelationsService.searchCitations(entry, shouldRefresh)
);
yield citedByTask;
}
};
}

private void onSearchForRelationsSucceed(BibEntry entry, CheckListView<CitationRelationItem> listView,
Button abortButton, Button refreshButton,
CitationFetcher.SearchType searchType, Button importButton,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@

import org.jabref.gui.DialogService;
import org.jabref.gui.StateManager;
import org.jabref.logic.importer.fetcher.CitationFetcher;
import org.jabref.gui.externalfiles.ImportHandler;
import org.jabref.gui.preferences.GuiPreferences;
import org.jabref.logic.citationkeypattern.CitationKeyGenerator;
import org.jabref.logic.citationkeypattern.CitationKeyPatternPreferences;
import org.jabref.logic.importer.fetcher.CitationFetcher;
import org.jabref.logic.util.TaskExecutor;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.BibEntry;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,20 @@
package org.jabref.logic.citation.repository;

import java.util.List;

import org.jabref.model.entry.BibEntry;

public interface BibEntryRelationsRepository {

void insertCitations(BibEntry entry, List<BibEntry> citations);

List<BibEntry> readCitations(BibEntry entry);

boolean containsCitations(BibEntry entry);

void insertReferences(BibEntry entry, List<BibEntry> citations);

List<BibEntry> readReferences(BibEntry entry);

/**
* Fetch citations for a bib entry and update local database.
* @param entry should not be null
* @deprecated fetching citations should be done by the service layer (calling code)
*/
@Deprecated
void forceRefreshCitations(BibEntry entry);

/**
* Fetch references made by a bib entry and update local database.
* @param entry should not be null
* @deprecated fetching references should be done by the service layer (calling code)
*/
@Deprecated
void forceRefreshReferences(BibEntry entry);
boolean containsReferences(BibEntry entry);
}
Original file line number Diff line number Diff line change
@@ -1,38 +1,57 @@
package org.jabref.logic.citation.repository;

import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import org.eclipse.jgit.util.LRUMap;
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<String, List<BibEntry>> CITATIONS_MAP = new LRUMap<>(MAX_CACHED_ENTRIES, MAX_CACHED_ENTRIES);
private static final Map<String, List<BibEntry>> REFERENCES_MAP = new LRUMap<>(MAX_CACHED_ENTRIES, MAX_CACHED_ENTRIES);
private static final Map<DOI, Set<BibEntry>> CITATIONS_MAP = new LRUMap<>(MAX_CACHED_ENTRIES, MAX_CACHED_ENTRIES);
private static final Map<DOI, Set<BibEntry>> REFERENCES_MAP = new LRUMap<>(MAX_CACHED_ENTRIES, MAX_CACHED_ENTRIES);

public List<BibEntry> getCitations(BibEntry entry) {
return CITATIONS_MAP.getOrDefault(entry.getDOI().map(DOI::getDOI).orElse(""), Collections.emptyList());
return entry
.getDOI()
.stream()
.flatMap(doi -> CITATIONS_MAP.getOrDefault(doi, Set.of()).stream())
.toList();
}

public List<BibEntry> getReferences(BibEntry entry) {
return REFERENCES_MAP.getOrDefault(entry.getDOI().map(DOI::getDOI).orElse(""), Collections.emptyList());
return entry
.getDOI()
.stream()
.flatMap(doi -> REFERENCES_MAP.getOrDefault(doi, Set.of()).stream())
.toList();
}

public void cacheOrMergeCitations(BibEntry entry, List<BibEntry> citations) {
entry.getDOI().ifPresent(doi -> CITATIONS_MAP.put(doi.getDOI(), 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<BibEntry> references) {
entry.getDOI().ifPresent(doi -> REFERENCES_MAP.putIfAbsent(doi.getDOI(), 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 CITATIONS_MAP.containsKey(entry.getDOI().map(DOI::getDOI).orElse(""));
return entry.getDOI().map(CITATIONS_MAP::containsKey).orElse(false);
}

public boolean referencesCached(BibEntry entry) {
return REFERENCES_MAP.containsKey(entry.getDOI().map(DOI::getDOI).orElse(""));
return entry.getDOI().map(REFERENCES_MAP::containsKey).orElse(false);
}
}
Original file line number Diff line number Diff line change
@@ -1,78 +1,49 @@
package org.jabref.logic.citation.repository;

import java.util.List;
import java.util.Objects;

import org.jabref.logic.importer.fetcher.CitationFetcher;
import org.jabref.logic.importer.FetcherException;
import org.jabref.model.entry.BibEntry;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LRUBibEntryRelationsRepository implements BibEntryRelationsRepository {
private static final Logger LOGGER = LoggerFactory
.getLogger(LRUBibEntryRelationsRepository.class);

private final CitationFetcher fetcher;
private final LRUBibEntryRelationsCache cache;

public LRUBibEntryRelationsRepository(CitationFetcher fetcher, LRUBibEntryRelationsCache cache) {
this.fetcher = fetcher;
public LRUBibEntryRelationsRepository(LRUBibEntryRelationsCache cache) {
this.cache = cache;
}

@Override
public List<BibEntry> readCitations(BibEntry entry) {
if (needToRefreshCitations(entry)) {
forceRefreshCitations(entry);
}

return cache.getCitations(entry);
public void insertCitations(BibEntry entry, List<BibEntry> citations) {
cache.cacheOrMergeCitations(
entry, Objects.requireNonNullElseGet(citations, List::of)
);
}

@Override
public List<BibEntry> readReferences(BibEntry entry) {
if (needToRefreshReferences(entry)) {
List<BibEntry> references;
try {
references = fetcher.searchCiting(entry);
} catch (FetcherException e) {
LOGGER.error("Error while fetching references", e);
references = List.of();
}
cache.cacheOrMergeReferences(entry, references);
}

return cache.getReferences(entry);
public List<BibEntry> readCitations(BibEntry entry) {
return cache.getCitations(entry);
}

@Override
public void forceRefreshCitations(BibEntry entry) {
try {
List<BibEntry> citations = fetcher.searchCitedBy(entry);
cache.cacheOrMergeCitations(entry, citations);
} catch (FetcherException e) {
LOGGER.error("Error while fetching citations", e);
}
public boolean containsCitations(BibEntry entry) {
return cache.citationsCached(entry);
}

private boolean needToRefreshCitations(BibEntry entry) {
return !cache.citationsCached(entry);
@Override
public void insertReferences(BibEntry entry, List<BibEntry> references) {
cache.cacheOrMergeReferences(
entry, Objects.requireNonNullElseGet(references, List::of)
);
}

private boolean needToRefreshReferences(BibEntry entry) {
return !cache.referencesCached(entry);
@Override
public List<BibEntry> readReferences(BibEntry entry) {
return cache.getReferences(entry);
}

@Override
public void forceRefreshReferences(BibEntry entry) {
List<BibEntry> references;
try {
references = fetcher.searchCiting(entry);
} catch (FetcherException e) {
LOGGER.error("Error while fetching references", e);
references = List.of();
}
cache.cacheOrMergeReferences(entry, references);
public boolean containsReferences(BibEntry entry) {
return cache.referencesCached(entry);
}
}
Loading

0 comments on commit 18db75e

Please sign in to comment.