From db81d758a947a9bdbb63fea9e872bc9b52a377ff Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Wed, 28 Jun 2023 19:10:37 +0100 Subject: [PATCH 01/23] OAI: add support to extract embargo from bitstreams and expose it in OAI metadata --- .../java/org/dspace/xoai/util/ItemUtils.java | 32 +++++++++++++++++++ .../oai/metadataFormats/uketd_dc.xsl | 5 +++ 2 files changed, 37 insertions(+) diff --git a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java index 955c3a78c392..f15e82ce52d0 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java @@ -11,6 +11,8 @@ import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.List; import com.lyncode.xoai.dataprovider.xml.xoai.Element; @@ -21,6 +23,9 @@ import org.dspace.app.util.factory.UtilServiceFactory; import org.dspace.app.util.service.MetadataExposureService; import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.ResourcePolicy; +import org.dspace.authorize.factory.AuthorizeServiceFactory; +import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.Item; @@ -34,6 +39,9 @@ import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.Utils; +import org.dspace.eperson.Group; +import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.eperson.service.GroupService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.xoai.data.DSpaceItem; @@ -57,6 +65,9 @@ public class ItemUtils { private static final BitstreamService bitstreamService = ContentServiceFactory.getInstance().getBitstreamService(); + private static final AuthorizeService authorizeService = + AuthorizeServiceFactory.getInstance().getAuthorizeService(); + private static final ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); /** @@ -136,6 +147,9 @@ private static Element createBundlesElement(Context context, Item item) throws S if (description != null) { bitstream.getField().add(createValue("description", description)); } + // Add bitstream embargo information (READ policy present, for Anonymous group with a start date) + addEmbargoField(context, bit, bitstream); + bitstream.getField().add(createValue("format", bit.getFormat(context).getMIMEType())); bitstream.getField().add(createValue("size", "" + bit.getSizeBytes())); bitstream.getField().add(createValue("url", url)); @@ -148,6 +162,24 @@ private static Element createBundlesElement(Context context, Item item) throws S return bundles; } + private static void addEmbargoField(Context context, Bitstream bit, Element bitstream) throws SQLException { + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + Group anonymousGroup = groupService.findByName(context, Group.ANONYMOUS); + List policies = authorizeService.findPoliciesByDSOAndType(context, bit, ResourcePolicy.TYPE_CUSTOM); + + for (ResourcePolicy policy : policies) { + if (policy.getGroup() == anonymousGroup && policy.getAction() == Constants.READ) { + Date startDate = policies.get(0).getStartDate(); + + if (startDate != null && startDate.after(new Date())) { + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); + bitstream.getField().add( + createValue("embargo", formatter.format(startDate))); + } + } + } + } + private static Element createLicenseElement(Context context, Item item) throws SQLException, AuthorizeException, IOException { Element license = create("license"); diff --git a/dspace/config/crosswalks/oai/metadataFormats/uketd_dc.xsl b/dspace/config/crosswalks/oai/metadataFormats/uketd_dc.xsl index a3a4e6667046..b9d81aef5da8 100644 --- a/dspace/config/crosswalks/oai/metadataFormats/uketd_dc.xsl +++ b/dspace/config/crosswalks/oai/metadataFormats/uketd_dc.xsl @@ -123,6 +123,11 @@ + + + + From 51e60fbcf92ea731c4e355c9cf080d251ffbf68f Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Wed, 28 Jun 2023 19:27:35 +0100 Subject: [PATCH 02/23] ItemUtils.java: added method doc --- .../main/java/org/dspace/xoai/util/ItemUtils.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java index f15e82ce52d0..b1b949770dbd 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java @@ -162,10 +162,17 @@ private static Element createBundlesElement(Context context, Item item) throws S return bundles; } - private static void addEmbargoField(Context context, Bitstream bit, Element bitstream) throws SQLException { + /** + * This method will add embargo metadata for all bitstreams with an active embargo + * @param context + * @param bitstream the bitstream object + * @param bitstreamEl the bitstream metadata object to add embargo value to + * @throws SQLException + */ + private static void addEmbargoField(Context context, Bitstream bitstream, Element bitstreamEl) throws SQLException { GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); Group anonymousGroup = groupService.findByName(context, Group.ANONYMOUS); - List policies = authorizeService.findPoliciesByDSOAndType(context, bit, ResourcePolicy.TYPE_CUSTOM); + List policies = authorizeService.findPoliciesByDSOAndType(context, bitstream, ResourcePolicy.TYPE_CUSTOM); for (ResourcePolicy policy : policies) { if (policy.getGroup() == anonymousGroup && policy.getAction() == Constants.READ) { @@ -173,7 +180,7 @@ private static void addEmbargoField(Context context, Bitstream bit, Element bits if (startDate != null && startDate.after(new Date())) { SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); - bitstream.getField().add( + bitstreamEl.getField().add( createValue("embargo", formatter.format(startDate))); } } From 538be7f09ba790a4ab7099e7027e1e8f6a9c62ea Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Thu, 29 Jun 2023 09:06:08 +0100 Subject: [PATCH 03/23] ItemUtils.java: improved method to account for multiple embargo policies and select the longest embargo --- .../java/org/dspace/xoai/util/ItemUtils.java | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java index b1b949770dbd..107454ecd082 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java @@ -14,6 +14,8 @@ import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; +import java.util.ArrayList; +import java.util.Collections; import com.lyncode.xoai.dataprovider.xml.xoai.Element; import com.lyncode.xoai.dataprovider.xml.xoai.Metadata; @@ -163,7 +165,8 @@ private static Element createBundlesElement(Context context, Item item) throws S } /** - * This method will add embargo metadata for all bitstreams with an active embargo + * This method will add embargo metadata for a give bitstream with an active embargo. + * It will parse of relevant policies and select the longest active embargo * @param context * @param bitstream the bitstream object * @param bitstreamEl the bitstream metadata object to add embargo value to @@ -174,17 +177,23 @@ private static void addEmbargoField(Context context, Bitstream bitstream, Elemen Group anonymousGroup = groupService.findByName(context, Group.ANONYMOUS); List policies = authorizeService.findPoliciesByDSOAndType(context, bitstream, ResourcePolicy.TYPE_CUSTOM); + List embargoDates = new ArrayList<>(); + // Account for cases where there could be more than one embargo policy for (ResourcePolicy policy : policies) { if (policy.getGroup() == anonymousGroup && policy.getAction() == Constants.READ) { - Date startDate = policies.get(0).getStartDate(); - + Date startDate = policy.getStartDate(); if (startDate != null && startDate.after(new Date())) { - SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); - bitstreamEl.getField().add( - createValue("embargo", formatter.format(startDate))); + embargoDates.add(startDate); } } } + if (embargoDates.size() >= 1) { + // Sort array of dates to extract the longest embargo + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); + Collections.sort(embargoDates, Date::compareTo); + bitstreamEl.getField().add( + createValue("embargo", formatter.format(embargoDates.get(embargoDates.size() - 1)))); + } } private static Element createLicenseElement(Context context, Item item) From 895926f021a355181faef47b5c41e78031700475 Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Wed, 2 Aug 2023 15:24:29 +0100 Subject: [PATCH 04/23] Refactored access-status to include embargo date based on the DefaultAccessStatusHelper logic (look at primary or first bitstream) --- .../access/status/AccessStatusHelper.java | 10 +++ .../status/AccessStatusServiceImpl.java | 5 ++ .../status/DefaultAccessStatusHelper.java | 89 ++++++++++++++++++- .../status/service/AccessStatusService.java | 12 +++ .../status/DefaultAccessStatusHelperTest.java | 7 ++ .../AccessStatusElementItemCompilePlugin.java | 14 +++ .../oai/metadataFormats/uketd_dc.xsl | 11 +-- 7 files changed, 139 insertions(+), 9 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/access/status/AccessStatusHelper.java b/dspace-api/src/main/java/org/dspace/access/status/AccessStatusHelper.java index 1cacbf6aedf6..d847e907b403 100644 --- a/dspace-api/src/main/java/org/dspace/access/status/AccessStatusHelper.java +++ b/dspace-api/src/main/java/org/dspace/access/status/AccessStatusHelper.java @@ -27,4 +27,14 @@ public interface AccessStatusHelper { */ public String getAccessStatusFromItem(Context context, Item item, Date threshold) throws SQLException; + + /** + * Retrieve embargo information for the item + * + * @param context the DSpace context + * @param item the item to check for embargo information + * @return an embargo date + * @throws SQLException An exception that provides information on a database access error or other errors. + */ + public String getEmbargoFromItem(Context context, Item item) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/access/status/AccessStatusServiceImpl.java b/dspace-api/src/main/java/org/dspace/access/status/AccessStatusServiceImpl.java index 544dc99cb4dd..f0f68b22a195 100644 --- a/dspace-api/src/main/java/org/dspace/access/status/AccessStatusServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/access/status/AccessStatusServiceImpl.java @@ -63,4 +63,9 @@ public void init() throws Exception { public String getAccessStatus(Context context, Item item) throws SQLException { return helper.getAccessStatusFromItem(context, item, forever_date); } + + @Override + public String getEmbargoFromItem(Context context, Item item) throws SQLException { + return helper.getEmbargoFromItem(context, item); + } } diff --git a/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java b/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java index a67fa67af3b9..e7055181aa95 100644 --- a/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java +++ b/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java @@ -26,6 +26,7 @@ import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.eperson.Group; +import org.joda.time.LocalDate; /** * Default plugin implementation of the access status helper. @@ -33,6 +34,11 @@ * calculate the access status of an item based on the policies of * the primary or the first bitstream in the original bundle. * Users can override this method for enhanced functionality. + * + * The getEmbargoInformationFromItem method provides a simple logic to + * * retrieve embargo information of bitstreams from an item based on the policies of + * * the primary or the first bitstream in the original bundle. + * * Users can override this method for enhanced functionality. */ public class DefaultAccessStatusHelper implements AccessStatusHelper { public static final String EMBARGO = "embargo"; @@ -54,12 +60,12 @@ public DefaultAccessStatusHelper() { /** * Look at the item's policies to determine an access status value. - * It is also considering a date threshold for embargos and restrictions. + * It is also considering a date threshold for embargoes and restrictions. * * If the item is null, simply returns the "unknown" value. * * @param context the DSpace context - * @param item the item to embargo + * @param item the item to check for embargoes * @param threshold the embargo threshold date * @return an access status value */ @@ -86,7 +92,7 @@ public String getAccessStatusFromItem(Context context, Item item, Date threshold .findFirst() .orElse(null); } - return caculateAccessStatusForDso(context, bitstream, threshold); + return calculateAccessStatusForDso(context, bitstream, threshold); } /** @@ -104,7 +110,7 @@ public String getAccessStatusFromItem(Context context, Item item, Date threshold * @param threshold the embargo threshold date * @return an access status value */ - private String caculateAccessStatusForDso(Context context, DSpaceObject dso, Date threshold) + private String calculateAccessStatusForDso(Context context, DSpaceObject dso, Date threshold) throws SQLException { if (dso == null) { return METADATA_ONLY; @@ -156,4 +162,79 @@ private String caculateAccessStatusForDso(Context context, DSpaceObject dso, Dat } return RESTRICTED; } + + /** + * Look at the policies of the primary (or first) bitstream of the item to retrieve its embargo. + * + * If the item is null, simply returns an empty map with no embargo information. + * + * @param context the DSpace context + * @param item the item to embargo + * @return an access status value + */ + @Override + public String getEmbargoFromItem(Context context, Item item) + throws SQLException { + Date embargoDate; + + if (item == null) { + return null; + } + // Consider only the original bundles. + List bundles = item.getBundles(Constants.DEFAULT_BUNDLE_NAME); + // Check for primary bitstreams first. + Bitstream bitstream = bundles.stream() + .map(bundle -> bundle.getPrimaryBitstream()) + .filter(Objects::nonNull) + .findFirst() + .orElse(null); + if (bitstream == null) { + // If there is no primary bitstream, + // take the first bitstream in the bundles. + bitstream = bundles.stream() + .map(bundle -> bundle.getBitstreams()) + .flatMap(List::stream) + .findFirst() + .orElse(null); + } + + embargoDate = this.retrieveLongestEmbargo(context, bitstream); + + return embargoDate != null ? embargoDate.toString() : null; + } + + /** + * + */ + private Date retrieveLongestEmbargo(Context context, Bitstream bitstream) throws SQLException { + Date embargoDate = null; + // Only consider read policies. + List policies = authorizeService + .getPoliciesActionFilter(context, bitstream, Constants.READ); + + // Looks at all read policies. + for (ResourcePolicy policy : policies) { + boolean isValid = resourcePolicyService.isDateValid(policy); + Group group = policy.getGroup(); + + if (group != null && StringUtils.equals(group.getName(), Group.ANONYMOUS)) { + // Only calculate the status for the anonymous group. + if (!isValid) { + // If the policy is not valid there is an active embargo + Date startDate = policy.getStartDate(); + + if (startDate != null && !startDate.before(LocalDate.now().toDate())) { + // There is an active embargo: aim to take the longest embargo + if (embargoDate == null) { + embargoDate = startDate; + } else { + embargoDate = startDate.after(embargoDate) ? startDate : embargoDate; + } + } + } + } + } + + return embargoDate; + } } diff --git a/dspace-api/src/main/java/org/dspace/access/status/service/AccessStatusService.java b/dspace-api/src/main/java/org/dspace/access/status/service/AccessStatusService.java index 43de5e3c47f1..937cb02692ff 100644 --- a/dspace-api/src/main/java/org/dspace/access/status/service/AccessStatusService.java +++ b/dspace-api/src/main/java/org/dspace/access/status/service/AccessStatusService.java @@ -8,6 +8,7 @@ package org.dspace.access.status.service; import java.sql.SQLException; +import java.util.Date; import org.dspace.content.Item; import org.dspace.core.Context; @@ -40,7 +41,18 @@ public interface AccessStatusService { * * @param context the DSpace context * @param item the item + * @return an access status value * @throws SQLException An exception that provides information on a database access error or other errors. */ public String getAccessStatus(Context context, Item item) throws SQLException; + + /** + * Retrieve embargo information for the item + * + * @param context the DSpace context + * @param item the item to check for embargo information + * @return an embargo date + * @throws SQLException An exception that provides information on a database access error or other errors. + */ + public String getEmbargoFromItem(Context context, Item item) throws SQLException; } diff --git a/dspace-api/src/test/java/org/dspace/access/status/DefaultAccessStatusHelperTest.java b/dspace-api/src/test/java/org/dspace/access/status/DefaultAccessStatusHelperTest.java index a41e985deb32..9d90452beeda 100644 --- a/dspace-api/src/test/java/org/dspace/access/status/DefaultAccessStatusHelperTest.java +++ b/dspace-api/src/test/java/org/dspace/access/status/DefaultAccessStatusHelperTest.java @@ -8,6 +8,7 @@ package org.dspace.access.status; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.fail; @@ -273,6 +274,8 @@ public void testWithEmbargo() throws Exception { context.restoreAuthSystemState(); String status = helper.getAccessStatusFromItem(context, itemWithEmbargo, threshold); assertThat("testWithEmbargo 0", status, equalTo(DefaultAccessStatusHelper.EMBARGO)); + String embargoDate = helper.getEmbargoFromItem(context, itemWithEmbargo); + assertThat("testWithEmbargo 1", embargoDate, equalTo(policy.getStartDate().toString())); } /** @@ -390,6 +393,8 @@ public void testWithPrimaryAndMultipleBitstreams() throws Exception { context.restoreAuthSystemState(); String status = helper.getAccessStatusFromItem(context, itemWithPrimaryAndMultipleBitstreams, threshold); assertThat("testWithPrimaryAndMultipleBitstreams 0", status, equalTo(DefaultAccessStatusHelper.EMBARGO)); + String embargoDate = helper.getEmbargoFromItem(context, itemWithPrimaryAndMultipleBitstreams); + assertThat("testWithPrimaryAndMultipleBitstreams 1", embargoDate, equalTo(policy.getStartDate().toString())); } /** @@ -419,5 +424,7 @@ public void testWithNoPrimaryAndMultipleBitstreams() throws Exception { context.restoreAuthSystemState(); String status = helper.getAccessStatusFromItem(context, itemWithoutPrimaryAndMultipleBitstreams, threshold); assertThat("testWithNoPrimaryAndMultipleBitstreams 0", status, equalTo(DefaultAccessStatusHelper.OPEN_ACCESS)); + String embargoDate = helper.getEmbargoFromItem(context, itemWithEmbargo); + assertThat("testWithNoPrimaryAndMultipleBitstreams 1", embargoDate, equalTo(null)); } } diff --git a/dspace-oai/src/main/java/org/dspace/xoai/app/plugins/AccessStatusElementItemCompilePlugin.java b/dspace-oai/src/main/java/org/dspace/xoai/app/plugins/AccessStatusElementItemCompilePlugin.java index 6b3c5ded9882..3201a0229178 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/app/plugins/AccessStatusElementItemCompilePlugin.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/app/plugins/AccessStatusElementItemCompilePlugin.java @@ -12,6 +12,7 @@ import com.lyncode.xoai.dataprovider.xml.xoai.Element; import com.lyncode.xoai.dataprovider.xml.xoai.Metadata; +import org.apache.commons.lang3.StringUtils; import org.dspace.access.status.factory.AccessStatusServiceFactory; import org.dspace.access.status.service.AccessStatusService; import org.dspace.content.Item; @@ -31,6 +32,13 @@ * open.access * * + * OR + * + * + * embargo + * 2024-10-10 + * + * * } * * Returning Values are based on: @@ -46,9 +54,15 @@ public Metadata additionalMetadata(Context context, Metadata metadata, Item item String accessStatusType; accessStatusType = accessStatusService.getAccessStatus(context, item); + String embargoFromItem = accessStatusService.getEmbargoFromItem(context, item); + Element accessStatus = ItemUtils.create("access-status"); accessStatus.getField().add(ItemUtils.createValue("value", accessStatusType)); + if (StringUtils.isNotEmpty(embargoFromItem)) { + accessStatus.getField().add(ItemUtils.createValue("embargo", embargoFromItem)); + } + Element others; List elements = metadata.getElement(); if (ItemUtils.getElement(elements, "others") != null) { diff --git a/dspace/config/crosswalks/oai/metadataFormats/uketd_dc.xsl b/dspace/config/crosswalks/oai/metadataFormats/uketd_dc.xsl index b9d81aef5da8..a180b49c561b 100644 --- a/dspace/config/crosswalks/oai/metadataFormats/uketd_dc.xsl +++ b/dspace/config/crosswalks/oai/metadataFormats/uketd_dc.xsl @@ -115,6 +115,12 @@ + + + + + + @@ -123,11 +129,6 @@ - - - - From 4bd2cfdf0f931aec7a05db42f255423fe806ea77 Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Wed, 2 Aug 2023 16:22:54 +0100 Subject: [PATCH 05/23] Remove unused imports --- .../org/dspace/access/status/service/AccessStatusService.java | 1 - .../org/dspace/access/status/DefaultAccessStatusHelperTest.java | 1 - 2 files changed, 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/access/status/service/AccessStatusService.java b/dspace-api/src/main/java/org/dspace/access/status/service/AccessStatusService.java index 937cb02692ff..2ed47bde4cd2 100644 --- a/dspace-api/src/main/java/org/dspace/access/status/service/AccessStatusService.java +++ b/dspace-api/src/main/java/org/dspace/access/status/service/AccessStatusService.java @@ -8,7 +8,6 @@ package org.dspace.access.status.service; import java.sql.SQLException; -import java.util.Date; import org.dspace.content.Item; import org.dspace.core.Context; diff --git a/dspace-api/src/test/java/org/dspace/access/status/DefaultAccessStatusHelperTest.java b/dspace-api/src/test/java/org/dspace/access/status/DefaultAccessStatusHelperTest.java index 9d90452beeda..f450f72e6a81 100644 --- a/dspace-api/src/test/java/org/dspace/access/status/DefaultAccessStatusHelperTest.java +++ b/dspace-api/src/test/java/org/dspace/access/status/DefaultAccessStatusHelperTest.java @@ -8,7 +8,6 @@ package org.dspace.access.status; import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.fail; From 724a4ffb0ed9ffefb2866930655767590b462bb5 Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Wed, 2 Aug 2023 18:01:07 +0100 Subject: [PATCH 06/23] Fix style issues --- .../src/main/java/org/dspace/xoai/util/ItemUtils.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java index 2d252ff47698..2af526c56046 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java @@ -13,9 +13,9 @@ import java.sql.SQLException; import java.text.SimpleDateFormat; import java.util.Date; -import java.util.List; import java.util.ArrayList; import java.util.Collections; +import java.util.List; import com.lyncode.xoai.dataprovider.xml.xoai.Element; import com.lyncode.xoai.dataprovider.xml.xoai.Metadata; @@ -180,7 +180,9 @@ private static Element createBundlesElement(Context context, Item item) throws S private static void addEmbargoField(Context context, Bitstream bitstream, Element bitstreamEl) throws SQLException { GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); Group anonymousGroup = groupService.findByName(context, Group.ANONYMOUS); - List policies = authorizeService.findPoliciesByDSOAndType(context, bitstream, ResourcePolicy.TYPE_CUSTOM); + List policies = authorizeService.findPoliciesByDSOAndType(context, + bitstream, + ResourcePolicy.TYPE_CUSTOM); List embargoDates = new ArrayList<>(); // Account for cases where there could be more than one embargo policy From 6e2c8a4ae0068d844d0fc796001c170c8849babf Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Wed, 2 Aug 2023 18:56:05 +0100 Subject: [PATCH 07/23] Fix style issues --- dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java index 2af526c56046..6a0808259e8e 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java @@ -12,9 +12,9 @@ import java.io.InputStream; import java.sql.SQLException; import java.text.SimpleDateFormat; -import java.util.Date; import java.util.ArrayList; import java.util.Collections; +import java.util.Date; import java.util.List; import com.lyncode.xoai.dataprovider.xml.xoai.Element; From 0de4c3945ed7f30d41841cda4bf01acf9ffc130f Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Thu, 3 Aug 2023 08:54:01 +0100 Subject: [PATCH 08/23] Add null check --- .../org/dspace/access/status/DefaultAccessStatusHelper.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java b/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java index e7055181aa95..9b5227491bc0 100644 --- a/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java +++ b/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java @@ -198,6 +198,10 @@ public String getEmbargoFromItem(Context context, Item item) .orElse(null); } + if (bitstream == null) { + return null; + } + embargoDate = this.retrieveLongestEmbargo(context, bitstream); return embargoDate != null ? embargoDate.toString() : null; From 291afa765d29836a67727fdd2f82ac0c9f9310c4 Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Thu, 3 Aug 2023 09:54:00 +0100 Subject: [PATCH 09/23] ItemUtils.java: refactored addEmbargoField --- .../java/org/dspace/xoai/util/ItemUtils.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java index 6a0808259e8e..80eb67a2b994 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java @@ -12,8 +12,6 @@ import java.io.InputStream; import java.sql.SQLException; import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Collections; import java.util.Date; import java.util.List; @@ -184,22 +182,28 @@ private static void addEmbargoField(Context context, Bitstream bitstream, Elemen bitstream, ResourcePolicy.TYPE_CUSTOM); - List embargoDates = new ArrayList<>(); + Date embargoDate = null; + // Account for cases where there could be more than one embargo policy for (ResourcePolicy policy : policies) { if (policy.getGroup() == anonymousGroup && policy.getAction() == Constants.READ) { Date startDate = policy.getStartDate(); if (startDate != null && startDate.after(new Date())) { - embargoDates.add(startDate); + // There is an active embargo: aim to take the longest embargo + if (embargoDate == null) { + embargoDate = startDate; + } else { + embargoDate = startDate.after(embargoDate) ? startDate : embargoDate; + } } } } - if (embargoDates.size() >= 1) { + + if (embargoDate != null) { // Sort array of dates to extract the longest embargo SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); - Collections.sort(embargoDates, Date::compareTo); bitstreamEl.getField().add( - createValue("embargo", formatter.format(embargoDates.get(embargoDates.size() - 1)))); + createValue("embargo", formatter.format(embargoDate))); } } From 4b40872a6d5a3934c1f79c6babf439a21ce25f66 Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Thu, 3 Aug 2023 14:30:33 +0100 Subject: [PATCH 10/23] uketd_dc.xsl: also expose access-status if embargo or restricted --- dspace/config/crosswalks/oai/metadataFormats/uketd_dc.xsl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dspace/config/crosswalks/oai/metadataFormats/uketd_dc.xsl b/dspace/config/crosswalks/oai/metadataFormats/uketd_dc.xsl index a180b49c561b..5c434e49ed35 100644 --- a/dspace/config/crosswalks/oai/metadataFormats/uketd_dc.xsl +++ b/dspace/config/crosswalks/oai/metadataFormats/uketd_dc.xsl @@ -115,6 +115,14 @@ + + + + + + + + From 6fbe4f4c0003dbb257deee79b2f6f57b4dc13b50 Mon Sep 17 00:00:00 2001 From: nwoodward Date: Wed, 9 Aug 2023 11:35:46 -0500 Subject: [PATCH 11/23] fix logical bug when checking if field is controlled authority --- .../src/main/java/org/dspace/app/bulkedit/MetadataImport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java index 4161bbb4d817..9044c723ff53 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java @@ -1363,7 +1363,7 @@ private int displayChanges(List changes, boolean changed) { * is the field is defined as authority controlled */ private static boolean isAuthorityControlledField(String md) { - String mdf = StringUtils.substringAfter(md, ":"); + String mdf = md.contains(":") ? StringUtils.substringAfter(md, ":") : md; mdf = StringUtils.substringBefore(mdf, "["); return authorityControlled.contains(mdf); } From d17ef09082aa237cffdc928d9560667487c2c976 Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Thu, 14 Sep 2023 10:02:24 +0100 Subject: [PATCH 12/23] DefaultAccessStatusHelper: fix logic to take shortest embargo --- .../access/status/DefaultAccessStatusHelper.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java b/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java index 9b5227491bc0..05f0757060ab 100644 --- a/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java +++ b/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java @@ -175,7 +175,7 @@ private String calculateAccessStatusForDso(Context context, DSpaceObject dso, Da @Override public String getEmbargoFromItem(Context context, Item item) throws SQLException { - Date embargoDate; + Date embargoedDate; if (item == null) { return null; @@ -202,15 +202,15 @@ public String getEmbargoFromItem(Context context, Item item) return null; } - embargoDate = this.retrieveLongestEmbargo(context, bitstream); + embargoedDate = this.retrieveShortestEmbargo(context, bitstream); - return embargoDate != null ? embargoDate.toString() : null; + return embargoedDate != null ? embargoedDate.toString() : null; } /** * */ - private Date retrieveLongestEmbargo(Context context, Bitstream bitstream) throws SQLException { + private Date retrieveShortestEmbargo(Context context, Bitstream bitstream) throws SQLException { Date embargoDate = null; // Only consider read policies. List policies = authorizeService @@ -228,11 +228,12 @@ private Date retrieveLongestEmbargo(Context context, Bitstream bitstream) throws Date startDate = policy.getStartDate(); if (startDate != null && !startDate.before(LocalDate.now().toDate())) { - // There is an active embargo: aim to take the longest embargo + // There is an active embargo: aim to take the shortest embargo (account for rare cases where + // more than one resource policy exists) if (embargoDate == null) { embargoDate = startDate; } else { - embargoDate = startDate.after(embargoDate) ? startDate : embargoDate; + embargoDate = startDate.before(embargoDate) ? startDate : embargoDate; } } } From 490a982e8055991a6b8cbacece22b924466e22df Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Thu, 14 Sep 2023 16:39:39 +0100 Subject: [PATCH 13/23] Remove currently unused customisation of ItemUtils --- .../java/org/dspace/xoai/util/ItemUtils.java | 54 ------------------- 1 file changed, 54 deletions(-) diff --git a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java index 80eb67a2b994..35bef8c8d77f 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java @@ -11,8 +11,6 @@ import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; -import java.text.SimpleDateFormat; -import java.util.Date; import java.util.List; import com.lyncode.xoai.dataprovider.xml.xoai.Element; @@ -23,9 +21,6 @@ import org.dspace.app.util.factory.UtilServiceFactory; import org.dspace.app.util.service.MetadataExposureService; import org.dspace.authorize.AuthorizeException; -import org.dspace.authorize.ResourcePolicy; -import org.dspace.authorize.factory.AuthorizeServiceFactory; -import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.Item; @@ -39,9 +34,6 @@ import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.Utils; -import org.dspace.eperson.Group; -import org.dspace.eperson.factory.EPersonServiceFactory; -import org.dspace.eperson.service.GroupService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.xoai.data.DSpaceItem; @@ -65,9 +57,6 @@ public class ItemUtils { private static final BitstreamService bitstreamService = ContentServiceFactory.getInstance().getBitstreamService(); - private static final AuthorizeService authorizeService = - AuthorizeServiceFactory.getInstance().getAuthorizeService(); - private static final ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); /** @@ -152,9 +141,6 @@ private static Element createBundlesElement(Context context, Item item) throws S if (description != null) { bitstream.getField().add(createValue("description", description)); } - // Add bitstream embargo information (READ policy present, for Anonymous group with a start date) - addEmbargoField(context, bit, bitstream); - bitstream.getField().add(createValue("format", bit.getFormat(context).getMIMEType())); bitstream.getField().add(createValue("size", "" + bit.getSizeBytes())); bitstream.getField().add(createValue("url", url)); @@ -167,46 +153,6 @@ private static Element createBundlesElement(Context context, Item item) throws S return bundles; } - /** - * This method will add embargo metadata for a give bitstream with an active embargo. - * It will parse of relevant policies and select the longest active embargo - * @param context - * @param bitstream the bitstream object - * @param bitstreamEl the bitstream metadata object to add embargo value to - * @throws SQLException - */ - private static void addEmbargoField(Context context, Bitstream bitstream, Element bitstreamEl) throws SQLException { - GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); - Group anonymousGroup = groupService.findByName(context, Group.ANONYMOUS); - List policies = authorizeService.findPoliciesByDSOAndType(context, - bitstream, - ResourcePolicy.TYPE_CUSTOM); - - Date embargoDate = null; - - // Account for cases where there could be more than one embargo policy - for (ResourcePolicy policy : policies) { - if (policy.getGroup() == anonymousGroup && policy.getAction() == Constants.READ) { - Date startDate = policy.getStartDate(); - if (startDate != null && startDate.after(new Date())) { - // There is an active embargo: aim to take the longest embargo - if (embargoDate == null) { - embargoDate = startDate; - } else { - embargoDate = startDate.after(embargoDate) ? startDate : embargoDate; - } - } - } - } - - if (embargoDate != null) { - // Sort array of dates to extract the longest embargo - SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); - bitstreamEl.getField().add( - createValue("embargo", formatter.format(embargoDate))); - } - } - private static Element createLicenseElement(Context context, Item item) throws SQLException, AuthorizeException, IOException { Element license = create("license"); From 51d8a7d9979b81ab42f712d4a37c1f8abdf9b039 Mon Sep 17 00:00:00 2001 From: nwoodward Date: Tue, 19 Sep 2023 15:08:42 -0500 Subject: [PATCH 14/23] remove optimize option from oai import --- .../src/main/java/org/dspace/xoai/app/XOAI.java | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java b/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java index e27a3ee947cb..4f842b8e944c 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java @@ -85,7 +85,6 @@ public class XOAI { // needed because the solr query only returns 10 rows by default private final Context context; - private boolean optimize; private final boolean verbose; private boolean clean; @@ -122,9 +121,8 @@ private List getFileFormats(Item item) { return formats; } - public XOAI(Context context, boolean optimize, boolean clean, boolean verbose) { + public XOAI(Context context, boolean clean, boolean verbose) { this.context = context; - this.optimize = optimize; this.clean = clean; this.verbose = verbose; @@ -173,12 +171,6 @@ public int index() throws DSpaceSolrIndexerException { } solrServerResolver.getServer().commit(); - if (optimize) { - println("Optimizing Index"); - solrServerResolver.getServer().optimize(); - println("Index optimized"); - } - // Set last compilation date xoaiLastCompilationCacheService.put(new Date()); return result; @@ -586,7 +578,6 @@ public static void main(String[] argv) throws IOException, ConfigurationExceptio CommandLineParser parser = new DefaultParser(); Options options = new Options(); options.addOption("c", "clear", false, "Clear index before indexing"); - options.addOption("o", "optimize", false, "Optimize index at the end"); options.addOption("v", "verbose", false, "Verbose output"); options.addOption("h", "help", false, "Shows some help"); options.addOption("n", "number", true, "FOR DEVELOPMENT MUST DELETE"); @@ -620,7 +611,7 @@ public static void main(String[] argv) throws IOException, ConfigurationExceptio if (COMMAND_IMPORT.equals(command)) { ctx = new Context(Context.Mode.READ_ONLY); - XOAI indexer = new XOAI(ctx, line.hasOption('o'), line.hasOption('c'), line.hasOption('v')); + XOAI indexer = new XOAI(ctx, line.hasOption('c'), line.hasOption('v')); applicationContext.getAutowireCapableBeanFactory().autowireBean(indexer); @@ -706,7 +697,6 @@ private static void usage() { System.out.println(" " + COMMAND_IMPORT + " - To import DSpace items into OAI index and cache system"); System.out.println(" " + COMMAND_CLEAN_CACHE + " - Cleans the OAI cached responses"); System.out.println("> Parameters:"); - System.out.println(" -o Optimize index after indexing (" + COMMAND_IMPORT + " only)"); System.out.println(" -c Clear index (" + COMMAND_IMPORT + " only)"); System.out.println(" -v Verbose output"); System.out.println(" -h Shows this text"); From e05e73a112ce60bd0689ce68af442382712bd5fc Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Wed, 20 Sep 2023 16:26:14 +0100 Subject: [PATCH 15/23] DefaultAccessStatusHelper: getEmbargoFromItem return null embargo if status than embargo --- .../dspace/access/status/AccessStatusHelper.java | 4 +++- .../access/status/AccessStatusServiceImpl.java | 2 +- .../access/status/DefaultAccessStatusHelper.java | 13 ++++++++----- .../status/DefaultAccessStatusHelperTest.java | 6 +++--- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/access/status/AccessStatusHelper.java b/dspace-api/src/main/java/org/dspace/access/status/AccessStatusHelper.java index d847e907b403..2d782dc3b82a 100644 --- a/dspace-api/src/main/java/org/dspace/access/status/AccessStatusHelper.java +++ b/dspace-api/src/main/java/org/dspace/access/status/AccessStatusHelper.java @@ -22,6 +22,7 @@ public interface AccessStatusHelper { * * @param context the DSpace context * @param item the item + * @param threshold the embargo threshold date * @return an access status value * @throws SQLException An exception that provides information on a database access error or other errors. */ @@ -33,8 +34,9 @@ public String getAccessStatusFromItem(Context context, Item item, Date threshold * * @param context the DSpace context * @param item the item to check for embargo information + * @param threshold the embargo threshold date * @return an embargo date * @throws SQLException An exception that provides information on a database access error or other errors. */ - public String getEmbargoFromItem(Context context, Item item) throws SQLException; + public String getEmbargoFromItem(Context context, Item item, Date threshold) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/access/status/AccessStatusServiceImpl.java b/dspace-api/src/main/java/org/dspace/access/status/AccessStatusServiceImpl.java index 1d4dc6088c3a..01b370747932 100644 --- a/dspace-api/src/main/java/org/dspace/access/status/AccessStatusServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/access/status/AccessStatusServiceImpl.java @@ -70,6 +70,6 @@ public String getAccessStatus(Context context, Item item) throws SQLException { @Override public String getEmbargoFromItem(Context context, Item item) throws SQLException { - return helper.getEmbargoFromItem(context, item); + return helper.getEmbargoFromItem(context, item, forever_date); } } diff --git a/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java b/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java index 05f0757060ab..5f0e6d8b259b 100644 --- a/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java +++ b/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java @@ -173,11 +173,14 @@ private String calculateAccessStatusForDso(Context context, DSpaceObject dso, Da * @return an access status value */ @Override - public String getEmbargoFromItem(Context context, Item item) + public String getEmbargoFromItem(Context context, Item item, Date threshold) throws SQLException { - Date embargoedDate; + Date embargoDate; - if (item == null) { + // If Item status is not "embargo" then return a null embargo date. + String accessStatus = getAccessStatusFromItem(context, item, threshold); + + if (item == null || !accessStatus.equals(EMBARGO)) { return null; } // Consider only the original bundles. @@ -202,9 +205,9 @@ public String getEmbargoFromItem(Context context, Item item) return null; } - embargoedDate = this.retrieveShortestEmbargo(context, bitstream); + embargoDate = this.retrieveShortestEmbargo(context, bitstream); - return embargoedDate != null ? embargoedDate.toString() : null; + return embargoDate != null ? embargoDate.toString() : null; } /** diff --git a/dspace-api/src/test/java/org/dspace/access/status/DefaultAccessStatusHelperTest.java b/dspace-api/src/test/java/org/dspace/access/status/DefaultAccessStatusHelperTest.java index c97349ac7c77..51291ee9850d 100644 --- a/dspace-api/src/test/java/org/dspace/access/status/DefaultAccessStatusHelperTest.java +++ b/dspace-api/src/test/java/org/dspace/access/status/DefaultAccessStatusHelperTest.java @@ -274,7 +274,7 @@ public void testWithEmbargo() throws Exception { context.restoreAuthSystemState(); String status = helper.getAccessStatusFromItem(context, itemWithEmbargo, threshold); assertThat("testWithEmbargo 0", status, equalTo(DefaultAccessStatusHelper.EMBARGO)); - String embargoDate = helper.getEmbargoFromItem(context, itemWithEmbargo); + String embargoDate = helper.getEmbargoFromItem(context, itemWithEmbargo, threshold); assertThat("testWithEmbargo 1", embargoDate, equalTo(policy.getStartDate().toString())); } @@ -393,7 +393,7 @@ public void testWithPrimaryAndMultipleBitstreams() throws Exception { context.restoreAuthSystemState(); String status = helper.getAccessStatusFromItem(context, itemWithPrimaryAndMultipleBitstreams, threshold); assertThat("testWithPrimaryAndMultipleBitstreams 0", status, equalTo(DefaultAccessStatusHelper.EMBARGO)); - String embargoDate = helper.getEmbargoFromItem(context, itemWithPrimaryAndMultipleBitstreams); + String embargoDate = helper.getEmbargoFromItem(context, itemWithPrimaryAndMultipleBitstreams, threshold); assertThat("testWithPrimaryAndMultipleBitstreams 1", embargoDate, equalTo(policy.getStartDate().toString())); } @@ -424,7 +424,7 @@ public void testWithNoPrimaryAndMultipleBitstreams() throws Exception { context.restoreAuthSystemState(); String status = helper.getAccessStatusFromItem(context, itemWithoutPrimaryAndMultipleBitstreams, threshold); assertThat("testWithNoPrimaryAndMultipleBitstreams 0", status, equalTo(DefaultAccessStatusHelper.OPEN_ACCESS)); - String embargoDate = helper.getEmbargoFromItem(context, itemWithEmbargo); + String embargoDate = helper.getEmbargoFromItem(context, itemWithEmbargo, threshold); assertThat("testWithNoPrimaryAndMultipleBitstreams 1", embargoDate, equalTo(null)); } From 68cdb108e9becf35c2435f46f22a0045549684cc Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Mon, 2 Oct 2023 17:44:50 +1300 Subject: [PATCH 16/23] Additional Item class cast fixes in handle providers DSOs were not properly checked if they were instanceof Item before attempting the cast in HandleIdentifierProvider and VersionedHandleIdentifierProviderWithCanonicalHandles --- .../org/dspace/identifier/HandleIdentifierProvider.java | 5 ++--- ...rsionedHandleIdentifierProviderWithCanonicalHandles.java | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/identifier/HandleIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/HandleIdentifierProvider.java index 59a1e13a2166..82358362da85 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/HandleIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/HandleIdentifierProvider.java @@ -68,10 +68,9 @@ public String register(Context context, DSpaceObject dso) { try { String id = mint(context, dso); - // move canonical to point the latest version + // Populate metadata if (dso instanceof Item || dso instanceof Collection || dso instanceof Community) { - Item item = (Item) dso; - populateHandleMetadata(context, item, id); + populateHandleMetadata(context, dso, id); } return id; diff --git a/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java b/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java index e6a092c47284..9993f78b4dd5 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java +++ b/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java @@ -95,11 +95,11 @@ public String register(Context context, DSpaceObject dso) { String id = mint(context, dso); // move canonical to point the latest version - if (dso != null && dso.getType() == Constants.ITEM) { + if (dso.getType() == Constants.ITEM && dso instanceof Item) { Item item = (Item) dso; - VersionHistory history = null; + VersionHistory history; try { - history = versionHistoryService.findByItem(context, (Item) dso); + history = versionHistoryService.findByItem(context, item); } catch (SQLException ex) { throw new RuntimeException("A problem with the database connection occured.", ex); } From 5e04edf41e452cd383597680da9c3101211156b8 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 2 Oct 2023 10:55:43 -0500 Subject: [PATCH 17/23] Remove Oracle script that accidentally made it in via #8800 --- .../V7.6_2023.04.19__process_parameters_to_text_type.sql | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.6_2023.04.19__process_parameters_to_text_type.sql diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.6_2023.04.19__process_parameters_to_text_type.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.6_2023.04.19__process_parameters_to_text_type.sql deleted file mode 100644 index 6b2dd705ea68..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.6_2023.04.19__process_parameters_to_text_type.sql +++ /dev/null @@ -1,9 +0,0 @@ --- --- The contents of this file are subject to the license and copyright --- detailed in the LICENSE and NOTICE files at the root of the source --- tree and available online at --- --- http://www.dspace.org/license/ --- - -ALTER TABLE process MODIFY (parameters CLOB); From db36d5eeae3e76b61178c2c7ac4243bc2fc20a97 Mon Sep 17 00:00:00 2001 From: aroman-arvo Date: Mon, 2 Oct 2023 18:00:09 +0200 Subject: [PATCH 18/23] 8968 - request-a-copy email: non ASCII characters are encoded as HTML character entity references --- .../dspace/app/rest/repository/RequestItemRepository.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java index 7c0694c52f26..8a60867f9e99 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java @@ -173,11 +173,10 @@ public RequestItemRest createAndReturn(Context ctx) username = user.getFullName(); } else { // An anonymous session may provide a name. // Escape username to evade nasty XSS attempts - username = StringEscapeUtils.escapeHtml4(rir.getRequestName()); + username = rir.getRequestName(); } - // Requester's message text, escaped to evade nasty XSS attempts - String message = StringEscapeUtils.escapeHtml4(rir.getRequestMessage()); + String message = rir.getRequestMessage(); // Create the request. String token; From bf6e042085140e305d43d61ddce564fbfe819c7f Mon Sep 17 00:00:00 2001 From: aroman-arvo Date: Mon, 2 Oct 2023 18:38:33 +0200 Subject: [PATCH 19/23] unused import --- .../org/dspace/app/rest/repository/RequestItemRepository.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java index 8a60867f9e99..bc276d73d5d6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java @@ -21,7 +21,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.commons.text.StringEscapeUtils; import org.apache.commons.validator.routines.EmailValidator; import org.apache.http.client.utils.URIBuilder; import org.apache.logging.log4j.LogManager; From 103c8ee75771d3d9e58e530b8855d07cc14598c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ad=C3=A1n=20Rom=C3=A1n=20Ruiz?= Date: Fri, 6 Oct 2023 17:27:53 +0200 Subject: [PATCH 20/23] 8968 - added custom StringEscapper --- .../org/dspace/util/StringEscapeUtils.java | 49 +++++++++++++++++++ .../repository/RequestItemRepository.java | 6 ++- 2 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/util/StringEscapeUtils.java diff --git a/dspace-api/src/main/java/org/dspace/util/StringEscapeUtils.java b/dspace-api/src/main/java/org/dspace/util/StringEscapeUtils.java new file mode 100644 index 000000000000..86010a5c19f1 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/util/StringEscapeUtils.java @@ -0,0 +1,49 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.util; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.text.translate.AggregateTranslator; +import org.apache.commons.text.translate.CharSequenceTranslator; +import org.apache.commons.text.translate.EntityArrays; +import org.apache.commons.text.translate.LookupTranslator; + +public class StringEscapeUtils extends org.apache.commons.text.StringEscapeUtils { + public static final CharSequenceTranslator ESCAPE_MAIL; + static { + final Map escapeMailMap = new HashMap<>(); + escapeMailMap.put("#", "#"); + ESCAPE_MAIL = new AggregateTranslator( + new LookupTranslator(EntityArrays.BASIC_ESCAPE), + new LookupTranslator(EntityArrays.APOS_ESCAPE), + new LookupTranslator(Collections.unmodifiableMap(escapeMailMap)) + ); + } + + /** + * Escapes the characters in a {@code String} using custom rules to avoid XSS attacks. + * + *

Escapes user-entered text that is sent with mail to avoid possible XSS attacks. + * It escapes double-quote, ampersand, less-than, greater-than, apostrophe, number sign (", &, <, >,',#)

+ * + *

Example:

+ *
+     * input string: 
lá lé lí ló LÚ pingüino & yo #
!!" + * output string: <div attr="*x" onblur="alert(1)*"> lá lé lí ló LÚ pingüino & yo # </div>!! + *
+ * + * @param input String to escape values in, may be null + * @return String with escaped values, {@code null} if null string input + */ + public static final String escapeMail(final String input) { + return ESCAPE_MAIL.translate(input); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java index bc276d73d5d6..863a5c414666 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java @@ -42,6 +42,7 @@ import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.services.ConfigurationService; +import org.dspace.util.StringEscapeUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -172,10 +173,11 @@ public RequestItemRest createAndReturn(Context ctx) username = user.getFullName(); } else { // An anonymous session may provide a name. // Escape username to evade nasty XSS attempts - username = rir.getRequestName(); + username = StringEscapeUtils.escapeMail(rir.getRequestName()); } - String message = rir.getRequestMessage(); + // Requester's message text, escaped to evade nasty XSS attempts + String message = StringEscapeUtils.escapeMail(rir.getRequestMessage()); // Create the request. String token; From 2c2b3b18dc781054539add48ca74e4bf688c400c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ad=C3=A1n=20Rom=C3=A1n=20Ruiz?= Date: Fri, 6 Oct 2023 17:42:14 +0200 Subject: [PATCH 21/23] checkstyle --- .../src/main/java/org/dspace/util/StringEscapeUtils.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/util/StringEscapeUtils.java b/dspace-api/src/main/java/org/dspace/util/StringEscapeUtils.java index 86010a5c19f1..dfc89ca1941f 100644 --- a/dspace-api/src/main/java/org/dspace/util/StringEscapeUtils.java +++ b/dspace-api/src/main/java/org/dspace/util/StringEscapeUtils.java @@ -37,7 +37,8 @@ public class StringEscapeUtils extends org.apache.commons.text.StringEscapeUtils *

Example:

*
      * input string: 
lá lé lí ló LÚ pingüino & yo #
!!" - * output string: <div attr="*x" onblur="alert(1)*"> lá lé lí ló LÚ pingüino & yo # </div>!! + * output string: <div attr="*x" onblur="alert(1)*"> lá lé lí ló LÚ + * pingüino & yo # </div>!! *
* * @param input String to escape values in, may be null From 090beedb6f692df29d1a61d4c2e6fde09d4b4c1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ad=C3=A1n=20Rom=C3=A1n=20Ruiz?= Date: Mon, 9 Oct 2023 18:01:46 +0200 Subject: [PATCH 22/23] 8968 - implementated using HtmlUtils scaping --- .../org/dspace/util/StringEscapeUtils.java | 50 ------------------- .../repository/RequestItemRepository.java | 8 +-- 2 files changed, 4 insertions(+), 54 deletions(-) delete mode 100644 dspace-api/src/main/java/org/dspace/util/StringEscapeUtils.java diff --git a/dspace-api/src/main/java/org/dspace/util/StringEscapeUtils.java b/dspace-api/src/main/java/org/dspace/util/StringEscapeUtils.java deleted file mode 100644 index dfc89ca1941f..000000000000 --- a/dspace-api/src/main/java/org/dspace/util/StringEscapeUtils.java +++ /dev/null @@ -1,50 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.util; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import org.apache.commons.text.translate.AggregateTranslator; -import org.apache.commons.text.translate.CharSequenceTranslator; -import org.apache.commons.text.translate.EntityArrays; -import org.apache.commons.text.translate.LookupTranslator; - -public class StringEscapeUtils extends org.apache.commons.text.StringEscapeUtils { - public static final CharSequenceTranslator ESCAPE_MAIL; - static { - final Map escapeMailMap = new HashMap<>(); - escapeMailMap.put("#", "#"); - ESCAPE_MAIL = new AggregateTranslator( - new LookupTranslator(EntityArrays.BASIC_ESCAPE), - new LookupTranslator(EntityArrays.APOS_ESCAPE), - new LookupTranslator(Collections.unmodifiableMap(escapeMailMap)) - ); - } - - /** - * Escapes the characters in a {@code String} using custom rules to avoid XSS attacks. - * - *

Escapes user-entered text that is sent with mail to avoid possible XSS attacks. - * It escapes double-quote, ampersand, less-than, greater-than, apostrophe, number sign (", &, <, >,',#)

- * - *

Example:

- *
-     * input string: 
lá lé lí ló LÚ pingüino & yo #
!!" - * output string: <div attr="*x" onblur="alert(1)*"> lá lé lí ló LÚ - * pingüino & yo # </div>!! - *
- * - * @param input String to escape values in, may be null - * @return String with escaped values, {@code null} if null string input - */ - public static final String escapeMail(final String input) { - return ESCAPE_MAIL.translate(input); - } -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java index 863a5c414666..945afe16e82c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java @@ -42,13 +42,13 @@ import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.services.ConfigurationService; -import org.dspace.util.StringEscapeUtils; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; - +import org.springframework.web.util.HtmlUtils; /** * Component to expose item requests. * @@ -173,11 +173,11 @@ public RequestItemRest createAndReturn(Context ctx) username = user.getFullName(); } else { // An anonymous session may provide a name. // Escape username to evade nasty XSS attempts - username = StringEscapeUtils.escapeMail(rir.getRequestName()); + username = HtmlUtils.htmlEscape(rir.getRequestName(),"UTF-8"); } // Requester's message text, escaped to evade nasty XSS attempts - String message = StringEscapeUtils.escapeMail(rir.getRequestMessage()); + String message = HtmlUtils.htmlEscape(rir.getRequestMessage(),"UTF-8"); // Create the request. String token; From d12fbe2c340e18e42dba4380ee9976bccb4ca421 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ad=C3=A1n=20Rom=C3=A1n=20Ruiz?= Date: Mon, 9 Oct 2023 18:18:35 +0200 Subject: [PATCH 23/23] checkstiye --- .../org/dspace/app/rest/repository/RequestItemRepository.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java index 945afe16e82c..f45dbee66f34 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java @@ -42,7 +42,6 @@ import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.services.ConfigurationService; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable;