Skip to content

ZCU-PUB/Add bitstream-level access status/embargo date REST endpoint - #1377

Merged
milanmajchrak merged 2 commits into
customer/zcu-pubfrom
zcu-pub/be-bitstream-embargo-badge
Jul 21, 2026
Merged

ZCU-PUB/Add bitstream-level access status/embargo date REST endpoint#1377
milanmajchrak merged 2 commits into
customer/zcu-pubfrom
zcu-pub/be-bitstream-embargo-badge

Conversation

@MatusBeke

Copy link
Copy Markdown
Collaborator

Problem

The bitstream-level embargo badge on the frontend (dataquest-dev/dspace-angular#1378) needs to know a restricted bitstream's embargo end date. On DSpace 9.x lines (e.g. customer/mendelu) this is exposed via GET /bitstreams/{id}/accessStatus; on customer/zcu-pub (DSpace 7.6.1) that endpoint doesn't exist — only the item-level equivalent does.

Fixes the backend half of dataquest-dev/dspace-customers#823.

Root cause

Never ported: upstream introduced this in 4 commits on the 9.x line (ed02a605, 43e41e0f, 6a229f15, 0a0b5752), none of which landed on the 7.x/zcu-pub line.

Change set

A raw cherry-pick of those 4 commits was not viable — upstream 9.x uses java.time.LocalDate + jakarta.* and a larger type-parameterized refactor (culminating in a Pair<String, LocalDate>AccessStatus object replacement) that doesn't fit zcu-pub's simpler joda-time/javax.*/anonymous-only model. Re-derived the minimal intent instead, reusing zcu-pub's existing generalized helper methods (calculateAccessStatusForDso, retrieveShortestEmbargo) rather than duplicating them:

  • dspace-api: AccessStatusHelper / DefaultAccessStatusHelper / AccessStatusServiceImpl / AccessStatusService — added getAccessStatusFromBitstream / getEmbargoFromBitstream.
  • dspace-server-webapp:
    • AccessStatusRest gains an embargoDate field.
    • BitstreamRest gains an ACCESS_STATUS link.
    • New BitstreamAccessStatusLinkRepository (mirrors ItemAccessStatusLinkRepository; @PreAuthorize on METADATA_READ, matching BitstreamFormatLinkRepository's existing convention).
    • ItemAccessStatusLinkRepository also now populates embargoDate (was status-only, even though the service could already compute it) — a small scope addition beyond strictly "bitstream," done for DTO consistency between the item- and bitstream-level endpoints. Easy to drop if you'd rather keep this PR strictly bitstream-scoped — flagging for review.
  • REST contract: GET /server/api/core/bitstreams/{id}/accessStatus -> {status, embargoDate} — field names match what the frontend (PR CLARIN-DSpace v9/Port #1350 + #1347 + #1338 (DOI dedup, Shib special groups, tgz preview) to the v9 base #1378) expects.

Out of scope: the 9.x-specific current-user-vs-anonymous status calculation and the PairAccessStatus object refactor (commits 2-4) — not applicable to zcu-pub's simpler anonymous-only model, so only their intent (bitstream embargo exposed via REST) was folded in, not their exact diffs.

Test evidence

mvn checkstyle:check -pl dspace-api                 -> BUILD SUCCESS, no violations
mvn checkstyle:check -pl dspace-server-webapp        -> BUILD SUCCESS, no violations
mvn test -pl dspace-api -Dtest=AccessStatusServiceTest,DefaultAccessStatusHelperTest
                                                      -> Tests run: 16, Failures: 0, Errors: 0
mvn test -pl dspace-server-webapp -Dtest=AccessStatusRestTest
                                                      -> Tests run: 4, Failures: 0, Errors: 0
mvn verify -pl dspace-server-webapp -Dtest=BitstreamRestRepositoryIT
                                                      -> Tests run: 56, Failures: 0, Errors: 0, Skipped: 1 (pre-existing @Ignore, unrelated)

The integration test run surfaced two wrong assumptions in the tests themselves (not production code), both fixed:

  1. This fork's @LinkRest has no embedOptional — every declared link auto-embeds, so accessStatus legitimately became a 5th default embed on bitstreams; updated BitstreamMatcher.matchFullEmbeds() to expect it.
  2. The bitstream-level link is gated on METADATA_READ, and BitstreamMetadataReadPermissionEvaluatorPlugin denies with 401 (not 404) for a nonexistent bitstream — pre-existing platform behavior, differs from the item-level test; fixed the not-found test's expectation with a comment explaining why.

Risk & rollback

Low risk: additive REST surface (new link + new field on an existing DTO), no changes to existing item-level behavior apart from populating a previously-unset field. Revert is a straight git revert of this PR.

Notes / assumptions

Ports the intent of upstream commits ed02a60, 43e41e0, 6a229f1, and
0a0b575 (dataquest-dev/DSpace 9.x line) onto zcu-pub's DSpace 7.6.1
codebase. A raw cherry-pick was not viable: upstream uses
java.time.LocalDate + jakarta.* and refactors the helper around a
Pair<String, LocalDate>/AccessStatus object with a current-user-vs-
anonymous calculation type, none of which match zcu-pub's simpler
joda-time + javax.* shape. Instead, re-derive the minimal intent by
generalizing the existing item-only helper methods to also accept a
Bitstream, reusing the already-DSpaceObject-generic
calculateAccessStatusForDso() and the bitstream-scoped
retrieveShortestEmbargo().

- AccessStatusHelper / DefaultAccessStatusHelper / AccessStatusServiceImpl:
  add getAccessStatusFromBitstream / getEmbargoFromBitstream and their
  AccessStatusService-level counterparts.
- AccessStatusRest: add the embargoDate field (previously only used at
  item level; zcu-pub's item-level link repository never actually
  populated it despite the service already computing it, so
  ItemAccessStatusLinkRepository now also sets it for consistency).
- BitstreamRest: add the ACCESS_STATUS link.
- New BitstreamAccessStatusLinkRepository, mirroring
  ItemAccessStatusLinkRepository and BitstreamFormatLinkRepository's
  existing METADATA_READ permission convention.
- Tests: extend AccessStatusServiceTest, DefaultAccessStatusHelperTest,
  AccessStatusRestTest, and BitstreamRestRepositoryIT for the new
  bitstream-level behavior.

Deliberately does not port the AccessStatus-object/Pair refactor or the
current-user-vs-anonymous calculation type from the later upstream
commits (6a229f1, 0a0b575) -- that's 9.x-specific complexity not
present in zcu-pub's simpler anonymous-only model.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ee2033af-91ee-4116-b829-dda21e2d5d71

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@MatusBeke

Copy link
Copy Markdown
Collaborator Author

Companion frontend PR: dataquest-dev/dspace-angular#1378 (the badge that consumes this endpoint). Tracking issue: dataquest-dev/dspace-customers#823.

@MatusBeke

Copy link
Copy Markdown
Collaborator Author

Sibling PR for the ZCU-DATA branch (same fix, same REST contract): #1380

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds a bitstream-level accessStatus REST endpoint/link (parity with item-level) and extends the AccessStatusRest DTO to include an embargoDate, enabling the frontend to display a bitstream embargo badge with an end date.

Changes:

  • Add GET /api/core/bitstreams/{id}/accessStatus via a new BitstreamAccessStatusLinkRepository and a new accessStatus link on BitstreamRest.
  • Extend AccessStatusRest with an embargoDate field, and start populating it for both bitstreams (new) and items (scope expansion).
  • Add/adjust unit + integration tests to cover the new DTO field and new bitstream link behavior.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
dspace-server-webapp/src/test/java/org/dspace/app/rest/model/AccessStatusRestTest.java Adds basic DTO tests for the new embargoDate field.
dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BitstreamMatcher.java Updates expected default embeds/links to include accessStatus.
dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java Adds IT coverage for bitstream /accessStatus (bad UUID, not-found/unauthorized, happy path).
dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemAccessStatusLinkRepository.java Populates embargoDate on item access status responses when status is embargo.
dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamAccessStatusLinkRepository.java New link repository exposing bitstream access status (and embargo date when applicable).
dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BitstreamRest.java Adds ACCESS_STATUS link declaration on bitstreams.
dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AccessStatusRest.java Adds embargoDate field + getters/setters and constructor initialization.
dspace-api/src/test/java/org/dspace/access/status/DefaultAccessStatusHelperTest.java Extends helper tests to cover bitstream access status/embargo behavior.
dspace-api/src/test/java/org/dspace/access/status/AccessStatusServiceTest.java Extends service tests to cover bitstream access status/embargo methods.
dspace-api/src/main/java/org/dspace/access/status/service/AccessStatusService.java Adds getAccessStatus(Context, Bitstream) and getEmbargoFromBitstream(...) to the public service API.
dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java Implements bitstream-level access status + embargo retrieval.
dspace-api/src/main/java/org/dspace/access/status/AccessStatusServiceImpl.java Wires new bitstream-level methods through to the helper using the configured threshold date.
dspace-api/src/main/java/org/dspace/access/status/AccessStatusHelper.java Extends helper interface with bitstream-level access status + embargo retrieval methods.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java Outdated
getEmbargoFromItem/getEmbargoFromBitstream returned Date#toString(),
a locale/timezone-dependent format (e.g. "Tue Jan 01 00:00:00 UTC
2050"), instead of the plain ISO yyyy-MM-dd format used elsewhere in
the REST API (see ResourcePolicyRest / ResourcePolicyRestRepositoryIT).
Since this PR is what first exposes embargoDate over REST, format it
consistently so the frontend (dspace-angular#1378) can reliably parse
it. Updated DefaultAccessStatusHelperTest assertions to match.

Also swap the EMBARGO.equals(status) comparisons in
Item/BitstreamAccessStatusLinkRepository to be constant-first, so a
null status from a custom AccessStatusHelper plugin can't NPE.

Addresses Copilot review comments on PR #1377.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
MatusBeke added a commit that referenced this pull request Jul 21, 2026
getEmbargoFromItem/getEmbargoFromBitstream returned Date#toString(),
a locale/timezone-dependent format (e.g. "Tue Jan 01 00:00:00 UTC
2050"), instead of the plain ISO yyyy-MM-dd format used elsewhere in
the REST API (see ResourcePolicyRest / ResourcePolicyRestRepositoryIT).
Since this PR is what first exposes embargoDate over REST, format it
consistently so the frontend can reliably parse it. Updated
DefaultAccessStatusHelperTest assertions to match. (Same fix as
sibling PR #1377, which shares this code.)

Also swap the EMBARGO.equals(status) comparisons in
Item/BitstreamAccessStatusLinkRepository to be constant-first, so a
null status from a custom AccessStatusHelper plugin can't NPE, as
flagged directly by Copilot review on this PR.

Addresses Copilot review comments on PR #1380.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@milanmajchrak
milanmajchrak merged commit d17e515 into customer/zcu-pub Jul 21, 2026
10 checks passed
milanmajchrak pushed a commit that referenced this pull request Jul 21, 2026
…#1380)

* Add bitstream-level access status/embargo date REST endpoint

Ports the intent of upstream commits ed02a60, 43e41e0, 6a229f1, and
0a0b575 (dataquest-dev/DSpace 9.x line) onto zcu-pub's DSpace 7.6.1
codebase. A raw cherry-pick was not viable: upstream uses
java.time.LocalDate + jakarta.* and refactors the helper around a
Pair<String, LocalDate>/AccessStatus object with a current-user-vs-
anonymous calculation type, none of which match zcu-pub's simpler
joda-time + javax.* shape. Instead, re-derive the minimal intent by
generalizing the existing item-only helper methods to also accept a
Bitstream, reusing the already-DSpaceObject-generic
calculateAccessStatusForDso() and the bitstream-scoped
retrieveShortestEmbargo().

- AccessStatusHelper / DefaultAccessStatusHelper / AccessStatusServiceImpl:
  add getAccessStatusFromBitstream / getEmbargoFromBitstream and their
  AccessStatusService-level counterparts.
- AccessStatusRest: add the embargoDate field (previously only used at
  item level; zcu-pub's item-level link repository never actually
  populated it despite the service already computing it, so
  ItemAccessStatusLinkRepository now also sets it for consistency).
- BitstreamRest: add the ACCESS_STATUS link.
- New BitstreamAccessStatusLinkRepository, mirroring
  ItemAccessStatusLinkRepository and BitstreamFormatLinkRepository's
  existing METADATA_READ permission convention.
- Tests: extend AccessStatusServiceTest, DefaultAccessStatusHelperTest,
  AccessStatusRestTest, and BitstreamRestRepositoryIT for the new
  bitstream-level behavior.

Deliberately does not port the AccessStatus-object/Pair refactor or the
current-user-vs-anonymous calculation type from the later upstream
commits (6a229f1, 0a0b575) -- that's 9.x-specific complexity not
present in zcu-pub's simpler anonymous-only model.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Expose access status/embargo date on the metadatabitstream endpoint

The ZCU-DATA Item View file listing doesn't use the standard
/bitstreams REST resource (and its new accessStatus link from this
same PR) at all - it goes through a separate, CLARIN-specific
endpoint (metadatabitstreams/search/byHandle -> MetadataBitstreamWrapperRest)
that returns a flat, lightweight DTO per file. Extend that DTO
directly instead, reusing the same AccessStatusService methods added
earlier in this PR.

- MetadataBitstreamWrapperRest gains status/embargoDate fields.
- MetadataBitstreamWrapperConverter populates them from the same
  Bitstream object it already has on hand, via
  AccessStatusService.getAccessStatus()/getEmbargoFromBitstream().
  Wrapped in try/catch so a failure here degrades to no status
  rather than 500ing the whole file listing.

Also fixes a real pre-existing bug surfaced while testing this against
an embargoed bitstream: MetadataBitstreamRestRepository.findOutCanPreview()
only caught MissingLicenseAgreementException, not the general
AuthorizeException thrown when the requesting user (e.g. anonymous)
lacks READ on the bitstream - e.g. exactly the embargo case. That
propagated uncaught and 500'd the entire file listing for the item
today, independent of this change. Fixed by catching it the same way
as the license case (same "can't preview" outcome).

Test evidence:
  mvn checkstyle:check -pl dspace-server-webapp -> BUILD SUCCESS
  mvn verify -pl dspace-server-webapp -DskipIntegrationTests=false
    -Dit.test=MetadataBitstreamRestRepositoryIT
    -> Tests run: 6, Failures: 0, Errors: 0
  New test (findByHandleEmbargoedBitstream) exercises exactly the path
  that used to 500: an item with an embargoed ORIGINAL bitstream,
  requested anonymously.

* Fix embargoDate REST format and null-safe EMBARGO comparison

getEmbargoFromItem/getEmbargoFromBitstream returned Date#toString(),
a locale/timezone-dependent format (e.g. "Tue Jan 01 00:00:00 UTC
2050"), instead of the plain ISO yyyy-MM-dd format used elsewhere in
the REST API (see ResourcePolicyRest / ResourcePolicyRestRepositoryIT).
Since this PR is what first exposes embargoDate over REST, format it
consistently so the frontend can reliably parse it. Updated
DefaultAccessStatusHelperTest assertions to match. (Same fix as
sibling PR #1377, which shares this code.)

Also swap the EMBARGO.equals(status) comparisons in
Item/BitstreamAccessStatusLinkRepository to be constant-first, so a
null status from a custom AccessStatusHelper plugin can't NPE, as
flagged directly by Copilot review on this PR.

Addresses Copilot review comments on PR #1380.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants