From 15e3df7a2edcf35d9c7e76a798e11a2a56103822 Mon Sep 17 00:00:00 2001 From: milanmajchrak Date: Wed, 18 Mar 2026 15:50:51 +0100 Subject: [PATCH 1/2] feat: set embargo resource policy start date to embargoend + 1 day feat: rewrite SAF update to do in-place metadata and embargo update --- .../app/itemimport/ItemImportServiceImpl.java | 136 ++++++++++-------- .../app/itemimport/EmbargoImportIT.java | 108 +++++++++++++- 2 files changed, 183 insertions(+), 61 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportServiceImpl.java index 09be2ebd474c..3e3811377163 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportServiceImpl.java @@ -35,6 +35,7 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; +import java.util.Calendar; import java.util.Date; import java.util.Enumeration; import java.util.GregorianCalendar; @@ -663,77 +664,67 @@ public void replaceItems(Context c, List mycollections, // read in HashMap first, to get list of handles & source dirs Map myHash = readMapFile(mapFile); - // for each handle, re-import the item, discard the new handle - // and re-assign the old handle + // for each handle, update the existing item's metadata and embargo for (Map.Entry mapEntry : myHash.entrySet()) { - // get the old handle String newItemName = mapEntry.getKey(); String oldHandle = mapEntry.getValue(); - Item oldItem = null; + Item existingItem = null; if (oldHandle.indexOf('/') != -1) { - logInfo("\tReplacing: " + oldHandle); - - // add new item, locate old one - oldItem = (Item) handleService.resolveToObject(c, oldHandle); + logInfo("\tUpdating: " + oldHandle); + existingItem = (Item) handleService.resolveToObject(c, oldHandle); } else { - oldItem = itemService.findByIdOrLegacyId(c, oldHandle); - } - - /* Rather than exposing public item methods to change handles -- - * two handles can't exist at the same time due to key constraints - * so would require temp handle being stored, old being copied to new and - * new being copied to old, all a bit messy -- a handle file is written to - * the import directory containing the old handle, the existing item is - * deleted and then the import runs as though it were loading an item which - * had already been assigned a handle (so a new handle is not even assigned). - * As a commit does not occur until after a successful add, it is safe to - * do a delete as any error results in an aborted transaction without harming - * the original item */ - File handleFile = new File(sourceDir + File.separatorChar + newItemName + File.separatorChar + "handle"); - PrintWriter handleOut = new PrintWriter(new FileWriter(handleFile, true)); - - handleOut.println(oldHandle); - handleOut.close(); - - deleteItem(c, oldItem); - Item newItem = addItem(c, mycollections, sourceDir, newItemName, null, template); - c.uncacheEntity(oldItem); - c.uncacheEntity(newItem); - - // attach license, license label requires an update - // get license name and check if exists and is not null, license name is stored in the metadatum - // `dc.rights` - List dcRights = - itemService.getMetadata(newItem, "dc", "rights", null, Item.ANY); - if (CollectionUtils.isEmpty(dcRights) || Objects.isNull(dcRights.get(0))) { - log.error("Item doesn't have the Clarin License name in the metadata `dc.rights`."); - continue; + existingItem = itemService.findByIdOrLegacyId(c, oldHandle); } - final String licenseName = dcRights.get(0).getValue(); - if (Objects.isNull(licenseName)) { - log.error("License name loaded from the `dc.rights` is null."); + if (existingItem == null) { + logError("ERROR: Item not found for handle/id: " + oldHandle); continue; } - final ClarinLicense license = clarinLicenseService.findByName(c, licenseName); - for (Bundle bundle : newItem.getBundles(CONTENT_BUNDLE_NAME)) { - for (Bitstream b : bundle.getBitstreams()) { - this.clarinLicenseResourceMappingService.detachLicenses(c, b); - // add the license to bitstream - this.clarinLicenseResourceMappingService.attachLicense(c, license, b); - } + // normalize and validate path + Path itemPath = new File(sourceDir + File.separatorChar + newItemName + File.separatorChar) + .toPath().normalize(); + if (!itemPath.startsWith(sourceDir)) { + throw new IOException("Illegal item metadata path: '" + itemPath); } + String itemPathDir = itemPath.toString() + File.separatorChar; + + // Clear existing metadata and load new metadata from SAF + itemService.clearMetadata(c, existingItem, Item.ANY, Item.ANY, Item.ANY, Item.ANY); + loadMetadata(c, existingItem, itemPathDir); + + // Remove existing embargo policies from bitstreams (Anonymous READ with startDate) + removeEmbargoFromItemBitstreams(c, existingItem); + + // Process embargo metadata and apply new embargo policies + processEmbargoMetadata(c, existingItem); - itemService.clearMetadata(c, newItem, "dc", "rights", "label", Item.ANY); - itemService.addMetadata(c, newItem, "dc", "rights", "label", Item.ANY, - license.getNonExtendedClarinLicenseLabel().getLabel()); - clarinItemService.updateItemFilesMetadata(c, newItem); + // Handle Clarin licenses + List dcRights = + itemService.getMetadata(existingItem, "dc", "rights", null, Item.ANY); + if (!CollectionUtils.isEmpty(dcRights) && !Objects.isNull(dcRights.get(0))) { + final String licenseName = dcRights.get(0).getValue(); + if (!Objects.isNull(licenseName)) { + final ClarinLicense license = clarinLicenseService.findByName(c, licenseName); + if (license != null) { + for (Bundle bundle : existingItem.getBundles(CONTENT_BUNDLE_NAME)) { + for (Bitstream b : bundle.getBitstreams()) { + this.clarinLicenseResourceMappingService.detachLicenses(c, b); + this.clarinLicenseResourceMappingService.attachLicense(c, license, b); + } + } + itemService.clearMetadata(c, existingItem, "dc", "rights", "label", Item.ANY); + itemService.addMetadata(c, existingItem, "dc", "rights", "label", Item.ANY, + license.getNonExtendedClarinLicenseLabel().getLabel()); + } + } + } - itemService.update(c, newItem); - c.uncacheEntity(newItem); + clarinItemService.updateItemFilesMetadata(c, existingItem); + itemService.update(c, existingItem); + c.uncacheEntity(existingItem); } } @@ -2592,6 +2583,14 @@ protected void processEmbargoMetadata(Context c, Item item) throws SQLException, ". Embargo will not be applied."); return; } + + // Resource policy start date = embargoend + 1 day + // The embargo end date is the last day of the embargo, + // so the file becomes accessible the day after. + Calendar cal = Calendar.getInstance(); + cal.setTime(endDate); + cal.add(Calendar.DAY_OF_MONTH, 1); + endDate = cal.getTime(); } catch (Exception e) { logError("ERROR: Failed to parse embargo end date: " + embargoEndDateStr + ". Error: " + e.getMessage()); @@ -2685,4 +2684,29 @@ protected void applyEmbargoToItemBitstreams(Context c, Item item, Date embargoEn } } + /** + * Remove existing embargo ResourcePolicies from all bitstreams in the item's ORIGINAL bundles. + * Removes Anonymous READ policies that have a start date (embargo policies). + */ + protected void removeEmbargoFromItemBitstreams(Context c, Item item) + throws SQLException, AuthorizeException { + Group anonymousGroup = groupService.findByName(c, Group.ANONYMOUS); + if (anonymousGroup == null) { + return; + } + + List originalBundles = item.getBundles("ORIGINAL"); + for (Bundle bundle : originalBundles) { + for (Bitstream bitstream : bundle.getBitstreams()) { + List policies = resourcePolicyService.find(c, bitstream, Constants.READ); + for (ResourcePolicy policy : policies) { + if (policy.getGroup() != null && policy.getGroup().equals(anonymousGroup) + && policy.getStartDate() != null) { + resourcePolicyService.delete(c, policy); + } + } + } + } + } + } diff --git a/dspace-api/src/test/java/org/dspace/app/itemimport/EmbargoImportIT.java b/dspace-api/src/test/java/org/dspace/app/itemimport/EmbargoImportIT.java index 631ca003c44a..03ea2e70775e 100644 --- a/dspace-api/src/test/java/org/dspace/app/itemimport/EmbargoImportIT.java +++ b/dspace-api/src/test/java/org/dspace/app/itemimport/EmbargoImportIT.java @@ -8,6 +8,7 @@ package org.dspace.app.itemimport; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -15,6 +16,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.text.SimpleDateFormat; +import java.util.Calendar; import java.util.List; import java.util.stream.Collectors; @@ -37,6 +39,8 @@ import org.dspace.eperson.Group; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.GroupService; +import org.dspace.handle.factory.HandleServiceFactory; +import org.dspace.handle.service.HandleService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.junit.After; @@ -53,6 +57,8 @@ public class EmbargoImportIT extends AbstractIntegrationTestWithDatabase { private static final String EMBARGOEND_DATE_FUTURE = "2026-06-30"; + // The resource policy start date should be embargoend + 1 day + private static final String EXPECTED_POLICY_START_DATE = "2026-07-01"; private static final String EMBARGOEND_DATE_PAST = "2020-01-01"; private static final String ITEM_TITLE = "Test Embargo Item"; @@ -60,6 +66,7 @@ public class EmbargoImportIT extends AbstractIntegrationTestWithDatabase { private ResourcePolicyService resourcePolicyService = AuthorizeServiceFactory.getInstance().getResourcePolicyService(); private GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + private HandleService handleService = HandleServiceFactory.getInstance().getHandleService(); private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); private MetadataSchemaService metadataSchemaService = @@ -171,10 +178,10 @@ public void testStandardEmbargoImport() throws Exception { assertNotNull("Should have embargo policy for Anonymous group", embargoPolicy); assertNotNull("Embargo policy should have start date", embargoPolicy.getStartDate()); - // Verify start date matches embargo end date + // Verify start date is embargoend + 1 day (file becomes accessible day after embargo ends) SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); - assertEquals("Embargo start date should match embargoend metadata", - EMBARGOEND_DATE_FUTURE, sdf.format(embargoPolicy.getStartDate())); + assertEquals("Embargo policy start date should be embargoend + 1 day", + EXPECTED_POLICY_START_DATE, sdf.format(embargoPolicy.getStartDate())); } /** @@ -360,8 +367,99 @@ public void testMultipleBitstreamsEmbargo() throws Exception { assertNotNull("Each embargo policy should have start date", embargoPolicy.getStartDate()); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); - assertEquals("Each embargo start date should match embargoend metadata", - EMBARGOEND_DATE_FUTURE, sdf.format(embargoPolicy.getStartDate())); + assertEquals("Each embargo start date should be embargoend + 1 day", + EXPECTED_POLICY_START_DATE, sdf.format(embargoPolicy.getStartDate())); } } + + /** + * Test SAF update: updating metadata and embargo on existing item + * without deleting the item (as opposed to replace which deletes and re-creates). + */ + @Test + public void testSafUpdateMetadataAndEmbargo() throws Exception { + // First, import an item WITHOUT embargo + Path safDir = Files.createDirectory(Path.of(tempDir.toString() + "/test")); + Path itemDir = Files.createDirectory(Path.of(safDir.toString() + "/item_000")); + + String dublinCoreContent = "\n" + + "\n" + + " " + ITEM_TITLE + "\n" + + " Original Author\n" + + ""; + Files.writeString(Path.of(itemDir.toString() + "/dublin_core.xml"), dublinCoreContent); + + // Add bitstream + Path contentsFile = Files.createFile(Path.of(itemDir.toString() + "/contents")); + Files.writeString(contentsFile, "test.txt"); + Files.writeString(Files.createFile(Path.of(itemDir.toString() + "/test.txt")), "TEST CONTENT"); + + String mapFilePath = tempDir.toString() + "/mapfile.out"; + String[] importArgs = new String[] { "import", "-a", "-e", admin.getEmail(), + "-c", collection.getID().toString(), + "-s", safDir.toString(), "-m", mapFilePath }; + runDSpaceScript(importArgs); + + // Verify initial import + Item item = itemService.findByMetadataField(context, "dc", "title", null, ITEM_TITLE).next(); + assertNotNull("Item should be created", item); + String itemHandle = item.getHandle(); + + // Verify no embargo policy initially + List bitstreams = item.getBundles("ORIGINAL").get(0).getBitstreams(); + Bitstream bitstream = bitstreams.get(0); + List initialPolicies = resourcePolicyService.find(context, bitstream, Constants.READ); + boolean hasInitialEmbargo = initialPolicies.stream() + .anyMatch(p -> p.getGroup() != null && p.getGroup().equals(anonymousGroup) && p.getStartDate() != null); + assertFalse("Should not have embargo policy initially", hasInitialEmbargo); + + // Now prepare SAF update directory with updated metadata (add embargo) + Path updateSafDir = Files.createDirectory(Path.of(tempDir.toString() + "/update")); + Path updateItemDir = Files.createDirectory(Path.of(updateSafDir.toString() + "/item_000")); + + String updatedDublinCoreContent = "\n" + + "\n" + + " " + ITEM_TITLE + " Updated\n" + + " Updated Author\n" + + " embargoedAccess\n" + + " " + EMBARGOEND_DATE_FUTURE + "\n" + + ""; + Files.writeString(Path.of(updateItemDir.toString() + "/dublin_core.xml"), updatedDublinCoreContent); + + // Create mapfile for update (item_000 -> handle) + String updateMapFilePath = tempDir.toString() + "/update_mapfile.out"; + Files.writeString(Path.of(updateMapFilePath), "item_000 " + itemHandle + "\n"); + + // Perform SAF update + String[] updateArgs = new String[] { "import", "-r", "-e", admin.getEmail(), + "-c", collection.getID().toString(), + "-s", updateSafDir.toString(), "-m", updateMapFilePath }; + runDSpaceScript(updateArgs); + + // Reload item + context.uncacheEntity(item); + Item updatedItem = (Item) handleService.resolveToObject(context, itemHandle); + assertNotNull("Updated item should exist", updatedItem); + + // Verify metadata was updated + assertEquals("Title should be updated", ITEM_TITLE + " Updated", updatedItem.getName()); + + // Verify embargo policy was applied + List updatedBitstreams = updatedItem.getBundles("ORIGINAL").get(0).getBitstreams(); + assertEquals("Should have one bitstream", 1, updatedBitstreams.size()); + + Bitstream updatedBitstream = updatedBitstreams.get(0); + List updatedPolicies = resourcePolicyService.find(context, updatedBitstream, Constants.READ); + + ResourcePolicy embargoPolicy = updatedPolicies.stream() + .filter(p -> p.getGroup() != null && p.getGroup().equals(anonymousGroup) && p.getStartDate() != null) + .findFirst() + .orElse(null); + + assertNotNull("Should have embargo policy after update", embargoPolicy); + + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + assertEquals("Embargo policy start date should be embargoend + 1 day", + EXPECTED_POLICY_START_DATE, sdf.format(embargoPolicy.getStartDate())); + } } From 2541042b4ec868887e0dc8d74c8f116f24a92b5d Mon Sep 17 00:00:00 2001 From: milanmajchrak Date: Thu, 19 Mar 2026 14:44:26 +0100 Subject: [PATCH 2/2] Reverted SAF update changes --- .../app/itemimport/ItemImportServiceImpl.java | 127 ++++++++---------- .../app/itemimport/EmbargoImportIT.java | 96 ------------- 2 files changed, 56 insertions(+), 167 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportServiceImpl.java index 3e3811377163..362872edfe14 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportServiceImpl.java @@ -664,67 +664,77 @@ public void replaceItems(Context c, List mycollections, // read in HashMap first, to get list of handles & source dirs Map myHash = readMapFile(mapFile); - // for each handle, update the existing item's metadata and embargo + // for each handle, re-import the item, discard the new handle + // and re-assign the old handle for (Map.Entry mapEntry : myHash.entrySet()) { + // get the old handle String newItemName = mapEntry.getKey(); String oldHandle = mapEntry.getValue(); - Item existingItem = null; + Item oldItem = null; if (oldHandle.indexOf('/') != -1) { - logInfo("\tUpdating: " + oldHandle); - existingItem = (Item) handleService.resolveToObject(c, oldHandle); - } else { - existingItem = itemService.findByIdOrLegacyId(c, oldHandle); - } + logInfo("\tReplacing: " + oldHandle); - if (existingItem == null) { - logError("ERROR: Item not found for handle/id: " + oldHandle); + // add new item, locate old one + oldItem = (Item) handleService.resolveToObject(c, oldHandle); + } else { + oldItem = itemService.findByIdOrLegacyId(c, oldHandle); + } + + /* Rather than exposing public item methods to change handles -- + * two handles can't exist at the same time due to key constraints + * so would require temp handle being stored, old being copied to new and + * new being copied to old, all a bit messy -- a handle file is written to + * the import directory containing the old handle, the existing item is + * deleted and then the import runs as though it were loading an item which + * had already been assigned a handle (so a new handle is not even assigned). + * As a commit does not occur until after a successful add, it is safe to + * do a delete as any error results in an aborted transaction without harming + * the original item */ + File handleFile = new File(sourceDir + File.separatorChar + newItemName + File.separatorChar + "handle"); + PrintWriter handleOut = new PrintWriter(new FileWriter(handleFile, true)); + + handleOut.println(oldHandle); + handleOut.close(); + + deleteItem(c, oldItem); + Item newItem = addItem(c, mycollections, sourceDir, newItemName, null, template); + c.uncacheEntity(oldItem); + c.uncacheEntity(newItem); + + // attach license, license label requires an update + // get license name and check if exists and is not null, license name is stored in the metadatum + // `dc.rights` + List dcRights = + itemService.getMetadata(newItem, "dc", "rights", null, Item.ANY); + if (CollectionUtils.isEmpty(dcRights) || Objects.isNull(dcRights.get(0))) { + log.error("Item doesn't have the Clarin License name in the metadata `dc.rights`."); continue; } - // normalize and validate path - Path itemPath = new File(sourceDir + File.separatorChar + newItemName + File.separatorChar) - .toPath().normalize(); - if (!itemPath.startsWith(sourceDir)) { - throw new IOException("Illegal item metadata path: '" + itemPath); + final String licenseName = dcRights.get(0).getValue(); + if (Objects.isNull(licenseName)) { + log.error("License name loaded from the `dc.rights` is null."); + continue; } - String itemPathDir = itemPath.toString() + File.separatorChar; - - // Clear existing metadata and load new metadata from SAF - itemService.clearMetadata(c, existingItem, Item.ANY, Item.ANY, Item.ANY, Item.ANY); - loadMetadata(c, existingItem, itemPathDir); - - // Remove existing embargo policies from bitstreams (Anonymous READ with startDate) - removeEmbargoFromItemBitstreams(c, existingItem); - // Process embargo metadata and apply new embargo policies - processEmbargoMetadata(c, existingItem); - - // Handle Clarin licenses - List dcRights = - itemService.getMetadata(existingItem, "dc", "rights", null, Item.ANY); - if (!CollectionUtils.isEmpty(dcRights) && !Objects.isNull(dcRights.get(0))) { - final String licenseName = dcRights.get(0).getValue(); - if (!Objects.isNull(licenseName)) { - final ClarinLicense license = clarinLicenseService.findByName(c, licenseName); - if (license != null) { - for (Bundle bundle : existingItem.getBundles(CONTENT_BUNDLE_NAME)) { - for (Bitstream b : bundle.getBitstreams()) { - this.clarinLicenseResourceMappingService.detachLicenses(c, b); - this.clarinLicenseResourceMappingService.attachLicense(c, license, b); - } - } - itemService.clearMetadata(c, existingItem, "dc", "rights", "label", Item.ANY); - itemService.addMetadata(c, existingItem, "dc", "rights", "label", Item.ANY, - license.getNonExtendedClarinLicenseLabel().getLabel()); - } + final ClarinLicense license = clarinLicenseService.findByName(c, licenseName); + for (Bundle bundle : newItem.getBundles(CONTENT_BUNDLE_NAME)) { + for (Bitstream b : bundle.getBitstreams()) { + this.clarinLicenseResourceMappingService.detachLicenses(c, b); + // add the license to bitstream + this.clarinLicenseResourceMappingService.attachLicense(c, license, b); } } - clarinItemService.updateItemFilesMetadata(c, existingItem); - itemService.update(c, existingItem); - c.uncacheEntity(existingItem); + itemService.clearMetadata(c, newItem, "dc", "rights", "label", Item.ANY); + itemService.addMetadata(c, newItem, "dc", "rights", "label", Item.ANY, + license.getNonExtendedClarinLicenseLabel().getLabel()); + clarinItemService.updateItemFilesMetadata(c, newItem); + + itemService.update(c, newItem); + c.uncacheEntity(newItem); } } @@ -2684,29 +2694,4 @@ protected void applyEmbargoToItemBitstreams(Context c, Item item, Date embargoEn } } - /** - * Remove existing embargo ResourcePolicies from all bitstreams in the item's ORIGINAL bundles. - * Removes Anonymous READ policies that have a start date (embargo policies). - */ - protected void removeEmbargoFromItemBitstreams(Context c, Item item) - throws SQLException, AuthorizeException { - Group anonymousGroup = groupService.findByName(c, Group.ANONYMOUS); - if (anonymousGroup == null) { - return; - } - - List originalBundles = item.getBundles("ORIGINAL"); - for (Bundle bundle : originalBundles) { - for (Bitstream bitstream : bundle.getBitstreams()) { - List policies = resourcePolicyService.find(c, bitstream, Constants.READ); - for (ResourcePolicy policy : policies) { - if (policy.getGroup() != null && policy.getGroup().equals(anonymousGroup) - && policy.getStartDate() != null) { - resourcePolicyService.delete(c, policy); - } - } - } - } - } - } diff --git a/dspace-api/src/test/java/org/dspace/app/itemimport/EmbargoImportIT.java b/dspace-api/src/test/java/org/dspace/app/itemimport/EmbargoImportIT.java index 03ea2e70775e..2200b84334cf 100644 --- a/dspace-api/src/test/java/org/dspace/app/itemimport/EmbargoImportIT.java +++ b/dspace-api/src/test/java/org/dspace/app/itemimport/EmbargoImportIT.java @@ -8,7 +8,6 @@ package org.dspace.app.itemimport; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -16,7 +15,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.text.SimpleDateFormat; -import java.util.Calendar; import java.util.List; import java.util.stream.Collectors; @@ -39,8 +37,6 @@ import org.dspace.eperson.Group; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.GroupService; -import org.dspace.handle.factory.HandleServiceFactory; -import org.dspace.handle.service.HandleService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.junit.After; @@ -66,7 +62,6 @@ public class EmbargoImportIT extends AbstractIntegrationTestWithDatabase { private ResourcePolicyService resourcePolicyService = AuthorizeServiceFactory.getInstance().getResourcePolicyService(); private GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); - private HandleService handleService = HandleServiceFactory.getInstance().getHandleService(); private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); private MetadataSchemaService metadataSchemaService = @@ -371,95 +366,4 @@ public void testMultipleBitstreamsEmbargo() throws Exception { EXPECTED_POLICY_START_DATE, sdf.format(embargoPolicy.getStartDate())); } } - - /** - * Test SAF update: updating metadata and embargo on existing item - * without deleting the item (as opposed to replace which deletes and re-creates). - */ - @Test - public void testSafUpdateMetadataAndEmbargo() throws Exception { - // First, import an item WITHOUT embargo - Path safDir = Files.createDirectory(Path.of(tempDir.toString() + "/test")); - Path itemDir = Files.createDirectory(Path.of(safDir.toString() + "/item_000")); - - String dublinCoreContent = "\n" + - "\n" + - " " + ITEM_TITLE + "\n" + - " Original Author\n" + - ""; - Files.writeString(Path.of(itemDir.toString() + "/dublin_core.xml"), dublinCoreContent); - - // Add bitstream - Path contentsFile = Files.createFile(Path.of(itemDir.toString() + "/contents")); - Files.writeString(contentsFile, "test.txt"); - Files.writeString(Files.createFile(Path.of(itemDir.toString() + "/test.txt")), "TEST CONTENT"); - - String mapFilePath = tempDir.toString() + "/mapfile.out"; - String[] importArgs = new String[] { "import", "-a", "-e", admin.getEmail(), - "-c", collection.getID().toString(), - "-s", safDir.toString(), "-m", mapFilePath }; - runDSpaceScript(importArgs); - - // Verify initial import - Item item = itemService.findByMetadataField(context, "dc", "title", null, ITEM_TITLE).next(); - assertNotNull("Item should be created", item); - String itemHandle = item.getHandle(); - - // Verify no embargo policy initially - List bitstreams = item.getBundles("ORIGINAL").get(0).getBitstreams(); - Bitstream bitstream = bitstreams.get(0); - List initialPolicies = resourcePolicyService.find(context, bitstream, Constants.READ); - boolean hasInitialEmbargo = initialPolicies.stream() - .anyMatch(p -> p.getGroup() != null && p.getGroup().equals(anonymousGroup) && p.getStartDate() != null); - assertFalse("Should not have embargo policy initially", hasInitialEmbargo); - - // Now prepare SAF update directory with updated metadata (add embargo) - Path updateSafDir = Files.createDirectory(Path.of(tempDir.toString() + "/update")); - Path updateItemDir = Files.createDirectory(Path.of(updateSafDir.toString() + "/item_000")); - - String updatedDublinCoreContent = "\n" + - "\n" + - " " + ITEM_TITLE + " Updated\n" + - " Updated Author\n" + - " embargoedAccess\n" + - " " + EMBARGOEND_DATE_FUTURE + "\n" + - ""; - Files.writeString(Path.of(updateItemDir.toString() + "/dublin_core.xml"), updatedDublinCoreContent); - - // Create mapfile for update (item_000 -> handle) - String updateMapFilePath = tempDir.toString() + "/update_mapfile.out"; - Files.writeString(Path.of(updateMapFilePath), "item_000 " + itemHandle + "\n"); - - // Perform SAF update - String[] updateArgs = new String[] { "import", "-r", "-e", admin.getEmail(), - "-c", collection.getID().toString(), - "-s", updateSafDir.toString(), "-m", updateMapFilePath }; - runDSpaceScript(updateArgs); - - // Reload item - context.uncacheEntity(item); - Item updatedItem = (Item) handleService.resolveToObject(context, itemHandle); - assertNotNull("Updated item should exist", updatedItem); - - // Verify metadata was updated - assertEquals("Title should be updated", ITEM_TITLE + " Updated", updatedItem.getName()); - - // Verify embargo policy was applied - List updatedBitstreams = updatedItem.getBundles("ORIGINAL").get(0).getBitstreams(); - assertEquals("Should have one bitstream", 1, updatedBitstreams.size()); - - Bitstream updatedBitstream = updatedBitstreams.get(0); - List updatedPolicies = resourcePolicyService.find(context, updatedBitstream, Constants.READ); - - ResourcePolicy embargoPolicy = updatedPolicies.stream() - .filter(p -> p.getGroup() != null && p.getGroup().equals(anonymousGroup) && p.getStartDate() != null) - .findFirst() - .orElse(null); - - assertNotNull("Should have embargo policy after update", embargoPolicy); - - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); - assertEquals("Embargo policy start date should be embargoend + 1 day", - EXPECTED_POLICY_START_DATE, sdf.format(embargoPolicy.getStartDate())); - } }