Skip to content

Add fulltext fetcher for Wiley via their TDM API#15388

Merged
subhramit merged 5 commits into
JabRef:mainfrom
hagerm98:add-wiley-tdm-fetcher-13404
Mar 27, 2026
Merged

Add fulltext fetcher for Wiley via their TDM API#15388
subhramit merged 5 commits into
JabRef:mainfrom
hagerm98:add-wiley-tdm-fetcher-13404

Conversation

@hagerm98

@hagerm98 hagerm98 commented Mar 22, 2026

Copy link
Copy Markdown
Contributor

Related issues and pull requests

Closes #13404

PR Description

Wiley journals block direct PDF downloads from website links with Cloudflare, so "Get fulltext" always fails for Wiley DOIs.

This PR adds a fulltext fetcher that uses Wiley's official TDM REST API to retrieve PDFs, with the user's personal TDM token configured in Preferences UI > Web search.

Since the TDM API requires an auth header (Wiley-TDM-Client-Token) on every request including both the isPDF check (HEAD requests) the PDF download itself, I had to touch across many files to extend the FullText fetchers download logic to support per-fetcher HTTP headers, and passing those headers down the way through to both the PDF validation and download steps. This explains the number of files touched in this PR

TODO/In-Progress: Currently working on user documentation and will submit another PR against https://github.com/JabRef/user-documentation

Steps to test

At the end a video of my end-to-end test

  1. Register for a Wiley TDM API token at Wiley Text and Data Mining (requires having a Wiley account and accepting the click-through license)
  2. Start JabRef from this branch add-wiley-tdm-fetcher-13404
  3. Go to File > Preferences > Web search, find "Wiley TDM" in the list, Click "Use custom API key" and paste your token
  4. Create a new entry with an article from Wiley (for ex: DOI 10.1002/we.2952 / a Wiley Wind Energy article)
  5. Select the entry, go to Lookup > Find full text documents online
  6. The PDF should download successfully

Without the API token configured, step 5 should return the error in the original issue "403 Access Denied" returned by Cloudflare.

To verify existing fetchers still work, try the same with an arXiv entry (e.g., DOI 10.48550/arXiv.2301.00234).

This below is an End-to-end video I recorded demonstrating all three scenarios (fail without key, succeed with key, arXiv still works/backward compatible):

wiley-fetcher-test-e2e.mp4

Here also is the screenshot asked for in the checklist below

jabref-hager-khamis-#13404

Checklist

  • I own the copyright of the code submitted and I license it under the MIT license
  • I manually tested my changes in running JabRef (always required)
  • I added JUnit tests for changes (if applicable)
  • I added screenshots in the PR description (if change is visible to the user)
  • I added a screenshot in the PR description showing a library with a single entry with me as author and as title the issue number
  • I described the change in CHANGELOG.md in a way that can be understood by the average user (if change is visible to the user)
  • I checked the user documentation for up to dateness and submitted a pull request to our user documentation repository

- Implemented a new `WileyFetcher` fulltext fetcher
- Refactored the Download and isPDF execution flow to pass down headers from fetchers till the actual file download logic to support request headers
- Adjusted the `getMimeType()` method to include the set headers in the HEAD request
- Added another loop in the Preferences UI View (`WebSearchTabViewModel`) to include `CustomizableKeyFetcher`(s) which were not included as `SearchBasedFetchers`
- Implemented Unit tests for `WileyFetcher` and added one new test to `FulltextFetchersTest` for asserting that headers propagate down to download logic
- Wired `WileyTdmApiKey` preference through BuildInfo, build.properties, build.gradle.kts and JabRefCliPreferences
- Updated devdocs (`fetchers.md`)
- Updated `CHANGELOG.md`
@github-actions

Copy link
Copy Markdown
Contributor

Hey @hagerm98! 👋

Thank you for contributing to JabRef!

We have automated checks in place, based on which you will soon get feedback if any of them are failing. We also use Qodo for review assistance. It will update your pull request description with a review help and offer suggestions to improve the pull request.

After all automated checks pass, a maintainer will also review your contribution. Once that happens, you can go through their comments in the "Files changed" tab and act on them, or reply to the conversation if you have further inputs. You can read about the whole pull request process in our contribution guide.

Please ensure that your pull request is in line with our AI Usage Policy and make necessary disclosures.

@github-actions github-actions Bot added first contrib good second issue Issues that involve a tour of two or three interweaved components in JabRef component: fetcher labels Mar 22, 2026
Comment thread jablib/src/main/java/org/jabref/logic/importer/FulltextFetcher.java
@testlens-app

This comment has been minimized.

