Skip to content

[Port to dtq-dev] Issue dspace-customers#903: fix SWORDv2 item double-delete with WorkflowManagerDefault - #1407

Merged
milanmajchrak merged 3 commits into
dtq-devfrom
903-be/swordv2-double-delete
Aug 14, 2026
Merged

[Port to dtq-dev] Issue dspace-customers#903: fix SWORDv2 item double-delete with WorkflowManagerDefault#1407
milanmajchrak merged 3 commits into
dtq-devfrom
903-be/swordv2-double-delete

Conversation

@jr-rk

@jr-rk jr-rk commented Aug 11, 2026

Copy link
Copy Markdown

Summary

Adds a defensive guard to the SWORDv2 edit-media DELETE path (ContainerManagerDSpace.doContainerDelete) so the underlying Item is deleted at most once per transaction, even if an upstream method deletes it first. The workspace and workflow branches keep base's deleteWrapper() shape; only the final itemService.delete() is now guarded.

Relates to dataquest-dev/dspace-customers#903 (item 3).

Background

Issue #903 (item 3) tracked a SWORDv2 item double-delete originally fixed on dtq-dev by f79e7043fc ("UFAL/Copy SWORDv2 fixes #957"). That historical bug came from workspaceItemService.deleteAll() (which deletes the item itself) followed by an unguarded itemService.delete(). The #1031 7.6.5 upgrade merge reverted the workspace path to deleteWrapper() + a single itemService.delete(), which is already double-delete-safe by construction — so the current dtq-dev base does not double-delete on any path (verified by trace and confirmed independently in review).

Change set

ContainerManagerDSpace.java only:

  • Add isItemAlreadyDeleted(context, uuid) — scans context.getEvents() for a pending Event.DELETE on Constants.ITEM, and guards the final itemService.delete() so it runs at most once.
  • Add imports java.util.UUID, org.dspace.event.Event (and use org.dspace.core.Constants).
  • Workspace and workflow branches keep deleteWrapper()unchanged from base.

An earlier revision of this PR switched the workspace path to deleteAll() + the guard; that was reverted per review (Copilot + integrator review). deleteAll() imposed a stricter admin-or-submitter authorization gate (which can only narrow authorization, never enable a delete) and reintroduced the very double-delete the guard then had to cancel. Keeping deleteWrapper() is simpler, matches base and the sibling workflow branch, and avoids the auth regression.

Effect

Behaviour-neutral vs base by design. Nothing on the SWORDv2 delete path currently deletes the item before the final itemService.delete(), so the guard acts purely as a safety net — it changes behaviour only if a future upstream change deletes the item within the same transaction. The two pre-existing integration tests (Swordv2IT.testDeleteWorkspaceManagerDefault, testDeleteWorkflowManagerDefault) exercise the delete path and should remain green on both base and this branch.

Validation

$ mvn -pl dspace-swordv2 checkstyle:check
0 Checkstyle violations -> BUILD SUCCESS
$ mvn -pl dspace-swordv2 -am compile
BUILD SUCCESS

Runtime integration tests are delegated to CI (docker unavailable locally).

Risk & rollback

Isolated to the SWORDv2 delete path; the guard is inert in normal operation. Revert = single commit.

…emoveItem

Deleting a SWORDv2 item with WorkflowManagerDefault deleted the item twice and
the second itemService.delete() threw. The fix -- deleteAll on the workspace
path plus an isItemAlreadyDeleted guard before the final delete -- was present
on dtq-dev via f79e704 and reverted by the #1031 7.6.5 upgrade merge, while
the two Swordv2IT tests that cover it were left behind. This restores the
reverted delta (identical to customer/zcu-pub 66af883).

Not taken from zcu-pub: its SwordUrlManager changes -- dtq-dev is ahead there.

Port of dataquest-dev/dspace-customers#903 (item 3).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jr-rk
jr-rk requested a lite review from Copilot and removed request for Copilot August 12, 2026 13:22

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

This PR restores a previously reverted SWORDv2 delete-path fix in ContainerManagerDSpace, preventing an item from being deleted twice in a single request/transaction (notably when the workflow manager is org.dspace.sword2.WorkflowManagerDefault).

Changes:

  • Switch workspace deletion from workspaceItemService.deleteWrapper() to workspaceItemService.deleteAll() (which also deletes the underlying Item).
  • Add an isItemAlreadyDeleted(Context, UUID) helper that scans Context events for an Event.DELETE on the Item, and guards the subsequent itemService.delete() call accordingly.
  • Add required imports (UUID, Event) to support the new guard logic.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Make the double-delete guard NPE-safe and more precise, and clarify its
Javadoc. Invert the UUID comparison to dereference the non-null itemUUID
instead of the nullable Event.getSubjectID(), and constrain the match to
Event.DELETE events on Constants.ITEM subjects. Behaviour is unchanged;
this only removes a theoretical NPE and documents that the guard checks a
pending (not-yet-dispatched) DELETE event queued on the context.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jr-rk

jr-rk commented Aug 12, 2026

Copy link
Copy Markdown
Author

Follow-up 4e9e976 — hardened the isItemAlreadyDeleted guard (no behaviour change):

  • NPE-safe compare: dereference the non-null itemUUID instead of the nullable Event.getSubjectID().
  • Constrained the match to Event.DELETE events on Constants.ITEM subjects.
  • Reworded the Javadoc to say what it checks — a pending, not-yet-dispatched DELETE event queued on the context, not DB state.

Validation: mvn -pl dspace-swordv2 checkstyle:check → 0 violations; module compiles.

Re: Copilot's automated review — it posted a descriptive overview only, with no inline comments or actionable findings, so there is nothing to resolve from it.

@jr-rk
jr-rk requested a lite review from Copilot August 13, 2026 07:39
@jr-rk jr-rk self-assigned this Aug 13, 2026

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

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment thread dspace-swordv2/src/main/java/org/dspace/sword2/ContainerManagerDSpace.java Outdated
Revert the workspace branch of doContainerDelete from deleteAll() back to
deleteWrapper() per PR review. deleteAll() imposed a stricter admin-or-submitter
authorization gate and deleted the item itself, which only narrowed authorization
(never enabled a delete) and reintroduced the very double-delete the guard then
had to cancel. deleteWrapper() removes only the workspace wrapper row and leaves
the single itemService.delete() below to remove the item, matching the base
branch and the sibling workflow path.

The isItemAlreadyDeleted() guard is retained as an explicit safety net against a
future upstream method deleting the item within the same transaction.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

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

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

@jr-rk
jr-rk requested a review from milanmajchrak August 13, 2026 08:43
@milanmajchrak
milanmajchrak merged commit f241c47 into dtq-dev Aug 14, 2026
14 checks passed
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