[Port to dtq-dev] Issue dspace-customers#903: fix SWORDv2 item double-delete with WorkflowManagerDefault - #1407
Merged
Merged
Conversation
…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>
There was a problem hiding this comment.
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()toworkspaceItemService.deleteAll()(which also deletes the underlyingItem). - Add an
isItemAlreadyDeleted(Context, UUID)helper that scansContextevents for anEvent.DELETEon theItem, and guards the subsequentitemService.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>
Author
|
Follow-up
Validation: 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. |
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a defensive guard to the SWORDv2 edit-media DELETE path (
ContainerManagerDSpace.doContainerDelete) so the underlyingItemis deleted at most once per transaction, even if an upstream method deletes it first. The workspace and workflow branches keep base'sdeleteWrapper()shape; only the finalitemService.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-devbyf79e7043fc("UFAL/Copy SWORDv2 fixes #957"). That historical bug came fromworkspaceItemService.deleteAll()(which deletes the item itself) followed by an unguardeditemService.delete(). The #1031 7.6.5 upgrade merge reverted the workspace path todeleteWrapper()+ a singleitemService.delete(), which is already double-delete-safe by construction — so the currentdtq-devbase does not double-delete on any path (verified by trace and confirmed independently in review).Change set
ContainerManagerDSpace.javaonly:isItemAlreadyDeleted(context, uuid)— scanscontext.getEvents()for a pendingEvent.DELETEonConstants.ITEM, and guards the finalitemService.delete()so it runs at most once.java.util.UUID,org.dspace.event.Event(and useorg.dspace.core.Constants).deleteWrapper()— unchanged from base.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
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.