@github-actions github-actions Bot added the status: changes-required Pull requests that are not yet complete label Mar 22, 2026
@testlens-app

This comment has been minimized.

@github-actions github-actions Bot added status: no-bot-comments and removed status: changes-required Pull requests that are not yet complete labels Mar 22, 2026
@testlens-app

This comment has been minimized.

@hagerm98 hagerm98 marked this pull request as ready for review March 22, 2026 22:04
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Review Summary by Qodo

Add Wiley TDM API fulltext fetcher with authenticated download headers support

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Added Wiley TDM API fulltext fetcher for PDF downloads from Wiley journals
• Extended fulltext fetcher infrastructure to support per-fetcher HTTP headers
• Refactored download flow to propagate authentication headers through validation and download steps
• Updated preferences UI to include customizable key fetchers in web search settings
• Added comprehensive unit tests for WileyFetcher and header propagation
Diagram
flowchart LR
  A["BibEntry with DOI"] -->|WileyFetcher| B["TDM API URL"]
  B -->|getDownloadHeaders| C["Auth Header Map"]
  C -->|FetcherResult| D["URL + Headers"]
  D -->|URLDownload| E["PDF Download"]
  E -->|LinkedFile| F["Entry Attachment"]
Loading

Grey Divider

File Changes

1. jablib/src/main/java/org/jabref/logic/importer/fetcher/WileyFetcher.java ✨ Enhancement +83/-0

New Wiley TDM API fulltext fetcher implementation

jablib/src/main/java/org/jabref/logic/importer/fetcher/WileyFetcher.java


2. jablib/src/main/java/org/jabref/logic/importer/FulltextFetcher.java ✨ Enhancement +9/-0

Added getDownloadHeaders default method to interface

jablib/src/main/java/org/jabref/logic/importer/FulltextFetcher.java


3. jablib/src/main/java/org/jabref/logic/importer/FetcherResult.java ✨ Enhancement +11/-0

Extended to include HTTP headers in result

jablib/src/main/java/org/jabref/logic/importer/FetcherResult.java


View more (17)
4. jablib/src/main/java/org/jabref/logic/importer/FulltextFetchers.java ✨ Enhancement +10/-8

Refactored to propagate headers through fetcher pipeline

jablib/src/main/java/org/jabref/logic/importer/FulltextFetchers.java


5. jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java ✨ Enhancement +4/-1

Registered WileyFetcher in fulltext and customizable fetchers

jablib/src/main/java/org/jabref/logic/importer/WebFetchers.java


6. jablib/src/main/java/org/jabref/logic/net/URLDownload.java ✨ Enhancement +3/-3

Updated getMimeType to include request headers

jablib/src/main/java/org/jabref/logic/net/URLDownload.java


7. jabgui/src/main/java/org/jabref/gui/externalfiles/DownloadFullTextAction.java ✨ Enhancement +11/-11

Changed return type to FetcherResult with headers support

jabgui/src/main/java/org/jabref/gui/externalfiles/DownloadFullTextAction.java


8. jabgui/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java ✨ Enhancement +5/-0

Added headers parameter to download method

jabgui/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java


9. jabgui/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditorViewModel.java ✨ Enhancement +9/-4

Updated to pass headers from fetcher result to download

jabgui/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditorViewModel.java


10. jabgui/src/main/java/org/jabref/gui/linkedfile/DownloadLinkedFileAction.java ✨ Enhancement +12/-2

Added downloadHeaders field and propagation to file download

jabgui/src/main/java/org/jabref/gui/linkedfile/DownloadLinkedFileAction.java


11. jabgui/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTabViewModel.java ✨ Enhancement +18/-0

Added loop to include non-search-based customizable fetchers

jabgui/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTabViewModel.java


12. jablib/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java ⚙️ Configuration changes +2/-0

Wired WileyFetcher API key from build configuration

jablib/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java


13. jablib/src/main/java/org/jabref/logic/util/BuildInfo.java ⚙️ Configuration changes +2/-0

Added wileyTdmApiKey field from build properties

jablib/src/main/java/org/jabref/logic/util/BuildInfo.java


14. jablib/build.gradle.kts ⚙️ Configuration changes +3/-0

Added WileyTdmApiKey environment variable and build property

jablib/build.gradle.kts


15. jablib/src/main/resources/build.properties ⚙️ Configuration changes +1/-0

Added wileyTdmApiKey placeholder for build expansion

jablib/src/main/resources/build.properties


16. jablib/src/test/java/org/jabref/logic/importer/fetcher/WileyFetcherTest.java 🧪 Tests +99/-0

