Skip to content

UFAL/Metadata value added to provenance when added metadata#1050

Merged
milanmajchrak merged 17 commits intodtq-devfrom
ufal/provenance-show-values-added-metadata
Sep 25, 2025
Merged

UFAL/Metadata value added to provenance when added metadata#1050
milanmajchrak merged 17 commits intodtq-devfrom
ufal/provenance-show-values-added-metadata

Conversation

@Kasinhou
Copy link

@Kasinhou Kasinhou commented Sep 3, 2025

Phases MP MM MB MR JM Total
ETA 0 0 0 0 0 0
Developing 0 0 0 0 0 0
Review 0 0 0 0 0 0
Total - - - - - 0
ETA est. 0
ETA cust. - - - - - 0

Problem description

Metadata value is not shown when the new metadata is added or updated.

How it should looks like
image

Summary by CodeRabbit

  • New Features

    • Provenance/audit logs now include the actual metadata value when adding metadata and show old→new transitions for replacements (including single-value edits).
  • Bug Fixes

    • Null/empty metadata normalized to "empty" for consistent provenance messages; item and bitstream provenance formatting made more consistent (some trailing bitstream counts removed).
  • Tests

    • Updated provenance templates and test fixtures to match new value-aware message formats and expectations.

@coderabbitai
Copy link

coderabbitai bot commented Sep 3, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Added explicit metadata value handling to provenance: ProvenanceService.addMetadata now accepts String metadataValue; replaceMetadata and replaceMetadataSingle now accept String newMtdVal. REST call sites, tests, and ProvenanceMessageFormatter were updated so provenance messages include explicit or replacement metadata values.

Changes

Cohort / File(s) Summary
Provenance API
dspace-api/src/main/java/org/dspace/core/ProvenanceService.java
Updated public method signatures: addMetadata(..., MetadataField, String metadataValue), replaceMetadata(..., MetadataField, String oldMtdVal, String newMtdVal), replaceMetadataSingle(..., MetadataField, String oldMtdVal, String newMtdVal).
Provenance implementation
dspace-api/src/main/java/org/dspace/core/ProvenanceServiceImpl.java
Implementations updated to accept metadataValue / newMtdVal; build provenance messages using messageProvider.getMetadata(...) and getMetadataReplacement(...), then call addProvenanceMetadata; item/bitstream branches preserved; error handling unchanged.
Message formatter
dspace-api/src/main/java/org/dspace/core/ProvenanceMessageFormatter.java
Added public String getMetadataReplacement(String metadataKey, String oldValue, String newValue) which formats metadataKey [oldValue -> newValue] and uses Objects.toString(..., "empty") to substitute nulls.
REST PATCH — add
dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/DSpaceObjectMetadataAddOperation.java
Call site updated to pass metadataValue.getValue() to provenanceService.addMetadata(..., metadataValue); core patch logic unchanged.
REST PATCH — replace
dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/DSpaceObjectMetadataReplaceOperation.java
Single-value and property replace logic now capture newMtdVal and pass both old/new values to provenanceService.replaceMetadata / replaceMetadataSingle; other replace handling preserved.
Tests / Expected messages
dspace-server-webapp/src/test/java/org/dspace/app/rest/ProvenanceExpectedMessages.java
dspace-server-webapp/src/test/resources/.../item-metadata-patch-suite.json
Expected provenance templates and test JSON updated to include explicit metadata values (e.g., dc.title: <value>) and replacement formatting (dc.title [old -> new]); some message newlines/bitstream-count text adjusted.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client
  participant REST as REST PATCH /dspaceobject/metadata
  participant Prov as ProvenanceService
  participant Impl as ProvenanceServiceImpl
  participant Store as MetadataStore

  Client->>REST: PATCH add metadata (field, value)
  REST->>Store: persist metadata addition
  REST->>Prov: addMetadata(context, dso, field, value)
  Prov->>Impl: delegate
  Impl->>Impl: build message using field + metadataValue
  Impl-->>Store: addProvenanceMetadata(message)
  REST-->>Client: 200 OK
  note right of Impl #a3be8c: Provenance now records explicit metadata value
Loading
sequenceDiagram
  autonumber
  participant Client
  participant REST as REST PATCH /dspaceobject/metadata
  participant Prov as ProvenanceService
  participant Impl as ProvenanceServiceImpl
  participant Store as MetadataStore

  Client->>REST: PATCH replace metadata (field, old -> new)
  REST->>Store: persist metadata replacement
  REST->>Prov: replaceMetadata(context, dso, field, old, new)
  Prov->>Impl: delegate
  Impl->>Impl: build replacement message via getMetadataReplacement(field, old, new)
  Impl-->>Store: addProvenanceMetadata(message)
  REST-->>Client: 200 OK
  note right of Impl #a3be8c: Provenance records old and new values
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

A rabbit hops through code with delight,
Values now logged in brackets, tidy and bright.
Old meets new in a gentle little swap,
Provenance notes prance — never a gap.
🐰🎉

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.

Pre-merge checks (1 passed, 2 warnings)

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 43.75% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description Check ⚠️ Warning The pull request description includes the effort table and a brief problem statement but does not fully adhere to the repository’s PR template. It omits the required “### Reported issues” and “### Not-reported issues” subsections and lacks both the “## Analysis” and “## Problems” sections. The current description also places the screenshot outside of the structured template headings, reducing clarity. Please update the PR description to include the missing template sections: populate “### Reported issues” and “### Not-reported issues” with relevant details, and add “## Analysis” and “## Problems” sections summarizing any findings or blockers. Ensure the screenshot is referenced under the correct heading to illustrate the expected behavior. Once updated, the description will meet the repository’s standards for clarity and completeness.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title accurately references that metadata values are now shown in provenance entries when metadata is added, which matches the core intent of the changeset, but the inclusion of the branch prefix "UFAL/" and the slightly repetitive wording reduce its clarity.
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ufal/provenance-show-values-added-metadata

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 and usage tips.

coderabbitai[bot]

This comment was marked as outdated.

@Kasinhou Kasinhou requested a review from Copilot September 3, 2025 12:49

This comment was marked as outdated.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
dspace-api/src/main/java/org/dspace/core/ProvenanceServiceImpl.java (1)

190-214: Guard against null/blank inputs; reuse computed field key; prefer else-if; log unsupported types

Avoid potential NPE on metadataField, normalize blank values to a consistent "empty" token (used elsewhere in this class), remove duplication of metadataField formatting, and align the conditional with the prevailing style. Also log when invoked with an unsupported DSO type.

-    public void addMetadata(Context context, DSpaceObject dso, MetadataField metadataField, String metadataValue) {
+    public void addMetadata(Context context, DSpaceObject dso, MetadataField metadataField, String metadataValue) {
         try {
-            if (Constants.ITEM == dso.getType()) {
+            Objects.requireNonNull(metadataField, "metadataField must not be null");
+            final String mtdKey = messageProvider.getMetadataField(metadataField);
+            final String safeValue = StringUtils.isBlank(metadataValue) ? "empty" : metadataValue;
+            if (Constants.ITEM == dso.getType()) {
                 String msg = messageProvider.getMessage(context, ProvenanceMessageTemplates.ITEM_METADATA.getTemplate(),
-                        (Item) dso, messageProvider.getMetadata(
-                                messageProvider.getMetadataField(metadataField), metadataValue), "added");
+                        (Item) dso, messageProvider.getMetadata(mtdKey, safeValue), "added");
                 addProvenanceMetadata(context, (Item) dso, msg);
-            }
-
-            if (dso.getType() == Constants.BITSTREAM) {
+            } else if (dso.getType() == Constants.BITSTREAM) {
                 Bitstream bitstream = (Bitstream) dso;
                 Item item = findItemByBitstream(context, bitstream);
                 if (Objects.nonNull(item)) {
                     String msg = messageProvider.getMessage(context,
                             ProvenanceMessageTemplates.BITSTREAM_METADATA.getTemplate(), item,
-                            messageProvider.getMetadata(
-                                    messageProvider.getMetadataField(metadataField), metadataValue), "added by",
+                            messageProvider.getMetadata(mtdKey, safeValue), "added by",
                             messageProvider.getMessage(bitstream));
                     addProvenanceMetadata(context, item, msg);
                 }
+            } else {
+                log.warn("addMetadata called for unsupported DSpaceObject type: {}", dso.getType());
             }
         } catch (SQLException | AuthorizeException e) {
             log.error("Unable to add new provenance metadata when adding metadata to a DSpace object.", e);
         }
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4e5cc2e and 410495f.

📒 Files selected for processing (1)
  • dspace-api/src/main/java/org/dspace/core/ProvenanceServiceImpl.java (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: dspace-cli / docker-build (linux/amd64, ubuntu-latest, true)
  • GitHub Check: dspace-test / docker-build (linux/amd64, ubuntu-latest, true)
  • GitHub Check: dspace / docker-build (linux/amd64, ubuntu-latest, true)
  • GitHub Check: Run Integration Tests
  • GitHub Check: Run Unit Tests
🔇 Additional comments (3)
dspace-api/src/main/java/org/dspace/core/ProvenanceServiceImpl.java (3)

194-196: Good: value is now included in provenance for ITEM metadata

Using messageProvider.getMetadata(getMetadataField(metadataField), metadataValue) ensures the stored provenance contains the actual value added. This aligns with how deletions/updates record values elsewhere in the class.


205-207: Good: bitstream provenance now records the concrete value

Bitstream branch mirrors ITEM handling and preserves the bitstream context. Looks correct.


190-214: No lingering 3-arg addMetadata calls detected. Verified via the provided ripgrep script; all callers now use the new 4-arg signature.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
dspace-api/src/main/java/org/dspace/core/ProvenanceServiceImpl.java (1)

190-214: Reduce nesting/noise: precompute metadata key/value once and reuse

Extracting mtdKey and the formatted metadata message trims nested calls and matches prior readability feedback.

 public void addMetadata(Context context, DSpaceObject dso, MetadataField metadataField, String metadataValue) {
     try {
-        final String safeValue = StringUtils.isBlank(metadataValue) ? "empty" : metadataValue;
+        final String safeValue = StringUtils.isBlank(metadataValue) ? "empty" : metadataValue;
+        final String mtdKey = messageProvider.getMetadataField(metadataField);
+        final String metadataMsg = messageProvider.getMetadata(mtdKey, safeValue);
         if (Constants.ITEM == dso.getType()) {
-            String mtdKey = messageProvider.getMetadataField(metadataField);
             String msg = messageProvider.getMessage(context, ProvenanceMessageTemplates.ITEM_METADATA.getTemplate(),
-                    (Item) dso, messageProvider.getMetadata(mtdKey, safeValue), "added");
+                    (Item) dso, metadataMsg, "added");
             addProvenanceMetadata(context, (Item) dso, msg);
         }
         if (dso.getType() == Constants.BITSTREAM) {
             Bitstream bitstream = (Bitstream) dso;
             Item item = findItemByBitstream(context, bitstream);
             if (Objects.nonNull(item)) {
-                String mtdKey = messageProvider.getMetadataField(metadataField);
                 String msg = messageProvider.getMessage(context,
                         ProvenanceMessageTemplates.BITSTREAM_METADATA.getTemplate(), item,
-                        messageProvider.getMetadata(mtdKey, safeValue),
-                        "added to", messageProvider.getMessage(bitstream));
+                        metadataMsg,
+                        "added to", messageProvider.getMessage(bitstream));
                 addProvenanceMetadata(context, item, msg);
             }
         }
🧹 Nitpick comments (2)
dspace-api/src/main/java/org/dspace/core/ProvenanceServiceImpl.java (2)

203-208: Provenance wording: “added to” (bitstream) for parity with “deleted from”

“Added by” reads like an actor, not a target. Suggest “added to” to mirror “deleted from” and improve clarity. Also reuse safeValue.

-                    String mtdKey = messageProvider.getMetadataField(metadataField);
+                    String mtdKey = messageProvider.getMetadataField(metadataField);
                     String msg = messageProvider.getMessage(context,
                             ProvenanceMessageTemplates.BITSTREAM_METADATA.getTemplate(), item,
-                            messageProvider.getMetadata(mtdKey, metadataValue),
-                            "added by", messageProvider.getMessage(bitstream));
+                            messageProvider.getMetadata(mtdKey, safeValue),
+                            "added to", messageProvider.getMessage(bitstream));

190-214: Stabilize action tokens via constants/enum

Hardcoded "added"/"deleted"/"updated" strings are brittle and harder to localize. Prefer ProvenanceAction enum or constants co-located with templates.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 410495f and 0a5e89f.

📒 Files selected for processing (1)
  • dspace-api/src/main/java/org/dspace/core/ProvenanceServiceImpl.java (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: dspace / docker-build (linux/amd64, ubuntu-latest, true)
  • GitHub Check: dspace-cli / docker-build (linux/amd64, ubuntu-latest, true)
  • GitHub Check: dspace-test / docker-build (linux/amd64, ubuntu-latest, true)
  • GitHub Check: Run Integration Tests
  • GitHub Check: Run Unit Tests
🔇 Additional comments (1)
dspace-api/src/main/java/org/dspace/core/ProvenanceServiceImpl.java (1)

190-214: No remaining 3-arg provenanceService.addMetadata calls
All references use the new 4-arg signature and REST PATCH paths supply a non-null value.

@Kasinhou Kasinhou requested a review from Copilot September 3, 2025 13:47

This comment was marked as outdated.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (3)
dspace-api/src/main/java/org/dspace/core/ProvenanceServiceImpl.java (3)

193-196: Flatten nested calls for readability and reuse the precomputed metadata string

Extract the metadata fragment once and reuse it in the message assembly.

Apply:

-                String msg = messageProvider.getMessage(context, ProvenanceMessageTemplates.ITEM_METADATA.getTemplate(),
-                        (Item) dso, messageProvider.getMetadata(
-                                messageProvider.getMetadataField(metadataField), metadataValue), "added");
+                String msg = messageProvider.getMessage(
+                        context,
+                        ProvenanceMessageTemplates.ITEM_METADATA.getTemplate(),
+                        (Item) dso,
+                        metadata,
+                        "added"
+                );

203-207: Reuse computed metadata; avoid repeating formatter chain

Keeps both ITEM and BITSTREAM branches consistent and easier to maintain.

Apply:

-                    String msg = messageProvider.getMessage(context,
-                            ProvenanceMessageTemplates.BITSTREAM_METADATA.getTemplate(), item,
-                            messageProvider.getMetadata(
-                                    messageProvider.getMetadataField(metadataField), metadataValue), "added by",
-                            messageProvider.getMessage(bitstream));
+                    String msg = messageProvider.getMessage(
+                            context,
+                            ProvenanceMessageTemplates.BITSTREAM_METADATA.getTemplate(),
+                            item,
+                            metadata,
+                            "added by",
+                            messageProvider.getMessage(bitstream)
+                    );

190-190: Add @OverRide and guard inputs + blank values to prevent NPEs and “null” in provenance

  • Annotate as @OverRide for compile-time safety.
  • Assert non-null dso/metadataField.
  • Normalize metadataValue to project’s "empty" fallback when null/blank, mirroring patterns elsewhere in this class.

Apply:

-    public void addMetadata(Context context, DSpaceObject dso, MetadataField metadataField, String metadataValue) {
+    @Override
+    public void addMetadata(Context context, DSpaceObject dso, MetadataField metadataField, String metadataValue) {
         try {
+            Objects.requireNonNull(dso, "dso");
+            Objects.requireNonNull(metadataField, "metadataField");
+            final String safeValue = StringUtils.isBlank(metadataValue) ? "empty" : metadataValue;
+            final String metadataFieldKey = messageProvider.getMetadataField(metadataField);
+            final String metadata = messageProvider.getMetadata(metadataFieldKey, safeValue);

Run to spot any lingering 3-arg calls after the API change:

#!/bin/bash
# heuristic grep for calls with only 3 args (may miss multiline)
rg -nP --type=java '\bprovenanceService\.addMetadata\s*\(\s*[^,]+,\s*[^,]+,\s*[^,)]+\)'
🧹 Nitpick comments (1)
dspace-server-webapp/src/test/java/org/dspace/app/rest/ProvenanceExpectedMessages.java (1)

26-26: Enum name vs. template mismatch: “REMOVE_BITSTREAM_MTD” says “was added by bitstream”

Confirm intent. If this constant validates an “add” event, consider renaming; if it’s for “remove,” adjust the template for clarity.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a366915 and 610a784.

📒 Files selected for processing (2)
  • dspace-api/src/main/java/org/dspace/core/ProvenanceServiceImpl.java (2 hunks)
  • dspace-server-webapp/src/test/java/org/dspace/app/rest/ProvenanceExpectedMessages.java (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: dspace-cli / docker-build (linux/amd64, ubuntu-latest, true)
  • GitHub Check: dspace-test / docker-build (linux/amd64, ubuntu-latest, true)
  • GitHub Check: dspace / docker-build (linux/amd64, ubuntu-latest, true)
  • GitHub Check: Run Unit Tests
  • GitHub Check: Run Integration Tests
🔇 Additional comments (1)
dspace-server-webapp/src/test/java/org/dspace/app/rest/ProvenanceExpectedMessages.java (1)

21-21: LGTM: expected message now includes explicit metadata value

Matches the new provenance behavior.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (4)
dspace-server-webapp/src/test/resources/org/dspace/app/rest/test/item-metadata-patch-suite.json (4)

26-26: Including explicit metadata value in provenance: confirm data-safety policy and max length.

Now that values appear in provenance, please confirm we’re not logging sensitive fields and that long values are truncated/sanitized consistently across services to avoid PII leakage or oversized logs.


68-73: Reduce brittleness of repeated hard-coded messages.

These messages repeat verbatim in many cases. Consider adding one higher-level test that asserts key substrings (field key + value) or using a single canonical fixture/template to cut duplication and ease future template tweaks.


123-128: Replace operations: should we log “modified” provenance?

Expectation shows no new provenance for value/language replace. If the product stance is “only added/deleted are tracked,” fine—otherwise consider adding a “modified” entry for audit completeness.


205-210: Newline portability in expected strings.

The literal “on \nNo. of bitstreams: 0” assumes LF. If the provider uses System.lineSeparator(), Windows builds may expect CRLF. Either normalize in tests or ensure the provider emits \n consistently.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 610a784 and c37450e.

📒 Files selected for processing (1)
  • dspace-server-webapp/src/test/resources/org/dspace/app/rest/test/item-metadata-patch-suite.json (8 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: dspace-cli / docker-build (linux/amd64, ubuntu-latest, true)
  • GitHub Check: dspace / docker-build (linux/amd64, ubuntu-latest, true)
  • GitHub Check: dspace-test / docker-build (linux/amd64, ubuntu-latest, true)
  • GitHub Check: Run Unit Tests
  • GitHub Check: Run Integration Tests
🔇 Additional comments (4)
dspace-server-webapp/src/test/resources/org/dspace/app/rest/test/item-metadata-patch-suite.json (4)

44-47: Ensure UTF-8 handling for non-ASCII titles.

The Japanese title looks good. Verify test/resource encoding and build are pinned to UTF-8 so this doesn’t break on different locales/CI images.


93-98: Reorder (move) does not emit provenance: confirm intended behavior.

Looks like reordering titles doesn’t add a new provenance entry. If that’s by design (no content change), great—please confirm that no add/modify hooks fire on move.


148-153: Copy operation lacks an “added” provenance entry: verify consistency.

Copying creates a new metadata value but no “was added” line is expected. If copy flows through the same add path now passing metadataValue, should we record it as an add? Please confirm intended behavior and update either implementation or expectations accordingly.


177-182: Deletes look correct with explicit values.

The two delete entries for “title A” align with the operations and ordering.

@Kasinhou Kasinhou requested a review from Copilot September 4, 2025 10:56

This comment was marked as outdated.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
dspace-api/src/main/java/org/dspace/core/ProvenanceServiceImpl.java (2)

190-196: Guard value and extract locals for readability (repeat of prior feedback)

Protect against null/blank values and simplify the nested call.

Apply:

-                String msg = messageProvider.getMessage(context, ProvenanceMessageTemplates.ITEM_METADATA.getTemplate(),
-                        (Item) dso, messageProvider.getMetadata(
-                                messageProvider.getMetadataField(metadataField), metadataValue), "added");
+                final String safeValue = StringUtils.isBlank(metadataValue) ? "[empty]" : metadataValue;
+                final String fieldKey = messageProvider.getMetadataField(metadataField);
+                final String metadataStr = messageProvider.getMetadata(fieldKey, safeValue);
+                String msg = messageProvider.getMessage(
+                        context,
+                        ProvenanceMessageTemplates.ITEM_METADATA.getTemplate(),
+                        (Item) dso,
+                        metadataStr,
+                        "added"
+                );

203-207: Ditto for bitstream branch (null/blank guard and locals)

Same readability and safety upgrade here.

Apply:

-                    String msg = messageProvider.getMessage(context,
-                            ProvenanceMessageTemplates.BITSTREAM_METADATA.getTemplate(), item,
-                            messageProvider.getMetadata(
-                                    messageProvider.getMetadataField(metadataField), metadataValue), "added by",
-                            messageProvider.getMessage(bitstream));
+                    final String safeValue = StringUtils.isBlank(metadataValue) ? "[empty]" : metadataValue;
+                    final String fieldKey = messageProvider.getMetadataField(metadataField);
+                    final String metadataStr = messageProvider.getMetadata(fieldKey, safeValue);
+                    String msg = messageProvider.getMessage(
+                            context,
+                            ProvenanceMessageTemplates.BITSTREAM_METADATA.getTemplate(),
+                            item,
+                            metadataStr,
+                            "added by",
+                            messageProvider.getMessage(bitstream)
+                    );
🧹 Nitpick comments (3)
dspace-api/src/main/java/org/dspace/core/ProvenanceMessageFormatter.java (1)

93-98: Unify the “empty” label and consider blank-string handling

Elsewhere you sometimes use "empty" (without brackets). Consider a single constant (e.g., "[empty]") across the codebase and treat "" as empty too for consistency.

Apply within this method:

-        if (oldValue == null) oldValue = "[empty]";
-        if (newValue == null) newValue = "[empty]";
+        if (oldValue == null || oldValue.isEmpty()) oldValue = "[empty]";
+        if (newValue == null || newValue.isEmpty()) newValue = "[empty]";

(Optional) If there’s any non-UTF-8 storage/log pipeline, consider ASCII "->" instead of the Unicode arrow.

dspace-server-webapp/src/test/resources/org/dspace/app/rest/test/item-metadata-patch-suite.json (1)

24-217: Add optional tests for empty/blank metadata values

No occurrences of old-style expectations (without : value) remain in dspace-server-webapp/src/test. Add test cases where new or existing metadata values are null or blank to assert the [empty] rendering and prevent regressions.

dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/DSpaceObjectMetadataReplaceOperation.java (1)

167-175: Skip no-op updates to reduce noise

If old and new value (and other properties) are equal, consider skipping provenance emission to avoid “updated” when nothing changed.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c37450e and 3da4bf9.

📒 Files selected for processing (5)
  • dspace-api/src/main/java/org/dspace/core/ProvenanceMessageFormatter.java (1 hunks)
  • dspace-api/src/main/java/org/dspace/core/ProvenanceService.java (2 hunks)
  • dspace-api/src/main/java/org/dspace/core/ProvenanceServiceImpl.java (3 hunks)
  • dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/DSpaceObjectMetadataReplaceOperation.java (1 hunks)
  • dspace-server-webapp/src/test/resources/org/dspace/app/rest/test/item-metadata-patch-suite.json (8 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: dspace-cli / docker-build (linux/amd64, ubuntu-latest, true)
  • GitHub Check: dspace / docker-build (linux/amd64, ubuntu-latest, true)
  • GitHub Check: dspace-test / docker-build (linux/amd64, ubuntu-latest, true)
  • GitHub Check: Run Integration Tests
  • GitHub Check: Run Unit Tests
🔇 Additional comments (6)
dspace-api/src/main/java/org/dspace/core/ProvenanceMessageFormatter.java (1)

93-98: Null-safe replacement formatter looks good

The API, null normalization, and output shape match the new provenance needs.

dspace-server-webapp/src/test/resources/org/dspace/app/rest/test/item-metadata-patch-suite.json (1)

24-217: Expected provenance updated correctly

The revised expectations reflect inclusion of values for adds and old→new for updates and align with formatter behavior.

dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/DSpaceObjectMetadataReplaceOperation.java (1)

168-175: Pass-through of new value to provenance — OK

Capturing newMtdVal and forwarding both old/new values to provenance matches the new service contract.

dspace-api/src/main/java/org/dspace/core/ProvenanceService.java (2)

119-122: New addMetadata signature — OK

Interface now reflects value-aware adds; Javadoc reads clearly.


151-152: All replaceMetadata callers updated to new signature
No remaining call sites use the old arity.

dspace-api/src/main/java/org/dspace/core/ProvenanceServiceImpl.java (1)

269-272: Replacement provenance with explicit old→new — LGTM

Uses formatter’s replacement helper and item template correctly.

@Kasinhou Kasinhou removed the request for review from milanmajchrak September 4, 2025 13:09
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
dspace-api/src/main/java/org/dspace/core/ProvenanceServiceImpl.java (2)

190-196: Null/blank guards and local vars for readability in addMetadata()

Protect against null/blank metadataValue and avoid nested calls by extracting fieldKey/safeValue once. Mirrors prior feedback.

 public void addMetadata(Context context, DSpaceObject dso, MetadataField metadataField, String metadataValue) {
     try {
+        Objects.requireNonNull(dso, "dso");
+        Objects.requireNonNull(metadataField, "metadataField");
+        final String fieldKey = messageProvider.getMetadataField(metadataField);
+        final String safeValue = StringUtils.isBlank(metadataValue) ? "empty" : metadataValue;
         if (Constants.ITEM == dso.getType()) {
             String msg = messageProvider.getMessage(context, ProvenanceMessageTemplates.ITEM_METADATA.getTemplate(),
-                    (Item) dso, messageProvider.getMetadata(
-                            messageProvider.getMetadataField(metadataField), metadataValue), "added");
+                    (Item) dso, messageProvider.getMetadata(fieldKey, safeValue), "added");
             addProvenanceMetadata(context, (Item) dso, msg);
         }
@@
                 if (Objects.nonNull(item)) {
                     String msg = messageProvider.getMessage(context,
                             ProvenanceMessageTemplates.BITSTREAM_METADATA.getTemplate(), item,
-                            messageProvider.getMetadata(
-                                    messageProvider.getMetadataField(metadataField), metadataValue), "added by",
+                            messageProvider.getMetadata(fieldKey, safeValue), "added by",
                             messageProvider.getMessage(bitstream));
                     addProvenanceMetadata(context, item, msg);
                 }

Also applies to: 205-207


263-273: Handle null/blank old/new values in replaceMetadata()

Use “empty” fallback and extract fieldKey to keep output consistent and avoid NPEs.

     try {
-        String msg = messageProvider.getMessage(context, ProvenanceMessageTemplates.ITEM_METADATA.getTemplate(),
-                (Item) dso, messageProvider.getMetadataReplacement(
-                        messageProvider.getMetadataField(metadataField), oldMtdVal, newMtdVal), "updated");
+        final String fieldKey = messageProvider.getMetadataField(metadataField);
+        final String safeOld = StringUtils.isBlank(oldMtdVal) ? "empty" : oldMtdVal;
+        final String safeNew = StringUtils.isBlank(newMtdVal) ? "empty" : newMtdVal;
+        String msg = messageProvider.getMessage(context, ProvenanceMessageTemplates.ITEM_METADATA.getTemplate(),
+                (Item) dso, messageProvider.getMetadataReplacement(fieldKey, safeOld, safeNew), "updated");
         addProvenanceMetadata(context, (Item) dso, msg);
🧹 Nitpick comments (7)
dspace-server-webapp/src/test/resources/org/dspace/app/rest/test/item-metadata-patch-suite.json (1)

121-129: Replace test likely missing a provenance entry.

Code now calls provenanceService.replaceMetadata/replaceMetadataSingle, so a replacement should append a provenance message. The “replace title 2 value and language” test currently expects no new entry, which will fail if the implementation emits one.

Suggest adding the expected REPLACE message (old → new) for the value change:

         "dc.description.provenance":[
           {"value":"Item metadata (dc.title: title 1) was added by first (admin) last (admin) (admin@email.com) on \nNo. of bitstreams: 0",
             "language":"en","authority":null,"confidence":-1,"place":0},
           {"value":"Item metadata (dc.title: 最後のタイトル) was added by first (admin) last (admin) (admin@email.com) on \nNo. of bitstreams: 0",
             "language":"en","authority":null,"confidence":-1,"place":1},
           {"value":"Item metadata (dc.title: title 0) was added by first (admin) last (admin) (admin@email.com) on \nNo. of bitstreams: 0",
-            "language":"en","authority":null,"confidence":-1,"place":2}
+            "language":"en","authority":null,"confidence":-1,"place":2},
+          {"value":"Item metadata (dc.title [最後のタイトル -> title A]) was updated by first (admin) last (admin) (admin@email.com) on \nNo. of bitstreams: 0",
+            "language":"en","authority":null,"confidence":-1,"place":3}
         ],

I can update the JSON and add a companion test covering null→value and value→null replacements to exercise “[empty]” rendering — want me to push that?

dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/DSpaceObjectMetadataReplaceOperation.java (4)

168-175: Avoid no-op provenance for unchanged values.

Skip logging when old and new values are equal to prevent noisy provenance on idempotent updates.

-                provenanceService.replaceMetadata(context, dso, metadataField, oldMtdVal, newMtdVal);
+                if (!java.util.Objects.equals(oldMtdVal, newMtdVal)) {
+                    provenanceService.replaceMetadata(context, dso, metadataField, oldMtdVal, newMtdVal);
+                }

205-221: Guard against nulls and tighten property handling; also avoid no-op provenance.

  • Use else-if chain and “constant-first” equals to dodge NPEs.
  • Handle confidence nulls to avoid NPE.
  • Only emit provenance when the value actually changes.
-                if (propertyOfMd.equals("authority")) {
+                if ("authority".equals(propertyOfMd)) {
                     existingMdv.setAuthority(valueMdProperty);
-                }
-                if (propertyOfMd.equals("confidence")) {
-                    existingMdv.setConfidence(Integer.valueOf(valueMdProperty));
-                }
-                if (propertyOfMd.equals("language")) {
+                } else if ("confidence".equals(propertyOfMd)) {
+                    existingMdv.setConfidence(valueMdProperty == null ? null : Integer.valueOf(valueMdProperty));
+                } else if ("language".equals(propertyOfMd)) {
                     existingMdv.setLanguage(valueMdProperty);
-                }
-                if (propertyOfMd.equals("value")) {
+                } else if ("value".equals(propertyOfMd)) {
                     newMtdVal = valueMdProperty;
                     existingMdv.setValue(valueMdProperty);
                 }
                 dsoService.setMetadataModified(dso);
-                provenanceService.replaceMetadataSingle(context, dso, metadataField, oldMtdVal, newMtdVal);
+                if (!java.util.Objects.equals(oldMtdVal, newMtdVal)) {
+                    provenanceService.replaceMetadataSingle(context, dso, metadataField, oldMtdVal, newMtdVal);
+                }

145-151: Javadoc typo and clarity.

“alerations” → “alterations”, and capitalize “MetadataValue”.

-     *      Retrieve the metadatavalue object & make alerations directly on this object
+     *      Retrieve the MetadataValue object and make alterations directly on this object

225-226: Improve error message clarity.

-            throw new IllegalArgumentException("Not all numbers are valid numbers. " +
-                    "(Index and confidence should be nr)", e);
+            throw new IllegalArgumentException("Invalid numeric value: index and confidence must be integers.", e);
dspace-server-webapp/src/test/java/org/dspace/app/rest/ProvenanceExpectedMessages.java (1)

26-26: Match enum name with its message
Change the enum’s message to use “deleted from bitstream” so it reflects the REMOVE_BITSTREAM_MTD name:

-    REMOVE_BITSTREAM_MTD("Item metadata (dc.description: test) was added by bitstream"),
+    REMOVE_BITSTREAM_MTD("Item metadata (dc.description: test) was deleted from bitstream"),
dspace-api/src/main/java/org/dspace/core/ProvenanceServiceImpl.java (1)

281-294: remove redundant semicolon
Change the double semicolon in

addProvenanceMetadata(context, item, msg);;

to a single ;. The existing getMetadataReplacement already treats null as “[empty]”; guarding blank values is optional.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2a27613 and 36b24bc.

📒 Files selected for processing (6)
  • dspace-api/src/main/java/org/dspace/core/ProvenanceMessageFormatter.java (1 hunks)
  • dspace-api/src/main/java/org/dspace/core/ProvenanceService.java (3 hunks)
  • dspace-api/src/main/java/org/dspace/core/ProvenanceServiceImpl.java (5 hunks)
  • dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/DSpaceObjectMetadataReplaceOperation.java (3 hunks)
  • dspace-server-webapp/src/test/java/org/dspace/app/rest/ProvenanceExpectedMessages.java (1 hunks)
  • dspace-server-webapp/src/test/resources/org/dspace/app/rest/test/item-metadata-patch-suite.json (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • dspace-api/src/main/java/org/dspace/core/ProvenanceMessageFormatter.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: dspace-cli / docker-build (linux/amd64, ubuntu-latest, true)
  • GitHub Check: dspace / docker-build (linux/amd64, ubuntu-latest, true)
  • GitHub Check: dspace-test / docker-build (linux/amd64, ubuntu-latest, true)
  • GitHub Check: Run Unit Tests
  • GitHub Check: Run Integration Tests
🔇 Additional comments (7)
dspace-server-webapp/src/test/resources/org/dspace/app/rest/test/item-metadata-patch-suite.json (2)

26-27: Provenance now includes explicit values for ADD — looks good.

The new expected string format with “dc.title: …” and the terminator “on \nNo. of bitstreams: 0” aligns with the PR goal.


183-186: DELETE provenance expectations look consistent.

The two delete entries align with removing both “title A” copies and the new message style.

dspace-api/src/main/java/org/dspace/core/ProvenanceService.java (3)

119-122: addMetadata signature extension looks good.

Javadoc correctly documents the new metadataValue parameter.


142-153: replaceMetadata API now records old and new — good.

Clearer contract; aligns with the new formatter support.


155-164: Javadoc for replaceMetadataSingle correctly documents newMtdVal and single-property scope; no outdated call sites remain.

dspace-server-webapp/src/test/java/org/dspace/app/rest/ProvenanceExpectedMessages.java (2)

21-21: Confirm trailing “on” formatting to avoid brittle assertions

Template ends with “on” (no space/newline). Verify the runtime builder appends the date with proper spacing so tests don’t become fragile.


22-23: LGTM: replacement format matches formatter contract

The “[old -> new]” shape aligns with getMetadataReplacement().

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
dspace-api/src/main/java/org/dspace/core/ProvenanceServiceImpl.java (1)

281-295: Fix bitstream provenance message to include new metadata value and remove extra semicolon

In ProvenanceServiceImpl.replaceMetadataSingle (dspace-api/src/main/java/org/dspace/core/ProvenanceServiceImpl.java), the bitstream branch still uses getMetadata(...), ignoring newMtdVal, and has a stray semicolon. Update that section as follows:

-    String msg = messageProvider.getMessage(context,
-            ProvenanceMessageTemplates.ITEM_REPLACE_SINGLE_METADATA.getTemplate(), item,
-            messageProvider.getMessage(bitstream),
-            messageProvider.getMetadata(messageProvider.getMetadataField(metadataField), oldMtdVal));
-    addProvenanceMetadata(context, item, msg);;
+    @Override
+    public void replaceMetadataSingle(Context context, DSpaceObject dso, MetadataField metadataField,
+                                      String oldMtdVal, String newMtdVal) {
+        if (dso.getType() != Constants.BITSTREAM) {
+            return;
+        }
+
+        Bitstream bitstream = (Bitstream) dso;
+        try {
+            Item item = findItemByBitstream(context, bitstream);
+            if (Objects.nonNull(item)) {
+                String msg = messageProvider.getMessage(
+                        context,
+                        ProvenanceMessageTemplates.ITEM_REPLACE_SINGLE_METADATA.getTemplate(),
+                        item,
+                        messageProvider.getMessage(bitstream),
+                        messageProvider.getMetadataReplacement(
+                                messageProvider.getMetadataField(metadataField),
+                                oldMtdVal,
+                                newMtdVal
+                        )
+                );
+                addProvenanceMetadata(context, item, msg);
+            }
+        } catch (SQLException | AuthorizeException e) {
+            log.error("Unable to add new provenance metadata when replacing metadata in a item.", e);
+        }
+    }
♻️ Duplicate comments (2)
dspace-api/src/main/java/org/dspace/core/ProvenanceServiceImpl.java (2)

190-196: Null/blank-safe value, add @OverRide, and simplify nested call (ITEM path).

Guard against null/blank metadataValue to avoid NPEs and leaking nulls into provenance, add @Override, and extract the field key for readability. This mirrors prior feedback.

+    @Override
     public void addMetadata(Context context, DSpaceObject dso, MetadataField metadataField, String metadataValue) {
-        try {
+        Objects.requireNonNull(dso, "dso");
+        Objects.requireNonNull(metadataField, "metadataField");
+        try {
             if (Constants.ITEM == dso.getType()) {
-                String msg = messageProvider.getMessage(context, ProvenanceMessageTemplates.ITEM_METADATA.getTemplate(),
-                        (Item) dso, messageProvider.getMetadata(
-                                messageProvider.getMetadataField(metadataField), metadataValue), "added");
+                final String safeValue = StringUtils.isBlank(metadataValue) ? "empty" : metadataValue;
+                final String fieldKey = messageProvider.getMetadataField(metadataField);
+                String msg = messageProvider.getMessage(
+                        context,
+                        ProvenanceMessageTemplates.ITEM_METADATA.getTemplate(),
+                        (Item) dso,
+                        messageProvider.getMetadata(fieldKey, safeValue),
+                        "added"
+                );
                 addProvenanceMetadata(context, (Item) dso, msg);
             }

205-207: Null/blank-safe value and extract key (BITSTREAM path).

Apply the same normalization and small readability tweak here.

-                            messageProvider.getMetadata(
-                                    messageProvider.getMetadataField(metadataField), metadataValue), "added by",
+                    final String safeValue = StringUtils.isBlank(metadataValue) ? "empty" : metadataValue;
+                    final String fieldKey = messageProvider.getMetadataField(metadataField);
+                            messageProvider.getMetadata(fieldKey, safeValue), "added by",
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 36b24bc and e9dd3ff.

📒 Files selected for processing (1)
  • dspace-api/src/main/java/org/dspace/core/ProvenanceServiceImpl.java (4 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: dspace-test / docker-build (linux/amd64, ubuntu-latest, true)
  • GitHub Check: dspace-cli / docker-build (linux/amd64, ubuntu-latest, true)
  • GitHub Check: dspace / docker-build (linux/amd64, ubuntu-latest, true)
  • GitHub Check: Run Unit Tests
  • GitHub Check: Run Integration Tests

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
dspace-api/src/main/java/org/dspace/core/ProvenanceServiceImpl.java (1)

280-295: newMtdVal is ignored and there’s a stray ‘;;’ (BITSTREAM replaceSingle).

Use the replacement helper so provenance shows “[old -> new]”, add @OverRide, fix the double semicolon.

Apply:

-    public void replaceMetadataSingle(Context context, DSpaceObject dso, MetadataField metadataField,
-                                      String oldMtdVal, String newMtdVal) {
+    @Override
+    public void replaceMetadataSingle(Context context, DSpaceObject dso, MetadataField metadataField,
+                                      String oldMtdVal, String newMtdVal) {
@@
             if (Objects.nonNull(item)) {
-                String msg = messageProvider.getMessage(context,
-                        ProvenanceMessageTemplates.ITEM_REPLACE_SINGLE_METADATA.getTemplate(), item,
-                        messageProvider.getMessage(bitstream),
-                        messageProvider.getMetadata(messageProvider.getMetadataField(metadataField), oldMtdVal));
-                addProvenanceMetadata(context, item, msg);;
+                final String fieldKey = messageProvider.getMetadataField(metadataField);
+                final String msg = messageProvider.getMessage(
+                        context,
+                        ProvenanceMessageTemplates.ITEM_REPLACE_SINGLE_METADATA.getTemplate(),
+                        item,
+                        messageProvider.getMessage(bitstream),
+                        messageProvider.getMetadataReplacement(fieldKey, oldMtdVal, newMtdVal)
+                );
+                addProvenanceMetadata(context, item, msg);
             }
♻️ Duplicate comments (2)
dspace-api/src/main/java/org/dspace/core/ProvenanceServiceImpl.java (2)

193-195: Minor: extract locals to flatten nested calls.

Pull out fieldKey/metadata segment to locals for readability and easier debugging.

Example:

-                final String msg = messageProvider.getMessage(
-                        context,
-                        ProvenanceMessageTemplates.ITEM_METADATA.getTemplate(),
-                        (Item) dso,
-                        messageProvider.getMetadata(fieldKey, safeValue),
-                        "added"
-                );
+                final String metadataSegment = messageProvider.getMetadata(fieldKey, safeValue);
+                final String msg = messageProvider.getMessage(
+                        context,
+                        ProvenanceMessageTemplates.ITEM_METADATA.getTemplate(),
+                        (Item) dso,
+                        metadataSegment,
+                        "added"
+                );

Also applies to: 205-206


190-196: Guard inputs, add @OverRide, and avoid null/blank leaking into messages (ITEM add).

Add null checks and normalize the value (or rely on the formatter consistently), plus mark as @OverRide.

Apply:

-    public void addMetadata(Context context, DSpaceObject dso, MetadataField metadataField, String metadataValue) {
-        try {
+    @Override
+    public void addMetadata(Context context, DSpaceObject dso, MetadataField metadataField, String metadataValue) {
+        Objects.requireNonNull(dso, "dso");
+        Objects.requireNonNull(metadataField, "metadataField");
+        final String fieldKey = messageProvider.getMetadataField(metadataField);
+        final String safeValue = StringUtils.isBlank(metadataValue) ? "empty" : metadataValue;
+        try {
             if (Constants.ITEM == dso.getType()) {
-                String msg = messageProvider.getMessage(context, ProvenanceMessageTemplates.ITEM_METADATA.getTemplate(),
-                        (Item) dso, messageProvider.getMetadata(
-                                messageProvider.getMetadataField(metadataField), metadataValue), "added");
+                final String msg = messageProvider.getMessage(
+                        context,
+                        ProvenanceMessageTemplates.ITEM_METADATA.getTemplate(),
+                        (Item) dso,
+                        messageProvider.getMetadata(fieldKey, safeValue),
+                        "added"
+                );
                 addProvenanceMetadata(context, (Item) dso, msg);
             }

Note: if the formatter already normalizes empties (e.g., to “[empty]”), replace safeValue with metadataValue to avoid double-normalization.

-        final String safeValue = StringUtils.isBlank(metadataValue) ? "empty" : metadataValue;
+        final String safeValue = metadataValue; // rely on formatter for normalization
🧹 Nitpick comments (2)
dspace-server-webapp/src/test/resources/org/dspace/app/rest/test/item-metadata-patch-suite.json (1)

44-47: Inconsistent "No. of bitstreams: 0" suffix across similar ADD cases.

Some expectations include the newline + bitstreams suffix, others don’t. Please align the template usage or the test oracle so the presence of this suffix is deterministic for ADD provenance entries.

Would you like me to propose a patch that normalizes these expectations in this file?

Also applies to: 68-74, 93-99, 123-129, 148-154, 205-215

dspace-api/src/main/java/org/dspace/core/ProvenanceServiceImpl.java (1)

204-207: Apply the same normalization and locals in BITSTREAM add path.

Reuse fieldKey/safeValue for consistency.

Apply:

-                    String msg = messageProvider.getMessage(context,
-                            ProvenanceMessageTemplates.BITSTREAM_METADATA.getTemplate(), item,
-                            messageProvider.getMetadata(
-                                    messageProvider.getMetadataField(metadataField), metadataValue), "added by",
-                            messageProvider.getMessage(bitstream));
+                    final String msg = messageProvider.getMessage(
+                            context,
+                            ProvenanceMessageTemplates.BITSTREAM_METADATA.getTemplate(),
+                            item,
+                            messageProvider.getMetadata(fieldKey, safeValue),
+                            "added by",
+                            messageProvider.getMessage(bitstream)
+                    );
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e9dd3ff and 4a9eb28.

📒 Files selected for processing (3)
  • dspace-api/src/main/java/org/dspace/core/ProvenanceServiceImpl.java (4 hunks)
  • dspace-server-webapp/src/test/java/org/dspace/app/rest/ProvenanceExpectedMessages.java (1 hunks)
  • dspace-server-webapp/src/test/resources/org/dspace/app/rest/test/item-metadata-patch-suite.json (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • dspace-server-webapp/src/test/java/org/dspace/app/rest/ProvenanceExpectedMessages.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: dspace-cli / docker-build (linux/amd64, ubuntu-latest, true)
  • GitHub Check: dspace-test / docker-build (linux/amd64, ubuntu-latest, true)
  • GitHub Check: dspace / docker-build (linux/amd64, ubuntu-latest, true)
  • GitHub Check: Run Integration Tests
  • GitHub Check: Run Unit Tests
🔇 Additional comments (2)
dspace-server-webapp/src/test/resources/org/dspace/app/rest/test/item-metadata-patch-suite.json (2)

26-26: Good: value surfaced in provenance for ADD.

Including the explicit value in the ADD message matches the PR goal.


177-186: Good: value included in DELETE provenance.

Deletion messages capture the deleted value explicitly.

@Kasinhou
Copy link
Author

Kasinhou commented Sep 8, 2025

The intended logic of this PR was done, but there is one thing which will be improved:

image

After replacing and removing metadata there is shown information about bitstreams. It is unnecessary so it will be removed.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
dspace-api/src/main/java/org/dspace/core/ProvenanceServiceImpl.java (3)

205-206: Apply same blank-value normalization for BITSTREAM add flow

Prevents empty/NULL values leaking into messages.

-                            messageProvider.getMetadata(
-                                    messageProvider.getMetadataField(metadataField), metadataValue), "added by",
+                            messageProvider.getMetadata(
+                                    messageProvider.getMetadataField(metadataField),
+                                    StringUtils.isBlank(metadataValue) ? "empty" : metadataValue
+                            ),
+                            "added by",

190-196: Extract intermediate variables for readability (fieldKey, safeValue)

Reduces nesting and aligns with prior nitpicks on this file.

Example:

final String fieldKey = messageProvider.getMetadataField(metadataField);
final String safeValue = StringUtils.isBlank(metadataValue) ? "empty" : metadataValue;
// then use messageProvider.getMetadata(fieldKey, safeValue)

Also applies to: 205-206


190-196: Normalize metadataValue and assert required inputs to avoid NPEs/“null” in provenance

Guard dso/metadataField and coerce blank metadataValue to "empty". Also flatten nested call slightly.

-    public void addMetadata(Context context, DSpaceObject dso, MetadataField metadataField, String metadataValue) {
-        try {
+    public void addMetadata(Context context, DSpaceObject dso, MetadataField metadataField, String metadataValue) {
+        Objects.requireNonNull(dso, "dso");
+        Objects.requireNonNull(metadataField, "metadataField");
+        try {
             if (Constants.ITEM == dso.getType()) {
-                String msg = messageProvider.getMessage(context, ProvenanceMessageTemplates.ITEM_METADATA.getTemplate(),
-                        messageProvider.getMetadata(
-                                messageProvider.getMetadataField(metadataField), metadataValue), "added");
+                String msg = messageProvider.getMessage(
+                        context,
+                        ProvenanceMessageTemplates.ITEM_METADATA.getTemplate(),
+                        messageProvider.getMetadata(
+                                messageProvider.getMetadataField(metadataField),
+                                StringUtils.isBlank(metadataValue) ? "empty" : metadataValue
+                        ),
+                        "added"
+                );
                 addProvenanceMetadata(context, (Item) dso, msg);
             }
🧹 Nitpick comments (2)
dspace-api/src/main/java/org/dspace/core/ProvenanceServiceImpl.java (2)

234-236: Normalize removed value and consider consistent context

Use "empty" when oldMtdValue is blank. Optionally align this call’s argument list with the add flow for consistency.

-                        ProvenanceMessageTemplates.BITSTREAM_METADATA.getTemplate(),
-                        messageProvider.getMetadata(messageProvider.getMetadataField(oldMtdKey), oldMtdValue),
-                        "deleted from", messageProvider.getMessage(bitstream));
+                        ProvenanceMessageTemplates.BITSTREAM_METADATA.getTemplate(),
+                        messageProvider.getMetadata(
+                                messageProvider.getMetadataField(oldMtdKey),
+                                StringUtils.isBlank(oldMtdValue) ? "empty" : oldMtdValue
+                        ),
+                        "deleted from",
+                        messageProvider.getMessage(bitstream));

255-255: Normalize removed value in item index-delete flow

Avoids “null”/blank provenance entries.

-                    messageProvider.getMetadata(oldMtdKey, oldMtdValue), "deleted");
+                    messageProvider.getMetadata(
+                        oldMtdKey,
+                        StringUtils.isBlank(oldMtdValue) ? "empty" : oldMtdValue
+                    ),
+                    "deleted");
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e6db99e and 337a8cc.

📒 Files selected for processing (1)
  • dspace-api/src/main/java/org/dspace/core/ProvenanceServiceImpl.java (6 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: dspace-cli / docker-build (linux/amd64, ubuntu-latest, true)
  • GitHub Check: dspace-test / docker-build (linux/amd64, ubuntu-latest, true)
  • GitHub Check: dspace / docker-build (linux/amd64, ubuntu-latest, true)
  • GitHub Check: Run Integration Tests
  • GitHub Check: Run Unit Tests
🔇 Additional comments (1)
dspace-api/src/main/java/org/dspace/core/ProvenanceServiceImpl.java (1)

263-264: replaceMetadata: blank-coerce, null-check, @OverRide & verify message signature

  • Coerce blank oldMtdVal/newMtdVal to "empty" in the provenance message.
  • Precede the logic with Objects.requireNonNull(metadataField, "metadataField"); to guard inputs.
  • Add @Override if this method implements the ProvenanceService interface.
  • Confirm that MessageProvider.getMessage(...) has the expected overload (with or without the Item parameter) to keep usage consistent—no such overload was found in a code search.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
dspace-server-webapp/src/test/resources/org/dspace/app/rest/test/item-metadata-patch-suite.json (1)

108-119: No provenance expectation for replace — consider adding coverage for old→new message.

Given the PR adds explicit replacement formatting, it’d be valuable to assert the expected provenance for this replace case here (or in a companion Java test) to lock the behavior.

If you confirm the exact replacement message template, I can draft the expected JSON block.

Also applies to: 121-135

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 337a8cc and d6798ec.

📒 Files selected for processing (2)
  • dspace-server-webapp/src/test/java/org/dspace/app/rest/ProvenanceExpectedMessages.java (1 hunks)
  • dspace-server-webapp/src/test/resources/org/dspace/app/rest/test/item-metadata-patch-suite.json (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • dspace-server-webapp/src/test/java/org/dspace/app/rest/ProvenanceExpectedMessages.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: dspace-cli / docker-build (linux/amd64, ubuntu-latest, true)
  • GitHub Check: dspace / docker-build (linux/amd64, ubuntu-latest, true)
  • GitHub Check: dspace-test / docker-build (linux/amd64, ubuntu-latest, true)
  • GitHub Check: Run Unit Tests
  • GitHub Check: Run Integration Tests
🔇 Additional comments (4)
dspace-server-webapp/src/test/resources/org/dspace/app/rest/test/item-metadata-patch-suite.json (4)

26-26: Provenance now includes concrete metadata values — looks correct.

The updated expectations for added/deleted titles correctly embed the dc.title value and remove the trailing “No. of bitstreams: 0”. Ordering and place indexes are consistent across scenarios.

Also applies to: 44-47, 68-74, 93-99, 123-129, 148-154, 177-186, 205-214


138-159: Confirm whether copy operations should emit “added” provenance.

Copy effectively creates a new metadata value. If the service treats this as an add, we may want an additional provenance entry here. If not, current expectations are fine—just confirm alignment with service semantics.


195-216: End-to-end provenance history after full removal — consistent.

The retained history (adds and deletes) and place ordering look coherent with prior steps.


26-26: Use endsWith-based matcher for provenance metadata
Replace full-equality checks of dc.description.provenance with MetadataMatcher.matchMetadataStringEndsWith("dc.description.provenance", "on ") (or a StringEndsWith.endsWith assertion) to avoid flakiness from the timestamp suffix, and confirm the test harness asserts only the prefix rather than the entire string.

Copy link

Copilot AI left a comment

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 enhances provenance metadata logging by including the actual metadata values when adding, updating, or removing metadata in DSpace objects. Previously, provenance logs only showed the metadata field names, but now they display the actual values and show old→new transitions for replacements.

  • Updates provenance service methods to accept and use metadata values in log messages
  • Modifies test fixtures to expect the new value-aware provenance message formats
  • Removes trailing bitstream counts from some provenance messages for cleaner formatting

Reviewed Changes

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

Show a summary per file
File Description
item-metadata-patch-suite.json Updates test expectations to include metadata values in provenance messages
ProvenanceExpectedMessages.java Updates message templates to include metadata values and removes bitstream counts
DSpaceObjectMetadataReplaceOperation.java Passes both old and new metadata values to provenance service
DSpaceObjectMetadataAddOperation.java Passes metadata value to provenance service when adding metadata
ProvenanceServiceImpl.java Updates methods to accept and format metadata values in provenance messages
ProvenanceService.java Updates interface to include metadata value parameters
ProvenanceMessageFormatter.java Adds method to format metadata replacement messages with old→new transitions

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
dspace-api/src/main/java/org/dspace/core/ProvenanceMessageFormatter.java (1)

45-46: Add optional bitstream flag to getMessage and update callers
Implement the non-breaking overload

public String getMessage(Context context, String messageTemplate, Item item,
                         boolean includeBitstreams, Object... args)
        throws SQLException, AuthorizeException {
    if (!includeBitstreams) {
        return getMessage(context, messageTemplate, args);
    }
    return getMessage(context, messageTemplate, item, args);
}

Then in ProvenanceServiceImpl:
– makeDiscoverable(...) →

messageProvider.getMessage(context, DISCOVERABLE.getTemplate(),
                           item, false, discoverable ? "" : "non-")

– uploadBitstream(...) →

messageProvider.getMessage(context, BUNDLE_ADDED.getTemplate(),
                           item, true, bundle.getID())
🧹 Nitpick comments (2)
dspace-api/src/main/java/org/dspace/core/ProvenanceMessageFormatter.java (2)

94-98: Make “empty” handling consistent for null/blank and keep messages single-line.
Today, null → "empty", but empty/blank strings render as empty and values with newlines create multi-line provenance entries. Normalize blanks and strip CR/LF.

Apply within this method:

-    public String getMetadataReplacement(String metadataKey, String oldValue, String newValue) {
-        return String.format("%s [%s -> %s]", metadataKey,
-                Objects.toString(oldValue, "empty"),
-                Objects.toString(newValue, "empty"));
-    }
+    public String getMetadataReplacement(String metadataKey, String oldValue, String newValue) {
+        String oldStr = (oldValue == null || oldValue.isBlank())
+                ? EMPTY_PLACEHOLDER : sanitizeForOneLine(oldValue);
+        String newStr = (newValue == null || newValue.isBlank())
+                ? EMPTY_PLACEHOLDER : sanitizeForOneLine(newValue);
+        return String.format("%s [%s -> %s]", metadataKey, oldStr, newStr);
+    }

Add outside this range (supporting code):

// near other fields
private static final String EMPTY_PLACEHOLDER = "empty";

// near other privates
private static String sanitizeForOneLine(String s) {
    return s.replace('\n', ' ').replace('\r', ' ');
}

I can push a patch that includes these helpers and updates tests expecting the exact message text.


90-92: Align add-metadata formatting with replacement formatting.
Use the same null/blank placeholder to avoid “key: null” and keep consistency.

-    return oldMtdKey + ": " + oldMtdValue;
+    return String.format("%s: %s",
+            oldMtdKey, Objects.toString(oldMtdValue, "empty"));

If you adopt EMPTY_PLACEHOLDER, switch the second arg to that constant.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d6798ec and 4bee9df.

📒 Files selected for processing (1)
  • dspace-api/src/main/java/org/dspace/core/ProvenanceMessageFormatter.java (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: dspace-test / docker-build (linux/amd64, ubuntu-latest, true)
  • GitHub Check: dspace-cli / docker-build (linux/amd64, ubuntu-latest, true)
  • GitHub Check: dspace / docker-build (linux/amd64, ubuntu-latest, true)
  • GitHub Check: Run Integration Tests
  • GitHub Check: Run Unit Tests
🔇 Additional comments (1)
dspace-api/src/main/java/org/dspace/core/ProvenanceMessageFormatter.java (1)

12-12: LGTM: Import for null-safe value rendering is appropriate.
Using java.util.Objects here is correct and scoped to the new helper.

@Kasinhou
Copy link
Author

This is the provenance after adding, updating and deleting metadata:
image

This is the provenance after adding, updating and deleting bitstreams:
image

@milanmajchrak milanmajchrak merged commit 560afce into dtq-dev Sep 25, 2025
16 checks passed
milanmajchrak pushed a commit that referenced this pull request Nov 11, 2025
* Metadata value in provenance when metadata is added

* Updated calling getMessage with item

* Implementing suggestion from copilot to better reliability

* Renamed mtdkey to metadataKey

* Updated provenance test messages for adding metadata

* Updated expectation in item-metadata-patch-suite.json

* Showing old and new value in provenance when updated metadata

* Corrected checkstyle

* Updated test for provenance when replacing metadata

* Updated replacing bitstream metadata output

* Fixed incorrect logic of update bitstream

* Removed unnecessary info about bitstreams and corrected IT

* Removed typo

* Solved failing integration tests

* Changed the message (removing info about bitstreams) in replace and remove metadata

* Updated expected messages

* Refactored for clean code

---------

Co-authored-by: Matus Kasak <matus.kasak@dataquest.sk>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

UFAL/Metadata provenance update metadata value

4 participants