New comprehensive unit tests for WileyFetcher functionality

jablib/src/test/java/org/jabref/logic/importer/fetcher/WileyFetcherTest.java


17. jablib/src/test/java/org/jabref/logic/importer/FulltextFetchersTest.java 🧪 Tests +21/-4

Added test for header propagation to fetcher results

jablib/src/test/java/org/jabref/logic/importer/FulltextFetchersTest.java


18. .linkspector.yml ⚙️ Configuration changes +2/-0

Excluded onlinelibrary.wiley.com from link checker

.linkspector.yml


19. CHANGELOG.md 📝 Documentation +1/-0

Documented new Wiley TDM fulltext fetcher feature

CHANGELOG.md


20. docs/code-howtos/fetchers.md 📝 Documentation +15/-0

Added Wiley TDM fetcher documentation and authenticated downloads section

docs/code-howtos/fetchers.md


Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Mar 22, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (1) 📎 Requirement gaps (0) 📐 Spec deviations (0)

Grey Divider


Action required

1. download overload has boolean 📘 Rule violation ⚙ Maintainability
Description
A new public method overload introduces a boolean flag parameter, which makes call sites ambiguous
and reduces API clarity. This violates the guideline to avoid boolean parameters in public methods.
Code

jabgui/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java[R456-462]

 public void download(boolean keepHtmlLink) {
+        download(keepHtmlLink, Map.of());
+    }
+
+    public void download(boolean keepHtmlLink, Map<String, String> headers) {
     LOGGER.info("Downloading file from {}", linkedFile.getSourceUrl());
     if (!linkedFile.isOnlineLink()) {
Evidence
PR Compliance ID 13 forbids introducing new public methods with boolean parameters controlling
behavior. The PR adds public void download(boolean keepHtmlLink, Map headers).

AGENTS.md
jabgui/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java[456-462]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A new public overload `download(boolean keepHtmlLink, Map&amp;lt;String, String&amp;gt; headers)` introduces a boolean flag parameter, which is discouraged for public APIs.
## Issue Context
Boolean flags make call sites unclear (e.g., `download(true, headers)`), and the compliance checklist requires avoiding boolean parameters in new public methods.
## Fix Focus Areas
- jabgui/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java[456-462]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Redirect loses auth headers 🐞 Bug ⛯ Reliability
Description
Authenticated downloads can fail after HTTP redirects because URLDownload.openConnection() follows
redirects by creating a new URLDownload instance without copying previously added headers, so the
redirected request drops the Wiley-TDM-Client-Token. This can lead to passing the PDF “isPDF”
validation (which keeps headers) but failing the actual file download when a redirect occurs.
Code

jabgui/src/main/java/org/jabref/gui/linkedfile/DownloadLinkedFileAction.java[R129-132]

     try {
         URLDownload urlDownload = new URLDownload(downloadUrl);
+            downloadHeaders.forEach(urlDownload::addHeader);
         if (!checkSSLHandshake(urlDownload)) {
Evidence
This PR starts attaching per-fetcher headers to URLDownload instances used for downloading (both in
the initial URLDownload and in the FileDownloadTask). However, URLDownload.openConnection()
implements redirect handling via recursion using new URLDownload(newUrl), which reinitializes the
internal header/parameters map (only User-Agent is set in the constructor), so the redirected
request is sent without the authentication headers. In contrast, the MIME-type/PDF check path uses
Unirest calls that include .headers(parameters) on each request, so it can succeed even when the
later openConnection-based download fails after redirect.

jabgui/src/main/java/org/jabref/gui/linkedfile/DownloadLinkedFileAction.java[129-137]
jabgui/src/main/java/org/jabref/gui/linkedfile/DownloadLinkedFileAction.java[296-320]
jablib/src/main/java/org/jabref/logic/net/URLDownload.java[334-407]
jablib/src/main/java/org/jabref/logic/net/URLDownload.java[125-176]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Authenticated fulltext downloads can break on redirects because `URLDownload.openConnection()` handles redirects by creating a fresh `URLDownload(newUrl)` which does not inherit previously added headers (e.g., `Wiley-TDM-Client-Token`). This can cause downloads to fail after a 30x redirect even though the PDF validation step succeeds.
### Issue Context
The PR introduces per-fetcher download headers and correctly applies them to `URLDownload` instances. The redirect behavior in `URLDownload.openConnection()` predates this PR, but it becomes a functional problem now that authenticated-download headers are relied on.
### Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/net/URLDownload.java[334-407]
- jabgui/src/main/java/org/jabref/gui/linkedfile/DownloadLinkedFileAction.java[129-137]
- jabgui/src/main/java/org/jabref/gui/linkedfile/DownloadLinkedFileAction.java[296-320]
### Suggested approach
1. Update redirect handling in `URLDownload.openConnection()` to preserve headers/parameters when following redirects. Options:
- Reuse the same `URLDownload` instance by updating its URL (requires refactor), or
- Create a new `URLDownload(newUrl)` and copy the existing `parameters` entries into it before opening the connection.
2. Add/adjust tests to cover redirect behavior with custom headers (can be a unit test around `URLDownload` that simulates a redirect and asserts headers applied on subsequent connection).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Changelog uses internal jargon📘 Rule violation ⚙ Maintainability
Description
The new CHANGELOG entry uses developer-centric wording (fulltext fetcher) instead of end-user
focused language. This reduces clarity for average users reading release notes.
Code

CHANGELOG.md[14]

+- We added a fulltext fetcher for Wiley journals using the Wiley TDM API. [#13404](https://github.com/JabRef/jabref/issues/13404)
Evidence
PR Compliance ID 32 requires CHANGELOG entries to be end-user focused. The added line describes an
implementation detail (fulltext fetcher) rather than user-facing behavior.

AGENTS.md
CHANGELOG.md[14-14]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The CHANGELOG entry is phrased in developer-centric terms (&amp;quot;fulltext fetcher&amp;quot;) instead of describing the user-facing benefit.
## Issue Context
CHANGELOG entries should be understandable to average users and focus on what changed for them.
## Fix Focus Areas
- CHANGELOG.md[14-14]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. DOI not URL-encoded 🐞 Bug ✓ Correctness
Description
WileyFetcher builds the TDM API URL by concatenating the raw DOI string into the path, which can
create an invalid URL for DOIs containing characters not accepted by java.net.URI. This causes
URLUtil.create() to throw and the fetcher to fail (returning empty) for otherwise valid entries.
Code

jablib/src/main/java/org/jabref/logic/importer/fetcher/WileyFetcher.java[R44-50]

+        Optional<DOI> doi = entry.getField(StandardField.DOI).flatMap(DOI::parse);
+        if (doi.isEmpty()) {
+            return Optional.empty();
+        }
+
+        return Optional.of(URLUtil.create(TDM_API_URL + doi.get().asString()));
+    }
Evidence
WileyFetcher constructs the URL as TDM_API_URL + doi.asString() and passes it to URLUtil.create,
which uses new URI(trimmedUrl) and rejects invalid URI characters. Elsewhere in the codebase, DOIs
are URL-encoded when inserted into URLs (e.g., Crossref/DOI agency lookup), indicating encoding is
expected for robustness.

jablib/src/main/java/org/jabref/logic/importer/fetcher/WileyFetcher.java[37-50]
jablib/src/main/java/org/jabref/logic/util/URLUtil.java[97-124]
jablib/src/main/java/org/jabref/logic/importer/fetcher/DoiFetcher.java[222-228]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`WileyFetcher` constructs a URL by concatenating the raw DOI into the request path. Some DOI strings can contain characters that must be percent-encoded for a valid URI, causing `URLUtil.create(...)` to throw and the Wiley fetch to fail.
### Issue Context
`URLUtil.create` is strict and uses `java.net.URI` parsing. Other fetchers encode DOI strings when embedding them into URLs.
### Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/importer/fetcher/WileyFetcher.java[37-50]
### Suggested approach
- Build the Wiley TDM URL using proper path-segment encoding.
- Prefer constructing via `URIBuilder` and appending DOI path segments (split on `/`) to preserve slashes while encoding unsafe characters in each segment.
- Alternatively, percent-encode each path segment of the DOI and join with `/`.
- Add a unit test using a DOI containing characters requiring encoding (e.g., parentheses or other reserved characters) to assert `findFullText` returns a valid URL instead of throwing.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@testlens-app

This comment has been minimized.

@calixtus

Copy link
Copy Markdown
Member

Dont be overwhelmed by our bots, sorry for the confusion, they are just ment for a first automated review, since we are just a small team maintaining jabref. We will look into your changes asap.

Comment thread jablib/src/main/java/org/jabref/logic/importer/FetcherResult.java
@subhramit

Copy link
Copy Markdown
Member

I think @InAnYan should also take a look at this one.

@testlens-app

testlens-app Bot commented Mar 24, 2026

Copy link
Copy Markdown

🚨 TestLens detected 12 failed tests 🚨

Here is what you can do:

  1. Inspect the test failures carefully.
  2. If you are convinced that some of the tests are flaky, you can mute them below.
  3. Finally, trigger a rerun by checking the rerun checkbox.

Test Summary

Check Project/Task Test Runs
Fetcher Tests / Fetcher tests :jablib:fetcherTest ArXivFetcherTest > abstractIsCleanedUp() ❌ ❌
Fetcher Tests / Fetcher tests :jablib:fetcherTest ArXivFetcherTest > findFullTextByDOI() ❌ ❌
Fetcher Tests / Fetcher tests :jablib:fetcherTest ArXivFetcherTest > findFullTextByTitle() ❌ ❌
Fetcher Tests / Fetcher tests :jablib:fetcherTest ArXivFetcherTest > findFullTextByTitleWithCurlyBracket() ❌ ❌
Fetcher Tests / Fetcher tests :jablib:fetcherTest ArXivFetcherTest > findFullTextByTitleWithCurlyBracketAndPartOfAuthor() ❌ ❌
Fetcher Tests / Fetcher tests :jablib:fetcherTest ArXivFetcherTest > searchEntryByOldId() ❌ ❌
Fetcher Tests / Fetcher tests :jablib:fetcherTest ArXivFetcherTest > searchEntryByPartOfTitle() ❌ ❌
Fetcher Tests / Fetcher tests :jablib:fetcherTest ArXivFetcherTest > searchEntryByPartOfTitleWithAcuteAccent() ❌ ❌
Fetcher Tests / Fetcher tests :jablib:fetcherTest ArXivFetcherTest > supportsPhraseSearch() ❌ ❌
Fetcher Tests / Fetcher tests :jablib:fetcherTest CompositeIdFetcherTest > performSearchByIdReturnsCorrectEntryForIdentifier(String, BibEntry, String) > 1 "performSearchByIdReturnsCorrectEntryForArXivId" ❌ ❌
Fetcher Tests / Fetcher tests :jablib:fetcherTest CrawlerTest > whetherAllFilesAreCreated() ❌ ❌
Fetcher Tests / Fetcher tests :jablib:fetcherTest CrossRefCitationFetcherTest > getReferences() ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest CrossRefTest > performSearchByIdFindsPaperWithoutTitle() ❌ ❌
Fetcher Tests / Fetcher tests :jablib:fetcherTest GrobidPlainCitationParserTest > grobidPerformSearchCorrectResultTest(String, BibEntry, String) > "example1" ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest GrobidPlainCitationParserTest > grobidPerformSearchCorrectResultTest(String, BibEntry, String) > "example2" ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest GrobidPlainCitationParserTest > grobidPerformSearchCorrectResultTest(String, BibEntry, String) > "example3" ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest GrobidPlainCitationParserTest > grobidPerformSearchCorrectResultTest(String, BibEntry, String) > "example4" ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest GrobidPlainCitationParserTest > grobidPerformSearchWithEmptyStringTest() ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest GrobidServiceTest > extractsReferencesFromPdf() ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest GrobidServiceTest > processEmptyStringTest() ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest GrobidServiceTest > processPdfTest() ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest GrobidServiceTest > processValidCitationTest() ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest ISIDOREFetcherTest > author() ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest ISIDOREFetcherTest > checkThesis() ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest ISIDOREFetcherTest > noResults() ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest LibraryOfCongressTest > performSearchById() ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest MedlineFetcherTest > emptyEntryList() ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest MedlineFetcherTest > multipleEntries() ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest MedlineFetcherTest > searchByIDEndharti() ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest MedlineFetcherTest > searchByIDIchikawa() ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest MedlineFetcherTest > searchByIDSari() ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest MedlineFetcherTest > searchByIDWijedasa() ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest MedlineFetcherTest > withLuceneQueryAuthorDate() ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest MedlineFetcherTest > withLuceneQueryAuthorDateRange() ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest OpenAlexFetcherTest > getURLForQueryBuildsSearchUrl() ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest OpenAlexFetcherTest > getURLForQueryWithLucene() ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest OpenAlexFetcherTest > parserParsesResultsArray() ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest OpenAlexFetcherTest > parserParsesSingleWorkObject() ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest OpenAlexFetcherTest > searchByQueryFindsEntry() ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest OpenAlexFetcherTest > searchByQuotedQueryFindsEntry() ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest PdfMergeMetadataImporterTest > fetchArxivInformationForPdfWithArxivId() ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest PdfMergeMetadataImporterTest > importRelativizesFilePath() ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest PdfMergeMetadataImporterTest > pdfMetadataExtractedFrom2024SPLCBecker() ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest SemanticScholarTest > getDocument() ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest URLDownloadTest > test429ErrorThrowsFetcherClientException() ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest URLDownloadTest > test503ErrorThrowsFetcherServerException() ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest ZbMATHTest > searchByEntryFindsEntry() ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest ZbMATHTest > searchByIdFindsEntry() ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest ZbMATHTest > searchByIdInEntryFindsEntry() ❌ 🚫
Fetcher Tests / Fetcher tests :jablib:fetcherTest ZbMATHTest > searchByQueryFindsEntry() ❌ 🚫

🏷️ Commit: a7e9b97
▶️ Tests: 10336 executed
⚪️ Checks: 79/79 completed

Test Failures (first 5 of 12)

ArXivFetcherTest > abstractIsCleanedUp() (:jablib:fetcherTest in Fetcher Tests / Fetcher tests)
org.jabref.logic.importer.FetcherException: arXiv API request failed
URL: https://export.arxiv.org/api/query?id_list=2407.02238&start=0&max_results=1
	at org.jabref.logic.importer.fetcher.ArXivFetcher$ArXiv.callApi(ArXivFetcher.java:535)
	at org.jabref.logic.importer.fetcher.ArXivFetcher$ArXiv.queryApi(ArXivFetcher.java:477)
	at org.jabref.logic.importer.fetcher.ArXivFetcher$ArXiv.searchForEntryById(ArXivFetcher.java:417)
	at org.jabref.logic.importer.fetcher.ArXivFetcher$ArXiv.performSearchById(ArXivFetcher.java:609)
	at org.jabref.logic.importer.fetcher.ArXivFetcher$ArXiv.lambda$asyncPerformSearchById$0(ArXivFetcher.java:600)
	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1789)
	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1781)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:511)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1450)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:2019)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:187)
Caused by: java.io.IOException: Server returned HTTP response code: 429 for URL: https://export.arxiv.org/api/query?id_list=2407.02238&start=0&max_results=1
	at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:483)
	at java.base/sun.net.www.protocol.http.HttpURLConnection.getChainedException(HttpURLConnection.java:1751)
	at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1321)
	at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1302)
	at java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:223)
	at org.jabref.logic.importer.fetcher.ArXivFetcher$ArXiv.callApi(ArXivFetcher.java:532)
	... 10 more
Caused by: java.io.IOException: Server returned HTTP response code: 429 for URL: https://export.arxiv.org/api/query?id_list=2407.02238&start=0&max_results=1
	at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1700)
	at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1302)
	at java.base/java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:493)
	at java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:307)
	at org.jabref.logic.importer.fetcher.ArXivFetcher$ArXiv.callApi(ArXivFetcher.java:528)
	... 10 more
ArXivFetcherTest > findFullTextByDOI() (:jablib:fetcherTest in Fetcher Tests / Fetcher tests)
org.opentest4j.AssertionFailedError: expected: <Optional[https://arxiv.org/pdf/cond-mat/0406246v1]> but was: <Optional.empty>
	at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:158)
	at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:139)
	at org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:201)
	at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:184)
	at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:179)
	at org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:1188)
	at org.jabref.logic.importer.fetcher.ArXivFetcherTest.findFullTextByDOI(ArXivFetcherTest.java:202)
expected actual
Optional[https://arxiv.org/pdf/cond-mat/0406246v1] Optional.empty
ArXivFetcherTest > findFullTextByTitle() (:jablib:fetcherTest in Fetcher Tests / Fetcher tests)
org.opentest4j.AssertionFailedError: expected: <Optional[https://arxiv.org/pdf/cond-mat/0406246v1]> but was: <Optional.empty>
	at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:158)
	at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:139)
	at org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:201)
	at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:184)
	at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:179)
	at org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:1188)
	at org.jabref.logic.importer.fetcher.ArXivFetcherTest.findFullTextByTitle(ArXivFetcherTest.java:229)
expected actual
Optional[https://arxiv.org/pdf/cond-mat/0406246v1] Optional.empty
ArXivFetcherTest > findFullTextByTitleWithCurlyBracket() (:jablib:fetcherTest in Fetcher Tests / Fetcher tests)
org.opentest4j.AssertionFailedError: expected: <Optional[https://arxiv.org/pdf/2010.15942v3]> but was: <Optional.empty>
	at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:158)
	at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:139)
	at org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:201)
	at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:184)
	at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:179)
	at org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:1188)
	at org.jabref.logic.importer.fetcher.ArXivFetcherTest.findFullTextByTitleWithCurlyBracket(ArXivFetcherTest.java:236)
expected actual
Optional[https://arxiv.org/pdf/2010.15942v3] Optional.empty
ArXivFetcherTest > findFullTextByTitleWithCurlyBracketAndPartOfAuthor() (:jablib:fetcherTest in Fetcher Tests / Fetcher tests)
org.opentest4j.AssertionFailedError: expected: <Optional[https://arxiv.org/pdf/2010.15942v3]> but was: <Optional.empty>
	at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:158)
	at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:139)
	at org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:201)
	at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:184)
	at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:179)
	at org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:1188)
	at org.jabref.logic.importer.fetcher.ArXivFetcherTest.findFullTextByTitleWithCurlyBracketAndPartOfAuthor(ArXivFetcherTest.java:268)
expected actual
Optional[https://arxiv.org/pdf/2010.15942v3] Optional.empty

Muted Tests

Select tests to mute in this pull request:

  • ArXivFetcherTest > abstractIsCleanedUp()
  • ArXivFetcherTest > findFullTextByDOI()
  • ArXivFetcherTest > findFullTextByTitle()
  • ArXivFetcherTest > findFullTextByTitleWithCurlyBracket()
  • ArXivFetcherTest > findFullTextByTitleWithCurlyBracketAndPartOfAuthor()
  • ArXivFetcherTest > searchEntryByOldId()
  • ArXivFetcherTest > searchEntryByPartOfTitle()
  • ArXivFetcherTest > searchEntryByPartOfTitleWithAcuteAccent()
  • ArXivFetcherTest > supportsPhraseSearch()
  • CompositeIdFetcherTest > performSearchByIdReturnsCorrectEntryForIdentifier(String, BibEntry, String)
  • CrawlerTest > whetherAllFilesAreCreated()
  • CrossRefCitationFetcherTest > getReferences()
  • CrossRefTest > performSearchByIdFindsPaperWithoutTitle()
  • GrobidPlainCitationParserTest > grobidPerformSearchCorrectResultTest(String, BibEntry, String)
  • GrobidPlainCitationParserTest > grobidPerformSearchWithEmptyStringTest()
  • GrobidServiceTest > extractsReferencesFromPdf()
  • GrobidServiceTest > processEmptyStringTest()
  • GrobidServiceTest > processPdfTest()
  • GrobidServiceTest > processValidCitationTest()
  • ISIDOREFetcherTest > author()
  • ISIDOREFetcherTest > checkThesis()
  • ISIDOREFetcherTest > noResults()
  • LibraryOfCongressTest > performSearchById()
  • MedlineFetcherTest > emptyEntryList()
  • MedlineFetcherTest > multipleEntries()
  • MedlineFetcherTest > searchByIDEndharti()
  • MedlineFetcherTest > searchByIDIchikawa()
  • MedlineFetcherTest > searchByIDSari()
  • MedlineFetcherTest > searchByIDWijedasa()
  • MedlineFetcherTest > withLuceneQueryAuthorDate()
  • MedlineFetcherTest > withLuceneQueryAuthorDateRange()
  • OpenAlexFetcherTest > getURLForQueryBuildsSearchUrl()
  • OpenAlexFetcherTest > getURLForQueryWithLucene()
  • OpenAlexFetcherTest > parserParsesResultsArray()
  • OpenAlexFetcherTest > parserParsesSingleWorkObject()
  • OpenAlexFetcherTest > searchByQueryFindsEntry()
  • OpenAlexFetcherTest > searchByQuotedQueryFindsEntry()
  • PdfMergeMetadataImporterTest > fetchArxivInformationForPdfWithArxivId()
  • PdfMergeMetadataImporterTest > importRelativizesFilePath()
  • PdfMergeMetadataImporterTest > pdfMetadataExtractedFrom2024SPLCBecker()
  • SemanticScholarTest > getDocument()
  • URLDownloadTest > test429ErrorThrowsFetcherClientException()
  • URLDownloadTest > test503ErrorThrowsFetcherServerException()
  • ZbMATHTest > searchByEntryFindsEntry()
  • ZbMATHTest > searchByIdFindsEntry()
  • ZbMATHTest > searchByIdInEntryFindsEntry()
  • ZbMATHTest > searchByQueryFindsEntry()

Reuse successful test results:

  • ♻️ Only rerun the tests that failed or were muted before

Click the checkbox to trigger a rerun:

  • Rerun jobs

Learn more about TestLens at testlens.app.

@calixtus calixtus requested a review from InAnYan March 24, 2026 22:08

@InAnYan InAnYan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, very good! The code quality is high, and I especially like that you introduced a new structure that captures the headers.

But I haven't tested the code.

About the remarque of the loop: I think it can be improved in a follow up

@hagerm98

Copy link
Copy Markdown
Contributor Author

Thanks @InAnYan for your review,

If you've got a moment can you check the failing CI above (Auto-remove review request / guard-reviewers (pull_request_target))) ? It seems flakey sometimes failing and some not and I don't have access to retry

@subhramit

subhramit commented Mar 27, 2026

Copy link
Copy Markdown
Member

If you've got a moment can you check the failing CI above (Auto-remove review request / guard-reviewers (pull_request_target))) ? It seems flakey sometimes failing and some not and I don't have access to retry

Tagging @koppor (run https://github.com/JabRef/jabref/actions/runs/23514594953/job/68491180540?pr=15388)

PR LGTM as well so letting this in. Thank you for leaving comments on your changes - it helped in reviewing.

@subhramit subhramit added this pull request to the merge queue Mar 27, 2026
@github-actions github-actions Bot added the status: to-be-merged PRs which are accepted and should go into the merge-queue. label Mar 27, 2026
Merged via the queue into JabRef:main with commit 98ee51e Mar 27, 2026
77 of 81 checks passed
@hagerm98

Copy link
Copy Markdown
Contributor Author

Thanks @subhramit for your review, appreciate it!

Regarding the user documentation update, I've opened a PR here, feel free to take a look when you get a chance
#622 Document Wiley TDM fulltext fetcher setup

Siedlerchr added a commit to geovani-rocha/jabref that referenced this pull request Mar 28, 2026
…o fix-group-icons

* 'fix-group-icons' of github.com:geovani-rocha/jabref: (26 commits)
  chore(deps): update dependency org.apache.logging.log4j:log4j-to-slf4j to v2.25.4 (JabRef#15436)
  chore(deps): update jackson monorepo to v3.1.1 (JabRef#15435)
  Fix PushToPreferences reset and import (JabRef#15395)
  Add fulltext fetcher for Wiley via their TDM API (JabRef#15388)
  Embed in-text nature in reference marks for CSL citations (JabRef#15381)
  Chore(deps): Bump com.gradleup.shadow:shadow-gradle-plugin (JabRef#15430)
  Fix not on fx thread exceptions for cleanup and cite key generator (JabRef#15424)
  Revert "Update gradle to nightly of 2026-03-23 (JabRef#15372)"
  feat: add benchmarks for Lucene fulltext search and linked file indexing, including setup and teardown of the index. (JabRef#15385)
  Chore(deps): Bump org.openrewrite.recipe:rewrite-recipe-bom (JabRef#15418)
  Add claude gitignore (JabRef#15413)
  Fix group filter icon in side pane (JabRef#15408)
  Add new prs_link feature
  Chore(deps): Bump org.glassfish.hk2:hk2-api in /versions (JabRef#15422)
  Chore(deps): Bump org.openrewrite.rewrite from 7.28.2 to 7.29.0 (JabRef#15419)
  Chore(deps): Bump jablib/src/main/resources/csl-styles (JabRef#15417)
  Fix for inconsistent "hide tab bar" behavior (JabRef#15409)
  Update dependency org.glassfish.hk2:hk2-utils to v4 (JabRef#15407)
  Persist file notifications (JabRef#15403)
  Update dependency org.glassfish.hk2:hk2-locator to v4 (JabRef#15405)
  ...
Ranjeet2702 pushed a commit to Ranjeet2702/jabref that referenced this pull request Apr 14, 2026
* Add fulltext fetcher for Wiley via their TDM API

- Implemented a new `WileyFetcher` fulltext fetcher
- Refactored the Download and isPDF execution flow to pass down headers from fetchers till the actual file download logic to support request headers
- Adjusted the `getMimeType()` method to include the set headers in the HEAD request
- Added another loop in the Preferences UI View (`WebSearchTabViewModel`) to include `CustomizableKeyFetcher`(s) which were not included as `SearchBasedFetchers`
- Implemented Unit tests for `WileyFetcher` and added one new test to `FulltextFetchersTest` for asserting that headers propagate down to download logic
- Wired `WileyTdmApiKey` preference through BuildInfo, build.properties, build.gradle.kts and JabRefCliPreferences
- Updated devdocs (`fetchers.md`)
- Updated `CHANGELOG.md`

* Move `setDownloadHeaders()` to correct position to fix constructor order

* Exclude onlinelibrary.wiley.com from link checker since blocked by Cloudflare

* Update CHANGELOG.md to use end user focused language

* Switch FetcherResult to a record and add TODO comment to refactor Preferences UI loop
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component: fetcher first contrib good second issue Issues that involve a tour of two or three interweaved components in JabRef status: no-bot-comments status: to-be-merged PRs which are accepted and should go into the merge-queue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Get fulltext does not work for Wiley Wind Energy

4 participants