From 16159f37611834bc177338f7f29fecfed4cbf368 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Thu, 28 Sep 2023 15:07:14 +0200 Subject: [PATCH 001/939] 106812: Refactor resource policies to have either group or eperson --- .../app/itemimport/ItemImportServiceImpl.java | 2 +- .../authorize/AuthorizeServiceImpl.java | 8 +- .../dspace/authorize/FixDefaultPolicies.java | 3 +- .../java/org/dspace/authorize/PolicySet.java | 9 +- .../authorize/ResourcePolicyServiceImpl.java | 16 +- .../service/ResourcePolicyService.java | 30 +- .../crosswalk/METSRightsCrosswalk.java | 108 ++--- ...ce_group_or_eperson_for_resourcepolicy.sql | 10 + .../status/DefaultAccessStatusHelperTest.java | 25 +- .../org/dspace/builder/CollectionBuilder.java | 3 +- .../dspace/builder/ResourcePolicyBuilder.java | 20 +- .../java/org/dspace/content/BundleTest.java | 6 +- .../java/org/dspace/content/ItemTest.java | 11 +- .../dspace/content/packager/ITDSpaceAIP.java | 11 +- .../dspace/content/service/ItemServiceIT.java | 12 +- .../ResourcePolicyRestRepository.java | 31 +- .../app/rest/BitstreamControllerIT.java | 130 +++--- .../app/rest/BitstreamRestControllerIT.java | 3 +- .../app/rest/BitstreamRestRepositoryIT.java | 13 +- .../app/rest/BundleRestRepositoryIT.java | 2 +- .../app/rest/CollectionRestRepositoryIT.java | 31 +- .../app/rest/CommunityRestRepositoryIT.java | 16 +- .../app/rest/GroupRestRepositoryIT.java | 18 +- ...wningCollectionUpdateRestControllerIT.java | 18 +- .../dspace/app/rest/ItemRestRepositoryIT.java | 3 +- .../rest/ItemTemplateRestControllerIT.java | 12 +- .../rest/ResourcePolicyRestRepositoryIT.java | 425 ++++++++---------- .../app/rest/StatisticsRestRepositoryIT.java | 15 +- .../rest/WorkspaceItemRestRepositoryIT.java | 64 +-- .../authorization/CCLicenseFeatureRestIT.java | 6 +- .../CanManageBitstreamBundlesFeatureIT.java | 20 +- .../CanManageMappingsFeatureIT.java | 9 +- .../authorization/CanSubscribeFeatureIT.java | 5 +- .../rest/authorization/EditItemFeatureIT.java | 12 +- .../GenericAuthorizationFeatureIT.java | 89 ++-- .../ManageAdminGroupFeatureIT.java | 6 +- .../ManageSubmitterGroupFeatureIT.java | 6 +- .../ManageTemplateItemFeatureIT.java | 6 +- .../ManageWorkflowGroupFeatureIT.java | 6 +- .../rest/authorization/SubmitFeatureIT.java | 24 +- 40 files changed, 572 insertions(+), 672 deletions(-) create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.09.28__enforce_group_or_eperson_for_resourcepolicy.sql 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 4148232cf3b..a77d854ade2 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 @@ -1810,7 +1810,7 @@ protected void setPermission(Context c, Group g, int actionID, Bitstream bs) authorizeService.removeAllPolicies(c, bs); // add the policy - ResourcePolicy rp = resourcePolicyService.create(c); + ResourcePolicy rp = resourcePolicyService.create(c, null, g); rp.setdSpaceObject(bs); rp.setAction(actionID); diff --git a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java index fc438c234cd..3f326de3c74 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java @@ -550,13 +550,11 @@ public void addPolicies(Context c, List policies, DSpaceObject d List newPolicies = new ArrayList<>(policies.size()); for (ResourcePolicy srp : policies) { - ResourcePolicy rp = resourcePolicyService.create(c); + ResourcePolicy rp = resourcePolicyService.create(c, srp.getEPerson(), srp.getGroup()); // copy over values rp.setdSpaceObject(dest); rp.setAction(srp.getAction()); - rp.setEPerson(srp.getEPerson()); - rp.setGroup(srp.getGroup()); rp.setStartDate(srp.getStartDate()); rp.setEndDate(srp.getEndDate()); rp.setRpName(srp.getRpName()); @@ -670,11 +668,9 @@ public ResourcePolicy createResourcePolicy(Context context, DSpaceObject dso, Gr "We need at least an eperson or a group in order to create a resource policy."); } - ResourcePolicy myPolicy = resourcePolicyService.create(context); + ResourcePolicy myPolicy = resourcePolicyService.create(context, eperson, group); myPolicy.setdSpaceObject(dso); myPolicy.setAction(type); - myPolicy.setGroup(group); - myPolicy.setEPerson(eperson); myPolicy.setRpType(rpType); myPolicy.setRpName(rpName); myPolicy.setRpDescription(rpDescription); diff --git a/dspace-api/src/main/java/org/dspace/authorize/FixDefaultPolicies.java b/dspace-api/src/main/java/org/dspace/authorize/FixDefaultPolicies.java index 61e783920ae..b430a2635fd 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/FixDefaultPolicies.java +++ b/dspace-api/src/main/java/org/dspace/authorize/FixDefaultPolicies.java @@ -126,10 +126,9 @@ private static void addAnonymousPolicy(Context c, DSpaceObject t, // now create the default policies for submitted items ResourcePolicyService resourcePolicyService = AuthorizeServiceFactory.getInstance().getResourcePolicyService(); - ResourcePolicy myPolicy = resourcePolicyService.create(c); + ResourcePolicy myPolicy = resourcePolicyService.create(c, null, anonymousGroup); myPolicy.setdSpaceObject(t); myPolicy.setAction(myaction); - myPolicy.setGroup(anonymousGroup); resourcePolicyService.update(c, myPolicy); } } diff --git a/dspace-api/src/main/java/org/dspace/authorize/PolicySet.java b/dspace-api/src/main/java/org/dspace/authorize/PolicySet.java index 72998b9fd18..e22b8e9df02 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/PolicySet.java +++ b/dspace-api/src/main/java/org/dspace/authorize/PolicySet.java @@ -229,11 +229,10 @@ public static void setPoliciesFilter(Context c, int containerType, // before create a new policy check if an identical policy is already in place if (!authorizeService.isAnIdenticalPolicyAlreadyInPlace(c, myitem, group, actionID, -1)) { // now add the policy - ResourcePolicy rp = resourcePolicyService.create(c); + ResourcePolicy rp = resourcePolicyService.create(c, null, group); rp.setdSpaceObject(myitem); rp.setAction(actionID); - rp.setGroup(group); rp.setRpName(name); rp.setRpDescription(description); @@ -262,11 +261,10 @@ public static void setPoliciesFilter(Context c, int containerType, // before create a new policy check if an identical policy is already in place if (!authorizeService.isAnIdenticalPolicyAlreadyInPlace(c, bundle, group, actionID, -1)) { // now add the policy - ResourcePolicy rp = resourcePolicyService.create(c); + ResourcePolicy rp = resourcePolicyService.create(c, null, group); rp.setdSpaceObject(bundle); rp.setAction(actionID); - rp.setGroup(group); rp.setRpName(name); rp.setRpDescription(description); @@ -305,11 +303,10 @@ public static void setPoliciesFilter(Context c, int containerType, if (!authorizeService .isAnIdenticalPolicyAlreadyInPlace(c, bitstream, group, actionID, -1)) { // now add the policy - ResourcePolicy rp = resourcePolicyService.create(c); + ResourcePolicy rp = resourcePolicyService.create(c, null, group); rp.setdSpaceObject(bitstream); rp.setAction(actionID); - rp.setGroup(group); rp.setRpName(name); rp.setRpDescription(description); diff --git a/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java b/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java index b762107a84c..7b93b912378 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java @@ -71,14 +71,22 @@ public ResourcePolicy find(Context context, int id) throws SQLException { * Create a new ResourcePolicy * * @param context DSpace context object + * @param ePerson + * @param group * @return ResourcePolicy * @throws SQLException if database error */ @Override - public ResourcePolicy create(Context context) throws SQLException { + public ResourcePolicy create(Context context, EPerson ePerson, Group group) throws SQLException { // FIXME: Check authorisation // Create a table row - ResourcePolicy resourcePolicy = resourcePolicyDAO.create(context, new ResourcePolicy()); + ResourcePolicy policyToBeCreated = new ResourcePolicy(); + if (ePerson == null && group == null) { + throw new IllegalArgumentException("A resource policy must contain a valid eperson or group"); + } + policyToBeCreated.setEPerson(ePerson); + policyToBeCreated.setGroup(group); + ResourcePolicy resourcePolicy = resourcePolicyDAO.create(context, policyToBeCreated); return resourcePolicy; } @@ -205,9 +213,7 @@ public boolean isDateValid(ResourcePolicy resourcePolicy) { @Override public ResourcePolicy clone(Context context, ResourcePolicy resourcePolicy) throws SQLException, AuthorizeException { - ResourcePolicy clone = create(context); - clone.setGroup(resourcePolicy.getGroup()); - clone.setEPerson(resourcePolicy.getEPerson()); + ResourcePolicy clone = create(context, resourcePolicy.getEPerson(), resourcePolicy.getGroup()); clone.setStartDate((Date) ObjectUtils.clone(resourcePolicy.getStartDate())); clone.setEndDate((Date) ObjectUtils.clone(resourcePolicy.getEndDate())); clone.setRpType((String) ObjectUtils.clone(resourcePolicy.getRpType())); diff --git a/dspace-api/src/main/java/org/dspace/authorize/service/ResourcePolicyService.java b/dspace-api/src/main/java/org/dspace/authorize/service/ResourcePolicyService.java index 43735fcd608..523bd64006d 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/service/ResourcePolicyService.java +++ b/dspace-api/src/main/java/org/dspace/authorize/service/ResourcePolicyService.java @@ -17,7 +17,6 @@ import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; -import org.dspace.service.DSpaceCRUDService; /** * Service interface class for the ResourcePolicy object. @@ -26,7 +25,34 @@ * * @author kevinvandevelde at atmire.com */ -public interface ResourcePolicyService extends DSpaceCRUDService { +public interface ResourcePolicyService { + + public ResourcePolicy create(Context context, EPerson eperson, Group group) throws SQLException, AuthorizeException; + + public ResourcePolicy find(Context context, int id) throws SQLException; + + /** + * Persist a model object. + * + * @param context + * @param resourcePolicy object to be persisted. + * @throws SQLException passed through. + * @throws AuthorizeException passed through. + */ + public void update(Context context, ResourcePolicy resourcePolicy) throws SQLException, AuthorizeException; + + + /** + * Persist a collection of model objects. + * + * @param context + * @param resourcePolicies object to be persisted. + * @throws SQLException passed through. + * @throws AuthorizeException passed through. + */ + public void update(Context context, List resourcePolicies) throws SQLException, AuthorizeException; + + public void delete(Context context, ResourcePolicy resourcePolicy) throws SQLException, AuthorizeException; public List find(Context c, DSpaceObject o) throws SQLException; diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/METSRightsCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/METSRightsCrosswalk.java index 7f6622841ba..66a1f334284 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/METSRightsCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/METSRightsCrosswalk.java @@ -432,31 +432,7 @@ public void ingest(Context context, DSpaceObject dso, List ml, boolean //get what class of context this is String contextClass = element.getAttributeValue("CONTEXTCLASS"); - ResourcePolicy rp = resourcePolicyService.create(context); - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); - - // get reference to the element - // Note: we are assuming here that there will only ever be ONE - // element. Currently there are no known use cases for multiple. - Element permsElement = element.getChild("Permissions", METSRights_NS); - if (permsElement == null) { - log.error("No element was found. Skipping this element."); - continue; - } - - if (element.getAttributeValue("rpName") != null) { - rp.setRpName(element.getAttributeValue("rpName")); - } - try { - if (element.getAttributeValue("start-date") != null) { - rp.setStartDate(sdf.parse(element.getAttributeValue("start-date"))); - } - if (element.getAttributeValue("end-date") != null) { - rp.setEndDate(sdf.parse(element.getAttributeValue("end-date"))); - } - } catch (ParseException ex) { - log.error("Failed to parse embargo date. The date needs to be in the format 'yyyy-MM-dd'.", ex); - } + ResourcePolicy rp = null; //Check if this permission pertains to Anonymous users if (ANONYMOUS_CONTEXTCLASS.equals(contextClass)) { @@ -464,22 +440,23 @@ public void ingest(Context context, DSpaceObject dso, List ml, boolean Group anonGroup = groupService.findByName(context, Group.ANONYMOUS); if (anonGroup == null) { throw new CrosswalkInternalException( - "The DSpace database has not been properly initialized. The Anonymous Group is " + - "missing from the database."); + "The DSpace database has not been properly initialized. The Anonymous Group is " + + "missing from the database."); } - rp.setGroup(anonGroup); + rp = resourcePolicyService.create(context, null, anonGroup); } else if (ADMIN_CONTEXTCLASS.equals(contextClass)) { // else if this permission declaration pertains to Administrators // get DSpace Administrator group Group adminGroup = groupService.findByName(context, Group.ADMIN); if (adminGroup == null) { throw new CrosswalkInternalException( - "The DSpace database has not been properly initialized. The Administrator Group is " + - "missing from the database."); + "The DSpace database has not been properly initialized. " + + "The Administrator Group is " + + "missing from the database."); } - rp.setGroup(adminGroup); + rp = resourcePolicyService.create(context, null, adminGroup); } else if (GROUP_CONTEXTCLASS.equals(contextClass)) { // else if this permission pertains to another DSpace group try { @@ -498,18 +475,17 @@ public void ingest(Context context, DSpaceObject dso, List ml, boolean //if not found, throw an error -- user should restore group from the SITE AIP if (group == null) { throw new CrosswalkInternalException("Cannot restore Group permissions on object (" - + "type=" + Constants.typeText[dso - .getType()] + ", " - + "handle=" + dso.getHandle() + ", " - + "ID=" + dso.getID() - + "). The Group named '" + groupName + "' is" + - " missing from DSpace. " - + "Please restore this group using the SITE " + - "AIP, or recreate it."); + + "type=" + Constants.typeText[dso.getType()] + ", " + + "handle=" + dso.getHandle() + ", " + + "ID=" + dso.getID() + + "). The Group named '" + groupName + "' is" + + " missing from DSpace. " + + "Please restore this group using the SITE " + + "AIP, or recreate it."); } //assign group to policy - rp.setGroup(group); + rp = resourcePolicyService.create(context, null, group); } catch (PackageException pe) { //A PackageException will only be thrown if translateDefaultGroupName() fails //We'll just wrap it as a CrosswalkException and throw it upwards @@ -535,25 +511,51 @@ public void ingest(Context context, DSpaceObject dso, List ml, boolean //if not found, throw an error -- user should restore person from the SITE AIP if (person == null) { throw new CrosswalkInternalException("Cannot restore Person permissions on object (" - + "type=" + Constants.typeText[dso - .getType()] + ", " - + "handle=" + dso.getHandle() + ", " - + "ID=" + dso.getID() - + "). The Person with email/netid '" + - personEmail + "' is missing from DSpace. " - + "Please restore this Person object using the " + - "SITE AIP, or recreate it."); + + "type=" + Constants.typeText[dso.getType()] + ", " + + "handle=" + dso.getHandle() + ", " + + "ID=" + dso.getID() + + "). The Person with email/netid '" + + personEmail + "' is missing from DSpace. " + + "Please restore this Person object using the " + + "SITE AIP, or recreate it."); } - //assign person to the policy - rp.setEPerson(person); + //create rp with the person + rp = resourcePolicyService.create(context, person, null); } else { log.error("Unrecognized CONTEXTCLASS: " + contextClass); } + if (rp != null) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + + // get reference to the element + // Note: we are assuming here that there will only ever be ONE + // element. Currently there are no known use cases for multiple. + Element permsElement = element.getChild("Permissions", METSRights_NS); + if (permsElement == null) { + log.error("No element was found. Skipping this element."); + continue; + } + + if (element.getAttributeValue("rpName") != null) { + rp.setRpName(element.getAttributeValue("rpName")); + } + try { + if (element.getAttributeValue("start-date") != null) { + rp.setStartDate(sdf.parse(element.getAttributeValue("start-date"))); + } + if (element.getAttributeValue("end-date") != null) { + rp.setEndDate(sdf.parse(element.getAttributeValue("end-date"))); + } + } catch (ParseException ex) { + log.error("Failed to parse embargo date. The date needs to be in the format 'yyyy-MM-dd'.", + ex); + } - //set permissions on policy add to list of policies - rp.setAction(parsePermissions(permsElement)); - policies.add(rp); + //set permissions on policy add to list of policies + rp.setAction(parsePermissions(permsElement)); + policies.add(rp); + } } //end if "Context" element } //end for loop diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.09.28__enforce_group_or_eperson_for_resourcepolicy.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.09.28__enforce_group_or_eperson_for_resourcepolicy.sql new file mode 100644 index 00000000000..de40d0415dc --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.09.28__enforce_group_or_eperson_for_resourcepolicy.sql @@ -0,0 +1,10 @@ +-- +-- 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 ResourcePolicy ADD CONSTRAINT resourcepolicy_eperson_and_epersongroup_not_nullobject_chk + CHECK (eperson_id is not null or epersongroup_id is not 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 1134990e84f..59dfbb2095e 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 @@ -261,10 +261,9 @@ public void testWithEmbargo() throws Exception { bitstream.setName(context, "primary"); bundle.setPrimaryBitstreamID(bitstream); List policies = new ArrayList<>(); - ResourcePolicy policy = resourcePolicyService.create(context); - policy.setRpName("Embargo"); Group group = groupService.findByName(context, Group.ANONYMOUS); - policy.setGroup(group); + ResourcePolicy policy = resourcePolicyService.create(context, null, group); + policy.setRpName("Embargo"); policy.setAction(Constants.READ); policy.setStartDate(new LocalDate(9999, 12, 31).toDate()); policies.add(policy); @@ -290,10 +289,9 @@ public void testWithDateRestriction() throws Exception { bitstream.setName(context, "primary"); bundle.setPrimaryBitstreamID(bitstream); List policies = new ArrayList<>(); - ResourcePolicy policy = resourcePolicyService.create(context); - policy.setRpName("Restriction"); Group group = groupService.findByName(context, Group.ANONYMOUS); - policy.setGroup(group); + ResourcePolicy policy = resourcePolicyService.create(context, null, group); + policy.setRpName("Restriction"); policy.setAction(Constants.READ); policy.setStartDate(new LocalDate(10000, 1, 1).toDate()); policies.add(policy); @@ -317,10 +315,9 @@ public void testWithGroupRestriction() throws Exception { bitstream.setName(context, "primary"); bundle.setPrimaryBitstreamID(bitstream); List policies = new ArrayList<>(); - ResourcePolicy policy = resourcePolicyService.create(context); - policy.setRpName("Restriction"); Group group = groupService.findByName(context, Group.ADMIN); - policy.setGroup(group); + ResourcePolicy policy = resourcePolicyService.create(context, null, group); + policy.setRpName("Restriction"); policy.setAction(Constants.READ); policies.add(policy); authorizeService.removeAllPolicies(context, bitstream); @@ -380,10 +377,9 @@ public void testWithPrimaryAndMultipleBitstreams() throws Exception { new ByteArrayInputStream("1".getBytes(StandardCharsets.UTF_8))); bundle.setPrimaryBitstreamID(primaryBitstream); List policies = new ArrayList<>(); - ResourcePolicy policy = resourcePolicyService.create(context); - policy.setRpName("Embargo"); Group group = groupService.findByName(context, Group.ANONYMOUS); - policy.setGroup(group); + ResourcePolicy policy = resourcePolicyService.create(context, null, group); + policy.setRpName("Embargo"); policy.setAction(Constants.READ); policy.setStartDate(new LocalDate(9999, 12, 31).toDate()); policies.add(policy); @@ -411,10 +407,9 @@ public void testWithNoPrimaryAndMultipleBitstreams() throws Exception { Bitstream anotherBitstream = bitstreamService.create(context, bundle, new ByteArrayInputStream("1".getBytes(StandardCharsets.UTF_8))); List policies = new ArrayList<>(); - ResourcePolicy policy = resourcePolicyService.create(context); - policy.setRpName("Embargo"); Group group = groupService.findByName(context, Group.ANONYMOUS); - policy.setGroup(group); + ResourcePolicy policy = resourcePolicyService.create(context, null, group); + policy.setRpName("Embargo"); policy.setAction(Constants.READ); policy.setStartDate(new LocalDate(9999, 12, 31).toDate()); policies.add(policy); diff --git a/dspace-api/src/test/java/org/dspace/builder/CollectionBuilder.java b/dspace-api/src/test/java/org/dspace/builder/CollectionBuilder.java index f287c7aa8d3..74d745c07c7 100644 --- a/dspace-api/src/test/java/org/dspace/builder/CollectionBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/CollectionBuilder.java @@ -253,8 +253,7 @@ public CollectionBuilder withAdminGroup(EPerson... members) throws SQLException, public CollectionBuilder withDefaultItemRead(Group group) throws SQLException, AuthorizeException { resourcePolicyService.removePolicies(context, collection, DEFAULT_ITEM_READ); - ResourcePolicy resourcePolicy = resourcePolicyService.create(context); - resourcePolicy.setGroup(group); + ResourcePolicy resourcePolicy = resourcePolicyService.create(context, null, group); resourcePolicy.setAction(DEFAULT_ITEM_READ); resourcePolicy.setdSpaceObject(collection); resourcePolicyService.update(context, resourcePolicy); diff --git a/dspace-api/src/test/java/org/dspace/builder/ResourcePolicyBuilder.java b/dspace-api/src/test/java/org/dspace/builder/ResourcePolicyBuilder.java index 70b1f8d73da..c3b1b658ee1 100644 --- a/dspace-api/src/test/java/org/dspace/builder/ResourcePolicyBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/ResourcePolicyBuilder.java @@ -110,31 +110,23 @@ public static void delete(Integer id) indexingService.commit(); } - public static ResourcePolicyBuilder createResourcePolicy(Context context) + public static ResourcePolicyBuilder createResourcePolicy(Context context, EPerson ePerson, + Group group) throws SQLException, AuthorizeException { ResourcePolicyBuilder resourcePolicyBuilder = new ResourcePolicyBuilder(context); - return resourcePolicyBuilder.create(context); + return resourcePolicyBuilder.create(context, ePerson, group); } - private ResourcePolicyBuilder create(Context context) + private ResourcePolicyBuilder create(Context context, final EPerson ePerson, + final Group epersonGroup) throws SQLException, AuthorizeException { this.context = context; - resourcePolicy = resourcePolicyService.create(context); + resourcePolicy = resourcePolicyService.create(context, ePerson, epersonGroup); return this; } - public ResourcePolicyBuilder withUser(EPerson ePerson) throws SQLException { - resourcePolicy.setEPerson(ePerson); - return this; - } - - public ResourcePolicyBuilder withGroup(Group epersonGroup) throws SQLException { - resourcePolicy.setGroup(epersonGroup); - return this; - } - public ResourcePolicyBuilder withAction(int action) throws SQLException { resourcePolicy.setAction(action); return this; diff --git a/dspace-api/src/test/java/org/dspace/content/BundleTest.java b/dspace-api/src/test/java/org/dspace/content/BundleTest.java index 4ff35f5b4df..e8ff69c47be 100644 --- a/dspace-api/src/test/java/org/dspace/content/BundleTest.java +++ b/dspace-api/src/test/java/org/dspace/content/BundleTest.java @@ -598,9 +598,9 @@ public void testInheritCollectionDefaultPolicies() throws AuthorizeException, SQ @Test public void testReplaceAllBitstreamPolicies() throws SQLException, AuthorizeException { List newpolicies = new ArrayList(); - newpolicies.add(resourcePolicyService.create(context)); - newpolicies.add(resourcePolicyService.create(context)); - newpolicies.add(resourcePolicyService.create(context)); + newpolicies.add(resourcePolicyService.create(context, eperson, null)); + newpolicies.add(resourcePolicyService.create(context, eperson, null)); + newpolicies.add(resourcePolicyService.create(context, eperson, null)); bundleService.replaceAllBitstreamPolicies(context, b, newpolicies); List bspolicies = bundleService.getBundlePolicies(context, b); diff --git a/dspace-api/src/test/java/org/dspace/content/ItemTest.java b/dspace-api/src/test/java/org/dspace/content/ItemTest.java index d440597ec41..ada9bbc1590 100644 --- a/dspace-api/src/test/java/org/dspace/content/ItemTest.java +++ b/dspace-api/src/test/java/org/dspace/content/ItemTest.java @@ -1257,7 +1257,7 @@ public void testGetType() { @Test public void testReplaceAllItemPolicies() throws Exception { List newpolicies = new ArrayList(); - ResourcePolicy pol1 = resourcePolicyService.create(context); + ResourcePolicy pol1 = resourcePolicyService.create(context, eperson, null); newpolicies.add(pol1); itemService.replaceAllItemPolicies(context, it, newpolicies); @@ -1284,9 +1284,9 @@ public void testReplaceAllBitstreamPolicies() throws Exception { bundleService.addBitstream(context, created, result); List newpolicies = new ArrayList(); - newpolicies.add(resourcePolicyService.create(context)); - newpolicies.add(resourcePolicyService.create(context)); - newpolicies.add(resourcePolicyService.create(context)); + newpolicies.add(resourcePolicyService.create(context, eperson, null)); + newpolicies.add(resourcePolicyService.create(context, eperson, null)); + newpolicies.add(resourcePolicyService.create(context, eperson, null)); context.restoreAuthSystemState(); itemService.replaceAllBitstreamPolicies(context, it, newpolicies); @@ -1316,9 +1316,8 @@ public void testRemoveGroupPolicies() throws Exception { context.turnOffAuthorisationSystem(); List newpolicies = new ArrayList(); Group g = groupService.create(context); - ResourcePolicy pol1 = resourcePolicyService.create(context); + ResourcePolicy pol1 = resourcePolicyService.create(context, null, g); newpolicies.add(pol1); - pol1.setGroup(g); itemService.replaceAllItemPolicies(context, it, newpolicies); itemService.removeGroupPolicies(context, it, g); diff --git a/dspace-api/src/test/java/org/dspace/content/packager/ITDSpaceAIP.java b/dspace-api/src/test/java/org/dspace/content/packager/ITDSpaceAIP.java index a634b98130a..c5a73ed539a 100644 --- a/dspace-api/src/test/java/org/dspace/content/packager/ITDSpaceAIP.java +++ b/dspace-api/src/test/java/org/dspace/content/packager/ITDSpaceAIP.java @@ -386,9 +386,8 @@ public void testRestoreRestrictedCommunity() throws Exception { // Create a custom resource policy for this community List policies = new ArrayList<>(); - ResourcePolicy policy = resourcePolicyService.create(context); + ResourcePolicy policy = resourcePolicyService.create(context, null, group); policy.setRpName("Special Read Only"); - policy.setGroup(group); policy.setAction(Constants.READ); policies.add(policy); @@ -600,9 +599,8 @@ public void testRestoreRestrictedCollection() throws Exception { // Create a custom resource policy for this Collection List policies = new ArrayList<>(); - ResourcePolicy policy = resourcePolicyService.create(context); + ResourcePolicy policy = resourcePolicyService.create(context, null, group); policy.setRpName("Special Read Only"); - policy.setGroup(group); policy.setAction(Constants.READ); policies.add(policy); @@ -822,10 +820,9 @@ public void testRestoreRestrictedItem() throws Exception { // Create a custom resource policy for this Item List policies = new ArrayList<>(); - ResourcePolicy admin_policy = resourcePolicyService.create(context); - admin_policy.setRpName("Admin Read-Only"); Group adminGroup = groupService.findByName(context, Group.ADMIN); - admin_policy.setGroup(adminGroup); + ResourcePolicy admin_policy = resourcePolicyService.create(context, null, adminGroup); + admin_policy.setRpName("Admin Read-Only"); admin_policy.setAction(Constants.READ); policies.add(admin_policy); itemService.replaceAllItemPolicies(context, item, policies); diff --git a/dspace-api/src/test/java/org/dspace/content/service/ItemServiceIT.java b/dspace-api/src/test/java/org/dspace/content/service/ItemServiceIT.java index e40577ef36e..e825acdad89 100644 --- a/dspace-api/src/test/java/org/dspace/content/service/ItemServiceIT.java +++ b/dspace-api/src/test/java/org/dspace/content/service/ItemServiceIT.java @@ -668,8 +668,7 @@ public void testFindItemsWithEditNoRights() throws Exception { @Test public void testFindAndCountItemsWithEditEPerson() throws Exception { - ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) - .withUser(eperson) + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withDspaceObject(item) .withAction(Constants.WRITE) .build(); @@ -682,8 +681,7 @@ public void testFindAndCountItemsWithEditEPerson() throws Exception { @Test public void testFindAndCountItemsWithAdminEPerson() throws Exception { - ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) - .withUser(eperson) + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withDspaceObject(item) .withAction(Constants.ADMIN) .build(); @@ -702,8 +700,7 @@ public void testFindAndCountItemsWithEditGroup() throws Exception { .build(); context.restoreAuthSystemState(); - ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) - .withGroup(group) + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context, null, group) .withDspaceObject(item) .withAction(Constants.WRITE) .build(); @@ -722,8 +719,7 @@ public void testFindAndCountItemsWithAdminGroup() throws Exception { .build(); context.restoreAuthSystemState(); - ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) - .withGroup(group) + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context, null, group) .withDspaceObject(item) .withAction(Constants.ADMIN) .build(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResourcePolicyRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResourcePolicyRestRepository.java index 0b77f96b9b5..a79a9fe4eaa 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResourcePolicyRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResourcePolicyRestRepository.java @@ -254,14 +254,6 @@ protected ResourcePolicyRest createAndReturn(Context context) throws AuthorizeEx if (dspaceObject == null) { throw new UnprocessableEntityException("DSpaceObject with this uuid: " + resourceUuid + " not found"); } - resourcePolicy = resourcePolicyService.create(context); - resourcePolicy.setRpType(resourcePolicyRest.getPolicyType()); - resourcePolicy.setdSpaceObject(dspaceObject); - resourcePolicy.setRpName(resourcePolicyRest.getName()); - resourcePolicy.setRpDescription(resourcePolicyRest.getDescription()); - resourcePolicy.setAction(Constants.getActionID(resourcePolicyRest.getAction())); - resourcePolicy.setStartDate(resourcePolicyRest.getStartDate()); - resourcePolicy.setEndDate(resourcePolicyRest.getEndDate()); if (epersonUuidStr != null) { try { @@ -270,12 +262,11 @@ protected ResourcePolicyRest createAndReturn(Context context) throws AuthorizeEx if (ePerson == null) { throw new UnprocessableEntityException("EPerson with uuid: " + epersonUuid + " not found"); } - resourcePolicy.setEPerson(ePerson); - resourcePolicyService.update(context, resourcePolicy); + resourcePolicy = resourcePolicyService.create(context, ePerson, null); + } catch (SQLException excSQL) { throw new RuntimeException(excSQL.getMessage(), excSQL); } - return converter.toRest(resourcePolicy, utils.obtainProjection()); } else { try { UUID groupUuid = UUID.fromString(groupUuidStr); @@ -283,13 +274,27 @@ protected ResourcePolicyRest createAndReturn(Context context) throws AuthorizeEx if (group == null) { throw new UnprocessableEntityException("Group with uuid: " + groupUuid + " not found"); } - resourcePolicy.setGroup(group); - resourcePolicyService.update(context, resourcePolicy); + resourcePolicy = resourcePolicyService.create(context, null, group); } catch (SQLException excSQL) { throw new RuntimeException(excSQL.getMessage(), excSQL); } + } + + if (resourcePolicy != null) { + + resourcePolicy.setRpType(resourcePolicyRest.getPolicyType()); + resourcePolicy.setdSpaceObject(dspaceObject); + resourcePolicy.setRpName(resourcePolicyRest.getName()); + resourcePolicy.setRpDescription(resourcePolicyRest.getDescription()); + resourcePolicy.setAction(Constants.getActionID(resourcePolicyRest.getAction())); + resourcePolicy.setStartDate(resourcePolicyRest.getStartDate()); + resourcePolicy.setEndDate(resourcePolicyRest.getEndDate()); + resourcePolicyService.update(context, resourcePolicy); return converter.toRest(resourcePolicy, utils.obtainProjection()); + } else { + throw new UnprocessableEntityException("A resource policy must contain a valid eperson or group"); } + } @Override diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamControllerIT.java index a326d195c2a..c31bb61f060 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamControllerIT.java @@ -302,25 +302,25 @@ public void putOnBitstreamInOneBundle() throws Exception { .withPassword("test") .withNameInMetadata("Bundle", "Put").build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.REMOVE) .withDspaceObject(bundle1).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(bundle1).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(targetBundle).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.ADD) .withDspaceObject(targetBundle).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(bitstream).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(publicItem1).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(targetItem).build(); @@ -471,31 +471,31 @@ public void putOnBitstreamInMultipleBundles() throws Exception { .withPassword("test") .withNameInMetadata("Bundle", "Put").build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.REMOVE) .withDspaceObject(bundle1).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(bundle1).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.REMOVE) .withDspaceObject(bundle2).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(bundle2).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(targetBundle).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.ADD) .withDspaceObject(targetBundle).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(bitstream).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(publicItem1).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(targetItem).build(); @@ -592,25 +592,25 @@ public void putOnBitstreamInNoBundle() throws Exception { .withPassword("test") .withNameInMetadata("Bundle", "Put").build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.REMOVE) .withDspaceObject(bundle1).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(bundle1).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(targetBundle).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.ADD) .withDspaceObject(targetBundle).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(bitstream).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(publicItem1).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(targetItem).build(); @@ -708,22 +708,22 @@ public void putOnBitstreamInOneBundleWithNoRemoveRights() throws Exception { .withPassword("test") .withNameInMetadata("Bundle", "Put").build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(bundle1).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(targetBundle).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.ADD) .withDspaceObject(targetBundle).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(bitstream).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(publicItem1).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(targetItem).build(); @@ -810,22 +810,22 @@ public void putOnBitstreamInOneBundleWithNoWriteOnCurrentBundleRights() throws E .withPassword("test") .withNameInMetadata("Bundle", "Put").build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.REMOVE) .withDspaceObject(bundle1).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(targetBundle).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.ADD) .withDspaceObject(targetBundle).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(bitstream).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(publicItem1).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(targetItem).build(); @@ -913,22 +913,22 @@ public void putOnBitstreamInOneBundleWithNoWriteRightsOnTargetBundle() throws Ex .withPassword("test") .withNameInMetadata("Bundle", "Put").build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.REMOVE) .withDspaceObject(bundle1).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(bundle1).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.ADD) .withDspaceObject(targetBundle).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(bitstream).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(publicItem1).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(targetItem).build(); @@ -1015,22 +1015,22 @@ public void putOnBitstreamInOneBundleWithNoAddRightsOnTargetBundle() throws Exce .withPassword("test") .withNameInMetadata("Bundle", "Put").build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.REMOVE) .withDspaceObject(bundle1).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(bundle1).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(targetBundle).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(bitstream).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(publicItem1).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(targetItem).build(); @@ -1117,22 +1117,22 @@ public void putOnBitstreamInOneBundleWithNoWriteRightsOnBitstream() throws Excep .withPassword("test") .withNameInMetadata("Bundle", "Put").build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.REMOVE) .withDspaceObject(bundle1).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(bundle1).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(targetBundle).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.ADD) .withDspaceObject(targetBundle).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(publicItem1).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(targetItem).build(); @@ -1220,22 +1220,22 @@ public void putOnBitstreamInOneBundleWithNoWriteRightsOnCurrentItem() throws Exc .withPassword("test") .withNameInMetadata("Bundle", "Put").build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.REMOVE) .withDspaceObject(bundle1).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(bundle1).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(targetBundle).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.ADD) .withDspaceObject(targetBundle).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(bitstream).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(targetItem).build(); @@ -1322,22 +1322,22 @@ public void putOnBitstreamInOneBundleWithNoWriteRightsOnTargetItem() throws Exce .withPassword("test") .withNameInMetadata("Bundle", "Put").build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.REMOVE) .withDspaceObject(bundle1).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(bundle1).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(targetBundle).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.ADD) .withDspaceObject(targetBundle).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(bitstream).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + ResourcePolicyBuilder.createResourcePolicy(context, putBundlePerson, null) .withAction(Constants.WRITE) .withDspaceObject(publicItem1).build(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java index 4813cc65969..d2223e6baa7 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java @@ -1086,8 +1086,7 @@ public void updateBitstreamFormatEPerson() throws Exception { context.turnOffAuthorisationSystem(); - createResourcePolicy(context) - .withUser(eperson) + createResourcePolicy(context, eperson, null) .withAction(WRITE) .withDspaceObject(bitstream) .build(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java index 8b34edb938a..104b73a8cba 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java @@ -644,7 +644,7 @@ public void findOneBitstreamTest_EmbargoedBitstream_ePersonREADRightsOnBundle() // Replace anon read policy on bundle of bitstream with ePerson READ policy resourcePolicyService.removePolicies(context, bitstream.getBundles().get(0), Constants.READ); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(eperson) + ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withAction(Constants.READ) .withDspaceObject(bitstream.getBundles().get(0)).build(); @@ -707,9 +707,9 @@ public void findOneBitstreamFormatTest_EmbargoedBitstream_ePersonREADRightsOnBun // Replace anon read policy on bundle of bitstream with ePerson READ policy resourcePolicyService.removePolicies(context, bitstream.getBundles().get(0), Constants.READ); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(eperson) - .withAction(Constants.READ) - .withDspaceObject(bitstream.getBundles().get(0)).build(); + ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) + .withAction(Constants.READ) + .withDspaceObject(bitstream.getBundles().get(0)).build(); context.restoreAuthSystemState(); @@ -832,7 +832,7 @@ public void findOneBitstreamTest_EmbargoedBitstream_ePersonREADRightsOnItem() th // Replace anon read policy on item of bitstream with ePerson READ policy resourcePolicyService.removePolicies(context, publicItem1, Constants.READ); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(eperson) + ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withAction(Constants.READ) .withDspaceObject(publicItem1).build(); @@ -1480,8 +1480,7 @@ public void testHiddenMetadataForUserWithWriteRights() throws Exception { .build(); } - ResourcePolicyBuilder.createResourcePolicy(context) - .withUser(eperson) + ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withAction(WRITE) .withDspaceObject(col1) .build(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java index 259580f8c08..6b148e88e13 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java @@ -347,7 +347,7 @@ public void createBundleWithSufficientPermissions() throws Exception { .withPassword("test") .withNameInMetadata("Create", "Bundle").build(); - ResourcePolicy rp1 = ResourcePolicyBuilder.createResourcePolicy(context).withUser(createBundleEperson) + ResourcePolicy rp1 = ResourcePolicyBuilder.createResourcePolicy(context, createBundleEperson, null) .withAction(Constants.ADD) .withDspaceObject(item).build(); context.restoreAuthSystemState(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java index ee522db170c..014a31c5011 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java @@ -1811,8 +1811,7 @@ public void testHiddenMetadataForUserWithWriteRights() throws Exception { - ResourcePolicyBuilder.createResourcePolicy(context) - .withUser(eperson) + ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withAction(WRITE) .withDspaceObject(col1) .build(); @@ -2894,11 +2893,10 @@ public void testSubGroupOfCommunityAdminGroupAuthorizedSearch() throws Exception communityC = CommunityBuilder.createCommunity(context) .withName("the last community is topLevelCommunityC") .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, null, groupService.findByName(context, "COMMUNITY_" + + topLevelCommunityA.getID() + "_ADMIN")) .withDspaceObject(communityB) - .withAction(Constants.ADMIN) - .withGroup(groupService.findByName(context, "COMMUNITY_" + topLevelCommunityA.getID() + "_ADMIN")) - .build(); + .withAction(Constants.ADMIN).build(); collectionB = CollectionBuilder.createCollection(context, subCommunityA) .withName("collectionB is a very original name") .build(); @@ -2950,11 +2948,10 @@ public void testSubGroupOfSubCommunityAdminGroupAuthorizedSearch() throws Except .withName("the last community is topLevelCommunityC") .addParentCommunity(context, topLevelCommunityA) .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, null, groupService.findByName(context, "COMMUNITY_" + + subCommunityA.getID() + "_ADMIN")) .withDspaceObject(communityB) - .withAction(Constants.ADMIN) - .withGroup(groupService.findByName(context, "COMMUNITY_" + subCommunityA.getID() + "_ADMIN")) - .build(); + .withAction(Constants.ADMIN).build(); collectionB = CollectionBuilder.createCollection(context, subCommunityA) .withName("collectionB is a very original name") .build(); @@ -3012,11 +3009,10 @@ public void testSubGroupOfCollectionAdminGroupAuthorizedSearch() throws Exceptio collectionC = CollectionBuilder.createCollection(context, communityC) .withName("the last collection is collectionC") .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, null, groupService.findByName(context, "COLLECTION_" + + collectionA.getID() + "_ADMIN")) .withDspaceObject(collectionB) - .withAction(Constants.ADMIN) - .withGroup(groupService.findByName(context, "COLLECTION_" + collectionA.getID() + "_ADMIN")) - .build(); + .withAction(Constants.ADMIN).build(); context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); @@ -3065,11 +3061,10 @@ public void testSubGroupOfSubmitterGroupAuthorizedSearch() throws Exception { collectionB = CollectionBuilder.createCollection(context, communityB) .withName("collectionB is a very original name") .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, null, groupService.findByName(context, "COLLECTION_" + + collectionA.getID() + "_SUBMIT")) .withDspaceObject(collectionB) - .withAction(Constants.ADD) - .withGroup(groupService.findByName(context, "COLLECTION_" + collectionA.getID() + "_SUBMIT")) - .build(); + .withAction(Constants.ADD).build(); collectionC = CollectionBuilder.createCollection(context, communityC) .withName("the last collection is collectionC") .build(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java index 30614e6125f..22e62c24168 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java @@ -2370,10 +2370,10 @@ public void testSubGroupOfCommunityAdminGroupAuthorizedSearch() throws Exception communityC = CommunityBuilder.createCommunity(context) .withName("the last community is topLevelCommunityC") .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, null, groupService.findByName(context, "COMMUNITY_" + + topLevelCommunityA.getID() + "_ADMIN")) .withDspaceObject(communityB) .withAction(Constants.ADMIN) - .withGroup(groupService.findByName(context, "COMMUNITY_" + topLevelCommunityA.getID() + "_ADMIN")) .build(); context.restoreAuthSystemState(); @@ -2423,10 +2423,10 @@ public void testSubGroupOfSubCommunityAdminGroupAuthorizedSearch() throws Except .withName("the last community is topLevelCommunityC") .addParentCommunity(context, topLevelCommunityA) .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, null, groupService.findByName(context, "COMMUNITY_" + + subCommunityA.getID() + "_ADMIN")) .withDspaceObject(communityB) .withAction(Constants.ADMIN) - .withGroup(groupService.findByName(context, "COMMUNITY_" + subCommunityA.getID() + "_ADMIN")) .build(); context.restoreAuthSystemState(); @@ -2473,10 +2473,10 @@ public void testSubGroupOfCollectionAdminGroupAuthorizedSearch() throws Exceptio Collection collectionB = CollectionBuilder.createCollection(context, communityB) .withName("collectionB") .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, null, groupService.findByName(context, "COLLECTION_" + + collectionA.getID() + "_ADMIN")) .withDspaceObject(collectionB) .withAction(Constants.ADMIN) - .withGroup(groupService.findByName(context, "COLLECTION_" + collectionA.getID() + "_ADMIN")) .build(); communityC = CommunityBuilder.createCommunity(context) .withName("the last community is topLevelCommunityC") @@ -2521,10 +2521,10 @@ public void testSubGroupOfSubmitterGroupAuthorizedSearch() throws Exception { Collection collectionB = CollectionBuilder.createCollection(context, communityB) .withName("collectionB") .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, null, groupService.findByName(context, "COLLECTION_" + + collectionA.getID() + "_SUBMIT")) .withDspaceObject(collectionB) .withAction(Constants.ADD) - .withGroup(groupService.findByName(context, "COLLECTION_" + collectionA.getID() + "_SUBMIT")) .build(); communityC = CommunityBuilder.createCommunity(context) .withName("the last community is topLevelCommunityC") diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java index fda8b15effa..ef4bfa2ac59 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java @@ -3124,10 +3124,10 @@ public void commAdminAndColAdminCannotExploitItemReadGroupTest() throws Exceptio .build(); Group adminGroup = groupService.findByName(context, Group.ADMIN); - ResourcePolicyBuilder.createResourcePolicy(context).withAction(Constants.DEFAULT_ITEM_READ) - .withGroup(adminGroup).withDspaceObject(child1).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withAction(Constants.DEFAULT_ITEM_READ) - .withGroup(adminGroup).withDspaceObject(col1).build(); + ResourcePolicyBuilder.createResourcePolicy(context, null, adminGroup).withAction(Constants.DEFAULT_ITEM_READ) + .withDspaceObject(child1).build(); + ResourcePolicyBuilder.createResourcePolicy(context, null, adminGroup).withAction(Constants.DEFAULT_ITEM_READ) + .withDspaceObject(col1).build(); context.restoreAuthSystemState(); String tokenAdminComm = getAuthToken(adminChild1.getEmail(), password); @@ -3187,10 +3187,12 @@ public void commAdminAndColAdminCannotExpoloitBitstreamReadGroupTest() throws Ex .build(); Group adminGroup = groupService.findByName(context, Group.ADMIN); - ResourcePolicyBuilder.createResourcePolicy(context).withAction(Constants.DEFAULT_BITSTREAM_READ) - .withGroup(adminGroup).withDspaceObject(child1).build(); - ResourcePolicyBuilder.createResourcePolicy(context).withAction(Constants.DEFAULT_BITSTREAM_READ) - .withGroup(adminGroup).withDspaceObject(col1).build(); + ResourcePolicyBuilder.createResourcePolicy(context, null, adminGroup) + .withAction(Constants.DEFAULT_BITSTREAM_READ) + .withDspaceObject(child1).build(); + ResourcePolicyBuilder.createResourcePolicy(context, null, adminGroup) + .withAction(Constants.DEFAULT_BITSTREAM_READ) + .withDspaceObject(col1).build(); context.restoreAuthSystemState(); String tokenAdminComm = getAuthToken(adminChild1.getEmail(), password); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemOwningCollectionUpdateRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemOwningCollectionUpdateRestControllerIT.java index 73c2c8a3fe5..7d2d1d901ec 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemOwningCollectionUpdateRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemOwningCollectionUpdateRestControllerIT.java @@ -131,13 +131,13 @@ public void moveItemTestByMinimallyAuthorizedUser() throws Exception { EPerson itemMoveEperson = EPersonBuilder.createEPerson(context).withEmail("item@move.org").withPassword("test") .withNameInMetadata("Item", "Move").build(); - ResourcePolicy rp1 = ResourcePolicyBuilder.createResourcePolicy(context).withUser(itemMoveEperson) + ResourcePolicy rp1 = ResourcePolicyBuilder.createResourcePolicy(context, itemMoveEperson, null) .withAction(Constants.ADMIN) .withDspaceObject(col1).build(); - ResourcePolicy rp2 = ResourcePolicyBuilder.createResourcePolicy(context).withUser(itemMoveEperson) + ResourcePolicy rp2 = ResourcePolicyBuilder.createResourcePolicy(context, itemMoveEperson, null) .withAction(Constants.WRITE) .withDspaceObject(publicItem1).build(); - ResourcePolicy rp3 = ResourcePolicyBuilder.createResourcePolicy(context).withUser(itemMoveEperson) + ResourcePolicy rp3 = ResourcePolicyBuilder.createResourcePolicy(context, itemMoveEperson, null) .withAction(Constants.ADD) .withDspaceObject(col2).build(); @@ -181,10 +181,10 @@ public void moveItemTestByAuthorizedUserWithoutAdd() throws Exception { EPerson itemMoveEperson = EPersonBuilder.createEPerson(context).withEmail("item@move.org").withPassword("test") .withNameInMetadata("Item", "Move").build(); - ResourcePolicy rp1 = ResourcePolicyBuilder.createResourcePolicy(context).withUser(itemMoveEperson) + ResourcePolicy rp1 = ResourcePolicyBuilder.createResourcePolicy(context, itemMoveEperson, null) .withAction(Constants.ADMIN) .withDspaceObject(col1).build(); - ResourcePolicy rp2 = ResourcePolicyBuilder.createResourcePolicy(context).withUser(itemMoveEperson) + ResourcePolicy rp2 = ResourcePolicyBuilder.createResourcePolicy(context, itemMoveEperson, null) .withAction(Constants.WRITE) .withDspaceObject(publicItem1).build(); @@ -222,10 +222,10 @@ public void moveItemTestByAuthorizedUserWithoutAdmin() throws Exception { EPerson itemMoveEperson = EPersonBuilder.createEPerson(context).withEmail("item@move.org").withPassword("test") .withNameInMetadata("Item", "Move").build(); - ResourcePolicy rp2 = ResourcePolicyBuilder.createResourcePolicy(context).withUser(itemMoveEperson) + ResourcePolicy rp2 = ResourcePolicyBuilder.createResourcePolicy(context, itemMoveEperson, null) .withAction(Constants.WRITE) .withDspaceObject(publicItem1).build(); - ResourcePolicy rp3 = ResourcePolicyBuilder.createResourcePolicy(context).withUser(itemMoveEperson) + ResourcePolicy rp3 = ResourcePolicyBuilder.createResourcePolicy(context, itemMoveEperson, null) .withAction(Constants.ADD) .withDspaceObject(col2).build(); @@ -263,10 +263,10 @@ public void moveItemTestByAuthorizedUserWithoutWrite() throws Exception { EPerson itemMoveEperson = EPersonBuilder.createEPerson(context).withEmail("item@move.org").withPassword("test") .withNameInMetadata("Item", "Move").build(); - ResourcePolicy rp1 = ResourcePolicyBuilder.createResourcePolicy(context).withUser(itemMoveEperson) + ResourcePolicy rp1 = ResourcePolicyBuilder.createResourcePolicy(context, itemMoveEperson, null) .withAction(Constants.ADMIN) .withDspaceObject(col1).build(); - ResourcePolicy rp3 = ResourcePolicyBuilder.createResourcePolicy(context).withUser(itemMoveEperson) + ResourcePolicy rp3 = ResourcePolicyBuilder.createResourcePolicy(context, itemMoveEperson, null) .withAction(Constants.ADD) .withDspaceObject(col2).build(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java index 801976be9f0..7558f321e58 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java @@ -3012,8 +3012,7 @@ public void testHiddenMetadataForUserWithWriteRights() throws Exception { context.restoreAuthSystemState(); - ResourcePolicyBuilder.createResourcePolicy(context) - .withUser(eperson) + ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withAction(WRITE) .withDspaceObject(item) .build(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemTemplateRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemTemplateRestControllerIT.java index 1fd9e81ca88..f3ba6d3aed1 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemTemplateRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemTemplateRestControllerIT.java @@ -253,9 +253,9 @@ public void patchTemplateItemAsCollectionAdmin() throws Exception { String itemId = installTestTemplate(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(eperson) - .withAction(Constants.ADMIN) - .withDspaceObject(childCollection).build(); + ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) + .withAction(Constants.ADMIN) + .withDspaceObject(childCollection).build(); String collAdminToken = getAuthToken(eperson.getEmail(), password); getClient(collAdminToken).perform(patch(getTemplateItemUrlTemplate(itemId)) @@ -374,9 +374,9 @@ public void deleteTemplateItemAsCollectionAdmin() throws Exception { setupTestTemplate(); String itemId = installTestTemplate(); - ResourcePolicyBuilder.createResourcePolicy(context).withUser(eperson) - .withAction(Constants.ADMIN) - .withDspaceObject(childCollection).build(); + ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) + .withAction(Constants.ADMIN) + .withDspaceObject(childCollection).build(); String collAdminToken = getAuthToken(eperson.getEmail(), password); getClient(collAdminToken).perform(delete(getTemplateItemUrlTemplate(itemId))) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java index 02834607113..e7d2634525d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java @@ -80,8 +80,10 @@ public void findAllTest() throws Exception { context.turnOffAuthorisationSystem(); Community community = CommunityBuilder.createCommunity(context).withName("My community").build(); - ResourcePolicyBuilder.createResourcePolicy(context).withDspaceObject(community).withAction(Constants.READ) - .withUser(admin).build(); + ResourcePolicyBuilder.createResourcePolicy(context, admin, null) + .withDspaceObject(community) + .withAction(Constants.READ) + .build(); context.restoreAuthSystemState(); String authToken = getAuthToken(admin.getEmail(), password); @@ -94,8 +96,10 @@ public void findAllUnAuthenticatedTest() throws Exception { context.turnOffAuthorisationSystem(); Community community = CommunityBuilder.createCommunity(context).withName("My community").build(); - ResourcePolicyBuilder.createResourcePolicy(context).withDspaceObject(community).withAction(Constants.READ) - .withUser(admin).build(); + ResourcePolicyBuilder.createResourcePolicy(context, admin, null) + .withDspaceObject(community) + .withAction(Constants.READ) + .build(); context.restoreAuthSystemState(); getClient().perform(get("/api/authz/resourcepolicies")).andExpect(status().isUnauthorized()); @@ -112,10 +116,9 @@ public void findOneTest() throws Exception { Community community = CommunityBuilder.createCommunity(context).withName("My community").build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, eperson1, null) .withDspaceObject(community) .withAction(Constants.READ) - .withUser(eperson1) .build(); context.restoreAuthSystemState(); @@ -144,10 +147,9 @@ public void findOneResourcePolicyOfAnonymousGroupTest() throws Exception { Community community = CommunityBuilder.createCommunity(context).withName("My community").build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, null, groupAnonymous) .withDspaceObject(community) .withAction(Constants.READ) - .withGroup(groupAnonymous) .build(); context.restoreAuthSystemState(); @@ -165,8 +167,10 @@ public void findOneUnAuthenticatedTest() throws Exception { context.turnOffAuthorisationSystem(); Community community = CommunityBuilder.createCommunity(context).withName("My community").build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context).withDspaceObject(community) - .withAction(Constants.READ).withUser(eperson).build(); + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) + .withDspaceObject(community) + .withAction(Constants.READ) + .build(); context.restoreAuthSystemState(); @@ -201,10 +205,9 @@ public void findOneForbiddenTest() throws Exception { Collection collection = CollectionBuilder.createCollection(context, community) .withName("My collection").build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, eperson1, null) .withDspaceObject(collection) - .withAction(Constants.WRITE) - .withUser(eperson1).build(); + .withAction(Constants.WRITE).build(); context.restoreAuthSystemState(); @@ -228,10 +231,9 @@ public void findOneAccessGrantToAdminTest() throws Exception { Community community = CommunityBuilder.createCommunity(context).withName("My community").build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, eperson1, null) .withDspaceObject(community) - .withAction(Constants.WRITE) - .withUser(eperson1).build(); + .withAction(Constants.WRITE).build(); context.restoreAuthSystemState(); @@ -262,10 +264,9 @@ public void findOneAccessGrantToSameUserTest() throws Exception { Collection collection = CollectionBuilder.createCollection(context, community) .withName("My collection").build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, null, group1) .withDspaceObject(collection) .withAction(Constants.ADD) - .withGroup(group1) .build(); context.restoreAuthSystemState(); @@ -297,15 +298,13 @@ public void findOneResoucesPolicyByEpersonUuidTest() throws Exception { Community community = CommunityBuilder.createCommunity(context).withName("My community").build(); Community community2 = CommunityBuilder.createCommunity(context).withName("My community_2").build(); - ResourcePolicy resourcePolicyOfEPerson1 = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicyOfEPerson1 = ResourcePolicyBuilder.createResourcePolicy(context, eperson1, null) .withDspaceObject(community) - .withAction(Constants.ADD) - .withUser(eperson1).build(); + .withAction(Constants.ADD).build(); - ResourcePolicy resourcePolicyOfEPerson2 = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicyOfEPerson2 = ResourcePolicyBuilder.createResourcePolicy(context, eperson2, null) .withDspaceObject(community2) - .withAction(Constants.REMOVE) - .withUser(eperson2).build(); + .withAction(Constants.REMOVE).build(); context.restoreAuthSystemState(); @@ -336,20 +335,18 @@ public void findResoucesPoliciesByEpersonUuidAndResourceUuidTest() throws Except Collection collection = CollectionBuilder.createCollection(context, community).withName("My collection") .build(); - ResourcePolicy resourcePolicyOfCommunity = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicyOfCommunity = ResourcePolicyBuilder.createResourcePolicy(context, eperson1, null) .withDspaceObject(community) - .withAction(Constants.READ) - .withUser(eperson1).build(); + .withAction(Constants.READ).build(); - ResourcePolicy secondResourcePolicyOfCommunity = ResourcePolicyBuilder.createResourcePolicy(context) - .withDspaceObject(community) - .withAction(Constants.REMOVE) - .withUser(eperson1).build(); + ResourcePolicy secondResourcePolicyOfCommunity = ResourcePolicyBuilder + .createResourcePolicy(context, eperson1, null) + .withDspaceObject(community) + .withAction(Constants.REMOVE).build(); - ResourcePolicy resourcePolicyOfCollection = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicyOfCollection = ResourcePolicyBuilder.createResourcePolicy(context, eperson1, null) .withDspaceObject(collection) - .withAction(Constants.REMOVE) - .withUser(eperson1).build(); + .withAction(Constants.REMOVE).build(); context.restoreAuthSystemState(); @@ -395,10 +392,9 @@ public void findResoucesPoliciesByEPersonUuidUnAuthenticatedTest() throws Except Community community = CommunityBuilder.createCommunity(context).withName("My community").build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, eperson1, null) .withDspaceObject(community) - .withAction(Constants.READ) - .withUser(eperson1).build(); + .withAction(Constants.READ).build(); context.restoreAuthSystemState(); @@ -437,15 +433,13 @@ public void findResourcesPoliciesByEPersonUuidForbiddenTest() throws Exception { Community community2 = CommunityBuilder.createCommunity(context).withName("My 2 community").build(); - ResourcePolicy resourcePolicyOfEPerson1 = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicyOfEPerson1 = ResourcePolicyBuilder.createResourcePolicy(context, eperson1, null) .withDspaceObject(community).withAction(Constants.WRITE) - .withPolicyType(ResourcePolicy.TYPE_CUSTOM) - .withUser(eperson1).build(); + .withPolicyType(ResourcePolicy.TYPE_CUSTOM).build(); - ResourcePolicy resourcePolicyOfEPerson2 = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicyOfEPerson2 = ResourcePolicyBuilder.createResourcePolicy(context, eperson2, null) .withDspaceObject(community2).withAction(Constants.ADD) - .withPolicyType(ResourcePolicy.TYPE_CUSTOM) - .withUser(eperson2).build(); + .withPolicyType(ResourcePolicy.TYPE_CUSTOM).build(); context.restoreAuthSystemState(); @@ -474,16 +468,18 @@ public void findResourcePoliciesOfOneResourceWithoutActionTest() throws Exceptio Community community2 = CommunityBuilder.createCommunity(context).withName("My second community").build(); - ResourcePolicy firstResourcePolicyOfEPerson1 = ResourcePolicyBuilder.createResourcePolicy(context) - .withDspaceObject(community) - .withAction(Constants.ADMIN) - .withUser(eperson1).build(); + ResourcePolicy firstResourcePolicyOfEPerson1 = ResourcePolicyBuilder + .createResourcePolicy(context, eperson1, null) + .withDspaceObject(community) + .withAction(Constants.ADMIN) + .build(); - ResourcePolicy firstResourcePolicyOfEPerson2 = ResourcePolicyBuilder.createResourcePolicy(context) - .withDspaceObject(community2) - .withAction(Constants.ADD) - .withPolicyType(ResourcePolicy.TYPE_CUSTOM) - .withUser(eperson2).build(); + ResourcePolicy firstResourcePolicyOfEPerson2 = ResourcePolicyBuilder + .createResourcePolicy(context, eperson2, null) + .withDspaceObject(community2) + .withAction(Constants.ADD) + .withPolicyType(ResourcePolicy.TYPE_CUSTOM) + .build(); ResourcePolicy resourcePolicyAnonymous = authorizeService .findByTypeGroupAction(context, community, EPersonServiceFactory.getInstance() @@ -528,20 +524,23 @@ public void findResourcePoliciesOfOneResourceWithActionTest() throws Exception { Community community2 = CommunityBuilder.createCommunity(context).withName("My 2 community").build(); - ResourcePolicy firstResourcePolicyOfEPerson1 = ResourcePolicyBuilder.createResourcePolicy(context) - .withDspaceObject(community) - .withAction(Constants.ADMIN) - .withUser(eperson1).build(); + ResourcePolicy firstResourcePolicyOfEPerson1 = ResourcePolicyBuilder + .createResourcePolicy(context, eperson1, null) + .withDspaceObject(community) + .withAction(Constants.ADMIN) + .build(); - ResourcePolicy firstResourcePolicyOfEPerson2 = ResourcePolicyBuilder.createResourcePolicy(context) - .withDspaceObject(community) - .withAction(Constants.ADD) - .withUser(eperson2).build(); + ResourcePolicy firstResourcePolicyOfEPerson2 = ResourcePolicyBuilder + .createResourcePolicy(context, eperson2, null) + .withDspaceObject(community) + .withAction(Constants.ADD) + .build(); - ResourcePolicy secondResourcePolicyOfEPerson2 = ResourcePolicyBuilder.createResourcePolicy(context) - .withDspaceObject(community2) - .withAction(Constants.ADD) - .withUser(eperson2).build(); + ResourcePolicy secondResourcePolicyOfEPerson2 = ResourcePolicyBuilder + .createResourcePolicy(context, eperson2, null) + .withDspaceObject(community2) + .withAction(Constants.ADD) + .build(); context.restoreAuthSystemState(); @@ -592,28 +591,32 @@ public void findResourcePoliciesOfOneResourcePaginationTest() throws Exception { Community community = CommunityBuilder.createCommunity(context).withName("My community").build(); - ResourcePolicy firstResourcePolicyOfEPerson1 = ResourcePolicyBuilder.createResourcePolicy(context) - .withDspaceObject(community) - .withAction(Constants.ADMIN) - .withUser(eperson1).build(); + ResourcePolicy firstResourcePolicyOfEPerson1 = ResourcePolicyBuilder + .createResourcePolicy(context, eperson1, null) + .withDspaceObject(community) + .withAction(Constants.ADMIN) + .build(); - ResourcePolicy firstResourcePolicyOfEPerson2 = ResourcePolicyBuilder.createResourcePolicy(context) - .withDspaceObject(community) - .withAction(Constants.ADD) - .withPolicyType(ResourcePolicy.TYPE_CUSTOM) - .withUser(eperson2).build(); + ResourcePolicy firstResourcePolicyOfEPerson2 = ResourcePolicyBuilder + .createResourcePolicy(context, eperson2, null) + .withDspaceObject(community) + .withAction(Constants.ADD) + .withPolicyType(ResourcePolicy.TYPE_CUSTOM) + .build(); - ResourcePolicy firstResourcePolicyOfEPerson3 = ResourcePolicyBuilder.createResourcePolicy(context) - .withDspaceObject(community) - .withAction(Constants.DELETE) - .withPolicyType(ResourcePolicy.TYPE_CUSTOM) - .withUser(eperson3).build(); + ResourcePolicy firstResourcePolicyOfEPerson3 = ResourcePolicyBuilder + .createResourcePolicy(context, eperson3, null) + .withDspaceObject(community) + .withAction(Constants.DELETE) + .withPolicyType(ResourcePolicy.TYPE_CUSTOM) + .build(); - ResourcePolicy firstResourcePolicyOfEPerson4 = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy firstResourcePolicyOfEPerson4 = ResourcePolicyBuilder + .createResourcePolicy(context, eperson4, null) .withDspaceObject(community) .withAction(Constants.WRITE) .withPolicyType(ResourcePolicy.TYPE_CUSTOM) - .withUser(eperson4).build(); + .build(); ResourcePolicy resourcePolicyAnonymous = authorizeService .findByTypeGroupAction(context, community, EPersonServiceFactory.getInstance() @@ -701,10 +704,9 @@ public void findResoucesPoliciesByResourceUuidUnAuthenticatedTest() throws Excep Community community = CommunityBuilder.createCommunity(context).withName("My community").build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, eperson1, null) .withDspaceObject(community) - .withAction(Constants.READ) - .withUser(eperson1).build(); + .withAction(Constants.READ).build(); context.restoreAuthSystemState(); @@ -742,17 +744,15 @@ public void findResourcesPoliciesByResourceUuidForbiddenTest() throws Exception Community community2 = CommunityBuilder.createCommunity(context).withName("My 2 community").build(); - ResourcePolicy resourcePolicyOfEPerson1 = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicyOfEPerson1 = ResourcePolicyBuilder.createResourcePolicy(context, eperson1, null) .withDspaceObject(community) .withAction(Constants.REMOVE) - .withPolicyType(ResourcePolicy.TYPE_CUSTOM) - .withUser(eperson1).build(); + .withPolicyType(ResourcePolicy.TYPE_CUSTOM).build(); - ResourcePolicy resourcePolicyOfEPerson2 = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicyOfEPerson2 = ResourcePolicyBuilder.createResourcePolicy(context, eperson2, null) .withDspaceObject(community2) .withAction(Constants.ADD) - .withPolicyType(ResourcePolicy.TYPE_CUSTOM) - .withUser(eperson2).build(); + .withPolicyType(ResourcePolicy.TYPE_CUSTOM).build(); context.restoreAuthSystemState(); @@ -791,25 +791,22 @@ public void findResourcePoliciesByGroupUuidTest() throws Exception { .withName("My collection") .build(); - ResourcePolicy firstResourcePolicyOfGroup1 = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy firstResourcePolicyOfGroup1 = ResourcePolicyBuilder.createResourcePolicy(context, null, group1) .withDspaceObject(community) - .withAction(Constants.ADD) - .withGroup(group1).build(); + .withAction(Constants.ADD).build(); - ResourcePolicy secondResourcePolicyOfGroup1 = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy secondResourcePolicyOfGroup1 = ResourcePolicyBuilder.createResourcePolicy(context, null, group1) .withDspaceObject(community) - .withAction(Constants.READ) - .withGroup(group1).build(); + .withAction(Constants.READ).build(); - ResourcePolicy collectionResourcePolicyOfGroup1 = ResourcePolicyBuilder.createResourcePolicy(context) - .withDspaceObject(collection) - .withAction(Constants.WRITE) - .withGroup(group1).build(); + ResourcePolicy collectionResourcePolicyOfGroup1 = ResourcePolicyBuilder + .createResourcePolicy(context, null, group1) + .withDspaceObject(collection) + .withAction(Constants.WRITE).build(); - ResourcePolicy firstResourcePolicyOfGroup2 = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy firstResourcePolicyOfGroup2 = ResourcePolicyBuilder.createResourcePolicy(context, null, group2) .withDspaceObject(community2) - .withAction(Constants.ADD) - .withGroup(group2).build(); + .withAction(Constants.ADD).build(); context.restoreAuthSystemState(); @@ -857,15 +854,13 @@ public void findResourcePoliciesByGroupUuidAndResourceUuidTest() throws Exceptio Community community = CommunityBuilder.createCommunity(context).withName("My community").build(); Community community2 = CommunityBuilder.createCommunity(context).withName("My second community").build(); - ResourcePolicy firstResourcePolicyOfGroup1 = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy firstResourcePolicyOfGroup1 = ResourcePolicyBuilder.createResourcePolicy(context, null, group1) .withDspaceObject(community) - .withAction(Constants.ADD) - .withGroup(group1).build(); + .withAction(Constants.ADD).build(); - ResourcePolicy secondResourcePolicyOfGroup1 = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy secondResourcePolicyOfGroup1 = ResourcePolicyBuilder.createResourcePolicy(context, null, group1) .withDspaceObject(community2) - .withAction(Constants.WRITE) - .withGroup(group1).build(); + .withAction(Constants.WRITE).build(); context.restoreAuthSystemState(); @@ -913,10 +908,9 @@ public void findResoucesPoliciesByGroupUuidUnAuthenticatedTest() throws Exceptio Community community = CommunityBuilder.createCommunity(context).withName("My community").build(); - ResourcePolicy firstResourcePolicyOfGroup1 = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy firstResourcePolicyOfGroup1 = ResourcePolicyBuilder.createResourcePolicy(context, null, group1) .withDspaceObject(community) - .withAction(Constants.ADD) - .withGroup(group1).build(); + .withAction(Constants.ADD).build(); context.restoreAuthSystemState(); @@ -953,10 +947,9 @@ public void findResourcesPoliciesByGroupUuidForbiddenTest() throws Exception { Community community = CommunityBuilder.createCommunity(context).withName("My community").build(); - ResourcePolicy resourcePolicyOfGroup1 = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicyOfGroup1 = ResourcePolicyBuilder.createResourcePolicy(context, null, group1) .withDspaceObject(community).withAction(Constants.WRITE) - .withPolicyType(ResourcePolicy.TYPE_CUSTOM) - .withGroup(group1).build(); + .withPolicyType(ResourcePolicy.TYPE_CUSTOM).build(); context.restoreAuthSystemState(); @@ -981,10 +974,10 @@ public void findResourcesPoliciesByGroupAnonymousTest() throws Exception { Community community = CommunityBuilder.createCommunity(context).withName("My community").build(); - ResourcePolicy resourcePolicyOfGroup1 = ResourcePolicyBuilder.createResourcePolicy(context) - .withDspaceObject(community).withAction(Constants.WRITE) - .withPolicyType(ResourcePolicy.TYPE_CUSTOM) - .withGroup(groupAnonymous).build(); + ResourcePolicy resourcePolicyOfGroup1 = ResourcePolicyBuilder + .createResourcePolicy(context, null, groupAnonymous) + .withDspaceObject(community).withAction(Constants.WRITE) + .withPolicyType(ResourcePolicy.TYPE_CUSTOM).build(); context.restoreAuthSystemState(); @@ -1143,9 +1136,8 @@ public void deleteOne() throws Exception { .withName("My community") .build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, eperson1, null) .withDspaceObject(community) - .withUser(eperson1) .withAction(Constants.ADMIN) .build(); @@ -1170,11 +1162,10 @@ public void deleteOneUnAuthenticatedTest() throws Exception { .withPassword("qwerty01") .build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, eperson1, null) .withDspaceObject(community) .withAction(Constants.DELETE) .withPolicyType(ResourcePolicy.TYPE_CUSTOM) - .withUser(eperson1) .build(); context.restoreAuthSystemState(); @@ -1200,10 +1191,9 @@ public void deleteOneForbiddenTest() throws Exception { Collection collection = CollectionBuilder.createCollection(context, community) .withName("My collection").build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, eperson1, null) .withDspaceObject(collection) - .withAction(Constants.ADD) - .withUser(eperson1).build(); + .withAction(Constants.ADD).build(); context.restoreAuthSystemState(); @@ -1253,10 +1243,10 @@ public void patchReplaceStartDateTest() throws Exception { Date data = calendar.getTime(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, null, + EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withAction(Constants.READ) .withDspaceObject(publicItem1) - .withGroup(EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withStartDate(data) .withPolicyType(ResourcePolicy.TYPE_CUSTOM) .build(); @@ -1323,10 +1313,10 @@ public void patchReplaceEndDateTest() throws Exception { Date date = calendar.getTime(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, null, + EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withAction(Constants.READ) .withDspaceObject(publicItem1) - .withGroup(EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withEndDate(date) .withPolicyType(ResourcePolicy.TYPE_CUSTOM) .build(); @@ -1385,10 +1375,10 @@ public void patchAddStartDateTest() throws Exception { .withTitle("Public item") .build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, null, + EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withAction(Constants.READ) .withDspaceObject(publicItem1) - .withGroup(EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withPolicyType(ResourcePolicy.TYPE_CUSTOM) .build(); @@ -1446,13 +1436,12 @@ public void patchAddEndDateTest() throws Exception { .withTitle("Public item") .build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context,null, + EPersonServiceFactory.getInstance().getGroupService() + .findByName(context, Group.ANONYMOUS) + ) .withAction(Constants.READ) .withDspaceObject(publicItem1) - .withGroup( - EPersonServiceFactory.getInstance().getGroupService() - .findByName(context, - Group.ANONYMOUS)) .withPolicyType(ResourcePolicy.TYPE_CUSTOM) .build(); @@ -1513,10 +1502,10 @@ public void patchRemoveStartDateTest() throws Exception { Date data = calendar.getTime(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, null, + EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withAction(Constants.READ) .withDspaceObject(publicItem1) - .withGroup(EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withStartDate(data) .withPolicyType(ResourcePolicy.TYPE_CUSTOM) .build(); @@ -1574,10 +1563,10 @@ public void patchReplaceStartDateBadRequestTest() throws Exception { Date date = calendar.getTime(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, null, + EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withAction(Constants.READ) .withDspaceObject(publicItem1) - .withGroup(EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withStartDate(date) .withDescription("my description") .withPolicyType(ResourcePolicy.TYPE_CUSTOM) @@ -1625,7 +1614,7 @@ public void patchReplaceStartDateUnAuthenticatedTest() throws Exception { Date date = calendar.getTime(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withAction(Constants.WRITE) .withDspaceObject(item) .withStartDate(date) @@ -1687,10 +1676,10 @@ public void patchReplaceStartDateForbiddenTest() throws Exception { Date date = calendar.getTime(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, null, + EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withAction(Constants.READ) .withDspaceObject(item) - .withGroup(EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withStartDate(date) .withPolicyType(ResourcePolicy.TYPE_CUSTOM) .build(); @@ -1793,10 +1782,9 @@ public void patchReplaceEndDateBeforeStartDateTest() throws Exception { Date endDate = calendarEndDate.getTime(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, eperson1, null) .withAction(Constants.READ) .withDspaceObject(publicItem1) - .withUser(eperson1) .withStartDate(startDate) .withEndDate(endDate) .withPolicyType(ResourcePolicy.TYPE_CUSTOM) @@ -1851,10 +1839,10 @@ public void patchReplaceDescriptionTest() throws Exception { .withTitle("Public item") .build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, null, + EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withAction(Constants.READ) .withDspaceObject(publicItem1) - .withGroup(EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withDescription("my description") .withPolicyType(ResourcePolicy.TYPE_CUSTOM) .build(); @@ -1903,10 +1891,10 @@ public void patchAddDescriptionTest() throws Exception { .withTitle("Public item") .build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, null, + EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withAction(Constants.READ) .withDspaceObject(item) - .withGroup(EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withPolicyType(ResourcePolicy.TYPE_CUSTOM) .build(); @@ -1954,11 +1942,11 @@ public void patchRemoveDescriptionTest() throws Exception { .withTitle("Public item") .build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, null, + EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withAction(Constants.READ) .withDspaceObject(item) .withDescription("my description") - .withGroup(EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withPolicyType(ResourcePolicy.TYPE_CUSTOM) .build(); @@ -1996,7 +1984,7 @@ public void patchReplaceDescriptionUnAuthenticatedTest() throws Exception { Item item = ItemBuilder.createItem(context, collection).build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withAction(Constants.WRITE) .withDspaceObject(item) .withDescription("My Description") @@ -2042,10 +2030,10 @@ public void patchReplaceDescriptionForbiddenTest() throws Exception { .withTitle("Public item") .build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, null, + EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withAction(Constants.READ) .withDspaceObject(item) - .withGroup(EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withDescription("My Description") .withPolicyType(ResourcePolicy.TYPE_CUSTOM) .build(); @@ -2098,10 +2086,10 @@ public void patchReplaceDescriptionBadRequestTest() throws Exception { .withTitle("Public item") .build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, null, + EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withAction(Constants.READ) .withDspaceObject(publicItem1) - .withGroup(EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withDescription("my description") .withPolicyType(ResourcePolicy.TYPE_CUSTOM) .build(); @@ -2146,10 +2134,10 @@ public void patchReplaceNameTest() throws Exception { .withTitle("Public item") .build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, null, + EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withAction(Constants.READ) .withDspaceObject(myItem) - .withGroup(EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withName("My name") .withPolicyType(ResourcePolicy.TYPE_CUSTOM) .build(); @@ -2197,10 +2185,10 @@ public void patchReplaceNameBadRequestTest() throws Exception { .withTitle("Public item") .build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, null, + EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withAction(Constants.READ) .withDspaceObject(myItem) - .withGroup(EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withName("My name") .withPolicyType(ResourcePolicy.TYPE_CUSTOM) .build(); @@ -2245,10 +2233,10 @@ public void patchAddNameTest() throws Exception { .withTitle("Public item") .build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, null, + EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withAction(Constants.READ) .withDspaceObject(myItem) - .withGroup(EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withPolicyType(ResourcePolicy.TYPE_CUSTOM) .build(); @@ -2290,10 +2278,9 @@ public void patchAddActionTest() throws Exception { .withTitle("Public item") .build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withAction(Constants.READ) .withDspaceObject(myItem) - .withUser(eperson) .withName("My Name") .build(); @@ -2334,10 +2321,9 @@ public void patchReplaceActionTest() throws Exception { .withTitle("Public item") .build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withAction(Constants.READ) .withDspaceObject(myItem) - .withUser(eperson) .withName("My name") .withPolicyType(ResourcePolicy.TYPE_SUBMISSION) .build(); @@ -2374,7 +2360,7 @@ public void patchReplaceActionUnauthenticatedTest() throws Exception { Item item = ItemBuilder.createItem(context, collection).build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withAction(Constants.WRITE) .withDspaceObject(item) .withPolicyType(ResourcePolicy.TYPE_CUSTOM) @@ -2413,10 +2399,9 @@ public void patchReplaceActionForbiddenTest() throws Exception { .withTitle("Public item") .build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withAction(Constants.READ) .withDspaceObject(item) - .withUser(eperson) .withPolicyType(ResourcePolicy.TYPE_CUSTOM) .build(); @@ -2464,10 +2449,9 @@ public void patchReplaceActionUnprocessableEntityTest() throws Exception { .withTitle("Public item") .build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withAction(Constants.READ) .withDspaceObject(publicItem1) - .withUser(eperson) .withPolicyType(ResourcePolicy.TYPE_CUSTOM) .build(); @@ -2500,10 +2484,9 @@ public void patchAddPolicyTypeTest() throws Exception { .withTitle("Public item") .build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withAction(Constants.READ) .withDspaceObject(myItem) - .withUser(eperson) .withName("My Name") .build(); @@ -2545,10 +2528,9 @@ public void patchRemovePolicyTypeTest() throws Exception { .withTitle("Public item") .build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withAction(Constants.READ) .withDspaceObject(myItem) - .withUser(eperson) .withName("My Name") .withPolicyType(ResourcePolicy.TYPE_CUSTOM) .build(); @@ -2590,10 +2572,9 @@ public void patchReplacePolicyTypeTest() throws Exception { .withTitle("Public item") .build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withAction(Constants.READ) .withDspaceObject(myItem) - .withUser(eperson) .withName("My name") .withPolicyType(ResourcePolicy.TYPE_SUBMISSION) .build(); @@ -2632,7 +2613,7 @@ public void patchReplacePolicyTypeUnauthenticatedTest() throws Exception { Item item = ItemBuilder.createItem(context, collection).build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withAction(Constants.WRITE) .withDspaceObject(item) .withPolicyType(ResourcePolicy.TYPE_CUSTOM) @@ -2672,10 +2653,9 @@ public void patchReplacePolicyTypeForbiddenTest() throws Exception { .withTitle("Public item") .build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withAction(Constants.READ) .withDspaceObject(item) - .withUser(eperson) .withPolicyType(ResourcePolicy.TYPE_CUSTOM) .build(); @@ -2722,10 +2702,9 @@ public void patchReplacePolicyTypeBadRequestTest() throws Exception { .withTitle("Public item") .build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withAction(Constants.READ) .withDspaceObject(publicItem1) - .withUser(eperson) .withPolicyType(ResourcePolicy.TYPE_CUSTOM) .build(); @@ -2763,11 +2742,11 @@ public void patchAddNameBadRequestTest() throws Exception { .withTitle("Public item") .build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, null, + EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withAction(Constants.READ) .withDspaceObject(myItem) .withName("My name") - .withGroup(EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withPolicyType(ResourcePolicy.TYPE_CUSTOM) .build(); @@ -2811,10 +2790,10 @@ public void patchRemoveNameTest() throws Exception { .withTitle("Public item") .build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, null, + EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withAction(Constants.READ) .withDspaceObject(myItem) - .withGroup(EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withName("My Name") .withPolicyType(ResourcePolicy.TYPE_CUSTOM) .build(); @@ -2860,10 +2839,10 @@ public void patchRemoveNameForbiddenTest() throws Exception { .withTitle("Public item") .build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, null, + EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withAction(Constants.READ) .withDspaceObject(myItem) - .withGroup(EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withName("My Name") .withPolicyType(ResourcePolicy.TYPE_CUSTOM) .build(); @@ -2923,12 +2902,12 @@ public void patchSuccessfulMultipleOperationsTest() throws Exception { Date endDate = calendarEndDate.getTime(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, null, + EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withAction(Constants.READ) .withDspaceObject(publicItem1) .withStartDate(startDate) .withEndDate(endDate) - .withGroup(EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withPolicyType(ResourcePolicy.TYPE_CUSTOM) .build(); @@ -3013,12 +2992,12 @@ public void patchWithMultipleOperationsFailTest() throws Exception { Date endDate = calendarEndDate.getTime(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, null, + EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withAction(Constants.READ) .withDspaceObject(publicItem1) .withName("My Name") .withEndDate(endDate) - .withGroup(EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS)) .withPolicyType(ResourcePolicy.TYPE_CUSTOM) .build(); @@ -3092,25 +3071,21 @@ public void findResourcePoliciesByGroupUuidPaginationTest() throws Exception { .withName("My collection") .build(); - ResourcePolicy rpCommunityADD = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy rpCommunityADD = ResourcePolicyBuilder.createResourcePolicy(context, null, group1) .withDspaceObject(community) - .withAction(Constants.ADD) - .withGroup(group1).build(); + .withAction(Constants.ADD).build(); - ResourcePolicy rpCommunityREAD = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy rpCommunityREAD = ResourcePolicyBuilder.createResourcePolicy(context, null, group1) .withDspaceObject(community) - .withAction(Constants.READ) - .withGroup(group1).build(); + .withAction(Constants.READ).build(); - ResourcePolicy rpCommunity2READ = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy rpCommunity2READ = ResourcePolicyBuilder.createResourcePolicy(context, null, group1) .withDspaceObject(community2) - .withAction(Constants.READ) - .withGroup(group1).build(); + .withAction(Constants.READ).build(); - ResourcePolicy rpCollectionWRITE = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy rpCollectionWRITE = ResourcePolicyBuilder.createResourcePolicy(context, null, group1) .withDspaceObject(collection) - .withAction(Constants.WRITE) - .withGroup(group1).build(); + .withAction(Constants.WRITE).build(); context.restoreAuthSystemState(); @@ -3213,10 +3188,9 @@ public void patchReplaceEPersonAdminTest() throws Exception { .withName("Collection 1") .build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withAction(Constants.ADD) .withDspaceObject(col) - .withUser(eperson) .withDescription("My Description") .withPolicyType(ResourcePolicy.TYPE_CUSTOM) .build(); @@ -3260,10 +3234,9 @@ public void patchReplaceEPersonForbiddenTest() throws Exception { .withName("Collection 1") .build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withAction(Constants.ADD) .withDspaceObject(col) - .withUser(eperson) .withDescription("My Description") .withPolicyType(ResourcePolicy.TYPE_CUSTOM) .build(); @@ -3308,10 +3281,9 @@ public void patchReplaceEPersonUnauthorizedTest() throws Exception { .withName("Collection 1") .build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withAction(Constants.ADD) .withDspaceObject(col) - .withUser(eperson) .withDescription("My Description") .withPolicyType(ResourcePolicy.TYPE_CUSTOM) .build(); @@ -3358,10 +3330,9 @@ public void patchReplaceGroupAdminTest() throws Exception { .withName("Collection 1") .build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, null, originGroup) .withAction(Constants.ADD) .withDspaceObject(col) - .withGroup(originGroup) .withDescription("My Description") .withPolicyType(ResourcePolicy.TYPE_CUSTOM) .build(); @@ -3408,10 +3379,9 @@ public void patchReplaceGroupForbiddenTest() throws Exception { .withName("Collection 1") .build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, null, originGroup) .withAction(Constants.ADD) .withDspaceObject(col) - .withGroup(originGroup) .withDescription("My Description") .withPolicyType(ResourcePolicy.TYPE_CUSTOM) .build(); @@ -3459,10 +3429,9 @@ public void patchReplaceGroupUnauthorizedTest() throws Exception { .withName("Collection 1") .build(); - ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context, null, originGroup) .withAction(Constants.ADD) .withDspaceObject(col) - .withGroup(originGroup) .withDescription("My Description") .withPolicyType(ResourcePolicy.TYPE_CUSTOM) .build(); @@ -3501,10 +3470,9 @@ public void updateResourcePolicyOfEPersonToGroupTest() throws Exception { .withName("My community") .build(); - ResourcePolicy resourcePolicyOfEPerson = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicyOfEPerson = ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withDspaceObject(community) .withAction(Constants.READ) - .withUser(eperson) .build(); context.restoreAuthSystemState(); @@ -3534,10 +3502,9 @@ public void updateResourcePolicyOfGroupToEPersonTest() throws Exception { .withName("My community") .build(); - ResourcePolicy resourcePolicyOfGroup = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicyOfGroup = ResourcePolicyBuilder.createResourcePolicy(context, null, group) .withDspaceObject(community) - .withAction(Constants.ADD) - .withGroup(group).build(); + .withAction(Constants.ADD).build(); context.restoreAuthSystemState(); @@ -3597,10 +3564,9 @@ public void updateResourcePolicyOfGroupWithEmptyTest() throws Exception { .build(); - ResourcePolicy resourcePolicyOfGroup = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicyOfGroup = ResourcePolicyBuilder.createResourcePolicy(context, null, group) .withDspaceObject(community) - .withAction(Constants.ADD) - .withGroup(group).build(); + .withAction(Constants.ADD).build(); context.restoreAuthSystemState(); String tokenAdmin = getAuthToken(admin.getEmail(), password); @@ -3622,10 +3588,9 @@ public void updateResourcePolicyOfGroupWithMultipleGroupsTest() throws Exception .withName("My community") .build(); - ResourcePolicy resourcePolicyOfGroup = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy resourcePolicyOfGroup = ResourcePolicyBuilder.createResourcePolicy(context, null, group1) .withDspaceObject(community) - .withAction(Constants.ADD) - .withGroup(group1).build(); + .withAction(Constants.ADD).build(); context.restoreAuthSystemState(); String tokenAdmin = getAuthToken(admin.getEmail(), password); @@ -3642,10 +3607,9 @@ public void updateResourcePolicyOfEPersonWithEmptyTest() throws Exception { Community community = CommunityBuilder.createCommunity(context).withName("My community").build(); - ResourcePolicy rpOfEPerson = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy rpOfEPerson = ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withDspaceObject(community) .withAction(Constants.READ) - .withUser(eperson) .build(); context.restoreAuthSystemState(); @@ -3671,10 +3635,9 @@ public void updateResourcePolicyOfEPersonWithMultipleEPersonsTest() throws Excep Community community = CommunityBuilder.createCommunity(context).withName("My community").build(); - ResourcePolicy rpOfEPerson = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy rpOfEPerson = ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withDspaceObject(community) .withAction(Constants.READ) - .withUser(eperson) .build(); context.restoreAuthSystemState(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/StatisticsRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/StatisticsRestRepositoryIT.java index 5544ecdb032..613075fe8f3 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/StatisticsRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/StatisticsRestRepositoryIT.java @@ -205,10 +205,9 @@ public void usagereport_loggedInUserReadRights() throws Exception { // ** WHEN ** context.turnOffAuthorisationSystem(); authorizeService.removeAllPolicies(context, itemNotVisitedWithBitstreams); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withDspaceObject(itemNotVisitedWithBitstreams) - .withAction(Constants.READ) - .withUser(eperson).build(); + .withAction(Constants.READ).build(); EPerson eperson1 = EPersonBuilder.createEPerson(context) .withEmail("eperson1@mail.com") @@ -239,10 +238,9 @@ public void usagereport_loggedInUserReadRights_and_usage_statistics_admin_is_fal configurationService.setProperty("usage-statistics.authorization.admin.usage", false); context.turnOffAuthorisationSystem(); authorizeService.removeAllPolicies(context, itemNotVisitedWithBitstreams); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withDspaceObject(itemNotVisitedWithBitstreams) - .withAction(Constants.READ) - .withUser(eperson).build(); + .withAction(Constants.READ).build(); EPerson eperson1 = EPersonBuilder.createEPerson(context) .withEmail("eperson1@mail.com") @@ -1150,10 +1148,9 @@ public void usagereportSearch_loggedInUserReadRights() throws Exception { // ** WHEN ** context.turnOffAuthorisationSystem(); authorizeService.removeAllPolicies(context, itemNotVisitedWithBitstreams); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withDspaceObject(itemNotVisitedWithBitstreams) - .withAction(Constants.READ) - .withUser(eperson).build(); + .withAction(Constants.READ).build(); EPerson eperson1 = EPersonBuilder.createEPerson(context) .withEmail("eperson1@mail.com") diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java index 8b2f3f093a3..9c69e4715d7 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java @@ -121,6 +121,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration private Group embargoedGroup1; private Group embargoedGroup2; private Group anonymousGroup; + private Group adminGroup; @Before @Override @@ -144,6 +145,7 @@ public void setUp() throws Exception { .build(); anonymousGroup = EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ANONYMOUS); + adminGroup = EPersonServiceFactory.getInstance().getGroupService().findByName(context, Group.ADMIN); context.restoreAuthSystemState(); } @@ -6717,13 +6719,10 @@ public void patchAccesConditionDiscoverableTest() throws Exception { .build(); witem.getItem().setDiscoverable(false); - ResourcePolicyBuilder.createResourcePolicy(context) - .withDspaceObject(witem.getItem()) - .withPolicyType(TYPE_CUSTOM) - .withName("administrator") +ResourcePolicyBuilder.createResourcePolicy(context, null, adminGroup) .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, null, anonymousGroup) .withDspaceObject(witem.getItem()) .withPolicyType(TYPE_CUSTOM) .withName("openaccess") @@ -6777,7 +6776,7 @@ public void patchAccesConditionDiscoverableWrongValueTest() throws Exception { .withSubject("ExtraEntry") .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, null, anonymousGroup) .withDspaceObject(witem.getItem()) .withPolicyType(TYPE_CUSTOM) .withName("openaccess") @@ -6824,7 +6823,7 @@ public void patcDiscoverableWithAccesConditionConfigurationDiscoverableDisabledT .withSubject("ExtraEntry") .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, null, anonymousGroup) .withDspaceObject(witem.getItem()) .withPolicyType(TYPE_CUSTOM) .withName("openaccess") @@ -6877,7 +6876,7 @@ public void patchAddAccesConditionTest() throws Exception { .withSubject("ExtraEntry") .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, null, anonymousGroup) .withDspaceObject(witem.getItem()) .withPolicyType(TYPE_CUSTOM) .withName("openaccess") @@ -6935,7 +6934,7 @@ public void patchAddNotSupportedAccesConditionTest() throws Exception { .withSubject("ExtraEntry") .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, null, anonymousGroup) .withDspaceObject(witem.getItem()) .withPolicyType(TYPE_CUSTOM) .withName("openaccess") @@ -7050,13 +7049,13 @@ public void patchRemoveAllAccesConditionsTest() throws Exception { .withSubject("ExtraEntry") .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, null, anonymousGroup) .withDspaceObject(witem.getItem()) .withPolicyType(TYPE_CUSTOM) .withName("openaccess") .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, null, adminGroup) .withDspaceObject(witem.getItem()) .withPolicyType(TYPE_CUSTOM) .withName("administrator") @@ -7110,16 +7109,13 @@ public void patchRemoveSpecificAccesConditionsTest() throws Exception { .withSubject("ExtraEntry") .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, null, anonymousGroup) .withDspaceObject(witem.getItem()) .withPolicyType(TYPE_CUSTOM) .withName("openaccess") .build(); - ResourcePolicyBuilder.createResourcePolicy(context) - .withDspaceObject(witem.getItem()) - .withPolicyType(TYPE_CUSTOM) - .withName("administrator") +ResourcePolicyBuilder.createResourcePolicy(context, null, adminGroup) .build(); Calendar calendar = Calendar.getInstance(); @@ -7130,7 +7126,7 @@ public void patchRemoveSpecificAccesConditionsTest() throws Exception { Date data = calendar.getTime(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, null, embargoedGroup1) .withDspaceObject(witem.getItem()) .withPolicyType(TYPE_CUSTOM) .withName("embargoed") @@ -7192,16 +7188,13 @@ public void patchRemoveFirstAccesConditionsTest() throws Exception { .withSubject("ExtraEntry") .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, null, anonymousGroup) .withDspaceObject(witem.getItem()) .withPolicyType(TYPE_CUSTOM) .withName("openaccess") .build(); - ResourcePolicyBuilder.createResourcePolicy(context) - .withDspaceObject(witem.getItem()) - .withPolicyType(TYPE_CUSTOM) - .withName("administrator") +ResourcePolicyBuilder.createResourcePolicy(context, null, adminGroup) .build(); context.restoreAuthSystemState(); @@ -7255,16 +7248,13 @@ public void patchRemoveAccesConditionsIdxNotSupportedTest() throws Exception { .withSubject("ExtraEntry") .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, null, anonymousGroup) .withDspaceObject(witem.getItem()) .withPolicyType(TYPE_CUSTOM) .withName("openaccess") .build(); - ResourcePolicyBuilder.createResourcePolicy(context) - .withDspaceObject(witem.getItem()) - .withPolicyType(TYPE_CUSTOM) - .withName("administrator") +ResourcePolicyBuilder.createResourcePolicy(context, null, adminGroup) .build(); context.restoreAuthSystemState(); @@ -7320,16 +7310,13 @@ public void patchRemoveAccesConditionsUnprocessableEntityTest() throws Exception .withSubject("ExtraEntry") .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, null, anonymousGroup) .withDspaceObject(witem.getItem()) .withPolicyType(TYPE_CUSTOM) .withName("openaccess") .build(); - ResourcePolicyBuilder.createResourcePolicy(context) - .withDspaceObject(witem.getItem()) - .withPolicyType(TYPE_CUSTOM) - .withName("administrator") +ResourcePolicyBuilder.createResourcePolicy(context, null, adminGroup) .build(); context.restoreAuthSystemState(); @@ -7385,16 +7372,13 @@ public void patchReplaceAccesConditionTest() throws Exception { .withSubject("ExtraEntry") .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, null, anonymousGroup) .withDspaceObject(witem.getItem()) .withPolicyType(TYPE_CUSTOM) .withName("openaccess") .build(); - ResourcePolicyBuilder.createResourcePolicy(context) - .withDspaceObject(witem.getItem()) - .withPolicyType(TYPE_CUSTOM) - .withName("administrator") +ResourcePolicyBuilder.createResourcePolicy(context, null, adminGroup) .build(); context.restoreAuthSystemState(); @@ -7455,7 +7439,7 @@ public void patchReplaceAccesConditionsUpdateEmbargoStartDateTest() throws Excep .withSubject("ExtraEntry") .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, null, anonymousGroup) .withDspaceObject(witem.getItem()) .withPolicyType(TYPE_CUSTOM) .withName("openaccess") @@ -7469,7 +7453,7 @@ public void patchReplaceAccesConditionsUpdateEmbargoStartDateTest() throws Excep Date data = calendar.getTime(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, null, embargoedGroup1) .withDspaceObject(witem.getItem()) .withPolicyType(TYPE_CUSTOM) .withName("embargo") @@ -7535,7 +7519,7 @@ public void patchReplaceAccesConditionsFromOpenaccessToAdministratorTest() throw .withSubject("ExtraEntry") .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, null, anonymousGroup) .withDspaceObject(witem.getItem()) .withPolicyType(TYPE_CUSTOM) .withName("openaccess") diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CCLicenseFeatureRestIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CCLicenseFeatureRestIT.java index 38fc9a06fd8..be8851cb5c0 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CCLicenseFeatureRestIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CCLicenseFeatureRestIT.java @@ -202,8 +202,10 @@ public void checkAuthorizationAsItemAdminTest() throws Exception { Community com = CommunityBuilder.createCommunity(context).withName("A community").build(); Collection col = CollectionBuilder.createCollection(context, com).withName("A collection").build(); Item item = ItemBuilder.createItem(context, col).withTitle("Item to withdraw").build(); - ResourcePolicy resource = ResourcePolicyBuilder.createResourcePolicy(context).withAction(Constants.ADMIN) - .withUser(eperson).withDspaceObject(item).build(); + ResourcePolicy resource = ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) + .withAction(Constants.ADMIN) + .withDspaceObject(item) + .build(); context.restoreAuthSystemState(); ItemRest itemRest = itemConverter.convert(item, Projection.DEFAULT); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanManageBitstreamBundlesFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanManageBitstreamBundlesFeatureIT.java index 6e29c5b9494..8d900bfd7c0 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanManageBitstreamBundlesFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanManageBitstreamBundlesFeatureIT.java @@ -216,9 +216,8 @@ public void checkCanCreateVersionsFeatureTest() throws Exception { @SuppressWarnings("unchecked") public void itemAdminSetPropertyCreateBitstreamToFalseTest() throws Exception { context.turnOffAuthorisationSystem(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, userA, null) .withAction(Constants.ADMIN) - .withUser(userA) .withDspaceObject(itemA).build(); configurationService.setProperty("core.authorization.item-admin.create-bitstream", false); @@ -260,9 +259,8 @@ public void itemAdminSetPropertyCreateBitstreamToFalseTest() throws Exception { @SuppressWarnings("unchecked") public void itemAdminSetPropertyDeleteBitstreamToFalseTest() throws Exception { context.turnOffAuthorisationSystem(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, userA, null) .withAction(Constants.ADMIN) - .withUser(userA) .withDspaceObject(itemA).build(); configurationService.setProperty("core.authorization.item-admin.delete-bitstream", false); @@ -304,9 +302,8 @@ public void itemAdminSetPropertyDeleteBitstreamToFalseTest() throws Exception { @SuppressWarnings("unchecked") public void itemAdminSetPropertyCollectionAdminCreateBitstreamToFalseTest() throws Exception { context.turnOffAuthorisationSystem(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, userA, null) .withAction(Constants.ADMIN) - .withUser(userA) .withDspaceObject(itemA).build(); configurationService.setProperty("core.authorization.collection-admin.item.create-bitstream", false); @@ -365,9 +362,8 @@ public void itemAdminSetPropertyCollectionAdminCreateBitstreamToFalseTest() thro @SuppressWarnings("unchecked") public void itemAdminSetPropertyCollectionAdminDeleteBitstreamToFalseTest() throws Exception { context.turnOffAuthorisationSystem(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, userA, null) .withAction(Constants.ADMIN) - .withUser(userA) .withDspaceObject(itemA).build(); configurationService.setProperty("core.authorization.collection-admin.item.delete-bitstream", false); @@ -426,9 +422,8 @@ public void itemAdminSetPropertyCollectionAdminDeleteBitstreamToFalseTest() thro @SuppressWarnings("unchecked") public void itemAdminSetPropertyCommunityAdminCreateBitstreamToFalseTest() throws Exception { context.turnOffAuthorisationSystem(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, userA, null) .withAction(Constants.ADMIN) - .withUser(userA) .withDspaceObject(itemA).build(); configurationService.setProperty("core.authorization.community-admin.item.create-bitstream", false); @@ -487,9 +482,8 @@ public void itemAdminSetPropertyCommunityAdminCreateBitstreamToFalseTest() throw @SuppressWarnings("unchecked") public void itemAdminSetPropertyCommunityAdminDeleteBitstreamToFalseTest() throws Exception { context.turnOffAuthorisationSystem(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, userA, null) .withAction(Constants.ADMIN) - .withUser(userA) .withDspaceObject(itemA).build(); configurationService.setProperty("core.authorization.community-admin.item.delete-bitstream", false); @@ -544,4 +538,4 @@ public void itemAdminSetPropertyCommunityAdminDeleteBitstreamToFalseTest() throw } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanManageMappingsFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanManageMappingsFeatureIT.java index e85ca857b91..498983ef65b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanManageMappingsFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanManageMappingsFeatureIT.java @@ -151,15 +151,13 @@ public void epersonCollectionNotFound() throws Exception { @Test public void addWriteEpersonCollectionSuccess() throws Exception { - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withDspaceObject(collectionA) .withAction(Constants.ADD) - .withUser(eperson) .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withDspaceObject(collectionA) .withAction(Constants.WRITE) - .withUser(eperson) .build(); String epersonToken = getAuthToken(eperson.getEmail(), password); @@ -175,10 +173,9 @@ public void addWriteEpersonCollectionSuccess() throws Exception { @Test public void adminEpersonCollectionSuccess() throws Exception { - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withDspaceObject(collectionA) .withAction(Constants.ADMIN) - .withUser(eperson) .build(); String epersonToken = getAuthToken(eperson.getEmail(), password); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanSubscribeFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanSubscribeFeatureIT.java index 7eb0960566f..224ec0472cd 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanSubscribeFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanSubscribeFeatureIT.java @@ -287,10 +287,9 @@ public void canNotSubscribeCommunityTest() throws Exception { private void setPermissions(DSpaceObject dSpaceObject, Group group, Integer permissions) { try { - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, null, group) .withDspaceObject(dSpaceObject) .withAction(permissions) - .withGroup(group) .build(); } catch (SQLException | AuthorizeException sqlException) { log.error(sqlException.getMessage()); @@ -307,4 +306,4 @@ private void cleanUpPermissions(List resourcePolicies) { } } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EditItemFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EditItemFeatureIT.java index 4bdc7743b57..ec9020a761b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EditItemFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EditItemFeatureIT.java @@ -96,8 +96,7 @@ public void testNoRights() throws Exception { @Test public void testDirectEPersonWritePolicy() throws Exception { - ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) - .withUser(eperson) + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withDspaceObject(itemA1X) .withAction(Constants.WRITE) .build(); @@ -108,8 +107,7 @@ public void testDirectEPersonWritePolicy() throws Exception { @Test public void testDirectGroupWritePolicy() throws Exception { - ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) - .withGroup(group) + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context, null, group) .withDspaceObject(itemA1X) .withAction(Constants.WRITE) .build(); @@ -120,8 +118,7 @@ public void testDirectGroupWritePolicy() throws Exception { @Test public void testDirectEPersonAdminPolicy() throws Exception { - ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) - .withUser(eperson) + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withDspaceObject(itemA1X) .withAction(Constants.ADMIN) .build(); @@ -132,8 +129,7 @@ public void testDirectEPersonAdminPolicy() throws Exception { @Test public void testDirectGroupAdminPolicy() throws Exception { - ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) - .withGroup(group) + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context, null, group) .withDspaceObject(itemA1X) .withAction(Constants.ADMIN) .build(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/GenericAuthorizationFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/GenericAuthorizationFeatureIT.java index 1d3b5b05160..8ddd24348c8 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/GenericAuthorizationFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/GenericAuthorizationFeatureIT.java @@ -148,25 +148,21 @@ public void setUp() throws Exception { .withName("item1AdminGroup") .addMember(item1Admin) .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, null, item1AdminGroup) .withDspaceObject(item1) .withAction(Constants.ADMIN) - .withGroup(item1AdminGroup) .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, communityAWriter, null) .withDspaceObject(communityA) .withAction(Constants.WRITE) - .withUser(communityAWriter) .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, collectionXWriter, null) .withDspaceObject(collectionX) .withAction(Constants.WRITE) - .withUser(collectionXWriter) .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, item1Writer, null) .withDspaceObject(item1) .withAction(Constants.WRITE) - .withUser(item1Writer) .build(); communityB = CommunityBuilder.createCommunity(context) @@ -798,10 +794,9 @@ public void testCanMoveAdmin() throws Exception { // grant item 1 admin REMOVE permissions on the item’s owning collection // verify item 1 admin has this feature on item 1 context.turnOffAuthorisationSystem(); - ResourcePolicy removePermission = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy removePermission = ResourcePolicyBuilder.createResourcePolicy(context, item1Writer, null) .withDspaceObject(collectionX) .withAction(Constants.REMOVE) - .withUser(item1Writer) .build(); context.restoreAuthSystemState(); @@ -820,10 +815,9 @@ public void testCanMoveWriter() throws Exception { // grant item 1 write REMOVE permissions on the item’s owning collection context.turnOffAuthorisationSystem(); - ResourcePolicy removePermission = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy removePermission = ResourcePolicyBuilder.createResourcePolicy(context, item1Writer, null) .withDspaceObject(collectionX) .withAction(Constants.REMOVE) - .withUser(item1Writer) .build(); context.restoreAuthSystemState(); @@ -901,10 +895,9 @@ public void testCanDeleteAdmin() throws Exception { .withEmail("communityAAAdmin@my.edu") .withPassword(password) .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, communityAAAdmin, null) .withDspaceObject(communityAA) .withAction(Constants.ADMIN) - .withUser(communityAAAdmin) .build(); context.restoreAuthSystemState(); String communityAAAdminToken = getAuthToken(communityAAAdmin.getEmail(), password); @@ -1082,10 +1075,9 @@ public void testCanDeleteAdminParent() throws Exception { .withEmail("communityAAAdmin@my.edu") .withPassword(password) .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, communityAAAdmin, null) .withDspaceObject(communityA) .withAction(Constants.REMOVE) - .withUser(communityAAAdmin) .build(); context.restoreAuthSystemState(); String communityAAAdminToken = getAuthToken(communityAAAdmin.getEmail(), password); @@ -1098,10 +1090,9 @@ public void testCanDeleteAdminParent() throws Exception { // Grant REMOVE permissions on community AA for collection X admin context.turnOffAuthorisationSystem(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, collectionXAdmin, null) .withDspaceObject(communityAA) .withAction(Constants.REMOVE) - .withUser(collectionXAdmin) .build(); context.restoreAuthSystemState(); // verify collection X admin has this feature on collection X @@ -1113,10 +1104,9 @@ public void testCanDeleteAdminParent() throws Exception { // Grant REMOVE permissions on collection X for item 1 admin context.turnOffAuthorisationSystem(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, item1Admin, null) .withDspaceObject(collectionX) .withAction(Constants.REMOVE) - .withUser(item1Admin) .build(); context.restoreAuthSystemState(); // verify item 1 admin has this feature on item 1 @@ -1143,10 +1133,9 @@ public void testCanDeleteMinimalPermissions() throws Exception { .withEmail("communityADeleter@my.edu") .withPassword(password) .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, communityADeleter, null) .withDspaceObject(communityA) .withAction(Constants.DELETE) - .withUser(communityADeleter) .build(); context.restoreAuthSystemState(); String communityADeleterToken = getAuthToken(communityADeleter.getEmail(), password); @@ -1171,10 +1160,9 @@ public void testCanDeleteMinimalPermissions() throws Exception { .withEmail("communityARemover@my.edu") .withPassword(password) .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, communityARemover, null) .withDspaceObject(communityA) .withAction(Constants.REMOVE) - .withUser(communityARemover) .build(); context.restoreAuthSystemState(); String communityARemoverToken = getAuthToken(communityARemover.getEmail(), password); @@ -1204,10 +1192,9 @@ public void testCanDeleteMinimalPermissions() throws Exception { .withEmail("communityAARemover@my.edu") .withPassword(password) .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, communityAARemover, null) .withDspaceObject(communityAA) .withAction(Constants.REMOVE) - .withUser(communityAARemover) .build(); context.restoreAuthSystemState(); String communityAARemoverToken = getAuthToken(communityAARemover.getEmail(), password); @@ -1237,10 +1224,9 @@ public void testCanDeleteMinimalPermissions() throws Exception { .withEmail("communityXRemover@my.edu") .withPassword(password) .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, collectionXRemover, null) .withDspaceObject(collectionX) .withAction(Constants.REMOVE) - .withUser(collectionXRemover) .build(); context.restoreAuthSystemState(); String collectionXRemoverToken = getAuthToken(collectionXRemover.getEmail(), password); @@ -1258,10 +1244,9 @@ public void testCanDeleteMinimalPermissions() throws Exception { .withEmail("item1Deleter@my.edu") .withPassword(password) .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, item1Deleter, null) .withDspaceObject(item1) .withAction(Constants.DELETE) - .withUser(item1Deleter) .build(); context.restoreAuthSystemState(); String item1DeleterToken = getAuthToken(item1Deleter.getEmail(), password); @@ -1279,15 +1264,13 @@ public void testCanDeleteMinimalPermissions() throws Exception { .withEmail("collectionXDeleter@my.edu") .withPassword(password) .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, collectionXRemoverItem1Deleter, null) .withDspaceObject(collectionX) .withAction(Constants.REMOVE) - .withUser(collectionXRemoverItem1Deleter) .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, collectionXRemoverItem1Deleter, null) .withDspaceObject(item1) .withAction(Constants.DELETE) - .withUser(collectionXRemoverItem1Deleter) .build(); context.restoreAuthSystemState(); String collectionXRemoverItem1DeleterToken = getAuthToken(collectionXRemoverItem1Deleter.getEmail(), password); @@ -1320,10 +1303,9 @@ public void testCanDeleteMinimalPermissions() throws Exception { .withEmail("item1Remover@my.edu") .withPassword(password) .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, item1Remover, null) .withDspaceObject(item1) .withAction(Constants.REMOVE) - .withUser(item1Remover) .build(); context.restoreAuthSystemState(); String item1RemoverToken = getAuthToken(item1Remover.getEmail(), password); @@ -1353,10 +1335,9 @@ public void testCanDeleteMinimalPermissions() throws Exception { .withEmail("bundle1Remover@my.edu") .withPassword(password) .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, bundle1Remover, null) .withDspaceObject(bundle1) .withAction(Constants.REMOVE) - .withUser(bundle1Remover) .build(); context.restoreAuthSystemState(); String bundle1RemoverToken = getAuthToken(bundle1Remover.getEmail(), password); @@ -1375,15 +1356,13 @@ public void testCanDeleteMinimalPermissions() throws Exception { .withEmail("bundle1item1Remover@my.edu") .withPassword(password) .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, bundle1item1Remover, null) .withDspaceObject(bundle1) .withAction(Constants.REMOVE) - .withUser(bundle1item1Remover) .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, bundle1item1Remover, null) .withDspaceObject(item1) .withAction(Constants.REMOVE) - .withUser(bundle1item1Remover) .build(); context.restoreAuthSystemState(); String bundle1item1RemoverToken = getAuthToken(bundle1item1Remover.getEmail(), password); @@ -1553,10 +1532,9 @@ public void testCanCreateBitstreamWriter() throws Exception { .withEmail("bundle1Writer@my.edu") .withPassword(password) .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, bundle1Writer, null) .withDspaceObject(bundle1) .withAction(Constants.WRITE) - .withUser(bundle1Writer) .build(); context.restoreAuthSystemState(); String bundle1WriterToken = getAuthToken(bundle1Writer.getEmail(), password); @@ -1574,10 +1552,9 @@ public void testCanCreateBitstreamWriter() throws Exception { .withEmail("bundle1Adder@my.edu") .withPassword(password) .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, bundle1Adder, null) .withDspaceObject(bundle1) .withAction(Constants.ADD) - .withUser(bundle1Adder) .build(); context.restoreAuthSystemState(); String bundle1AdderToken = getAuthToken(bundle1Adder.getEmail(), password); @@ -1596,25 +1573,21 @@ public void testCanCreateBitstreamWriter() throws Exception { .withEmail("bundle1WriterAdder@my.edu") .withPassword(password) .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, bundle1WriterAdder, null) .withDspaceObject(bundle1) .withAction(Constants.ADD) - .withUser(bundle1WriterAdder) .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, bundle1WriterAdder, null) .withDspaceObject(bundle1) .withAction(Constants.WRITE) - .withUser(bundle1WriterAdder) .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, bundle1WriterAdder, null) .withDspaceObject(item1) .withAction(Constants.ADD) - .withUser(bundle1WriterAdder) .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, bundle1WriterAdder, null) .withDspaceObject(item1) .withAction(Constants.WRITE) - .withUser(bundle1WriterAdder) .build(); context.restoreAuthSystemState(); String bundle1WriterAdderToken = getAuthToken(bundle1WriterAdder.getEmail(), password); @@ -1666,15 +1639,13 @@ public void testCanCreateBundleWriter() throws Exception { .withEmail("item1AdderWriter@my.edu") .withPassword(password) .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, item1AdderWriter, null) .withDspaceObject(item1) .withAction(Constants.ADD) - .withUser(item1AdderWriter) .build(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, item1AdderWriter, null) .withDspaceObject(item1) .withAction(Constants.WRITE) - .withUser(item1AdderWriter) .build(); context.restoreAuthSystemState(); String item1AdderWriterToken = getAuthToken(item1AdderWriter.getEmail(), password); @@ -1685,4 +1656,4 @@ public void testCanCreateBundleWriter() throws Exception { .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ManageAdminGroupFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ManageAdminGroupFeatureIT.java index cadb3e1ebba..49082a1613f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ManageAdminGroupFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ManageAdminGroupFeatureIT.java @@ -123,10 +123,9 @@ public void anonymousCommunityTestNotFound() throws Exception { @Test public void collectionAdminCollectionTestSuccess() throws Exception { context.turnOffAuthorisationSystem(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withDspaceObject(collectionA) .withAction(Constants.ADMIN) - .withUser(eperson) .build(); context.restoreAuthSystemState(); @@ -144,10 +143,9 @@ public void collectionAdminCollectionTestSuccess() throws Exception { @Test public void collectionAdminCommunityTestNotFound() throws Exception { context.turnOffAuthorisationSystem(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withDspaceObject(collectionA) .withAction(Constants.ADMIN) - .withUser(eperson) .build(); context.restoreAuthSystemState(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ManageSubmitterGroupFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ManageSubmitterGroupFeatureIT.java index 42ad0c11055..fbf20337ebb 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ManageSubmitterGroupFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ManageSubmitterGroupFeatureIT.java @@ -123,10 +123,9 @@ public void anonymousCommunityTestNotFound() throws Exception { @Test public void collectionAdminCollectionTestSuccess() throws Exception { context.turnOffAuthorisationSystem(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withDspaceObject(collectionA) .withAction(Constants.ADMIN) - .withUser(eperson) .build(); context.restoreAuthSystemState(); @@ -144,10 +143,9 @@ public void collectionAdminCollectionTestSuccess() throws Exception { @Test public void collectionAdminCommunityTestNotFound() throws Exception { context.turnOffAuthorisationSystem(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withDspaceObject(collectionA) .withAction(Constants.ADMIN) - .withUser(eperson) .build(); context.restoreAuthSystemState(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ManageTemplateItemFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ManageTemplateItemFeatureIT.java index 613dfe11513..97e32e55be5 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ManageTemplateItemFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ManageTemplateItemFeatureIT.java @@ -123,10 +123,9 @@ public void anonymousCommunityTestNotFound() throws Exception { @Test public void collectionAdminCollectionTestSuccess() throws Exception { context.turnOffAuthorisationSystem(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withDspaceObject(collectionA) .withAction(Constants.ADMIN) - .withUser(eperson) .build(); context.restoreAuthSystemState(); @@ -144,10 +143,9 @@ public void collectionAdminCollectionTestSuccess() throws Exception { @Test public void collectionAdminCommunityTestNotFound() throws Exception { context.turnOffAuthorisationSystem(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withDspaceObject(collectionA) .withAction(Constants.ADMIN) - .withUser(eperson) .build(); context.restoreAuthSystemState(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ManageWorkflowGroupFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ManageWorkflowGroupFeatureIT.java index ddf0bbac5c4..411aa76a235 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ManageWorkflowGroupFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ManageWorkflowGroupFeatureIT.java @@ -123,10 +123,9 @@ public void anonymousCommunityTestNotFound() throws Exception { @Test public void collectionAdminCollectionTestSuccess() throws Exception { context.turnOffAuthorisationSystem(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withDspaceObject(collectionA) .withAction(Constants.ADMIN) - .withUser(eperson) .build(); context.restoreAuthSystemState(); @@ -144,10 +143,9 @@ public void collectionAdminCollectionTestSuccess() throws Exception { @Test public void collectionAdminCommunityTestNotFound() throws Exception { context.turnOffAuthorisationSystem(); - ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withDspaceObject(collectionA) .withAction(Constants.ADMIN) - .withUser(eperson) .build(); context.restoreAuthSystemState(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/SubmitFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/SubmitFeatureIT.java index bba45ecd229..2613cf8ad20 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/SubmitFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/SubmitFeatureIT.java @@ -89,8 +89,7 @@ public void testNoRights() throws Exception { @Test public void testDirectEPersonAddPolicy() throws Exception { - ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) - .withUser(eperson) + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withDspaceObject(collectionA1) .withAction(Constants.ADD) .build(); @@ -99,8 +98,7 @@ public void testDirectEPersonAddPolicy() throws Exception { @Test public void testDirectGroupAddPolicy() throws Exception { - ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) - .withGroup(group) + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context, null, group) .withDspaceObject(collectionA1) .withAction(Constants.ADD) .build(); @@ -109,8 +107,7 @@ public void testDirectGroupAddPolicy() throws Exception { @Test public void testDirectEPersonAdminPolicy() throws Exception { - ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) - .withUser(eperson) + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withDspaceObject(collectionA1) .withAction(Constants.ADMIN) .build(); @@ -119,8 +116,7 @@ public void testDirectEPersonAdminPolicy() throws Exception { @Test public void testDirectGroupAdminPolicy() throws Exception { - ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) - .withGroup(group) + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context, null, group) .withDspaceObject(collectionA1) .withAction(Constants.ADMIN) .build(); @@ -199,8 +195,7 @@ public void testNoRightsOnCollection() throws Exception { @Test public void testDirectEPersonAddPolicyOnCollection() throws Exception { - ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) - .withUser(eperson) + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withDspaceObject(collectionA1) .withAction(Constants.ADD) .build(); @@ -210,8 +205,7 @@ public void testDirectEPersonAddPolicyOnCollection() throws Exception { @Test public void testDirectGroupAddPolicyOnCollection() throws Exception { - ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) - .withGroup(group) + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context, null, group) .withDspaceObject(collectionA1) .withAction(Constants.ADD) .build(); @@ -221,8 +215,7 @@ public void testDirectGroupAddPolicyOnCollection() throws Exception { @Test public void testDirectEPersonAdminPolicyOnCollection() throws Exception { - ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) - .withUser(eperson) + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withDspaceObject(collectionA1) .withAction(Constants.ADMIN) .build(); @@ -232,8 +225,7 @@ public void testDirectEPersonAdminPolicyOnCollection() throws Exception { @Test public void testDirectGroupAdminPolicyOnCollection() throws Exception { - ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) - .withGroup(group) + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context, null, group) .withDspaceObject(collectionA1) .withAction(Constants.ADMIN) .build(); From b923e13726a744f36d628b015ae30c89582ab0ad Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Fri, 29 Sep 2023 10:36:43 +0200 Subject: [PATCH 002/939] 106812: Fix and add new tests --- .../rest/ResourcePolicyRestRepositoryIT.java | 94 ++++++++++++++++++- .../rest/WorkspaceItemRestRepositoryIT.java | 29 ++++-- 2 files changed, 116 insertions(+), 7 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java index e7d2634525d..5d2a05ab644 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java @@ -998,7 +998,7 @@ public void createWithEPersonTest() throws Exception { AtomicReference idRef = new AtomicReference(); try { Community community = CommunityBuilder.createCommunity(context) - .withName("My commynity") + .withName("My community") .build(); EPerson eperson1 = EPersonBuilder.createEPerson(context) @@ -1045,6 +1045,98 @@ public void createWithEPersonTest() throws Exception { ResourcePolicyBuilder.delete(idRef.get()); } } + @Test + public void createWithGroupTest() throws Exception { + context.turnOffAuthorisationSystem(); + + AtomicReference idRef = new AtomicReference(); + try { + Community community = CommunityBuilder.createCommunity(context) + .withName("My community") + .build(); + + EPerson eperson1 = EPersonBuilder.createEPerson(context) + .withEmail("eperson1@mail.com") + .withPassword("qwerty01") + .build(); + + Group group1 = GroupBuilder.createGroup(context) + .withName("Group 1") + .addMember(eperson1) + .build(); + + context.restoreAuthSystemState(); + + ObjectMapper mapper = new ObjectMapper(); + ResourcePolicyRest resourcePolicyRest = new ResourcePolicyRest(); + + resourcePolicyRest.setPolicyType(ResourcePolicy.TYPE_SUBMISSION); + resourcePolicyRest.setAction(Constants.actionText[Constants.READ]); + + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken) + .perform(post("/api/authz/resourcepolicies") + .content(mapper.writeValueAsBytes(resourcePolicyRest)) + .param("resource", community.getID().toString()) + .param("group", group1.getID().toString()) + .param("projections", "full") + .contentType(contentType)) + .andExpect(status().isCreated()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", ResourcePolicyMatcher.matchFullEmbeds())) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.name", is(resourcePolicyRest.getName())), + hasJsonPath("$.description", is(resourcePolicyRest.getDescription())), + hasJsonPath("$.policyType", is(resourcePolicyRest.getPolicyType())), + hasJsonPath("$.action", is(resourcePolicyRest.getAction())), + hasJsonPath("$.startDate", is(resourcePolicyRest.getStartDate())), + hasJsonPath("$.endDate", is(resourcePolicyRest.getEndDate())), + hasJsonPath("$.type", is(resourcePolicyRest.getType()))))) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + + String authToken1 = getAuthToken(eperson1.getEmail(), "qwerty01"); + getClient(authToken1).perform(get("/api/authz/resourcepolicies/" + idRef.get())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/authz/resourcepolicies/" + idRef.get()))); + } finally { + ResourcePolicyBuilder.delete(idRef.get()); + } + } + + @Test + public void createWithoutGroupOrPersonTest() throws Exception { + context.turnOffAuthorisationSystem(); + Community community = CommunityBuilder.createCommunity(context) + .withName("My commynity") + .build(); + + + context.restoreAuthSystemState(); + + ObjectMapper mapper = new ObjectMapper(); + ResourcePolicyRest resourcePolicyRest = new ResourcePolicyRest(); + + resourcePolicyRest.setPolicyType(ResourcePolicy.TYPE_SUBMISSION); + resourcePolicyRest.setAction(Constants.actionText[Constants.ADMIN]); + + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(post("/api/authz/resourcepolicies") + .content(mapper.writeValueAsBytes(resourcePolicyRest)) + .param("resource", community.getID().toString()) + .contentType(contentType)) + .andExpect(status().isBadRequest()); + + getClient(authToken).perform(get("/api/authz/resourcepolicies/search/resource") + .param("uuid", community.getID().toString()) + .param("action", "ADMIN")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("api/authz/resourcepolicies/search/resource"))) + .andExpect(jsonPath("$.page.totalElements", is(0))); + } @Test public void createOneUnAuthenticatedTest() throws Exception { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java index 9c69e4715d7..c403ec4236a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java @@ -6719,7 +6719,9 @@ public void patchAccesConditionDiscoverableTest() throws Exception { .build(); witem.getItem().setDiscoverable(false); -ResourcePolicyBuilder.createResourcePolicy(context, null, adminGroup) + ResourcePolicyBuilder.createResourcePolicy(context, null, adminGroup).withDspaceObject(witem.getItem()) + .withPolicyType(TYPE_CUSTOM) + .withName("administrator") .build(); ResourcePolicyBuilder.createResourcePolicy(context, null, anonymousGroup) @@ -7116,7 +7118,10 @@ public void patchRemoveSpecificAccesConditionsTest() throws Exception { .build(); ResourcePolicyBuilder.createResourcePolicy(context, null, adminGroup) - .build(); + .withDspaceObject(witem.getItem()) + .withPolicyType(TYPE_CUSTOM) + .withName("administrator") + .build(); Calendar calendar = Calendar.getInstance(); @@ -7195,7 +7200,10 @@ public void patchRemoveFirstAccesConditionsTest() throws Exception { .build(); ResourcePolicyBuilder.createResourcePolicy(context, null, adminGroup) - .build(); + .withDspaceObject(witem.getItem()) + .withPolicyType(TYPE_CUSTOM) + .withName("administrator") + .build(); context.restoreAuthSystemState(); @@ -7255,7 +7263,10 @@ public void patchRemoveAccesConditionsIdxNotSupportedTest() throws Exception { .build(); ResourcePolicyBuilder.createResourcePolicy(context, null, adminGroup) - .build(); + .withDspaceObject(witem.getItem()) + .withPolicyType(TYPE_CUSTOM) + .withName("administrator") + .build(); context.restoreAuthSystemState(); @@ -7317,7 +7328,10 @@ public void patchRemoveAccesConditionsUnprocessableEntityTest() throws Exception .build(); ResourcePolicyBuilder.createResourcePolicy(context, null, adminGroup) - .build(); + .withDspaceObject(witem.getItem()) + .withPolicyType(TYPE_CUSTOM) + .withName("administrator") + .build(); context.restoreAuthSystemState(); @@ -7379,7 +7393,10 @@ public void patchReplaceAccesConditionTest() throws Exception { .build(); ResourcePolicyBuilder.createResourcePolicy(context, null, adminGroup) - .build(); + .withDspaceObject(witem.getItem()) + .withPolicyType(TYPE_CUSTOM) + .withName("administrator") + .build(); context.restoreAuthSystemState(); From 1cf13e3bfef7e3d1f5d09ae3086c7245fabd00df Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Fri, 29 Sep 2023 11:44:29 +0200 Subject: [PATCH 003/939] 106812: Fix old rest resource policy creations --- .../src/main/java/org/dspace/rest/BitstreamResource.java | 5 +++-- dspace-rest/src/main/java/org/dspace/rest/ItemsResource.java | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/dspace-rest/src/main/java/org/dspace/rest/BitstreamResource.java b/dspace-rest/src/main/java/org/dspace/rest/BitstreamResource.java index 3a6ad859603..b71bfad592c 100644 --- a/dspace-rest/src/main/java/org/dspace/rest/BitstreamResource.java +++ b/dspace-rest/src/main/java/org/dspace/rest/BitstreamResource.java @@ -729,9 +729,10 @@ static String getMimeType(String name) { private void addPolicyToBitstream(org.dspace.core.Context context, ResourcePolicy policy, org.dspace.content.Bitstream dspaceBitstream) throws SQLException, AuthorizeException { - org.dspace.authorize.ResourcePolicy dspacePolicy = resourcePolicyService.create(context); + org.dspace.authorize.ResourcePolicy dspacePolicy = + resourcePolicyService.create(context, null, + groupService.findByIdOrLegacyId(context, policy.getGroupId())); dspacePolicy.setAction(policy.getActionInt()); - dspacePolicy.setGroup(groupService.findByIdOrLegacyId(context, policy.getGroupId())); dspacePolicy.setdSpaceObject(dspaceBitstream); dspacePolicy.setStartDate(policy.getStartDate()); dspacePolicy.setEndDate(policy.getEndDate()); diff --git a/dspace-rest/src/main/java/org/dspace/rest/ItemsResource.java b/dspace-rest/src/main/java/org/dspace/rest/ItemsResource.java index 615aacac21c..761d4dfc8ae 100644 --- a/dspace-rest/src/main/java/org/dspace/rest/ItemsResource.java +++ b/dspace-rest/src/main/java/org/dspace/rest/ItemsResource.java @@ -516,9 +516,10 @@ public Bitstream addItemBitstream(@PathParam("item_id") String itemId, InputStre bitstreamsPolicies.remove(policy); } - org.dspace.authorize.ResourcePolicy dspacePolicy = resourcePolicyService.create(context); + org.dspace.authorize.ResourcePolicy dspacePolicy = + resourcePolicyService.create(context, + null, groupService.findByIdOrLegacyId(context, groupId)); dspacePolicy.setAction(org.dspace.core.Constants.READ); - dspacePolicy.setGroup(groupService.findByIdOrLegacyId(context, groupId)); dspacePolicy.setdSpaceObject(dspaceBitstream); if ((year != null) || (month != null) || (day != null)) { Date date = new Date(); From 9880e697b65c2284aea9d626008a96010af664de Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Fri, 29 Sep 2023 15:36:53 +0200 Subject: [PATCH 004/939] 106812: Add h2 sql file --- ...28__enforce_group_or_eperson_for_resourcepolicy.sql | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.09.28__enforce_group_or_eperson_for_resourcepolicy.sql diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.09.28__enforce_group_or_eperson_for_resourcepolicy.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.09.28__enforce_group_or_eperson_for_resourcepolicy.sql new file mode 100644 index 00000000000..de40d0415dc --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.09.28__enforce_group_or_eperson_for_resourcepolicy.sql @@ -0,0 +1,10 @@ +-- +-- 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 ResourcePolicy ADD CONSTRAINT resourcepolicy_eperson_and_epersongroup_not_nullobject_chk + CHECK (eperson_id is not null or epersongroup_id is not null) ; From 9b1234fc85728e3def4940d5b25b9e49e3db422a Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Fri, 10 Nov 2023 14:05:38 +0100 Subject: [PATCH 005/939] 106812: Remove stray settting of group for an rp --- .../java/org/dspace/app/itemimport/ItemImportServiceImpl.java | 1 - 1 file changed, 1 deletion(-) 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 a77d854ade2..9532ecdb470 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 @@ -1814,7 +1814,6 @@ protected void setPermission(Context c, Group g, int actionID, Bitstream bs) rp.setdSpaceObject(bs); rp.setAction(actionID); - rp.setGroup(g); resourcePolicyService.update(c, rp); } else { From 76658e7c967813e789dd0b5a60ef3c6e9d3d7b0f Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 15 Nov 2023 14:16:40 -0600 Subject: [PATCH 006/939] [maven-release-plugin] prepare for next development iteration --- dspace-api/pom.xml | 2 +- dspace-iiif/pom.xml | 2 +- dspace-oai/pom.xml | 2 +- dspace-rdf/pom.xml | 2 +- dspace-rest/pom.xml | 4 ++-- dspace-server-webapp/pom.xml | 2 +- dspace-services/pom.xml | 2 +- dspace-sword/pom.xml | 2 +- dspace-swordv2/pom.xml | 2 +- dspace/modules/additions/pom.xml | 2 +- dspace/modules/pom.xml | 2 +- dspace/modules/rest/pom.xml | 2 +- dspace/modules/server/pom.xml | 2 +- dspace/pom.xml | 2 +- pom.xml | 32 ++++++++++++++++---------------- 15 files changed, 31 insertions(+), 31 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 97ff29e5c58..2fddacc7e4b 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6.1 + 7.6.2-SNAPSHOT .. diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index eff53478ed9..c8db035d864 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.1 + 7.6.2-SNAPSHOT .. diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index 8aaad80861f..c9d8285559c 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - 7.6.1 + 7.6.2-SNAPSHOT .. diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index f425ea7d908..f118ebddb37 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.1 + 7.6.2-SNAPSHOT .. diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index c1ac8ac2302..5688424a1c2 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -3,7 +3,7 @@ org.dspace dspace-rest war - 7.6.1 + 7.6.2-SNAPSHOT DSpace (Deprecated) REST Webapp DSpace RESTful Web Services API. NOTE: this REST API is DEPRECATED. Please consider using the REST API in the dspace-server-webapp instead! @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6.1 + 7.6.2-SNAPSHOT .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index b23db1960ce..b3e903a47b4 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.1 + 7.6.2-SNAPSHOT .. diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index c539dac94f5..98e0253a498 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.1 + 7.6.2-SNAPSHOT diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index 244a661f0c5..900ede5fc50 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.1 + 7.6.2-SNAPSHOT .. diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index 4879d7a2913..8d618bdf5f8 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - 7.6.1 + 7.6.2-SNAPSHOT .. diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index fa7d75f82d8..84b53171bf3 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - 7.6.1 + 7.6.2-SNAPSHOT .. diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index eaf25a568b6..5819158505f 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - 7.6.1 + 7.6.2-SNAPSHOT ../../pom.xml diff --git a/dspace/modules/rest/pom.xml b/dspace/modules/rest/pom.xml index 66c5ea86f30..7797346c51d 100644 --- a/dspace/modules/rest/pom.xml +++ b/dspace/modules/rest/pom.xml @@ -13,7 +13,7 @@ org.dspace modules - 7.6.1 + 7.6.2-SNAPSHOT .. diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index 195623f72da..564f8159377 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -13,7 +13,7 @@ just adding new jar in the classloader modules org.dspace - 7.6.1 + 7.6.2-SNAPSHOT .. diff --git a/dspace/pom.xml b/dspace/pom.xml index f61b27c1c9a..f9dc6126e60 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - 7.6.1 + 7.6.2-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index 614bb39a1fe..1c3479fc0f5 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - 7.6.1 + 7.6.2-SNAPSHOT DSpace Parent Project DSpace open source software is a turnkey institutional repository application. @@ -872,14 +872,14 @@ org.dspace dspace-rest - 7.6.1 + 7.6.2-SNAPSHOT jar classes org.dspace dspace-rest - 7.6.1 + 7.6.2-SNAPSHOT war @@ -1030,69 +1030,69 @@ org.dspace dspace-api - 7.6.1 + 7.6.2-SNAPSHOT org.dspace dspace-api test-jar - 7.6.1 + 7.6.2-SNAPSHOT test org.dspace.modules additions - 7.6.1 + 7.6.2-SNAPSHOT org.dspace dspace-sword - 7.6.1 + 7.6.2-SNAPSHOT org.dspace dspace-swordv2 - 7.6.1 + 7.6.2-SNAPSHOT org.dspace dspace-oai - 7.6.1 + 7.6.2-SNAPSHOT org.dspace dspace-services - 7.6.1 + 7.6.2-SNAPSHOT org.dspace dspace-server-webapp test-jar - 7.6.1 + 7.6.2-SNAPSHOT test org.dspace dspace-rdf - 7.6.1 + 7.6.2-SNAPSHOT org.dspace dspace-iiif - 7.6.1 + 7.6.2-SNAPSHOT org.dspace dspace-server-webapp - 7.6.1 + 7.6.2-SNAPSHOT jar classes org.dspace dspace-server-webapp - 7.6.1 + 7.6.2-SNAPSHOT war @@ -1927,7 +1927,7 @@ scm:git:git@github.com:DSpace/DSpace.git scm:git:git@github.com:DSpace/DSpace.git git@github.com:DSpace/DSpace.git - dspace-7.6.1 + dspace-7_x From 722ad0275757b1859b1ebeb95d41b4c2792652ce Mon Sep 17 00:00:00 2001 From: Philipp Rumpf Date: Tue, 14 Nov 2023 09:38:35 +0000 Subject: [PATCH 007/939] CrossRefImport: ignore empty responses rather than generating empty phantom ImportRecords Fixes https://github.com/DSpace/DSpace/issues/9202 . --- .../CrossRefImportMetadataSourceServiceImpl.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefImportMetadataSourceServiceImpl.java index 7dde330b27e..71b088ff162 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefImportMetadataSourceServiceImpl.java @@ -162,7 +162,9 @@ public List call() throws Exception { Iterator nodes = jsonNode.at("/message/items").iterator(); while (nodes.hasNext()) { JsonNode node = nodes.next(); - results.add(transformSourceRecords(node.toString())); + if (!node.isMissingNode()) { + results.add(transformSourceRecords(node.toString())); + } } return results; } @@ -196,7 +198,9 @@ public List call() throws Exception { String responseString = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); JsonNode jsonNode = convertStringJsonToJsonNode(responseString); JsonNode messageNode = jsonNode.at("/message"); - results.add(transformSourceRecords(messageNode.toString())); + if (!messageNode.isMissingNode()) { + results.add(transformSourceRecords(messageNode.toString())); + } return results; } } @@ -250,7 +254,9 @@ public List call() throws Exception { Iterator nodes = jsonNode.at("/message/items").iterator(); while (nodes.hasNext()) { JsonNode node = nodes.next(); - results.add(transformSourceRecords(node.toString())); + if (!node.isMissingNode()) { + results.add(transformSourceRecords(node.toString())); + } } return results; } @@ -333,4 +339,4 @@ public void setUrl(String url) { this.url = url; } -} \ No newline at end of file +} From 21a7f251186f8f450f7bf9187dc6670514d0a51f Mon Sep 17 00:00:00 2001 From: Philipp Rumpf Date: Thu, 16 Nov 2023 14:15:32 +0000 Subject: [PATCH 008/939] CrossRefImportMetadataSourceServiceIT: Test empty responses don't result in ... results. --- .../CrossRefImportMetadataSourceServiceIT.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CrossRefImportMetadataSourceServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CrossRefImportMetadataSourceServiceIT.java index 72524709ec6..31c22692f00 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CrossRefImportMetadataSourceServiceIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CrossRefImportMetadataSourceServiceIT.java @@ -48,6 +48,24 @@ public class CrossRefImportMetadataSourceServiceIT extends AbstractLiveImportInt @Autowired private CrossRefImportMetadataSourceServiceImpl crossRefServiceImpl; + @Test + public void crossRefImportMetadataGetNoRecordsTest() throws Exception { + context.turnOffAuthorisationSystem(); + CloseableHttpClient originalHttpClient = liveImportClientImpl.getHttpClient(); + CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class); + try { + liveImportClientImpl.setHttpClient(httpClient); + CloseableHttpResponse response = mockResponse("" , 404, "Not Found"); + when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response); + + context.restoreAuthSystemState(); + Collection recordsImported = crossRefServiceImpl.getRecords("test query", 0, 2); + assertEquals(0, recordsImported.size()); + } finally { + liveImportClientImpl.setHttpClient(originalHttpClient); + } + } + @Test public void crossRefImportMetadataGetRecordsTest() throws Exception { context.turnOffAuthorisationSystem(); From c9c57faa21b165784c4c0d99c932b1dc56e30b7d Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 22 Nov 2023 14:06:05 -0600 Subject: [PATCH 009/939] Update GitHub action plugin versions. Minor fixes including using built-in Maven caching & fix to CodeCov action (cherry picked from commit 2aae4cd78de318e816591932187049eb135fa60d) --- .github/workflows/build.yml | 22 ++--- .github/workflows/codescan.yml | 2 +- .github/workflows/docker.yml | 84 +++++++++---------- .../workflows/port_merged_pull_request.yml | 4 +- .github/workflows/pull_request_opened.yml | 2 +- 5 files changed, 54 insertions(+), 60 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 99c9efe0190..d6913078e47 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -45,7 +45,7 @@ jobs: steps: # https://github.com/actions/checkout - name: Checkout codebase - uses: actions/checkout@v3 + uses: actions/checkout@v4 # https://github.com/actions/setup-java - name: Install JDK ${{ matrix.java }} @@ -53,16 +53,7 @@ jobs: with: java-version: ${{ matrix.java }} distribution: 'temurin' - - # https://github.com/actions/cache - - name: Cache Maven dependencies - uses: actions/cache@v3 - with: - # Cache entire ~/.m2/repository - path: ~/.m2/repository - # Cache key is hash of all pom.xml files. Therefore any changes to POMs will invalidate cache - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: ${{ runner.os }}-maven- + cache: 'maven' # Run parallel Maven builds based on the above 'strategy.matrix' - name: Run Maven ${{ matrix.type }} @@ -96,7 +87,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Download artifacts from previous 'tests' job - name: Download coverage artifacts @@ -108,10 +99,13 @@ jobs: # Retry action: https://github.com/marketplace/actions/retry-action # Codecov action: https://github.com/codecov/codecov-action - name: Upload coverage to Codecov.io - uses: Wandalen/wretry.action@v1.0.36 + uses: Wandalen/wretry.action@v1.3.0 with: action: codecov/codecov-action@v3 - # Try upload 5 times max + # Ensure codecov-action throws an error when it fails to upload + with: | + fail_ci_if_error: true + # Try re-running action 5 times max attempt_limit: 5 # Run again in 30 seconds attempt_delay: 30000 diff --git a/.github/workflows/codescan.yml b/.github/workflows/codescan.yml index 9e6dcc0b23a..13bb0d2278a 100644 --- a/.github/workflows/codescan.yml +++ b/.github/workflows/codescan.yml @@ -35,7 +35,7 @@ jobs: steps: # https://github.com/actions/checkout - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # https://github.com/actions/setup-java - name: Install JDK diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index f1ae184fd5c..c538d324cde 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -39,7 +39,7 @@ env: jobs: #################################################### # Build/Push the 'dspace/dspace-dependencies' image. - # This image is used by all other jobs. + # This image is used by all other DSpace build jobs. #################################################### dspace-dependencies: # Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace' @@ -49,21 +49,21 @@ jobs: steps: # https://github.com/actions/checkout - name: Checkout codebase - uses: actions/checkout@v3 + uses: actions/checkout@v4 # https://github.com/docker/setup-buildx-action - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 # https://github.com/docker/setup-qemu-action - name: Set up QEMU emulation to build for multiple architectures - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 # https://github.com/docker/login-action - name: Login to DockerHub # Only login if not a PR, as PRs only trigger a Docker build and not a push if: github.event_name != 'pull_request' - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_ACCESS_TOKEN }} @@ -72,7 +72,7 @@ jobs: # Get Metadata for docker_build_deps step below - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-dependencies' image id: meta_build_deps - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: dspace/dspace-dependencies tags: ${{ env.IMAGE_TAGS }} @@ -81,7 +81,7 @@ jobs: # https://github.com/docker/build-push-action - name: Build and push 'dspace-dependencies' image id: docker_build_deps - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: context: . file: ./Dockerfile.dependencies @@ -106,21 +106,21 @@ jobs: steps: # https://github.com/actions/checkout - name: Checkout codebase - uses: actions/checkout@v3 + uses: actions/checkout@v4 # https://github.com/docker/setup-buildx-action - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 # https://github.com/docker/setup-qemu-action - name: Set up QEMU emulation to build for multiple architectures - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 # https://github.com/docker/login-action - name: Login to DockerHub # Only login if not a PR, as PRs only trigger a Docker build and not a push if: github.event_name != 'pull_request' - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_ACCESS_TOKEN }} @@ -128,7 +128,7 @@ jobs: # Get Metadata for docker_build step below - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace' image id: meta_build - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: dspace/dspace tags: ${{ env.IMAGE_TAGS }} @@ -136,7 +136,7 @@ jobs: - name: Build and push 'dspace' image id: docker_build - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: context: . file: ./Dockerfile @@ -161,21 +161,21 @@ jobs: steps: # https://github.com/actions/checkout - name: Checkout codebase - uses: actions/checkout@v3 + uses: actions/checkout@v4 # https://github.com/docker/setup-buildx-action - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 # https://github.com/docker/setup-qemu-action - name: Set up QEMU emulation to build for multiple architectures - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 # https://github.com/docker/login-action - name: Login to DockerHub # Only login if not a PR, as PRs only trigger a Docker build and not a push if: github.event_name != 'pull_request' - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_ACCESS_TOKEN }} @@ -183,7 +183,7 @@ jobs: # Get Metadata for docker_build_test step below - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-test' image id: meta_build_test - uses: docker/metadata-action@v4 + uses: docker/metadata-action@5 with: images: dspace/dspace tags: ${{ env.IMAGE_TAGS }} @@ -194,7 +194,7 @@ jobs: - name: Build and push 'dspace-test' image id: docker_build_test - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: context: . file: ./Dockerfile.test @@ -219,21 +219,21 @@ jobs: steps: # https://github.com/actions/checkout - name: Checkout codebase - uses: actions/checkout@v3 + uses: actions/checkout@v4 # https://github.com/docker/setup-buildx-action - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 # https://github.com/docker/setup-qemu-action - name: Set up QEMU emulation to build for multiple architectures - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 # https://github.com/docker/login-action - name: Login to DockerHub # Only login if not a PR, as PRs only trigger a Docker build and not a push if: github.event_name != 'pull_request' - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_ACCESS_TOKEN }} @@ -241,7 +241,7 @@ jobs: # Get Metadata for docker_build_test step below - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-cli' image id: meta_build_cli - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: dspace/dspace-cli tags: ${{ env.IMAGE_TAGS }} @@ -249,7 +249,7 @@ jobs: - name: Build and push 'dspace-cli' image id: docker_build_cli - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: context: . file: ./Dockerfile.cli @@ -276,17 +276,17 @@ jobs: # https://github.com/docker/setup-buildx-action - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 # https://github.com/docker/setup-qemu-action - name: Set up QEMU emulation to build for multiple architectures - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 # https://github.com/docker/login-action - name: Login to DockerHub # Only login if not a PR, as PRs only trigger a Docker build and not a push if: github.event_name != 'pull_request' - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_ACCESS_TOKEN }} @@ -294,7 +294,7 @@ jobs: # Get Metadata for docker_build_solr step below - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-solr' image id: meta_build_solr - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: dspace/dspace-solr tags: ${{ env.IMAGE_TAGS }} @@ -302,7 +302,7 @@ jobs: - name: Build and push 'dspace-solr' image id: docker_build_solr - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: context: . file: ./dspace/src/main/docker/dspace-solr/Dockerfile @@ -325,21 +325,21 @@ jobs: steps: # https://github.com/actions/checkout - name: Checkout codebase - uses: actions/checkout@v3 + uses: actions/checkout@v4 # https://github.com/docker/setup-buildx-action - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 # https://github.com/docker/setup-qemu-action - name: Set up QEMU emulation to build for multiple architectures - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 # https://github.com/docker/login-action - name: Login to DockerHub # Only login if not a PR, as PRs only trigger a Docker build and not a push if: github.event_name != 'pull_request' - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_ACCESS_TOKEN }} @@ -347,7 +347,7 @@ jobs: # Get Metadata for docker_build_postgres step below - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-postgres-pgcrypto' image id: meta_build_postgres - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: dspace/dspace-postgres-pgcrypto tags: ${{ env.IMAGE_TAGS }} @@ -355,7 +355,7 @@ jobs: - name: Build and push 'dspace-postgres-pgcrypto' image id: docker_build_postgres - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: # Must build out of subdirectory to have access to install script for pgcrypto context: ./dspace/src/main/docker/dspace-postgres-pgcrypto/ @@ -379,21 +379,21 @@ jobs: steps: # https://github.com/actions/checkout - name: Checkout codebase - uses: actions/checkout@v3 + uses: actions/checkout@v4 # https://github.com/docker/setup-buildx-action - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 # https://github.com/docker/setup-qemu-action - name: Set up QEMU emulation to build for multiple architectures - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 # https://github.com/docker/login-action - name: Login to DockerHub # Only login if not a PR, as PRs only trigger a Docker build and not a push if: github.event_name != 'pull_request' - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_ACCESS_TOKEN }} @@ -401,7 +401,7 @@ jobs: # Get Metadata for docker_build_postgres_loadsql step below - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-postgres-pgcrypto-loadsql' image id: meta_build_postgres_loadsql - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: dspace/dspace-postgres-pgcrypto tags: ${{ env.IMAGE_TAGS }} @@ -412,7 +412,7 @@ jobs: - name: Build and push 'dspace-postgres-pgcrypto-loadsql' image id: docker_build_postgres_loadsql - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: # Must build out of subdirectory to have access to install script for pgcrypto context: ./dspace/src/main/docker/dspace-postgres-pgcrypto-curl/ diff --git a/.github/workflows/port_merged_pull_request.yml b/.github/workflows/port_merged_pull_request.yml index 109835d14d3..857f22755e4 100644 --- a/.github/workflows/port_merged_pull_request.yml +++ b/.github/workflows/port_merged_pull_request.yml @@ -23,11 +23,11 @@ jobs: if: github.event.pull_request.merged steps: # Checkout code - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # Port PR to other branch (ONLY if labeled with "port to") # See https://github.com/korthout/backport-action - name: Create backport pull requests - uses: korthout/backport-action@v1 + uses: korthout/backport-action@v2 with: # Trigger based on a "port to [branch]" label on PR # (This label must specify the branch name to port to) diff --git a/.github/workflows/pull_request_opened.yml b/.github/workflows/pull_request_opened.yml index 9b61af72d18..f16e81c9fd2 100644 --- a/.github/workflows/pull_request_opened.yml +++ b/.github/workflows/pull_request_opened.yml @@ -21,4 +21,4 @@ jobs: # Assign the PR to whomever created it. This is useful for visualizing assignments on project boards # See https://github.com/toshimaru/auto-author-assign - name: Assign PR to creator - uses: toshimaru/auto-author-assign@v1.6.2 + uses: toshimaru/auto-author-assign@v2.0.1 From 68f29c45b6cfadc3ad11e078d4fe7340d5c6f04c Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 22 Nov 2023 14:07:05 -0600 Subject: [PATCH 010/939] Minor fixes to Dockerfiles. No longer need 'git'. Use Maven flags to slightly speed up build/install steps. (cherry picked from commit 538833f8a8573e55b49cb28ee6bfbc5330ad6bc4) --- .github/workflows/docker.yml | 2 +- Dockerfile | 5 ++++- Dockerfile.dependencies | 10 ++++------ 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index c538d324cde..8be8ac13fe6 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -183,7 +183,7 @@ jobs: # Get Metadata for docker_build_test step below - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-test' image id: meta_build_test - uses: docker/metadata-action@5 + uses: docker/metadata-action@v5 with: images: dspace/dspace tags: ${{ env.IMAGE_TAGS }} diff --git a/Dockerfile b/Dockerfile index 9c32ecb50cd..3c536b31762 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,7 +20,10 @@ USER dspace ADD --chown=dspace . /app/ # Build DSpace (note: this build doesn't include the optional, deprecated "dspace-rest" webapp) # Copy the dspace-installer directory to /install. Clean up the build to keep the docker image small -RUN mvn --no-transfer-progress package && \ +# Maven flags here ensure that we skip building test environment and skip all code verification checks. +# These flags speed up this compilation as much as reasonably possible. +ENV MAVEN_FLAGS="-P-test-environment -Denforcer.skip=true -Dcheckstyle.skip=true -Dlicense.skip=true -Dxml.skip=true" +RUN mvn --no-transfer-progress package ${MAVEN_FLAGS} && \ mv /app/dspace/target/${TARGET_DIR}/* /install && \ mvn clean diff --git a/Dockerfile.dependencies b/Dockerfile.dependencies index a55b323339d..6f72ab05853 100644 --- a/Dockerfile.dependencies +++ b/Dockerfile.dependencies @@ -15,11 +15,6 @@ RUN useradd dspace \ && mkdir -p /home/dspace \ && chown -Rv dspace: /home/dspace RUN chown -Rv dspace: /app -# Need git to support buildnumber-maven-plugin, which lets us know what version of DSpace is being run. -RUN apt-get update \ - && apt-get install -y --no-install-recommends git \ - && apt-get purge -y --auto-remove \ - && rm -rf /var/lib/apt/lists/* # Switch to dspace user & run below commands as that user USER dspace @@ -28,7 +23,10 @@ USER dspace ADD --chown=dspace . /app/ # Trigger the installation of all maven dependencies (hide download progress messages) -RUN mvn --no-transfer-progress package +# Maven flags here ensure that we skip final assembly, skip building test environment and skip all code verification checks. +# These flags speed up this installation as much as reasonably possible. +ENV MAVEN_FLAGS="-P-assembly -P-test-environment -Denforcer.skip=true -Dcheckstyle.skip=true -Dlicense.skip=true -Dxml.skip=true" +RUN mvn --no-transfer-progress install ${MAVEN_FLAGS} # Clear the contents of the /app directory (including all maven builds), so no artifacts remain. # This ensures when dspace:dspace is built, it will use the Maven local cache (~/.m2) for dependencies From 93fc3ce8d09e4fa03055000447f6957c655ecb12 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 22 Nov 2023 15:54:28 -0600 Subject: [PATCH 011/939] Refactor docker.yml to use a separate reusable-docker-build.yml script for each image build. (cherry picked from commit 0e88bfdae7844ea2313238a96975b3d8c36f0a68) --- .github/workflows/docker.yml | 408 ++++---------------- .github/workflows/reusable-docker-build.yml | 217 +++++++++++ 2 files changed, 288 insertions(+), 337 deletions(-) create mode 100644 .github/workflows/reusable-docker-build.yml diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 8be8ac13fe6..2adceaa4d3c 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -3,6 +3,7 @@ name: Docker images # Run this Build for all pushes to 'main' or maintenance branches, or tagged releases. # Also run for PRs to ensure PR doesn't break Docker build process +# NOTE: uses "reusable-docker-build.yml" to actually build each of the Docker images. on: push: branches: @@ -30,11 +31,6 @@ env: # We manage the 'latest' tag ourselves to the 'main' branch (see settings above) TAGS_FLAVOR: | latest=false - # Architectures / Platforms for which we will build Docker images - # If this is a PR, we ONLY build for AMD64. For PRs we only do a sanity check test to ensure Docker builds work. - # If this is NOT a PR (e.g. a tag or merge commit), also build for ARM64. NOTE: The ARM64 build takes MUCH - # longer (around 45mins or so) which is why we only run it when pushing a new Docker image. - PLATFORMS: linux/amd64${{ github.event_name != 'pull_request' && ', linux/arm64' || '' }} jobs: #################################################### @@ -44,54 +40,14 @@ jobs: dspace-dependencies: # Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace' if: github.repository == 'dspace/dspace' - runs-on: ubuntu-latest - - steps: - # https://github.com/actions/checkout - - name: Checkout codebase - uses: actions/checkout@v4 - - # https://github.com/docker/setup-buildx-action - - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v3 - - # https://github.com/docker/setup-qemu-action - - name: Set up QEMU emulation to build for multiple architectures - uses: docker/setup-qemu-action@v3 - - # https://github.com/docker/login-action - - name: Login to DockerHub - # Only login if not a PR, as PRs only trigger a Docker build and not a push - if: github.event_name != 'pull_request' - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_ACCESS_TOKEN }} - - # https://github.com/docker/metadata-action - # Get Metadata for docker_build_deps step below - - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-dependencies' image - id: meta_build_deps - uses: docker/metadata-action@v5 - with: - images: dspace/dspace-dependencies - tags: ${{ env.IMAGE_TAGS }} - flavor: ${{ env.TAGS_FLAVOR }} - - # https://github.com/docker/build-push-action - - name: Build and push 'dspace-dependencies' image - id: docker_build_deps - uses: docker/build-push-action@v5 - with: - context: . - file: ./Dockerfile.dependencies - platforms: ${{ env.PLATFORMS }} - # For pull requests, we run the Docker build (to ensure no PR changes break the build), - # but we ONLY do an image push to DockerHub if it's NOT a PR - push: ${{ github.event_name != 'pull_request' }} - # Use tags / labels provided by 'docker/metadata-action' above - tags: ${{ steps.meta_build_deps.outputs.tags }} - labels: ${{ steps.meta_build_deps.outputs.labels }} + uses: ./.github/workflows/reusable-docker-build.yml + with: + build_id: dspace-dependencies + image_name: dspace/dspace-dependencies + dockerfile_path: ./Dockerfile.dependencies + secrets: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }} ####################################### # Build/Push the 'dspace/dspace' image @@ -101,52 +57,18 @@ jobs: if: github.repository == 'dspace/dspace' # Must run after 'dspace-dependencies' job above needs: dspace-dependencies - runs-on: ubuntu-latest - - steps: - # https://github.com/actions/checkout - - name: Checkout codebase - uses: actions/checkout@v4 - - # https://github.com/docker/setup-buildx-action - - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v3 - - # https://github.com/docker/setup-qemu-action - - name: Set up QEMU emulation to build for multiple architectures - uses: docker/setup-qemu-action@v3 - - # https://github.com/docker/login-action - - name: Login to DockerHub - # Only login if not a PR, as PRs only trigger a Docker build and not a push - if: github.event_name != 'pull_request' - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_ACCESS_TOKEN }} - - # Get Metadata for docker_build step below - - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace' image - id: meta_build - uses: docker/metadata-action@v5 - with: - images: dspace/dspace - tags: ${{ env.IMAGE_TAGS }} - flavor: ${{ env.TAGS_FLAVOR }} - - - name: Build and push 'dspace' image - id: docker_build - uses: docker/build-push-action@v5 - with: - context: . - file: ./Dockerfile - platforms: ${{ env.PLATFORMS }} - # For pull requests, we run the Docker build (to ensure no PR changes break the build), - # but we ONLY do an image push to DockerHub if it's NOT a PR - push: ${{ github.event_name != 'pull_request' }} - # Use tags / labels provided by 'docker/metadata-action' above - tags: ${{ steps.meta_build.outputs.tags }} - labels: ${{ steps.meta_build.outputs.labels }} + uses: ./.github/workflows/reusable-docker-build.yml + with: + build_id: dspace + image_name: dspace/dspace + dockerfile_path: ./Dockerfile + secrets: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }} + # Enable redeploy of sandbox & demo if the branch for this image matches the deployment branch of + # these sites as specified in reusable-docker-build.xml + REDEPLOY_SANDBOX_URL: ${{ secrets.REDEPLOY_SANDBOX_URL }} + REDEPLOY_DEMO_URL: ${{ secrets.REDEPLOY_DEMO_URL }} ############################################################# # Build/Push the 'dspace/dspace' image ('-test' tag) @@ -156,55 +78,17 @@ jobs: if: github.repository == 'dspace/dspace' # Must run after 'dspace-dependencies' job above needs: dspace-dependencies - runs-on: ubuntu-latest - - steps: - # https://github.com/actions/checkout - - name: Checkout codebase - uses: actions/checkout@v4 - - # https://github.com/docker/setup-buildx-action - - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v3 - - # https://github.com/docker/setup-qemu-action - - name: Set up QEMU emulation to build for multiple architectures - uses: docker/setup-qemu-action@v3 - - # https://github.com/docker/login-action - - name: Login to DockerHub - # Only login if not a PR, as PRs only trigger a Docker build and not a push - if: github.event_name != 'pull_request' - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_ACCESS_TOKEN }} - - # Get Metadata for docker_build_test step below - - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-test' image - id: meta_build_test - uses: docker/metadata-action@v5 - with: - images: dspace/dspace - tags: ${{ env.IMAGE_TAGS }} - # As this is a test/development image, its tags are all suffixed with "-test". Otherwise, it uses the same - # tagging logic as the primary 'dspace/dspace' image above. - flavor: ${{ env.TAGS_FLAVOR }} - suffix=-test - - - name: Build and push 'dspace-test' image - id: docker_build_test - uses: docker/build-push-action@v5 - with: - context: . - file: ./Dockerfile.test - platforms: ${{ env.PLATFORMS }} - # For pull requests, we run the Docker build (to ensure no PR changes break the build), - # but we ONLY do an image push to DockerHub if it's NOT a PR - push: ${{ github.event_name != 'pull_request' }} - # Use tags / labels provided by 'docker/metadata-action' above - tags: ${{ steps.meta_build_test.outputs.tags }} - labels: ${{ steps.meta_build_test.outputs.labels }} + uses: ./.github/workflows/reusable-docker-build.yml + with: + build_id: dspace-test + image_name: dspace/dspace + dockerfile_path: ./Dockerfile.test + # As this is a test/development image, its tags are all suffixed with "-test". Otherwise, it uses the same + # tagging logic as the primary 'dspace/dspace' image above. + tags_flavor: suffix=-test + secrets: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }} ########################################### # Build/Push the 'dspace/dspace-cli' image @@ -214,52 +98,14 @@ jobs: if: github.repository == 'dspace/dspace' # Must run after 'dspace-dependencies' job above needs: dspace-dependencies - runs-on: ubuntu-latest - - steps: - # https://github.com/actions/checkout - - name: Checkout codebase - uses: actions/checkout@v4 - - # https://github.com/docker/setup-buildx-action - - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v3 - - # https://github.com/docker/setup-qemu-action - - name: Set up QEMU emulation to build for multiple architectures - uses: docker/setup-qemu-action@v3 - - # https://github.com/docker/login-action - - name: Login to DockerHub - # Only login if not a PR, as PRs only trigger a Docker build and not a push - if: github.event_name != 'pull_request' - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_ACCESS_TOKEN }} - - # Get Metadata for docker_build_test step below - - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-cli' image - id: meta_build_cli - uses: docker/metadata-action@v5 - with: - images: dspace/dspace-cli - tags: ${{ env.IMAGE_TAGS }} - flavor: ${{ env.TAGS_FLAVOR }} - - - name: Build and push 'dspace-cli' image - id: docker_build_cli - uses: docker/build-push-action@v5 - with: - context: . - file: ./Dockerfile.cli - platforms: ${{ env.PLATFORMS }} - # For pull requests, we run the Docker build (to ensure no PR changes break the build), - # but we ONLY do an image push to DockerHub if it's NOT a PR - push: ${{ github.event_name != 'pull_request' }} - # Use tags / labels provided by 'docker/metadata-action' above - tags: ${{ steps.meta_build_cli.outputs.tags }} - labels: ${{ steps.meta_build_cli.outputs.labels }} + uses: ./.github/workflows/reusable-docker-build.yml + with: + build_id: dspace-cli + image_name: dspace/dspace-cli + dockerfile_path: ./Dockerfile.cli + secrets: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }} ########################################### # Build/Push the 'dspace/dspace-solr' image @@ -267,52 +113,14 @@ jobs: dspace-solr: # Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace' if: github.repository == 'dspace/dspace' - runs-on: ubuntu-latest - - steps: - # https://github.com/actions/checkout - - name: Checkout codebase - uses: actions/checkout@v3 - - # https://github.com/docker/setup-buildx-action - - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v3 - - # https://github.com/docker/setup-qemu-action - - name: Set up QEMU emulation to build for multiple architectures - uses: docker/setup-qemu-action@v3 - - # https://github.com/docker/login-action - - name: Login to DockerHub - # Only login if not a PR, as PRs only trigger a Docker build and not a push - if: github.event_name != 'pull_request' - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_ACCESS_TOKEN }} - - # Get Metadata for docker_build_solr step below - - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-solr' image - id: meta_build_solr - uses: docker/metadata-action@v5 - with: - images: dspace/dspace-solr - tags: ${{ env.IMAGE_TAGS }} - flavor: ${{ env.TAGS_FLAVOR }} - - - name: Build and push 'dspace-solr' image - id: docker_build_solr - uses: docker/build-push-action@v5 - with: - context: . - file: ./dspace/src/main/docker/dspace-solr/Dockerfile - platforms: ${{ env.PLATFORMS }} - # For pull requests, we run the Docker build (to ensure no PR changes break the build), - # but we ONLY do an image push to DockerHub if it's NOT a PR - push: ${{ github.event_name != 'pull_request' }} - # Use tags / labels provided by 'docker/metadata-action' above - tags: ${{ steps.meta_build_solr.outputs.tags }} - labels: ${{ steps.meta_build_solr.outputs.labels }} + uses: ./.github/workflows/reusable-docker-build.yml + with: + build_id: dspace-solr + image_name: dspace/dspace-solr + dockerfile_path: ./dspace/src/main/docker/dspace-solr/Dockerfile + secrets: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }} ########################################################### # Build/Push the 'dspace/dspace-postgres-pgcrypto' image @@ -320,53 +128,16 @@ jobs: dspace-postgres-pgcrypto: # Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace' if: github.repository == 'dspace/dspace' - runs-on: ubuntu-latest - - steps: - # https://github.com/actions/checkout - - name: Checkout codebase - uses: actions/checkout@v4 - - # https://github.com/docker/setup-buildx-action - - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v3 - - # https://github.com/docker/setup-qemu-action - - name: Set up QEMU emulation to build for multiple architectures - uses: docker/setup-qemu-action@v3 - - # https://github.com/docker/login-action - - name: Login to DockerHub - # Only login if not a PR, as PRs only trigger a Docker build and not a push - if: github.event_name != 'pull_request' - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_ACCESS_TOKEN }} - - # Get Metadata for docker_build_postgres step below - - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-postgres-pgcrypto' image - id: meta_build_postgres - uses: docker/metadata-action@v5 - with: - images: dspace/dspace-postgres-pgcrypto - tags: ${{ env.IMAGE_TAGS }} - flavor: ${{ env.TAGS_FLAVOR }} - - - name: Build and push 'dspace-postgres-pgcrypto' image - id: docker_build_postgres - uses: docker/build-push-action@v5 - with: - # Must build out of subdirectory to have access to install script for pgcrypto - context: ./dspace/src/main/docker/dspace-postgres-pgcrypto/ - dockerfile: Dockerfile - platforms: ${{ env.PLATFORMS }} - # For pull requests, we run the Docker build (to ensure no PR changes break the build), - # but we ONLY do an image push to DockerHub if it's NOT a PR - push: ${{ github.event_name != 'pull_request' }} - # Use tags / labels provided by 'docker/metadata-action' above - tags: ${{ steps.meta_build_postgres.outputs.tags }} - labels: ${{ steps.meta_build_postgres.outputs.labels }} + uses: ./.github/workflows/reusable-docker-build.yml + with: + build_id: dspace-postgres-pgcrypto + image_name: dspace/dspace-postgres-pgcrypto + # Must build out of subdirectory to have access to install script for pgcrypto. + # NOTE: this context will build the image based on the Dockerfile in the specified directory + dockerfile_context: ./dspace/src/main/docker/dspace-postgres-pgcrypto/ + secrets: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }} ######################################################################## # Build/Push the 'dspace/dspace-postgres-pgcrypto' image (-loadsql tag) @@ -374,53 +145,16 @@ jobs: dspace-postgres-pgcrypto-loadsql: # Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace' if: github.repository == 'dspace/dspace' - runs-on: ubuntu-latest - - steps: - # https://github.com/actions/checkout - - name: Checkout codebase - uses: actions/checkout@v4 - - # https://github.com/docker/setup-buildx-action - - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v3 - - # https://github.com/docker/setup-qemu-action - - name: Set up QEMU emulation to build for multiple architectures - uses: docker/setup-qemu-action@v3 - - # https://github.com/docker/login-action - - name: Login to DockerHub - # Only login if not a PR, as PRs only trigger a Docker build and not a push - if: github.event_name != 'pull_request' - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_ACCESS_TOKEN }} - - # Get Metadata for docker_build_postgres_loadsql step below - - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-postgres-pgcrypto-loadsql' image - id: meta_build_postgres_loadsql - uses: docker/metadata-action@v5 - with: - images: dspace/dspace-postgres-pgcrypto - tags: ${{ env.IMAGE_TAGS }} - # Suffix all tags with "-loadsql". Otherwise, it uses the same - # tagging logic as the primary 'dspace/dspace-postgres-pgcrypto' image above. - flavor: ${{ env.TAGS_FLAVOR }} - suffix=-loadsql - - - name: Build and push 'dspace-postgres-pgcrypto-loadsql' image - id: docker_build_postgres_loadsql - uses: docker/build-push-action@v5 - with: - # Must build out of subdirectory to have access to install script for pgcrypto - context: ./dspace/src/main/docker/dspace-postgres-pgcrypto-curl/ - dockerfile: Dockerfile - platforms: ${{ env.PLATFORMS }} - # For pull requests, we run the Docker build (to ensure no PR changes break the build), - # but we ONLY do an image push to DockerHub if it's NOT a PR - push: ${{ github.event_name != 'pull_request' }} - # Use tags / labels provided by 'docker/metadata-action' above - tags: ${{ steps.meta_build_postgres_loadsql.outputs.tags }} - labels: ${{ steps.meta_build_postgres_loadsql.outputs.labels }} \ No newline at end of file + uses: ./.github/workflows/reusable-docker-build.yml + with: + build_id: dspace-postgres-pgcrypto-loadsql + image_name: dspace/dspace-postgres-pgcrypto + # Must build out of subdirectory to have access to install script for pgcrypto. + # NOTE: this context will build the image based on the Dockerfile in the specified directory + dockerfile_context: ./dspace/src/main/docker/dspace-postgres-pgcrypto-curl/ + # Suffix all tags with "-loadsql". Otherwise, it uses the same + # tagging logic as the primary 'dspace/dspace-postgres-pgcrypto' image above. + tags_flavor: suffix=-loadsql + secrets: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml new file mode 100644 index 00000000000..46bdab3b682 --- /dev/null +++ b/.github/workflows/reusable-docker-build.yml @@ -0,0 +1,217 @@ +# +# DSpace's reusable Docker build/push workflow. +# +# This is used by docker.yml for all Docker image builds +name: Reusable DSpace Docker Build + +on: + workflow_call: + # Possible Inputs to this reusable job + inputs: + # Build name/id for this Docker build. Used for digest storage to avoid digest overlap between builds. + build_id: + required: true + type: string + # Requires the image name to build (e.g dspace/dspace-test) + image_name: + required: true + type: string + # Optionally the path to the Dockerfile to use for the build. (Default is [dockerfile_context]/Dockerfile) + dockerfile_path: + required: false + type: string + # Optionally the context directory to build the Dockerfile within. Defaults to "." (current directory) + dockerfile_context: + required: false + type: string + # If Docker image should have additional tag flavor details (e.g. a suffix), it may be passed in. + tags_flavor: + required: false + type: string + secrets: + # Requires that Docker login info be passed in as secrets. + DOCKER_USERNAME: + required: true + DOCKER_ACCESS_TOKEN: + required: true + # These URL secrets are optional. When specified & branch checks match, the redeployment code below will trigger. + # Therefore builds which need to trigger redeployment MUST specify these URLs. All others should leave them empty. + REDEPLOY_SANDBOX_URL: + required: false + REDEPLOY_DEMO_URL: + required: false + +# Define shared default settings as environment variables +env: + IMAGE_NAME: ${{ inputs.image_name }} + # Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action) + # For a new commit on default branch (main), use the literal tag 'latest' on Docker image. + # For a new commit on other branches, use the branch name as the tag for Docker image. + # For a new tag, copy that tag name as the tag for Docker image. + IMAGE_TAGS: | + type=raw,value=latest,enable=${{ github.ref_name == github.event.repository.default_branch }} + type=ref,event=branch,enable=${{ github.ref_name != github.event.repository.default_branch }} + type=ref,event=tag + # Define default tag "flavor" for docker/metadata-action per + # https://github.com/docker/metadata-action#flavor-input + # We manage the 'latest' tag ourselves to the 'main' branch (see settings above) + TAGS_FLAVOR: | + latest=false + ${{ inputs.tags_flavor }} + # When these URL variables are specified & required branch matches, then the sandbox or demo site will be redeployed. + # See "Redeploy" steps below for more details. + REDEPLOY_SANDBOX_URL: ${{ secrets.REDEPLOY_SANDBOX_URL }} + REDEPLOY_DEMO_URL: ${{ secrets.REDEPLOY_DEMO_URL }} + # Current DSpace maintenance branch (and architecture) which is deployed to demo.dspace.org / sandbox.dspace.org + # (NOTE: No deployment branch specified for sandbox.dspace.org as it uses the default_branch) + DEPLOY_DEMO_BRANCH: 'dspace-7_x' + DEPLOY_ARCH: 'linux/amd64' + +jobs: + docker-build: + + strategy: + matrix: + # Architectures / Platforms for which we will build Docker images + arch: [ 'linux/amd64', 'linux/arm64' ] + os: [ ubuntu-latest ] + isPr: + - ${{ github.event_name == 'pull_request' }} + # If this is a PR, we ONLY build for AMD64. For PRs we only do a sanity check test to ensure Docker builds work. + # The below exclude therefore ensures we do NOT build ARM64 for PRs. + exclude: + - isPr: true + os: ubuntu-latest + arch: linux/arm64 + + runs-on: ${{ matrix.os }} + + steps: + # https://github.com/actions/checkout + - name: Checkout codebase + uses: actions/checkout@v4 + + # https://github.com/docker/setup-buildx-action + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v3 + + # https://github.com/docker/setup-qemu-action + - name: Set up QEMU emulation to build for multiple architectures + uses: docker/setup-qemu-action@v3 + + # https://github.com/docker/login-action + - name: Login to DockerHub + # Only login if not a PR, as PRs only trigger a Docker build and not a push + if: ${{ ! matrix.isPr }} + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_ACCESS_TOKEN }} + + # https://github.com/docker/metadata-action + # Get Metadata for docker_build_deps step below + - name: Sync metadata (tags, labels) from GitHub to Docker for image + id: meta_build + uses: docker/metadata-action@v5 + with: + images: ${{ env.IMAGE_NAME }} + tags: ${{ env.IMAGE_TAGS }} + flavor: ${{ env.TAGS_FLAVOR }} + + # https://github.com/docker/build-push-action + - name: Build and push image + id: docker_build + uses: docker/build-push-action@v5 + with: + context: ${{ inputs.dockerfile_context || '.' }} + file: ${{ inputs.dockerfile_path }} + platforms: ${{ matrix.arch }} + # For pull requests, we run the Docker build (to ensure no PR changes break the build), + # but we ONLY do an image push to DockerHub if it's NOT a PR + push: ${{ ! matrix.isPr }} + # Use tags / labels provided by 'docker/metadata-action' above + tags: ${{ steps.meta_build.outputs.tags }} + labels: ${{ steps.meta_build.outputs.labels }} + + # Export the digest of Docker build locally (for non PRs only) + - name: Export Docker build digest + if: ${{ ! matrix.isPr }} + run: | + mkdir -p /tmp/digests + digest="${{ steps.docker_build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + # Upload digest to an artifact, so that it can be used in manifest below + - name: Upload Docker build digest to artifact + if: ${{ ! matrix.isPr }} + uses: actions/upload-artifact@v3 + with: + name: digests-${{ inputs.build_id }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + # If this build is NOT a PR and passed in a REDEPLOY_SANDBOX_URL secret, + # Then redeploy https://sandbox.dspace.org if this build is for our deployment architecture and 'main' branch. + - name: Redeploy sandbox.dspace.org (based on main branch) + if: | + !matrix.isPR && + env.REDEPLOY_SANDBOX_URL != '' && + matrix.arch == env.DEPLOY_ARCH && + github.ref_name == github.event.repository.default_branch + run: | + curl -X POST $REDEPLOY_SANDBOX_URL + + # If this build is NOT a PR and passed in a REDEPLOY_DEMO_URL secret, + # Then redeploy https://demo.dspace.org if this build is for our deployment architecture and demo branch. + - name: Redeploy demo.dspace.org (based on maintenace branch) + if: | + !matrix.isPR && + env.REDEPLOY_DEMO_URL != '' && + matrix.arch == env.DEPLOY_ARCH && + github.ref_name == env.DEPLOY_DEMO_BRANCH + run: | + curl -X POST $REDEPLOY_DEMO_URL + + # Merge Docker digests (from various architectures) into a manifest. + # This runs after all Docker builds complete above, and it tells hub.docker.com + # that these builds should be all included in the manifest for this tag. + # (e.g. AMD64 and ARM64 should be listed as options under the same tagged Docker image) + docker-build_manifest: + if: ${{ github.event_name != 'pull_request' }} + runs-on: ubuntu-latest + needs: + - docker-build + steps: + - name: Download Docker build digests + uses: actions/download-artifact@v3 + with: + name: digests-${{ inputs.build_id }} + path: /tmp/digests + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Add Docker metadata for image + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.IMAGE_NAME }} + tags: ${{ env.IMAGE_TAGS }} + flavor: ${{ env.TAGS_FLAVOR }} + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_ACCESS_TOKEN }} + + - name: Create manifest list from digests and push + working-directory: /tmp/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.IMAGE_NAME }}@sha256:%s ' *) + + - name: Inspect image + run: | + docker buildx imagetools inspect ${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }} From af1550d415c72540bb79c80344c706797f27fd2d Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 28 Nov 2023 14:28:14 -0600 Subject: [PATCH 012/939] Ensure dspace-solr redeploys the Solr instances for Demo/Sandbox --- .github/workflows/docker.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 2adceaa4d3c..dd44e92f047 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -121,6 +121,10 @@ jobs: secrets: DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }} + # Enable redeploy of sandbox & demo SOLR instance whenever dspace-solr image changes for deployed branch. + # These URLs MUST use different secrets than 'dspace/dspace' image build above as they are deployed separately. + REDEPLOY_SANDBOX_URL: ${{ secrets.REDEPLOY_SANDBOX_SOLR_URL }} + REDEPLOY_DEMO_URL: ${{ secrets.REDEPLOY_DEMO_SOLR_URL }} ########################################################### # Build/Push the 'dspace/dspace-postgres-pgcrypto' image From 7524d169f5782eab26967b3eba90c3247576ab72 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 28 Nov 2023 16:59:41 -0600 Subject: [PATCH 013/939] Remove unused env variables from docker.yml build script --- .github/workflows/docker.yml | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index dd44e92f047..338c7371f61 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -16,22 +16,6 @@ on: permissions: contents: read # to fetch code (actions/checkout) -# Define shared environment variables for all jobs below -env: - # Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action) - # For a new commit on default branch (main), use the literal tag 'latest' on Docker image. - # For a new commit on other branches, use the branch name as the tag for Docker image. - # For a new tag, copy that tag name as the tag for Docker image. - IMAGE_TAGS: | - type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }} - type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }} - type=ref,event=tag - # Define default tag "flavor" for docker/metadata-action per - # https://github.com/docker/metadata-action#flavor-input - # We manage the 'latest' tag ourselves to the 'main' branch (see settings above) - TAGS_FLAVOR: | - latest=false - jobs: #################################################### # Build/Push the 'dspace/dspace-dependencies' image. From 16112a44d8f8ba5c097c2f0f383da53b644553c1 Mon Sep 17 00:00:00 2001 From: Shankeerthan Kasilingam Date: Tue, 5 Dec 2023 16:22:01 +0530 Subject: [PATCH 014/939] fix: Failure of org.dspace.app.rest.SitemapRestControllerIT when running locally (cherry picked from commit 44fc15f74baf6aa23c70f3b73975f3c82401e499) --- .../dspace/app/rest/SitemapRestControllerIT.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SitemapRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SitemapRestControllerIT.java index 175fb34e6ca..04d22718e84 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SitemapRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SitemapRestControllerIT.java @@ -216,7 +216,12 @@ public void testSitemap_sitemapIndexXml() throws Exception { //** THEN ** .andExpect(status().isOk()) //We expect the content type to match - .andExpect(content().contentType("application/xml;charset=UTF-8")) + .andExpect(res -> { + String actual = res.getResponse().getContentType(); + assertTrue("Content Type", + "text/xml;charset=UTF-8".equals(actual) || + "application/xml;charset=UTF-8".equals(actual)); + }) .andReturn(); String response = result.getResponse().getContentAsString(); @@ -232,7 +237,12 @@ public void testSitemap_sitemap0Xml() throws Exception { //** THEN ** .andExpect(status().isOk()) //We expect the content type to match - .andExpect(content().contentType("application/xml;charset=UTF-8")) + .andExpect(res -> { + String actual = res.getResponse().getContentType(); + assertTrue("Content Type", + "text/xml;charset=UTF-8".equals(actual) || + "application/xml;charset=UTF-8".equals(actual)); + }) .andReturn(); String response = result.getResponse().getContentAsString(); From 75365372067140d6407aeb4d9fb301722c3d1e72 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Fri, 29 Dec 2023 05:55:48 +0100 Subject: [PATCH 015/939] 90830/104654: issue PR#8267 - Only first 10 predb records resynced (default size) - IT proving issue --- .../SolrDatabaseResyncIT.java | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/dspace-api/src/test/java/org/dspace/app/solrdatabaseresync/SolrDatabaseResyncIT.java b/dspace-api/src/test/java/org/dspace/app/solrdatabaseresync/SolrDatabaseResyncIT.java index 4fa881257e0..fd7c3c4934f 100644 --- a/dspace-api/src/test/java/org/dspace/app/solrdatabaseresync/SolrDatabaseResyncIT.java +++ b/dspace-api/src/test/java/org/dspace/app/solrdatabaseresync/SolrDatabaseResyncIT.java @@ -35,6 +35,9 @@ import org.junit.Before; import org.junit.Test; +/** + * IT for {@link org.dspace.app.solrdatabaseresync.SolrDatabaseResyncIT} + */ public class SolrDatabaseResyncIT extends AbstractIntegrationTestWithDatabase { private final ConfigurationService configurationService = @@ -48,6 +51,15 @@ public class SolrDatabaseResyncIT extends AbstractIntegrationTestWithDatabase { private Collection col; private Item item1; private Item item2; + private Item item3; + private Item item4; + private Item item5; + private Item item6; + private Item item7; + private Item item8; + private Item item9; + private Item item10; + private Item item11; @Before public void setUp() throws Exception { @@ -75,6 +87,16 @@ public void setUp() throws Exception { .withSubject("TestingForMore") .build(); + item3 = ItemBuilder.createItem(context, col).withTitle("Public item 3").build(); + item4 = ItemBuilder.createItem(context, col).withTitle("Public item 4").build(); + item5 = ItemBuilder.createItem(context, col).withTitle("Public item 5").build(); + item6 = ItemBuilder.createItem(context, col).withTitle("Public item 6").build(); + item7 = ItemBuilder.createItem(context, col).withTitle("Public item 7").build(); + item8 = ItemBuilder.createItem(context, col).withTitle("Public item 8").build(); + item9 = ItemBuilder.createItem(context, col).withTitle("Public item 9").build(); + item10 = ItemBuilder.createItem(context, col).withTitle("Public item 10").build(); + item11 = ItemBuilder.createItem(context, col).withTitle("Public item 11").build(); + context.setDispatcher("noindex"); } @@ -83,12 +105,30 @@ public void solrPreDBStatusExistingItemTest() throws Exception { // Items were created, they should contain a predb status in solr assertHasPreDBStatus(item1); assertHasPreDBStatus(item2); + assertHasPreDBStatus(item3); + assertHasPreDBStatus(item4); + assertHasPreDBStatus(item5); + assertHasPreDBStatus(item6); + assertHasPreDBStatus(item7); + assertHasPreDBStatus(item8); + assertHasPreDBStatus(item9); + assertHasPreDBStatus(item10); + assertHasPreDBStatus(item11); performSolrDatabaseResyncScript(); // Database status script was performed, their predb status should be removed assertHasNoPreDBStatus(item1); assertHasNoPreDBStatus(item2); + assertHasNoPreDBStatus(item3); + assertHasNoPreDBStatus(item4); + assertHasNoPreDBStatus(item5); + assertHasNoPreDBStatus(item6); + assertHasNoPreDBStatus(item7); + assertHasNoPreDBStatus(item8); + assertHasNoPreDBStatus(item9); + assertHasNoPreDBStatus(item10); + assertHasNoPreDBStatus(item11); context.restoreAuthSystemState(); } @@ -98,22 +138,50 @@ public void solrPreDBStatusRemovedItemTest() throws Exception { // Items were created, they should contain a predb status in solr assertHasPreDBStatus(item1); assertHasPreDBStatus(item2); + assertHasPreDBStatus(item3); + assertHasPreDBStatus(item4); + assertHasPreDBStatus(item5); + assertHasPreDBStatus(item6); + assertHasPreDBStatus(item7); + assertHasPreDBStatus(item8); + assertHasPreDBStatus(item9); + assertHasPreDBStatus(item10); + assertHasPreDBStatus(item11); collectionService.delete(context, col); // Items were deleted, they should still contain a predb status in solr for now assertHasPreDBStatus(item1); assertHasPreDBStatus(item2); + assertHasPreDBStatus(item3); + assertHasPreDBStatus(item4); + assertHasPreDBStatus(item5); + assertHasPreDBStatus(item6); + assertHasPreDBStatus(item7); + assertHasPreDBStatus(item8); + assertHasPreDBStatus(item9); + assertHasPreDBStatus(item10); + assertHasPreDBStatus(item11); performSolrDatabaseResyncScript(); // Database status script was performed, their solr document should have been removed assertNoSolrDocument(item1); assertNoSolrDocument(item2); + assertNoSolrDocument(item3); + assertNoSolrDocument(item4); + assertNoSolrDocument(item5); + assertNoSolrDocument(item6); + assertNoSolrDocument(item7); + assertNoSolrDocument(item8); + assertNoSolrDocument(item9); + assertNoSolrDocument(item10); + assertNoSolrDocument(item11); context.restoreAuthSystemState(); } + public void assertHasNoPreDBStatus(Item item) throws Exception { assertNotEquals(STATUS_FIELD_PREDB, getStatus(item)); } From 91f53c22870bcb28719ded08bf642d269d13f4a2 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Fri, 29 Dec 2023 05:58:25 +0100 Subject: [PATCH 016/939] 90830/104654: issue PR#8267 - Only first 10 predb records resynced (default size) - Fix: - Query with 0 rows to get total number of records to process - Process them in batches of 100 --- .../SolrDatabaseResyncCli.java | 70 +++++++++++-------- .../SolrDatabaseResyncIT.java | 1 + 2 files changed, 43 insertions(+), 28 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/solrdatabaseresync/SolrDatabaseResyncCli.java b/dspace-api/src/main/java/org/dspace/app/solrdatabaseresync/SolrDatabaseResyncCli.java index f901c9ca569..aac42ce1acf 100644 --- a/dspace-api/src/main/java/org/dspace/app/solrdatabaseresync/SolrDatabaseResyncCli.java +++ b/dspace-api/src/main/java/org/dspace/app/solrdatabaseresync/SolrDatabaseResyncCli.java @@ -105,35 +105,17 @@ private void performStatusUpdate(Context context) throws SearchServiceException, solrQuery.addFilterQuery(dateRangeFilter); solrQuery.addField(SearchUtils.RESOURCE_ID_FIELD); solrQuery.addField(SearchUtils.RESOURCE_UNIQUE_ID); + solrQuery.setRows(0); QueryResponse response = solrSearchCore.getSolr().query(solrQuery, solrSearchCore.REQUEST_METHOD); - - if (response != null) { - logInfoAndOut(response.getResults().size() + " items found to process"); - - for (SolrDocument doc : response.getResults()) { - String uuid = (String) doc.getFirstValue(SearchUtils.RESOURCE_ID_FIELD); - String uniqueId = (String) doc.getFirstValue(SearchUtils.RESOURCE_UNIQUE_ID); - logDebugAndOut("Processing item with UUID: " + uuid); - - Optional indexableObject = Optional.empty(); - try { - indexableObject = indexObjectServiceFactory - .getIndexableObjectFactory(uniqueId).findIndexableObject(context, uuid); - } catch (SQLException e) { - log.warn("An exception occurred when attempting to retrieve item with UUID \"" + uuid + - "\" from the database, removing related solr document", e); - } - - try { - if (indexableObject.isPresent()) { - logDebugAndOut("Item exists in DB, updating solr document"); - updateItem(context, indexableObject.get()); - } else { - logDebugAndOut("Item doesn't exist in DB, removing solr document"); - removeItem(context, uniqueId); - } - } catch (SQLException | IOException e) { - log.error(e.getMessage(), e); + if (response != null && response.getResults() != null) { + long nrOfPreDBResults = response.getResults().getNumFound(); + if (nrOfPreDBResults > 0) { + logInfoAndOut(nrOfPreDBResults + " items found to process"); + int batchSize = configurationService.getIntProperty("script.solr-database-resync.batch-size", 100); + for (int start = 0; start < nrOfPreDBResults; start += batchSize) { + solrQuery.setStart(start); + solrQuery.setRows(batchSize); + performStatusUpdateOnNextBatch(context, solrQuery); } } } @@ -141,6 +123,38 @@ private void performStatusUpdate(Context context) throws SearchServiceException, indexingService.commit(); } + private void performStatusUpdateOnNextBatch(Context context, SolrQuery solrQuery) + throws SolrServerException, IOException { + QueryResponse response = solrSearchCore.getSolr().query(solrQuery, solrSearchCore.REQUEST_METHOD); + + for (SolrDocument doc : response.getResults()) { + String uuid = (String) doc.getFirstValue(SearchUtils.RESOURCE_ID_FIELD); + String uniqueId = (String) doc.getFirstValue(SearchUtils.RESOURCE_UNIQUE_ID); + logDebugAndOut("Processing item with UUID: " + uuid); + + Optional indexableObject = Optional.empty(); + try { + indexableObject = indexObjectServiceFactory + .getIndexableObjectFactory(uniqueId).findIndexableObject(context, uuid); + } catch (SQLException e) { + log.warn("An exception occurred when attempting to retrieve item with UUID \"" + uuid + + "\" from the database, removing related solr document", e); + } + + try { + if (indexableObject.isPresent()) { + logDebugAndOut("Item exists in DB, updating solr document"); + updateItem(context, indexableObject.get()); + } else { + logDebugAndOut("Item doesn't exist in DB, removing solr document"); + removeItem(context, uniqueId); + } + } catch (SQLException | IOException e) { + log.error(e.getMessage(), e); + } + } + } + private void updateItem(Context context, IndexableObject indexableObject) throws SolrServerException, IOException { Map fieldModifier = new HashMap<>(1); fieldModifier.put("remove", STATUS_FIELD_PREDB); diff --git a/dspace-api/src/test/java/org/dspace/app/solrdatabaseresync/SolrDatabaseResyncIT.java b/dspace-api/src/test/java/org/dspace/app/solrdatabaseresync/SolrDatabaseResyncIT.java index fd7c3c4934f..d1fa0db9089 100644 --- a/dspace-api/src/test/java/org/dspace/app/solrdatabaseresync/SolrDatabaseResyncIT.java +++ b/dspace-api/src/test/java/org/dspace/app/solrdatabaseresync/SolrDatabaseResyncIT.java @@ -65,6 +65,7 @@ public class SolrDatabaseResyncIT extends AbstractIntegrationTestWithDatabase { public void setUp() throws Exception { super.setUp(); configurationService.setProperty("solr-database-resync.time-until-reindex", 1); + configurationService.setProperty("script.solr-database-resync.batch-size", 5); ServiceManager serviceManager = DSpaceServicesFactory.getInstance().getServiceManager(); searchService = serviceManager.getServiceByName(null, MockSolrSearchCore.class); From c7113e5d2450f7d7d8f4c8a26e83c43a0477aa4e Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Fri, 27 Oct 2023 14:10:49 +0200 Subject: [PATCH 017/939] Correct response of byMetadataAndCollection operation of Controlled Vocabularies Endpoint when no controlled vocabulary is available for the specified metadata and collection (cherry picked from commit eb2e4f1155864d190a3458210419c02978519ec0) --- .../dspace/app/rest/repository/VocabularyRestRepository.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java index fcc37d13160..783ed418233 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java @@ -100,6 +100,9 @@ public VocabularyRest findByMetadataAndCollection( } String authorityName = cas.getChoiceAuthorityName(tokens[0], tokens[1], tokens[2], collection); + if (authorityName == null) { + return null; + } ChoiceAuthority source = cas.getChoiceAuthorityByAuthorityName(authorityName); return authorityUtils.convertAuthority(source, authorityName, utils.obtainProjection()); } From 14c6ccce21d0cb563b9d9ba88a73309d910e28ce Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Fri, 27 Oct 2023 14:26:37 +0200 Subject: [PATCH 018/939] Add missing test for byMetadataAndCollection operation of Controlled Vocabularies endpoint (cherry picked from commit a294f996cf8c6169d6414609363997e118bc41d0) --- .../app/rest/VocabularyRestRepositoryIT.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java index f9b31fb06de..81d2dba6791 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java @@ -394,6 +394,20 @@ public void findByMetadataAndCollectionTest() throws Exception { ))); } + @Test + public void findByMetadataAndCollectionWithMetadataWithoutVocabularyTest() throws Exception { + context.turnOffAuthorisationSystem(); + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test collection") + .build(); + context.restoreAuthSystemState(); + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/submission/vocabularies/search/byMetadataAndCollection") + .param("metadata", "dc.title") + .param("collection", collection.getID().toString())) + .andExpect(status().isNoContent()); + } + @Test public void findByMetadataAndCollectionUnprocessableEntityTest() throws Exception { context.turnOffAuthorisationSystem(); From 37ed65850fcba45ddf9dbbe615bd5f2792e406ac Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Tue, 9 Jan 2024 11:34:52 +0300 Subject: [PATCH 019/939] dspace-api: fix typo in AuthorizeServiceImpl log (cherry picked from commit 127b1ae868534b318f8fc5c0c21d300502183961) --- .../main/java/org/dspace/authorize/AuthorizeServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java index 5dffe5fdfc1..beff6fdc48e 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java @@ -895,7 +895,7 @@ private boolean performCheck(Context context, String query) throws SQLException return true; } } catch (SearchServiceException e) { - log.error("Failed getting getting community/collection admin status for " + log.error("Failed getting community/collection admin status for " + context.getCurrentUser().getEmail() + " The search error is: " + e.getMessage() + " The search resourceType filter was: " + query); } From b8d00afd121404446a5021020e9f834951b9dfc3 Mon Sep 17 00:00:00 2001 From: William Welling Date: Mon, 8 Jan 2024 10:22:52 -0600 Subject: [PATCH 020/939] Return headers for HEAD request (cherry picked from commit 64ae49a29ff4edb87053e8319b23a6a60696f65a) --- .../dspace/app/rest/BitstreamRestController.java | 6 ++++++ .../app/rest/utils/HttpHeadersInitializer.java | 15 ++++++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java index ebd84270f28..063e4760fa4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java @@ -167,6 +167,12 @@ public ResponseEntity retrieve(@PathVariable UUID uuid, HttpServletResponse resp //Send the data if (httpHeadersInitializer.isValid()) { HttpHeaders httpHeaders = httpHeadersInitializer.initialiseHeaders(); + + if (RequestMethod.HEAD.name().equals(request.getMethod())) { + log.debug("HEAD request - no response body"); + return ResponseEntity.ok().headers(httpHeaders).build(); + } + return ResponseEntity.ok().headers(httpHeaders).body(bitstreamResource); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java index a69da4c5e86..854aec8868a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java @@ -33,7 +33,6 @@ public class HttpHeadersInitializer { protected final Logger log = LoggerFactory.getLogger(this.getClass()); - private static final String METHOD_HEAD = "HEAD"; private static final String MULTIPART_BOUNDARY = "MULTIPART_BYTERANGES"; private static final String CONTENT_TYPE_MULTITYPE_WITH_BOUNDARY = "multipart/byteranges; boundary=" + MULTIPART_BOUNDARY; @@ -165,16 +164,14 @@ public HttpHeaders initialiseHeaders() throws IOException { httpHeaders.put(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, Collections.singletonList(HttpHeaders.ACCEPT_RANGES)); - httpHeaders.put(CONTENT_DISPOSITION, Collections.singletonList(String.format(CONTENT_DISPOSITION_FORMAT, - disposition, - encodeText(fileName)))); - log.debug("Content-Disposition : {}", disposition); - // Content phase - if (METHOD_HEAD.equals(request.getMethod())) { - log.debug("HEAD request - skipping content"); - return null; + // distposition may be null here if contentType is null + if (!isNullOrEmpty(disposition)) { + httpHeaders.put(CONTENT_DISPOSITION, Collections.singletonList(String.format(CONTENT_DISPOSITION_FORMAT, + disposition, + encodeText(fileName)))); } + log.debug("Content-Disposition : {}", disposition); return httpHeaders; From ebbae391d74775fb6c406d56ee0038e115ec069a Mon Sep 17 00:00:00 2001 From: William Welling Date: Wed, 10 Jan 2024 13:51:11 -0600 Subject: [PATCH 021/939] Add content-length to bitstream (cherry picked from commit 6be7e4e37047c3f33d4aec371240a3bf2f02a3ef) --- .../dspace/app/rest/BitstreamRestController.java | 1 + .../app/rest/utils/HttpHeadersInitializer.java | 4 ++++ .../dspace/app/rest/BitstreamRestControllerIT.java | 14 +++++++++++++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java index 063e4760fa4..9b5ede37c85 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java @@ -138,6 +138,7 @@ public ResponseEntity retrieve(@PathVariable UUID uuid, HttpServletResponse resp .withBufferSize(BUFFER_SIZE) .withFileName(name) .withChecksum(bit.getChecksum()) + .withLength(bit.getSizeBytes()) .withMimetype(mimetype) .with(request) .with(response); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java index 854aec8868a..d68c710a3c7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java @@ -14,6 +14,7 @@ import java.io.IOException; import java.util.Arrays; import java.util.Collections; +import java.util.Objects; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -143,6 +144,9 @@ public HttpHeaders initialiseHeaders() throws IOException { if (checksum != null) { httpHeaders.put(ETAG, Collections.singletonList(checksum)); } + if (Objects.nonNull((Long.valueOf(this.length)))) { + httpHeaders.put(HttpHeaders.CONTENT_LENGTH, Collections.singletonList(String.valueOf(this.length))); + } httpHeaders.put(LAST_MODIFIED, Collections.singletonList(FastHttpDateFormat.formatDate(lastModified))); httpHeaders.put(EXPIRES, Collections.singletonList(FastHttpDateFormat.formatDate( System.currentTimeMillis() + DEFAULT_EXPIRE_TIME))); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java index 4813cc65969..47c48846ad2 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java @@ -205,6 +205,18 @@ public void retrieveFullBitstream() throws Exception { } context.restoreAuthSystemState(); + //** WHEN ** + // we want to know what we are downloading before we download it + getClient().perform(head("/api/core/bitstreams/" + bitstream.getID() + "/content")) + //** THEN ** + .andExpect(status().isOk()) + + //The Content Length must match the full length + .andExpect(header().longValue("Content-Length", bitstreamContent.getBytes().length)) + .andExpect(header().string("Content-Type", "text/plain;charset=UTF-8")) + .andExpect(header().string("ETag", "\"" + bitstream.getChecksum() + "\"")) + .andExpect(content().bytes(new byte[] {})); + //** WHEN ** //We download the bitstream getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) @@ -231,7 +243,7 @@ public void retrieveFullBitstream() throws Exception { .andExpect(status().isNotModified()); //The download and head request should also be logged as a statistics record - checkNumberOfStatsRecords(bitstream, 2); + checkNumberOfStatsRecords(bitstream, 3); } @Test From 9a0db2a642025423dcc8d3c890350057b79c821e Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Wed, 10 Jan 2024 13:08:02 +0000 Subject: [PATCH 022/939] Skip recording usage event if administrator (cherry picked from commit 73e5c43f7c565166a7b4b920a8958be0cc0c6c01) --- .../dspace/statistics/SolrLoggerServiceImpl.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java b/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java index 97585f5a47c..5f976bbfd94 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java @@ -81,6 +81,7 @@ import org.apache.solr.common.params.ModifiableSolrParams; import org.apache.solr.common.params.ShardParams; import org.apache.solr.common.util.NamedList; +import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.Collection; @@ -146,6 +147,8 @@ public class SolrLoggerServiceImpl implements SolrLoggerService, InitializingBea private SolrStatisticsCore solrStatisticsCore; @Autowired private GeoIpService geoIpService; + @Autowired + private AuthorizeService authorizeService; /** URL to the current-year statistics core. Prior-year shards will have a year suffixed. */ private String statisticsCoreURL; @@ -219,6 +222,16 @@ public void postView(DSpaceObject dspaceObject, HttpServletRequest request, @Override public void postView(DSpaceObject dspaceObject, HttpServletRequest request, EPerson currentUser, String referrer) { + Context context = new Context(); + // Do not record statistics for Admin users + try { + if (authorizeService.isAdmin(context, currentUser)) { + return; + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + if (solr == null) { return; } From dc57aceeaf74204326862a4d0efb781789c2b9aa Mon Sep 17 00:00:00 2001 From: nwoodward Date: Tue, 16 Jan 2024 13:28:40 -0600 Subject: [PATCH 023/939] catch exceptions stemming from invalid id's (cherry picked from commit 848df25984b8d6cabb8b6b0c831070ffed66919a) --- .../org/dspace/content/BitstreamServiceImpl.java | 13 +++++++++---- .../java/org/dspace/content/BundleServiceImpl.java | 13 +++++++++---- .../org/dspace/content/CollectionServiceImpl.java | 13 +++++++++---- .../org/dspace/content/CommunityServiceImpl.java | 13 +++++++++---- .../java/org/dspace/content/ItemServiceImpl.java | 13 +++++++++---- .../java/org/dspace/eperson/EPersonServiceImpl.java | 13 +++++++++---- .../java/org/dspace/eperson/GroupServiceImpl.java | 13 +++++++++---- 7 files changed, 63 insertions(+), 28 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java index 691d38f0303..e23e5ce2c82 100644 --- a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java @@ -458,10 +458,15 @@ public int countTotal(Context context) throws SQLException { @Override public Bitstream findByIdOrLegacyId(Context context, String id) throws SQLException { - if (StringUtils.isNumeric(id)) { - return findByLegacyId(context, Integer.parseInt(id)); - } else { - return find(context, UUID.fromString(id)); + try { + if (StringUtils.isNumeric(id)) { + return findByLegacyId(context, Integer.parseInt(id)); + } else { + return find(context, UUID.fromString(id)); + } + } catch (IllegalArgumentException e) { + // Not a valid legacy ID or valid UUID + return null; } } diff --git a/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java index 546d48d4306..3ba90c8cc2a 100644 --- a/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java @@ -562,10 +562,15 @@ public int getSupportsTypeConstant() { @Override public Bundle findByIdOrLegacyId(Context context, String id) throws SQLException { - if (StringUtils.isNumeric(id)) { - return findByLegacyId(context, Integer.parseInt(id)); - } else { - return find(context, UUID.fromString(id)); + try { + if (StringUtils.isNumeric(id)) { + return findByLegacyId(context, Integer.parseInt(id)); + } else { + return find(context, UUID.fromString(id)); + } + } catch (IllegalArgumentException e) { + // Not a valid legacy ID or valid UUID + return null; } } diff --git a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java index 84ca1692ccf..0d9507e3aa3 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -895,10 +895,15 @@ public void updateLastModified(Context context, Collection collection) throws SQ @Override public Collection findByIdOrLegacyId(Context context, String id) throws SQLException { - if (StringUtils.isNumeric(id)) { - return findByLegacyId(context, Integer.parseInt(id)); - } else { - return find(context, UUID.fromString(id)); + try { + if (StringUtils.isNumeric(id)) { + return findByLegacyId(context, Integer.parseInt(id)); + } else { + return find(context, UUID.fromString(id)); + } + } catch (IllegalArgumentException e) { + // Not a valid legacy ID or valid UUID + return null; } } diff --git a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java index 15ac1c58a69..045adc229e7 100644 --- a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java @@ -694,10 +694,15 @@ public void updateLastModified(Context context, Community community) { @Override public Community findByIdOrLegacyId(Context context, String id) throws SQLException { - if (StringUtils.isNumeric(id)) { - return findByLegacyId(context, Integer.parseInt(id)); - } else { - return find(context, UUID.fromString(id)); + try { + if (StringUtils.isNumeric(id)) { + return findByLegacyId(context, Integer.parseInt(id)); + } else { + return find(context, UUID.fromString(id)); + } + } catch (IllegalArgumentException e) { + // Not a valid legacy ID or valid UUID + return null; } } diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java index ebea2aa5b82..26a61877fe7 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -1609,10 +1609,15 @@ protected void getAuthoritiesAndConfidences(String fieldKey, Collection collecti @Override public Item findByIdOrLegacyId(Context context, String id) throws SQLException { - if (StringUtils.isNumeric(id)) { - return findByLegacyId(context, Integer.parseInt(id)); - } else { - return find(context, UUID.fromString(id)); + try { + if (StringUtils.isNumeric(id)) { + return findByLegacyId(context, Integer.parseInt(id)); + } else { + return find(context, UUID.fromString(id)); + } + } catch (IllegalArgumentException e) { + // Not a valid legacy ID or valid UUID + return null; } } diff --git a/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java index 66fe6562ea2..453d5d0726b 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java @@ -142,10 +142,15 @@ public EPerson getSystemEPerson(Context c) @Override public EPerson findByIdOrLegacyId(Context context, String id) throws SQLException { - if (StringUtils.isNumeric(id)) { - return findByLegacyId(context, Integer.parseInt(id)); - } else { - return find(context, UUID.fromString(id)); + try { + if (StringUtils.isNumeric(id)) { + return findByLegacyId(context, Integer.parseInt(id)); + } else { + return find(context, UUID.fromString(id)); + } + } catch (IllegalArgumentException e) { + // Not a valid legacy ID or valid UUID + return null; } } diff --git a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java index b8d8c75d0f2..730053e42ce 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java @@ -872,10 +872,15 @@ protected Set getChildren(Map> parents, UUID parent) { @Override public Group findByIdOrLegacyId(Context context, String id) throws SQLException { - if (org.apache.commons.lang3.StringUtils.isNumeric(id)) { - return findByLegacyId(context, Integer.parseInt(id)); - } else { - return find(context, UUIDUtils.fromString(id)); + try { + if (StringUtils.isNumeric(id)) { + return findByLegacyId(context, Integer.parseInt(id)); + } else { + return find(context, UUID.fromString(id)); + } + } catch (IllegalArgumentException e) { + // Not a valid legacy ID or valid UUID + return null; } } From a22a01aa5c26dc49316523c089326d9c7a3568eb Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Mon, 22 Jan 2024 15:14:54 +0100 Subject: [PATCH 024/939] search on labels, not values on DCInputs (cherry picked from commit 5ebe1a9402d8d0be85284d722f65bc71836113df) --- .../java/org/dspace/content/authority/DCInputAuthority.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java index b1d8cf36a5d..902bded33ef 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java @@ -156,7 +156,8 @@ public Choices getMatches(String query, int start, int limit, String locale) { int found = 0; List v = new ArrayList(); for (int i = 0; i < valuesLocale.length; ++i) { - if (query == null || StringUtils.containsIgnoreCase(valuesLocale[i], query)) { + // In a DCInputAuthority context, a user will want to query the labels, not the values + if (query == null || StringUtils.containsIgnoreCase(labelsLocale[i], query)) { if (found >= start && v.size() < limit) { v.add(new Choice(null, valuesLocale[i], labelsLocale[i])); if (valuesLocale[i].equalsIgnoreCase(query)) { From 25496630810e535c17588a965adc33740d8b6384 Mon Sep 17 00:00:00 2001 From: milanmajchrak Date: Sat, 27 Jan 2024 13:38:22 +0100 Subject: [PATCH 025/939] The cas.init() method is called in the VocabularyRestRepositoryIT#setup() method (cherry picked from commit 85b5e06fb0946a15f71abaa1d27ef5914f7b8df2) --- .../java/org/dspace/app/rest/VocabularyRestRepositoryIT.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java index 81d2dba6791..30890d7ef83 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java @@ -92,6 +92,11 @@ public void setup() throws Exception { // the properties that we're altering above and this is only used within the tests DCInputAuthority.reset(); pluginService.clearNamedPluginClasses(); + + // The following line is needed to call init() method in the ChoiceAuthorityServiceImpl class, without it + // the `submissionConfigService` will be null what will cause a NPE in the clearCache() method + // https://github.com/DSpace/DSpace/issues/9292 + cas.getChoiceAuthoritiesNames(); cas.clearCache(); context.turnOffAuthorisationSystem(); From a0ba470197613873bc18031daad9d760a689b107 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 2 Feb 2024 09:02:19 -0600 Subject: [PATCH 026/939] Fix "cannot override networks.dspacenet" by updating all compose files to reference 'dspacenet' network --- docker-compose-cli.yml | 12 ++++++------ docker-compose.yml | 2 +- dspace/src/main/docker-compose/README.md | 1 + .../main/docker-compose/docker-compose-angular.yml | 8 +++++--- .../src/main/docker-compose/docker-compose-iiif.yml | 8 +++++--- .../docker-compose/docker-compose-shibboleth.yml | 8 +++++--- 6 files changed, 23 insertions(+), 16 deletions(-) diff --git a/docker-compose-cli.yml b/docker-compose-cli.yml index 9c66fed6835..4f4d4a18916 100644 --- a/docker-compose-cli.yml +++ b/docker-compose-cli.yml @@ -1,5 +1,10 @@ version: "3.7" - +networks: + # Default to using network named 'dspacenet' from docker-compose.yml. + # Its full name will be prepended with the project name (e.g. "-p d7" means it will be named "d7_dspacenet") + default: + name: ${COMPOSE_PROJECT_NAME}_dspacenet + external: true services: dspace-cli: image: "${DOCKER_OWNER:-dspace}/dspace-cli:${DSPACE_VER:-dspace-7_x}" @@ -26,13 +31,8 @@ services: - ./dspace/config:/dspace/config entrypoint: /dspace/bin/dspace command: help - networks: - - dspacenet tty: true stdin_open: true volumes: assetstore: - -networks: - dspacenet: diff --git a/docker-compose.yml b/docker-compose.yml index 6c161504072..7801a681031 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -36,7 +36,7 @@ services: depends_on: - dspacedb networks: - dspacenet: + - dspacenet ports: - published: 8080 target: 8080 diff --git a/dspace/src/main/docker-compose/README.md b/dspace/src/main/docker-compose/README.md index 35a6e605543..1c9acea30cc 100644 --- a/dspace/src/main/docker-compose/README.md +++ b/dspace/src/main/docker-compose/README.md @@ -78,6 +78,7 @@ docker-compose -p d7 up -d ``` docker-compose -p d7 -f docker-compose.yml -f dspace/src/main/docker-compose/docker-compose-angular.yml up -d ``` +NOTE: This starts the UI in development mode. It will take a few minutes to see the UI as the Angular code needs to be compiled. ## Run DSpace REST and DSpace Angular from local branches diff --git a/dspace/src/main/docker-compose/docker-compose-angular.yml b/dspace/src/main/docker-compose/docker-compose-angular.yml index 00dde2e8318..8f9bdf94b38 100644 --- a/dspace/src/main/docker-compose/docker-compose-angular.yml +++ b/dspace/src/main/docker-compose/docker-compose-angular.yml @@ -8,7 +8,11 @@ version: '3.7' networks: - dspacenet: + # Default to using network named 'dspacenet' from docker-compose.yml. + # Its full name will be prepended with the project name (e.g. "-p d7" means it will be named "d7_dspacenet") + default: + name: ${COMPOSE_PROJECT_NAME}_dspacenet + external: true services: dspace-angular: container_name: dspace-angular @@ -24,8 +28,6 @@ services: DSPACE_REST_PORT: 8080 DSPACE_REST_NAMESPACE: /server image: dspace/dspace-angular:dspace-7_x - networks: - dspacenet: ports: - published: 4000 target: 4000 diff --git a/dspace/src/main/docker-compose/docker-compose-iiif.yml b/dspace/src/main/docker-compose/docker-compose-iiif.yml index 2ab58d9014f..d795192f13c 100644 --- a/dspace/src/main/docker-compose/docker-compose-iiif.yml +++ b/dspace/src/main/docker-compose/docker-compose-iiif.yml @@ -12,7 +12,11 @@ # version: '3.7' networks: - dspacenet: + # Default to using network named 'dspacenet' from docker-compose.yml. + # Its full name will be prepended with the project name (e.g. "-p d7" means it will be named "d7_dspacenet") + default: + name: ${COMPOSE_PROJECT_NAME}_dspacenet + external: true services: dspace-iiif: container_name: dspace-iiif @@ -21,8 +25,6 @@ services: # Using UCLA Library image as it seems to be most maintained at this time. There is no official image. # https://hub.docker.com/r/uclalibrary/cantaloupe image: uclalibrary/cantaloupe:5.0.4-0 - networks: - dspacenet: ports: - '8182:8182' # For a guide of environment variables that can be used, see diff --git a/dspace/src/main/docker-compose/docker-compose-shibboleth.yml b/dspace/src/main/docker-compose/docker-compose-shibboleth.yml index 58f1527d6cc..33eadcb142d 100644 --- a/dspace/src/main/docker-compose/docker-compose-shibboleth.yml +++ b/dspace/src/main/docker-compose/docker-compose-shibboleth.yml @@ -12,7 +12,11 @@ # version: '3.7' networks: - dspacenet: + # Default to using network named 'dspacenet' from docker-compose.yml. + # Its full name will be prepended with the project name (e.g. "-p d7" means it will be named "d7_dspacenet") + default: + name: ${COMPOSE_PROJECT_NAME}_dspacenet + external: true services: dspace-shibboleth: container_name: dspace-shibboleth @@ -22,8 +26,6 @@ services: build: # Must be relative to root, so that it can be built alongside [src]/docker-compose.yml context: ./dspace/src/main/docker/dspace-shibboleth - networks: - dspacenet: ports: - published: 80 target: 80 From 250e82af9d2b1c812341dc4af2294010a603f36d Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 2 Feb 2024 09:13:16 -0600 Subject: [PATCH 027/939] Fix build issues with dockersolr by using "additional_contexts" to pass solr config path to Dockerfile. --- docker-compose.yml | 6 ++++-- dspace/src/main/docker/dspace-solr/Dockerfile | 10 ++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 7801a681031..a7894135c6a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -89,8 +89,10 @@ services: container_name: dspacesolr image: "${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-dspace-7_x}" build: - context: . - dockerfile: ./dspace/src/main/docker/dspace-solr/Dockerfile + context: ./dspace/src/main/docker/dspace-solr/ + # Provide path to Solr configs necessary to build Docker image + additional_contexts: + solrconfigs: ./dspace/solr/ args: SOLR_VERSION: "${SOLR_VER:-8.11}" networks: diff --git a/dspace/src/main/docker/dspace-solr/Dockerfile b/dspace/src/main/docker/dspace-solr/Dockerfile index 9fe9adf9440..eb8e93493fa 100644 --- a/dspace/src/main/docker/dspace-solr/Dockerfile +++ b/dspace/src/main/docker/dspace-solr/Dockerfile @@ -26,10 +26,12 @@ RUN mkdir -p $AUTHORITY_CONFIGSET_PATH && \ mkdir -p $SEARCH_CONFIGSET_PATH && \ mkdir -p $STATISTICS_CONFIGSET_PATH -COPY dspace/solr/authority/conf/* $AUTHORITY_CONFIGSET_PATH/ -COPY dspace/solr/oai/conf/* $OAI_CONFIGSET_PATH/ -COPY dspace/solr/search/conf/* $SEARCH_CONFIGSET_PATH/ -COPY dspace/solr/statistics/conf/* $STATISTICS_CONFIGSET_PATH/ +# NOTE: "solrconfigs" MUST be passed in by docker-compose via "additional_contexts" +# OR via "docker build --build-context solrconfigs=[path-to-dspace/solr]" +COPY --from=solrconfigs authority/conf/* $AUTHORITY_CONFIGSET_PATH/ +COPY --from=solrconfigs oai/conf/* $OAI_CONFIGSET_PATH/ +COPY --from=solrconfigs search/conf/* $SEARCH_CONFIGSET_PATH/ +COPY --from=solrconfigs statistics/conf/* $STATISTICS_CONFIGSET_PATH/ RUN chown -R solr:solr /opt/solr/server/solr/configsets From 414837a12a2634eb2d5217d11776f2cdf241bd42 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 2 Feb 2024 10:09:08 -0600 Subject: [PATCH 028/939] Fix 'dspacesolr' build issues in GitHub actions by adding 'solrconfig' as an additional_context --- .github/workflows/docker.yml | 2 ++ .github/workflows/reusable-docker-build.yml | 10 +++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 338c7371f61..9f1e407cff4 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -102,6 +102,8 @@ jobs: build_id: dspace-solr image_name: dspace/dspace-solr dockerfile_path: ./dspace/src/main/docker/dspace-solr/Dockerfile + # Must pass solrconfigs to the Dockerfile so that it can find the required Solr config files + dockerfile_additional_contexts: 'solrconfigs=./dspace/solr/' secrets: DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }} diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml index 46bdab3b682..aa8327f4d11 100644 --- a/.github/workflows/reusable-docker-build.yml +++ b/.github/workflows/reusable-docker-build.yml @@ -24,6 +24,12 @@ on: dockerfile_context: required: false type: string + default: '.' + # Optionally a list of "additional_contexts" to pass to Dockerfile. Defaults to empty + dockerfile_additional_contexts: + required: false + type: string + default: '' # If Docker image should have additional tag flavor details (e.g. a suffix), it may be passed in. tags_flavor: required: false @@ -123,7 +129,9 @@ jobs: id: docker_build uses: docker/build-push-action@v5 with: - context: ${{ inputs.dockerfile_context || '.' }} + build-contexts: | + ${{ inputs.dockerfile_additional_contexts }} + context: ${{ inputs.dockerfile_context }} file: ${{ inputs.dockerfile_path }} platforms: ${{ matrix.arch }} # For pull requests, we run the Docker build (to ensure no PR changes break the build), From 3bfcf829772b535c6bb90007d211f853869e5145 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 31 May 2023 16:45:54 +0000 Subject: [PATCH 029/939] Update dependency com.flipkart.zjsonpatch:zjsonpatch to v0.4.14 --- dspace-server-webapp/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index b3e903a47b4..3df2f2d5e22 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -288,7 +288,7 @@ com.flipkart.zjsonpatch zjsonpatch - 0.4.6 + 0.4.14 From 774f3bbcd5c490b1619c6e9fdd5a837949eb5913 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 10:08:29 +0000 Subject: [PATCH 030/939] Update dependency de.digitalcollections.iiif:iiif-apis to v0.3.10 --- dspace-iiif/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index c8db035d864..8cba9055248 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -93,7 +93,7 @@ de.digitalcollections.iiif iiif-apis - 0.3.9 + 0.3.10 org.javassist From 5893c7768e22ad784cdb07698ad3624d71d8f1b1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 10:08:34 +0000 Subject: [PATCH 031/939] Update dependency dnsjava:dnsjava to v2.1.9 --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 2fddacc7e4b..7b4cc0b6f59 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -635,7 +635,7 @@ dnsjava dnsjava - 2.1.7 + 2.1.9 From bd7214ad1f515c266355be7a646174d3c14cca92 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Fri, 2 Jun 2023 19:36:59 +0300 Subject: [PATCH 032/939] pom.xml: bump com.google.code.findbugs:jsr305 Closes: https://github.com/alanorth/DSpace/pull/12 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1c3479fc0f5..08fd32f7202 100644 --- a/pom.xml +++ b/pom.xml @@ -1738,7 +1738,7 @@ com.google.code.findbugs jsr305 - 3.0.1 + 3.0.2 provided From aeb5300c6ff81d26cb48d3f9ee8f103288f79b18 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 18:40:03 +0000 Subject: [PATCH 033/939] Update dependency javax.cache:cache-api to v1.1.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 08fd32f7202..e1b7afc8cad 100644 --- a/pom.xml +++ b/pom.xml @@ -35,7 +35,7 @@ 1.3.2 2.3.1 2.3.1 - 1.1.0 + 1.1.1 9.4.53.v20231009 2.20.0 From 84452a97d8c477a04407f8abeb4f05d68cbacdc5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 16:54:30 +0000 Subject: [PATCH 034/939] Update dependency net.handle:handle to v9.3.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e1b7afc8cad..711d9e439b8 100644 --- a/pom.xml +++ b/pom.xml @@ -1358,7 +1358,7 @@ net.handle handle - 9.3.0 + 9.3.1 From be7bdcf3980784fa820355e0ee4cfc0930fcd38a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 16:57:30 +0000 Subject: [PATCH 035/939] Update dependency org.apache.ant:ant to v1.10.13 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 711d9e439b8..52308f07784 100644 --- a/pom.xml +++ b/pom.xml @@ -1336,7 +1336,7 @@ org.apache.ant ant - 1.10.11 + 1.10.13 org.apache.jena From 969bee48715c9870aedb9e401078f22ef6ee5e0a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 15:00:31 +0000 Subject: [PATCH 036/939] Update dependency junit:junit to v4.13.2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 52308f07784..1f6f108f7b8 100644 --- a/pom.xml +++ b/pom.xml @@ -1669,7 +1669,7 @@ junit junit - 4.13.1 + 4.13.2 test From 95ac7dea42e5494e3214d9590f85cb6ab129cd2d Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Fri, 2 Jun 2023 22:25:29 +0300 Subject: [PATCH 037/939] pom.xml: bump org.apache.httpcomponents Closes: https://github.com/alanorth/DSpace/pull/21 Closes: https://github.com/alanorth/DSpace/pull/22 Closes: https://github.com/alanorth/DSpace/pull/23 --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 1f6f108f7b8..233a70e43c7 100644 --- a/pom.xml +++ b/pom.xml @@ -1626,17 +1626,17 @@ org.apache.httpcomponents httpcore - 4.4.15 + 4.4.16 org.apache.httpcomponents httpclient - 4.5.13 + 4.5.14 org.apache.httpcomponents httpmime - 4.5.13 + 4.5.14 org.slf4j From bbe05833a98f844d98e69e6295f766b042b83b64 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 19:52:57 +0000 Subject: [PATCH 038/939] Update dependency org.ehcache:ehcache to v3.10.8 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 233a70e43c7..5da3aef2621 100644 --- a/pom.xml +++ b/pom.xml @@ -27,7 +27,7 @@ 42.6.0 8.11.2 - 3.4.0 + 3.10.8 2.10.0 2.13.4 From e2d809ffb4fadae00a42c46ca7fc6ca042ee4a7b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 19:37:49 +0000 Subject: [PATCH 039/939] Update dependency org.flywaydb:flyway-core to v8.5.13 --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 7b4cc0b6f59..17479e3c141 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -671,7 +671,7 @@ org.flywaydb flyway-core - 8.4.4 + 8.5.13 From 91f9d8c4dd7c67e7cb3595431354fbe26c5d30cc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 20:33:06 +0000 Subject: [PATCH 040/939] Update dependency com.opencsv:opencsv to v5.7.1 --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 17479e3c141..62964053961 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -794,7 +794,7 @@ com.opencsv opencsv - 5.6 + 5.7.1 From ba95c1852f199dc6d95855e399dd820aa6e5b54f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 20:32:55 +0000 Subject: [PATCH 041/939] Update dependency org.glassfish.jaxb:jaxb-runtime to v2.3.8 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5da3aef2621..e51bc02cf57 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ 2.13.4.2 1.3.2 2.3.1 - 2.3.1 + 2.3.8 1.1.1 9.4.53.v20231009 From e14f267229371eaac186ad66ee21561f684c1e47 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 19:32:15 +0000 Subject: [PATCH 042/939] Update dependency org.apache.james:apache-mime4j-core to v0.8.9 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e51bc02cf57..8af14a95e27 100644 --- a/pom.xml +++ b/pom.xml @@ -1302,7 +1302,7 @@ org.apache.james apache-mime4j-core - 0.8.4 + 0.8.9 From 53c4e18159db21bdac2d32d1ab56a4bc4d11560f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 3 Jun 2023 14:47:00 +0000 Subject: [PATCH 043/939] Update dependency commons-io:commons-io to v2.12.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8af14a95e27..ac998527645 100644 --- a/pom.xml +++ b/pom.xml @@ -1489,7 +1489,7 @@ commons-io commons-io - 2.7 + 2.12.0 org.apache.commons From 8895aca3eb2f1c3ea645595b3ef9d14fd8b272fb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 3 Jun 2023 14:55:45 +0000 Subject: [PATCH 044/939] Update dependency commons-validator:commons-validator to v1.7 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ac998527645..93ad8008e66 100644 --- a/pom.xml +++ b/pom.xml @@ -1516,7 +1516,7 @@ commons-validator commons-validator - 1.5.0 + 1.7 joda-time From 167033d8305bb521c71ab8192451875a0462ee0e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 3 Jun 2023 14:55:40 +0000 Subject: [PATCH 045/939] Update dependency commons-codec:commons-codec to v1.15 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 93ad8008e66..44e032e2462 100644 --- a/pom.xml +++ b/pom.xml @@ -1456,7 +1456,7 @@ commons-codec commons-codec - 1.10 + 1.15 org.apache.commons From a9a8021cf658c151aa38579a211658219248a561 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 3 Jun 2023 15:27:30 +0000 Subject: [PATCH 046/939] Update dependency commons-cli:commons-cli to v1.5.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 44e032e2462..7b0edc065c5 100644 --- a/pom.xml +++ b/pom.xml @@ -1451,7 +1451,7 @@ commons-cli commons-cli - 1.4 + 1.5.0 commons-codec From b5a59c76e8fc6e202d0362a0c46858a6070334c6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 3 Jun 2023 19:33:38 +0000 Subject: [PATCH 047/939] Update dependency joda-time:joda-time to v2.12.5 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7b0edc065c5..6e47dc59ecf 100644 --- a/pom.xml +++ b/pom.xml @@ -1521,7 +1521,7 @@ joda-time joda-time - 2.9.2 + 2.12.5 com.sun.mail From 8e66812dbbd85eddc51bfc5036bd249ddd67cf96 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Sun, 4 Jun 2023 22:25:40 +0300 Subject: [PATCH 048/939] pom.xml: bump Jersey Bump jersey due to jersey-media-json-jackson pulling in a conflicting jakarta.xml.bind-api via transitive dependency in dspace-rest, which is the legacy DSpace 6 REST API. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6e47dc59ecf..054a04dbd74 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,7 @@ https://jena.apache.org/documentation/migrate_jena2_jena3.html --> 2.13.0 - 2.35 + 2.39.1 UTF-8 From f9469c9b6f97c2031d76b4671f357fb93ec5b4c5 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Sun, 4 Jun 2023 22:26:54 +0300 Subject: [PATCH 049/939] dspace-api/pom.xml: add exclusion for javassist Add an exclusion for org.javassist:javassist due to a dependency convergence error caused by eu.openaire:funders-model pulling in a version conflicting with Jersey's transitive dependency. --- dspace-api/pom.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 62964053961..a90dc0e1d92 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -821,6 +821,13 @@ eu.openaire funders-model 2.0.0 + + + + org.javassist + javassist + + From 36c042bc98fc932f21bdd938e205f99fa30e4dc9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 5 Jun 2023 06:03:24 +0000 Subject: [PATCH 050/939] Update dependency org.webjars.bowergithub.medialize:uri.js to v1.19.11 --- dspace-server-webapp/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 3df2f2d5e22..4b3e79431e9 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -322,7 +322,7 @@ org.webjars.bowergithub.medialize uri.js - 1.19.10 + 1.19.11 From fe8c3ef38843a576c5e75accbf51b1e4c0616943 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 5 Jun 2023 06:01:11 +0000 Subject: [PATCH 051/939] Update dependency com.fasterxml:classmate to v1.5.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 054a04dbd74..1ff8a92524c 100644 --- a/pom.xml +++ b/pom.xml @@ -1752,7 +1752,7 @@ com.fasterxml classmate - 1.3.0 + 1.5.1 com.fasterxml.jackson.core From 7b0b21f84de121713fcb080171feac6064117e95 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 5 Jun 2023 06:03:29 +0000 Subject: [PATCH 052/939] Update dependency org.webjars.bowergithub.jquery:jquery-dist to v3.7.0 --- dspace-server-webapp/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 4b3e79431e9..40b61139ee7 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -308,7 +308,7 @@ org.webjars.bowergithub.jquery jquery-dist - 3.6.0 + 3.7.0 From 69e1ff98a0de01995ac10a1d69d33ceca064e39b Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Mon, 5 Jun 2023 10:33:46 +0300 Subject: [PATCH 053/939] Bump xom:xom dependency No breaking changes, but some bug fixes, performance improvements, and compatibility fixes with Java 17+. See: https://xom.nu/history.html --- dspace-sword/pom.xml | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index 900ede5fc50..52783e3ec57 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -104,7 +104,7 @@ xom xom - 1.3.7 + 1.3.9 commons-io diff --git a/pom.xml b/pom.xml index 1ff8a92524c..31fcf8f75a5 100644 --- a/pom.xml +++ b/pom.xml @@ -1784,7 +1784,7 @@ xom xom - 1.2.5 + 1.3.9 From 096fb3fb74e29b8c6eee8155c6940084a00f8039 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Mon, 5 Jun 2023 10:36:44 +0300 Subject: [PATCH 054/939] Bump jaxen:jaxen dependency to 2.0.0 Should be mostly drop-in API compatible with Jaxen 1.1.x, but more importantly it makes the xom dependency optional so we can remove the exclusions in our various pom.xml files. See: http://cafeconleche.org/jaxen/releases.html --- dspace-api/pom.xml | 6 ------ pom.xml | 8 +------- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index a90dc0e1d92..0fdf045c10a 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -495,12 +495,6 @@ jaxen jaxen - - - xom - xom - - org.jdom diff --git a/pom.xml b/pom.xml index 31fcf8f75a5..2d7edd8763d 100644 --- a/pom.xml +++ b/pom.xml @@ -1538,13 +1538,7 @@ jaxen jaxen - 1.1.6 - - - xom - xom - - + 2.0.0 org.jdom From 6b4e5ed9c8236490157bde819d570c64b3a2aca1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 25 Jun 2023 16:37:20 +0000 Subject: [PATCH 055/939] Update dependency org.apache.bcel:bcel to v6.7.0 --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 0fdf045c10a..bb9bf675584 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -806,7 +806,7 @@ org.apache.bcel bcel - 6.6.0 + 6.7.0 test From e92e5ee6d847974da9f2f530393af9a7a52818b6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 04:40:30 +0000 Subject: [PATCH 056/939] Update dependency org.scala-lang:scala-library to v2.13.11 --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index bb9bf675584..57362fa7539 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -926,7 +926,7 @@ org.scala-lang scala-library - 2.13.9 + 2.13.11 test From af29486965dafd017bcc471d45898c4a1e9330c9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 04:53:45 +0000 Subject: [PATCH 057/939] Update dependency commons-io:commons-io to v2.13.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2d7edd8763d..8b53afa9e5c 100644 --- a/pom.xml +++ b/pom.xml @@ -1489,7 +1489,7 @@ commons-io commons-io - 2.12.0 + 2.13.0 org.apache.commons From 5152e99bda9f3ef9ac531f9bd460d60d9c8ba6fb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 04:56:57 +0000 Subject: [PATCH 058/939] Update dependency org.exparity:hamcrest-date to v2.0.8 --- dspace-server-webapp/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 40b61139ee7..7f48c5dde2a 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -541,7 +541,7 @@ org.exparity hamcrest-date - 2.0.7 + 2.0.8 test From 972c2d69c8ba890b13d9365f5809d1e14d20be17 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 27 Jun 2023 10:49:50 +0000 Subject: [PATCH 059/939] Update dependency commons-codec:commons-codec to v1.16.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8b53afa9e5c..1d3fed2d51f 100644 --- a/pom.xml +++ b/pom.xml @@ -1456,7 +1456,7 @@ commons-codec commons-codec - 1.15 + 1.16.0 org.apache.commons From ad061c962ba7c5ac27e9649f9dac6b0750daeada Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Fri, 30 Jun 2023 21:38:32 +0300 Subject: [PATCH 060/939] pom.xml: update spring boot to v2.7.13 Minor update. Also bump the spring security version to 5.7.9 as is used by spring boot. See: https://github.com/spring-projects/spring-boot/releases/tag/v2.7.13 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 1d3fed2d51f..15039783cee 100644 --- a/pom.xml +++ b/pom.xml @@ -20,8 +20,8 @@ 11 5.3.27 - 2.7.12 - 5.7.8 + 2.7.13 + 5.7.9 5.6.15.Final 6.2.5.Final 42.6.0 From 16b1104b0164eb397e2b2f8915282adb2a59ab50 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Fri, 30 Jun 2023 21:41:41 +0300 Subject: [PATCH 061/939] pom.xml: bump spring core version to v5.3.28 Minor version bump with some bug fixes. See: https://github.com/spring-projects/spring-framework/releases/tag/v5.3.28 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 15039783cee..a071ae2c7a0 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ 11 - 5.3.27 + 5.3.28 2.7.13 5.7.9 5.6.15.Final From 79178e3b62cf83e4dcfca1bdab8c483da89561a4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 2 Jul 2023 15:18:28 +0000 Subject: [PATCH 062/939] Update pdfbox-version to v2.0.29 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a071ae2c7a0..36f6a880e9c 100644 --- a/pom.xml +++ b/pom.xml @@ -39,7 +39,7 @@ 9.4.53.v20231009 2.20.0 - 2.0.28 + 2.0.29 1.19.0 1.7.36 2.5.0 From 0d1dd13c842d625bd8ab6661281905963cde2cc0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 05:24:34 +0000 Subject: [PATCH 063/939] Update dependency com.h2database:h2 to v2.2.224 (cherry picked from commit 0d4c1ea63ab7cdd5ea79511228a5486a46c2a515) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 36f6a880e9c..5f349232886 100644 --- a/pom.xml +++ b/pom.xml @@ -1688,7 +1688,7 @@ com.h2database h2 - 2.2.220 + 2.2.224 test From 7a38f0097d57907555b434f83492e80bcc065cc5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 05:38:10 +0000 Subject: [PATCH 064/939] Update dependency org.glassfish.jaxb:jaxb-runtime to v2.3.9 (cherry picked from commit 8d6f6e37b3beed98b9d20327cbb67ce683da9b3c) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5f349232886..df57ea81656 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ 2.13.4.2 1.3.2 2.3.1 - 2.3.8 + 2.3.9 1.1.1 9.4.53.v20231009 From a50534d617a3f14264a1750cc5d7ebbe3d737557 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 05:44:02 +0000 Subject: [PATCH 065/939] Update pdfbox-version to v2.0.30 (cherry picked from commit fe7800ab5a7d9b77e9783d09b000aff19be38c96) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index df57ea81656..f63ac3d053c 100644 --- a/pom.xml +++ b/pom.xml @@ -39,7 +39,7 @@ 9.4.53.v20231009 2.20.0 - 2.0.29 + 2.0.30 1.19.0 1.7.36 2.5.0 From 602e9e854f23932a60be6be9ddafa0f6362738f7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 05:51:40 +0000 Subject: [PATCH 066/939] Update dependency commons-cli:commons-cli to v1.6.0 (cherry picked from commit f67e0f65091b85d191d3694ea80339d42833ed9f) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f63ac3d053c..be0c732165e 100644 --- a/pom.xml +++ b/pom.xml @@ -1451,7 +1451,7 @@ commons-cli commons-cli - 1.5.0 + 1.6.0 commons-codec From f7c7abc36201a38154b2d3ec3246aa8bb9e9bf22 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 05:52:16 +0000 Subject: [PATCH 067/939] Update dependency com.opencsv:opencsv to v5.9 (cherry picked from commit ab9279cbb88e8511f28fa01f7d2a005b61eca1d2) --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 57362fa7539..b79a088dc90 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -788,7 +788,7 @@ com.opencsv opencsv - 5.7.1 + 5.9 From ed74d3198cf277b73fc1516e26f99b0e9ac74690 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 05:52:20 +0000 Subject: [PATCH 068/939] Update dependency commons-io:commons-io to v2.15.1 (cherry picked from commit 70646a30de182a23b3f7ee675ff74a9a79c76b1f) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index be0c732165e..e3b05649ef6 100644 --- a/pom.xml +++ b/pom.xml @@ -1489,7 +1489,7 @@ commons-io commons-io - 2.13.0 + 2.15.1 org.apache.commons From 279a315301c7bf4df56bb95fde959c3256bd1506 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 06:24:36 +0000 Subject: [PATCH 069/939] Update log4j.version to v2.22.1 (cherry picked from commit a6978137203c6c8112b9d31c095bf8c7823fe8db) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e3b05649ef6..66f243304b1 100644 --- a/pom.xml +++ b/pom.xml @@ -38,7 +38,7 @@ 1.1.1 9.4.53.v20231009 - 2.20.0 + 2.22.1 2.0.30 1.19.0 1.7.36 From 8edd8bc09bfe002f9e2840854a15bde7aab9d8ec Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 06:33:06 +0000 Subject: [PATCH 070/939] Update dependency com.fasterxml:classmate to v1.6.0 (cherry picked from commit 07ec54832a7a8380a0d1c7b361e57938ed88360f) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 66f243304b1..b69554a72d8 100644 --- a/pom.xml +++ b/pom.xml @@ -1746,7 +1746,7 @@ com.fasterxml classmate - 1.5.1 + 1.6.0 com.fasterxml.jackson.core From 28d051e88d7f03a2b51261622d6b0b0fb4d49758 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 08:13:14 +0000 Subject: [PATCH 071/939] Update dependency org.xmlunit:xmlunit-core to v2.9.1 (cherry picked from commit 0958a98b1f4d339cfdca50ff65081ae0730b0d74) --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index b79a088dc90..1b539a5cc39 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -900,7 +900,7 @@ org.xmlunit xmlunit-core - 2.8.0 + 2.9.1 test From a77628bd1e2e55b47492938ab68b0be7fb2b2ef9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 08:16:05 +0000 Subject: [PATCH 072/939] Update dependency com.maxmind.geoip2:geoip2 to v2.17.0 (cherry picked from commit 68caa1dcf8e7c5687fc250ed62704832d93aa362) --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 1b539a5cc39..920cdfcf462 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -620,7 +620,7 @@ com.maxmind.geoip2 geoip2 - 2.11.0 + 2.17.0 org.apache.ant From 4840b1de834a00d8eebe7f3e2461ff77c2839482 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 09:06:03 +0000 Subject: [PATCH 073/939] Update netty monorepo to v4.1.106.Final (cherry picked from commit 71ea2a752665bd6eaeb27575bd3fdf3ac05eacd4) --- dspace-api/pom.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 920cdfcf462..b1aa3f246cf 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -865,32 +865,32 @@ io.netty netty-buffer - 4.1.94.Final + 4.1.106.Final io.netty netty-transport - 4.1.94.Final + 4.1.106.Final io.netty netty-transport-native-unix-common - 4.1.94.Final + 4.1.106.Final io.netty netty-common - 4.1.94.Final + 4.1.106.Final io.netty netty-handler - 4.1.94.Final + 4.1.106.Final io.netty netty-codec - 4.1.94.Final + 4.1.106.Final org.apache.velocity From b603776a08c7367c15eef94f03e7eee3a34de79f Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Wed, 29 Nov 2023 08:33:39 +0300 Subject: [PATCH 074/939] pom.xml: update Spring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We need to keep Spring Framework, Spring Boot, and Spring Security versions updated together: - Spring Framework: 5.3.28→5.3.31 - Spring Boot: 2.7.13→2.7.18 - Spring Security: 5.7.9→5.7.11 (cherry picked from commit f42a981d1bfc588c5a42d918036110f2b92b1b8a) --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index b69554a72d8..1502c0f7c50 100644 --- a/pom.xml +++ b/pom.xml @@ -19,9 +19,9 @@ 11 - 5.3.28 - 2.7.13 - 5.7.9 + 5.3.31 + 2.7.18 + 5.7.11 5.6.15.Final 6.2.5.Final 42.6.0 From 487202e246d7afe10e614da12e4afbb998cd4e50 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Wed, 29 Nov 2023 10:01:27 +0300 Subject: [PATCH 075/939] pom.xml: update spotbugs and spotbugs-maven-plugin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update to latest versions: - spotbugs v4.1.2→v4.8.2 - spotbugs-maven-plugin v4.0.4→v4.8.2.0 These are not run in CI and seem to only run manually when asked, ie via maven: $ mvn spotbugs:spotbugs (cherry picked from commit fc6a9ca5cb2dec321c2ad67f722dcc6a2e894bc1) --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 1502c0f7c50..fc20c0059ac 100644 --- a/pom.xml +++ b/pom.xml @@ -294,7 +294,7 @@ com.github.spotbugs spotbugs-maven-plugin - 4.0.4 + 4.8.2.0 Max Low @@ -304,7 +304,7 @@ com.github.spotbugs spotbugs - 4.1.2 + 4.8.2 From 5342d540751c6a0837fcf2636222d40adeea3b4c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 29 Nov 2023 07:07:13 +0000 Subject: [PATCH 076/939] Update dependency org.apache.ant:ant to v1.10.14 (cherry picked from commit 4e071b2428d66d7350abc451598f75ba647e3394) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fc20c0059ac..e89b5c9dae2 100644 --- a/pom.xml +++ b/pom.xml @@ -1336,7 +1336,7 @@ org.apache.ant ant - 1.10.13 + 1.10.14 org.apache.jena From b2494c6be2a942f1b89d4b80b5b934db633cda34 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 29 Nov 2023 07:24:40 +0000 Subject: [PATCH 077/939] Update json-path.version to v2.8.0 (cherry picked from commit 8809e98a18ba1980e99584b21ffb427ed70e028e) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e89b5c9dae2..da087bdc9fe 100644 --- a/pom.xml +++ b/pom.xml @@ -48,7 +48,7 @@ - 2.6.0 + 2.8.0 7.9 From c7c8062f014fbf8cbc4609b676c31f6cc8c07e68 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Wed, 29 Nov 2023 12:46:35 +0300 Subject: [PATCH 078/939] Update hamcrest to v2.2 Due to changes in hamcrest packaging we only need the main artifact now, but we add hamcrest-core (which is an empty pom) so it doesn't get pulled in by other deps. Last, the hamcrest docs recommend that we put hamcrest first so that we don't have dependency convergence issues from junit. See: https://hamcrest.org/JavaHamcrest/distributables (cherry picked from commit 710feb798d2ad2192f2941fd5beeede30b7d8498) --- dspace-api/pom.xml | 2 +- dspace-oai/pom.xml | 2 +- dspace-server-webapp/pom.xml | 2 +- dspace/modules/additions/pom.xml | 2 +- pom.xml | 16 ++++++++-------- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index b1aa3f246cf..5b578fa49d5 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -528,7 +528,7 @@ org.hamcrest - hamcrest-all + hamcrest test diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index c9d8285559c..748f6ad2dee 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -156,7 +156,7 @@ org.hamcrest - hamcrest-all + hamcrest compile diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 7f48c5dde2a..05a24cf7d16 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -502,7 +502,7 @@ org.hamcrest - hamcrest-all + hamcrest test diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index 84b53171bf3..7e60e982ec4 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -273,7 +273,7 @@ org.hamcrest - hamcrest-core + hamcrest test diff --git a/pom.xml b/pom.xml index da087bdc9fe..7e078fa21f9 100644 --- a/pom.xml +++ b/pom.xml @@ -1661,21 +1661,21 @@ - junit - junit - 4.13.2 + org.hamcrest + hamcrest + 2.2 test org.hamcrest - hamcrest-all - 1.3 + hamcrest-core + 2.2 test - org.hamcrest - hamcrest-core - 1.3 + junit + junit + 4.13.2 test From 752d94efb4a32d4da6d019261f8ce25779843bd8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 30 Nov 2023 07:37:42 +0000 Subject: [PATCH 079/939] Update dependency org.apache.maven.plugins:maven-checkstyle-plugin to v3.3.1 (cherry picked from commit ae12f1865fd18b5ba642bdd1852934c57dfbb49d) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7e078fa21f9..9770e2085e5 100644 --- a/pom.xml +++ b/pom.xml @@ -257,7 +257,7 @@ org.apache.maven.plugins maven-checkstyle-plugin - 3.1.0 + 3.3.1 verify-style From 88edfbbfdfde82498a4e492e274bd5c0eabab485 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 30 Nov 2023 09:53:22 +0000 Subject: [PATCH 080/939] Update dependency org.apache.maven.plugins:maven-assembly-plugin to v3.6.0 (cherry picked from commit 275757e6d46b9175b81748722114fcef9a62e1b4) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9770e2085e5..fe6a9fd9363 100644 --- a/pom.xml +++ b/pom.xml @@ -334,7 +334,7 @@ maven-assembly-plugin - 3.2.0 + 3.6.0 org.apache.maven.plugins From 9cb4eac8027c9cddbf8c3c3a7f6573b15fa4da87 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 1 Dec 2023 06:31:33 +0000 Subject: [PATCH 081/939] Update dependency org.webjars.bowergithub.twbs:bootstrap to v4.6.2 (cherry picked from commit 2c1a45bc8840614690bacfbeb8fa4dfbe5b42d70) --- dspace-server-webapp/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 05a24cf7d16..5f2a256f3e0 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -354,7 +354,7 @@ org.webjars.bowergithub.twbs bootstrap - 4.6.1 + 4.6.2 From 4dcbe16e24fa7903a90bea99517ab5c63c226091 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 1 Dec 2023 06:32:41 +0000 Subject: [PATCH 082/939] Update dependency org.webjars.bowergithub.jquery:jquery-dist to v3.7.1 (cherry picked from commit 6d0b5deb8e2553be8afc1f4892f5e74353b5fdc2) --- dspace-server-webapp/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 5f2a256f3e0..2165d546caf 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -308,7 +308,7 @@ org.webjars.bowergithub.jquery jquery-dist - 3.7.0 + 3.7.1 From 4682db506ec5e7c89cfb2c8cdf8ba30dc3192f9a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 1 Dec 2023 06:31:39 +0000 Subject: [PATCH 083/939] Update dependency org.apache.commons:commons-lang3 to v3.14.0 (cherry picked from commit 3f675d9cd5bddea7d31de39db44183b36941492c) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fe6a9fd9363..50a76f9cd0e 100644 --- a/pom.xml +++ b/pom.xml @@ -1494,7 +1494,7 @@ org.apache.commons commons-lang3 - 3.12.0 + 3.14.0 From 186453eb3f472951fbfe7a6e869bd8ebe04f2482 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 1 Dec 2023 06:30:59 +0000 Subject: [PATCH 084/939] Update dependency commons-logging:commons-logging to v1.3.0 (cherry picked from commit e76132b4d8c1984994bfb150e3a3abdcff6f69a8) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 50a76f9cd0e..44cde8d4c64 100644 --- a/pom.xml +++ b/pom.xml @@ -1501,7 +1501,7 @@ commons-logging commons-logging - 1.2 + 1.3.0 org.apache.commons From 1f67a2bffeaade8813a9f5b9b41d9c30235ffc75 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 1 Dec 2023 09:42:09 +0000 Subject: [PATCH 085/939] Update dependency org.apache.commons:commons-configuration2 to v2.9.0 (cherry picked from commit b583029a7d1e3b6a56f31ef2e086147ff005bab6) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 44cde8d4c64..af6df1ae933 100644 --- a/pom.xml +++ b/pom.xml @@ -1474,7 +1474,7 @@ org.apache.commons commons-configuration2 - 2.8.0 + 2.9.0 org.apache.commons From bdb19787bb3a1e6042a018b5c0b9ab7d20ce83c5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 1 Dec 2023 09:42:44 +0000 Subject: [PATCH 086/939] Update dependency org.apache.commons:commons-collections4 to v4.4 (cherry picked from commit 6de85adeb7afa0d91ee0bf3273b8494d5b5d33c1) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index af6df1ae933..63e130b0810 100644 --- a/pom.xml +++ b/pom.xml @@ -1461,7 +1461,7 @@ org.apache.commons commons-collections4 - 4.1 + 4.4 - 2.13.4 - 2.13.4.2 + 2.16.0 + 2.16.0 1.3.2 2.3.1 2.3.9 From 762c0397fe252061b382adf5e3304f8eb29d1dd8 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Mon, 5 Feb 2024 17:41:40 +0300 Subject: [PATCH 088/939] pom.xml: update commons-dbcp2 and commons-pool2 Update commons-dbcp2 and commons-pool2 to latest stable versions. (cherry picked from commit bafb1b56570df3b4bfea045ccc913af0f0994cce) --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 8626865f2dc..d3a0128ce34 100644 --- a/pom.xml +++ b/pom.xml @@ -1479,7 +1479,7 @@ org.apache.commons commons-dbcp2 - 2.9.0 + 2.11.0 commons-fileupload @@ -1506,7 +1506,7 @@ org.apache.commons commons-pool2 - 2.11.1 + 2.12.0 org.apache.commons From 9274a117597d85ad4e485091a6452e935d31b0de Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Mon, 5 Feb 2024 22:21:50 +0300 Subject: [PATCH 089/939] dspace-server-webapp/pom.xml: upgrade zjsonpatch Upgrade zjsonpatch from v0.4.6 to v0.4.16. (cherry picked from commit 20fc8e4fb7c5deb81f66d926712da7fdbe25a687) --- dspace-server-webapp/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 2165d546caf..5395169950b 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -288,7 +288,7 @@ com.flipkart.zjsonpatch zjsonpatch - 0.4.14 + 0.4.16 From 6d5949729d0c7c68d01dfe277d5aadc51005cf77 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Feb 2024 16:30:28 +0000 Subject: [PATCH 090/939] Bump com.jayway.jsonpath:json-path from 2.6.0 to 2.9.0 Bumps [com.jayway.jsonpath:json-path](https://github.com/jayway/JsonPath) from 2.6.0 to 2.9.0. - [Release notes](https://github.com/jayway/JsonPath/releases) - [Changelog](https://github.com/json-path/JsonPath/blob/master/changelog.md) - [Commits](https://github.com/jayway/JsonPath/compare/json-path-2.6.0...json-path-2.9.0) --- updated-dependencies: - dependency-name: com.jayway.jsonpath:json-path dependency-type: direct:development ... Signed-off-by: dependabot[bot] (cherry picked from commit db56de3887109404bb849413f5a02023ebb9bcae) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d3a0128ce34..f940dfb97a3 100644 --- a/pom.xml +++ b/pom.xml @@ -48,7 +48,7 @@ - 2.8.0 + 2.9.0 7.9 From 1dada28c89b8ad514cdf0d538476ec16a82926ae Mon Sep 17 00:00:00 2001 From: Koen Pauwels Date: Wed, 7 Feb 2024 14:35:46 +0100 Subject: [PATCH 091/939] 111719 Fix bug where old style of retrieving config value was being used --- .../src/main/java/org/dspace/sword/CollectionDepositor.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/dspace-sword/src/main/java/org/dspace/sword/CollectionDepositor.java b/dspace-sword/src/main/java/org/dspace/sword/CollectionDepositor.java index 2550d947d32..3fe47cfaa62 100644 --- a/dspace-sword/src/main/java/org/dspace/sword/CollectionDepositor.java +++ b/dspace-sword/src/main/java/org/dspace/sword/CollectionDepositor.java @@ -150,11 +150,7 @@ public DepositResult doDeposit(Deposit deposit) // for a moment context.turnOffAuthorisationSystem(); - String bundleName = configurationService.getProperty( - "sword-server", "bundle.name"); - if (bundleName == null || "".equals(bundleName)) { - bundleName = "SWORD"; - } + String bundleName = configurationService.getProperty("sword-server.bundle.name", "SWORD"); Item item = result.getItem(); List bundles = item.getBundles(); Bundle swordBundle = null; From 6624248c7affed8f8b801f113d70c0912f26f074 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 7 Feb 2024 12:11:06 +1300 Subject: [PATCH 092/939] Improve default identifiers.cfg properties (Related to dspace-angular#2765) (cherry picked from commit 40b7427d884002a0916ec4a3e687280e9426d081) --- dspace/config/modules/identifiers.cfg | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/dspace/config/modules/identifiers.cfg b/dspace/config/modules/identifiers.cfg index 63a9cda30f1..2660646af39 100644 --- a/dspace/config/modules/identifiers.cfg +++ b/dspace/config/modules/identifiers.cfg @@ -41,9 +41,7 @@ # Show Register DOI button in item status page? # Default: false -# This configuration property is exposed over rest. For dspace-angular to work, -# this property must always have a true or false value. Do not comment it out! -identifiers.item-status.register-doi = false +#identifiers.item-status.register-doi = true # Which identifier types to show in submission step? # Default: handle, doi (currently the only supported identifier 'types') From 09357b133e033f9ba9da3593ad650830a7d81143 Mon Sep 17 00:00:00 2001 From: MajoBerger Date: Fri, 9 Jun 2023 11:22:50 +0200 Subject: [PATCH 093/939] added failsafe while creating admin when db is not connected (cherry picked from commit efcf9dba20ac4c49516d8486a33e14f06db86080) --- .../org/dspace/administer/CreateAdministrator.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/administer/CreateAdministrator.java b/dspace-api/src/main/java/org/dspace/administer/CreateAdministrator.java index 81250e9c825..58b85493915 100644 --- a/dspace-api/src/main/java/org/dspace/administer/CreateAdministrator.java +++ b/dspace-api/src/main/java/org/dspace/administer/CreateAdministrator.java @@ -116,6 +116,17 @@ public static void main(String[] argv) protected CreateAdministrator() throws Exception { context = new Context(); + try { + context.getDBConfig(); + } catch (NullPointerException npr) { + // if database is null, there is no point in continuing. Prior to this exception and catch, + // NullPointerException was thrown, that wasn't very helpful. + throw new IllegalStateException("Problem connecting to database. This " + + "indicates issue with either network or version (or possibly some other). " + + "If you are running this in docker-compose, please make sure dspace-cli was " + + "built from the same sources as running dspace container AND that they are in " + + "the same project/network."); + } groupService = EPersonServiceFactory.getInstance().getGroupService(); ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); } From 4942fd272ce2259e3199f07f821236c05f9dae36 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Thu, 15 Feb 2024 10:31:04 +0100 Subject: [PATCH 094/939] 106812: Fix compile issue due to new test --- .../test/java/org/dspace/app/rest/ItemRestRepositoryIT.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java index 73bae1219d5..714ad0b419a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java @@ -2936,7 +2936,7 @@ public void specificEmbedTestMultipleLevelOfLinksWithData() throws Exception { BitstreamMatcher.matchBitstreamEntryWithoutEmbed(bitstream2.getID(), bitstream2.getSizeBytes()) ))) .andExpect(jsonPath("$._embedded.owningCollection._embedded.mappedItems." + - "_embedded.mappedItems[0]_embedded.relationships").doesNotExist()) + "_embedded.mappedItems[0]._embedded.relationships").doesNotExist()) .andExpect(jsonPath("$._embedded.owningCollection._embedded.mappedItems" + "._embedded.mappedItems[0]._embedded.bundles._embedded.bundles[0]." + "_embedded.primaryBitstream").doesNotExist()) @@ -3045,8 +3045,7 @@ public void testHiddenMetadataForUserWithReadRights() throws Exception { context.restoreAuthSystemState(); - ResourcePolicyBuilder.createResourcePolicy(context) - .withUser(eperson) + ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) .withAction(READ) .withDspaceObject(item) .build(); From 2cbcf9006dcc07a6197f55be79db7602e90e37fd Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Thu, 15 Feb 2024 18:36:45 +0100 Subject: [PATCH 095/939] SWORD config fixes: Instances where old style module/fileName, configName params are used from long-deprecated ConfigurationManager instead of configName, defaultValue --- .../src/main/java/org/dspace/sword/SWORDUrlManager.java | 3 +-- .../src/main/java/org/dspace/sword2/SwordUrlManager.java | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/dspace-sword/src/main/java/org/dspace/sword/SWORDUrlManager.java b/dspace-sword/src/main/java/org/dspace/sword/SWORDUrlManager.java index c145c9d3671..6672027003e 100644 --- a/dspace-sword/src/main/java/org/dspace/sword/SWORDUrlManager.java +++ b/dspace-sword/src/main/java/org/dspace/sword/SWORDUrlManager.java @@ -431,8 +431,7 @@ public String getBitstreamUrl(Bitstream bitstream) */ public String getBaseMediaLinkUrl() throws DSpaceSWORDException { - String mlUrl = configurationService.getProperty( - "sword-server", "media-link.url"); + String mlUrl = configurationService.getProperty("sword-server.media-link.url"); if (StringUtils.isBlank(mlUrl)) { if (dspaceUrl == null || "".equals(dspaceUrl)) { throw new DSpaceSWORDException( diff --git a/dspace-swordv2/src/main/java/org/dspace/sword2/SwordUrlManager.java b/dspace-swordv2/src/main/java/org/dspace/sword2/SwordUrlManager.java index f3b2cf43965..eee3627c404 100644 --- a/dspace-swordv2/src/main/java/org/dspace/sword2/SwordUrlManager.java +++ b/dspace-swordv2/src/main/java/org/dspace/sword2/SwordUrlManager.java @@ -458,10 +458,9 @@ public String getSplashUrl(Item item) throws DSpaceSwordException { WorkflowTools wft = new WorkflowTools(); - // if the item is in the workspace, we need to give it it's own special identifier + // if the item is in the workspace, we need to give it its own special identifier if (wft.isItemInWorkspace(context, item)) { - String urlTemplate = configurationService - .getProperty("swordv2-server", "workspace.url-template"); + String urlTemplate = configurationService.getProperty("swordv2-server.workspace.url-template"); if (urlTemplate != null) { return urlTemplate.replace("#wsid#", Integer.toString( wft.getWorkspaceItem(context, item).getID())); From b97b093d6c97d47599ea7ed00a6b93fd2d3fd1b6 Mon Sep 17 00:00:00 2001 From: mohamed eskander Date: Wed, 17 Jan 2024 10:39:18 +0200 Subject: [PATCH 096/939] [DURACOM-143] Fix indexing errors & further improvements (cherry picked from commit d645939baf07d10d0f9f0b5e7b2722556400ff97) --- .../indexobject/IndexFactoryImpl.java | 50 ++++++++++++------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexFactoryImpl.java index 55c99b168e7..4b012a072f3 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexFactoryImpl.java @@ -2,7 +2,7 @@ * 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.discovery.indexobject; @@ -64,7 +64,14 @@ public SolrInputDocument buildDocument(Context context, T indexableObject) throw //Do any additional indexing, depends on the plugins for (SolrServiceIndexPlugin solrServiceIndexPlugin : ListUtils.emptyIfNull(solrServiceIndexPlugins)) { - solrServiceIndexPlugin.additionalIndex(context, indexableObject, doc); + try { + solrServiceIndexPlugin.additionalIndex(context, indexableObject, doc); + } catch (Exception e) { + log.error("An error occurred while indexing additional fields. " + + "Could not fully index item with UUID: {}. Plugin: {}", + indexableObject.getUniqueIndexID(), solrServiceIndexPlugin.getClass().getSimpleName()); + + } } return doc; @@ -82,7 +89,7 @@ public void writeDocument(Context context, T indexableObject, SolrInputDocument writeDocument(solrInputDocument, null); } catch (Exception e) { log.error("Error occurred while writing SOLR document for {} object {}", - indexableObject.getType(), indexableObject.getID(), e); + indexableObject.getType(), indexableObject.getID(), e); } } @@ -101,8 +108,8 @@ protected void writeDocument(SolrInputDocument doc, FullTextContentStreams strea if (streams != null && !streams.isEmpty()) { // limit full text indexing to first 100,000 characters unless configured otherwise final int charLimit = DSpaceServicesFactory.getInstance().getConfigurationService() - .getIntProperty("discovery.solr.fulltext.charLimit", - 100000); + .getIntProperty("discovery.solr.fulltext.charLimit", + 100000); // Use Tika's Text parser as the streams are always from the TEXT bundle (i.e. already extracted text) TextAndCSVParser tikaParser = new TextAndCSVParser(); @@ -113,6 +120,18 @@ protected void writeDocument(SolrInputDocument doc, FullTextContentStreams strea // Use Apache Tika to parse the full text stream(s) try (InputStream fullTextStreams = streams.getStream()) { tikaParser.parse(fullTextStreams, tikaHandler, tikaMetadata, tikaContext); + + // Write Tika metadata to "tika_meta_*" fields. + // This metadata is not very useful right now, + // but we'll keep it just in case it becomes more useful. + for (String name : tikaMetadata.names()) { + for (String value : tikaMetadata.getValues(name)) { + doc.addField("tika_meta_" + name, value); + } + } + + // Save (parsed) full text to "fulltext" field + doc.addField("fulltext", tikaHandler.toString()); } catch (SAXException saxe) { // Check if this SAXException is just a notice that this file was longer than the character limit. // Unfortunately there is not a unique, public exception type to catch here. This error is thrown @@ -121,30 +140,23 @@ protected void writeDocument(SolrInputDocument doc, FullTextContentStreams strea if (saxe.getMessage().contains("limit has been reached")) { // log that we only indexed up to that configured limit log.info("Full text is larger than the configured limit (discovery.solr.fulltext.charLimit)." - + " Only the first {} characters were indexed.", charLimit); + + " Only the first {} characters were indexed.", charLimit); } else { log.error("Tika parsing error. Could not index full text.", saxe); throw new IOException("Tika parsing error. Could not index full text.", saxe); } - } catch (TikaException ex) { + } catch (TikaException | IOException ex) { log.error("Tika parsing error. Could not index full text.", ex); throw new IOException("Tika parsing error. Could not index full text.", ex); + } finally { + // Add document to index + solr.add(doc); } - - // Write Tika metadata to "tika_meta_*" fields. - // This metadata is not very useful right now, but we'll keep it just in case it becomes more useful. - for (String name : tikaMetadata.names()) { - for (String value : tikaMetadata.getValues(name)) { - doc.addField("tika_meta_" + name, value); - } - } - - // Save (parsed) full text to "fulltext" field - doc.addField("fulltext", tikaHandler.toString()); + return; } - // Add document to index solr.add(doc); + } } From 14f593e097d8947549b446e0f21923ec0c13e79c Mon Sep 17 00:00:00 2001 From: mohamed eskander Date: Wed, 17 Jan 2024 10:56:08 +0200 Subject: [PATCH 097/939] [DURACOM-143] Fix license (cherry picked from commit 324d2e3184dd35d2980e599f173adf18a7e31d64) --- .../java/org/dspace/discovery/indexobject/IndexFactoryImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexFactoryImpl.java index 4b012a072f3..f1ae137b916 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexFactoryImpl.java @@ -2,7 +2,7 @@ * 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.discovery.indexobject; From adec8e2e608f7e290c504c8d675cadf84a3102e6 Mon Sep 17 00:00:00 2001 From: haoueclf Date: Fri, 16 Feb 2024 16:51:41 +0100 Subject: [PATCH 098/939] [DS-9345] Correct the package name of the IIIF search plugin (cherry picked from commit 03fe58668114db529413d13d98af57c6e045e9df) --- dspace/config/modules/iiif.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace/config/modules/iiif.cfg b/dspace/config/modules/iiif.cfg index fc1e9bdf9f8..9a2a3bb0d13 100644 --- a/dspace/config/modules/iiif.cfg +++ b/dspace/config/modules/iiif.cfg @@ -15,7 +15,7 @@ iiif.image.server = http://localhost:8182/iiif/2/ # The search plugin used to support (experimental) IIIF Search. # This is the class used with https://dbmdz.github.io/solr-ocrhighlighting/ # It is currently the only supported option. -# iiif.search.plugin = org.dspace.app.rest.iiif.service.WordHighlightSolrSearch +# iiif.search.plugin = org.dspace.app.iiif.service.WordHighlightSolrSearch # Sets the viewing hint. Possible values: "paged" or "individuals". # Typically "paged" is preferred for multi-age documents. Use "individuals" From dddd500bd19de851aa82773b685afe4967f8e6cd Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Mon, 19 Feb 2024 10:06:33 +0100 Subject: [PATCH 099/939] 106812: Add flyway migration to remove faulty rp entries --- ..._2023.09.28__enforce_group_or_eperson_for_resourcepolicy.sql | 2 ++ ..._2023.09.28__enforce_group_or_eperson_for_resourcepolicy.sql | 2 ++ 2 files changed, 4 insertions(+) diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.09.28__enforce_group_or_eperson_for_resourcepolicy.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.09.28__enforce_group_or_eperson_for_resourcepolicy.sql index de40d0415dc..2e352e2ee7c 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.09.28__enforce_group_or_eperson_for_resourcepolicy.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.09.28__enforce_group_or_eperson_for_resourcepolicy.sql @@ -8,3 +8,5 @@ ALTER TABLE ResourcePolicy ADD CONSTRAINT resourcepolicy_eperson_and_epersongroup_not_nullobject_chk CHECK (eperson_id is not null or epersongroup_id is not null) ; + +DELETE FROM ResourcePolicy WHERE eperson_id is null and epersongroup_id is null; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.09.28__enforce_group_or_eperson_for_resourcepolicy.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.09.28__enforce_group_or_eperson_for_resourcepolicy.sql index de40d0415dc..2e352e2ee7c 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.09.28__enforce_group_or_eperson_for_resourcepolicy.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.09.28__enforce_group_or_eperson_for_resourcepolicy.sql @@ -8,3 +8,5 @@ ALTER TABLE ResourcePolicy ADD CONSTRAINT resourcepolicy_eperson_and_epersongroup_not_nullobject_chk CHECK (eperson_id is not null or epersongroup_id is not null) ; + +DELETE FROM ResourcePolicy WHERE eperson_id is null and epersongroup_id is null; From d172865f520d3ad38dffb95372a2cf7a80f2dfd1 Mon Sep 17 00:00:00 2001 From: eskander Date: Fri, 9 Feb 2024 18:27:43 +0200 Subject: [PATCH 100/939] [DURACOM-232] solved Community/Collection admins can't edit logo for communities/collections (cherry picked from commit d1812866a6593c9efea88d555d64b057f7db3cad) --- .../java/org/dspace/content/Bitstream.java | 8 +++ .../java/org/dspace/content/Collection.java | 3 + .../java/org/dspace/content/Community.java | 3 + .../app/rest/CollectionLogoControllerIT.java | 67 +++++++++++++++++++ .../app/rest/CommunityLogoControllerIT.java | 58 ++++++++++++++++ 5 files changed, 139 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/content/Bitstream.java b/dspace-api/src/main/java/org/dspace/content/Bitstream.java index 451a3b75784..5485735a281 100644 --- a/dspace-api/src/main/java/org/dspace/content/Bitstream.java +++ b/dspace-api/src/main/java/org/dspace/content/Bitstream.java @@ -307,10 +307,18 @@ public Collection getCollection() { return collection; } + public void setCollection(Collection collection) { + this.collection = collection; + } + public Community getCommunity() { return community; } + public void setCommunity(Community community) { + this.community = community; + } + /** * Get the asset store number where this bitstream is stored * diff --git a/dspace-api/src/main/java/org/dspace/content/Collection.java b/dspace-api/src/main/java/org/dspace/content/Collection.java index 53b63dbef1f..a13c19d46cf 100644 --- a/dspace-api/src/main/java/org/dspace/content/Collection.java +++ b/dspace-api/src/main/java/org/dspace/content/Collection.java @@ -135,6 +135,9 @@ public Bitstream getLogo() { protected void setLogo(Bitstream logo) { this.logo = logo; + if (logo != null) { + logo.setCollection(this); + } setModified(); } diff --git a/dspace-api/src/main/java/org/dspace/content/Community.java b/dspace-api/src/main/java/org/dspace/content/Community.java index dd6d978936d..d82e08bab72 100644 --- a/dspace-api/src/main/java/org/dspace/content/Community.java +++ b/dspace-api/src/main/java/org/dspace/content/Community.java @@ -123,6 +123,9 @@ public Bitstream getLogo() { void setLogo(Bitstream logo) { this.logo = logo; + if (logo != null) { + logo.setCommunity(this); + } setModified(); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionLogoControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionLogoControllerIT.java index fa0732f6b77..8fd7f58d9cd 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionLogoControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionLogoControllerIT.java @@ -17,7 +17,10 @@ import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.eperson.EPerson; import org.junit.Before; import org.junit.Test; import org.springframework.http.MediaType; @@ -93,6 +96,34 @@ public void createLogoNoRights() throws Exception { .andExpect(status().isForbidden()); } + @Test + public void createLogoByCollectionAdmin() throws Exception { + context.turnOffAuthorisationSystem(); + + EPerson collectionAdmin = EPersonBuilder.createEPerson(context) + .withEmail("test4@mail.com") + .withPassword(password) + .withCanLogin(true) + .build(); + + Community community = CommunityBuilder.createCommunity(context) + .withName("New Community") + .build(); + + childCollection = CollectionBuilder.createCollection(context, community) + .withName("name of collection") + .withAdminGroup(collectionAdmin) + .build(); + + context.restoreAuthSystemState(); + + String userToken = getAuthToken(collectionAdmin.getEmail(), password); + getClient(userToken).perform( + MockMvcRequestBuilders.multipart(getLogoUrlTemplate(childCollection.getID().toString())) + .file(bitstreamFile)) + .andExpect(status().isCreated()); + } + @Test public void createDuplicateLogo() throws Exception { getClient(adminAuthToken).perform( @@ -142,6 +173,42 @@ public void deleteLogoNoRights() throws Exception { .andExpect(status().isForbidden()); } + @Test + public void deleteLogoByCollectionAdmin() throws Exception { + context.turnOffAuthorisationSystem(); + + EPerson collectionAdmin = EPersonBuilder.createEPerson(context) + .withEmail("test4@mail.com") + .withPassword(password) + .withCanLogin(true) + .build(); + + Community community = CommunityBuilder.createCommunity(context) + .withName("New Community") + .build(); + + childCollection = CollectionBuilder.createCollection(context, community) + .withName("name of collection") + .withAdminGroup(collectionAdmin) + .build(); + + context.restoreAuthSystemState(); + + String userToken = getAuthToken(collectionAdmin.getEmail(), password); + MvcResult mvcPostResult = getClient(userToken).perform( + MockMvcRequestBuilders.multipart(getLogoUrlTemplate(childCollection.getID().toString())) + .file(bitstreamFile)) + .andExpect(status().isCreated()) + .andReturn(); + + String postContent = mvcPostResult.getResponse().getContentAsString(); + Map mapPostResult = mapper.readValue(postContent, Map.class); + + getClient(userToken) + .perform(delete(getBitstreamUrlTemplate(String.valueOf(mapPostResult.get("uuid"))))) + .andExpect(status().isNoContent()); + } + private String getLogoUrlTemplate(String uuid) { return "/api/core/collections/" + uuid + "/logo"; } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityLogoControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityLogoControllerIT.java index 3a0edc93104..f03e27b6774 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityLogoControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityLogoControllerIT.java @@ -16,6 +16,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.content.Community; +import org.dspace.eperson.EPerson; import org.junit.Before; import org.junit.Test; import org.springframework.http.MediaType; @@ -88,6 +91,29 @@ public void createLogoNoRights() throws Exception { .andExpect(status().isForbidden()); } + @Test + public void createLogoBYCommunityAdmin() throws Exception { + context.turnOffAuthorisationSystem(); + + EPerson communityAdmin = EPersonBuilder.createEPerson(context) + .withEmail("test4@mail.com") + .withPassword(password) + .withCanLogin(true) + .build(); + + Community community = CommunityBuilder.createCommunity(context) + .withName("New Community") + .withAdminGroup(communityAdmin) + .build(); + + context.restoreAuthSystemState(); + String userToken = getAuthToken(communityAdmin.getEmail(), password); + getClient(userToken).perform( + MockMvcRequestBuilders.multipart(getLogoUrlTemplate(community.getID().toString())) + .file(bitstreamFile)) + .andExpect(status().isCreated()); + } + @Test public void createDuplicateLogo() throws Exception { getClient(adminAuthToken).perform( @@ -137,6 +163,38 @@ public void deleteLogoNoRights() throws Exception { .andExpect(status().isForbidden()); } + @Test + public void deleteLogoByCommunityAdmin() throws Exception { + context.turnOffAuthorisationSystem(); + + EPerson communityAdmin = EPersonBuilder.createEPerson(context) + .withEmail("test4@mail.com") + .withPassword(password) + .withCanLogin(true) + .build(); + + Community community = CommunityBuilder.createCommunity(context) + .withName("New Community") + .withAdminGroup(communityAdmin) + .build(); + + context.restoreAuthSystemState(); + + String userToken = getAuthToken(communityAdmin.getEmail(), password); + MvcResult mvcPostResult = getClient(userToken).perform( + MockMvcRequestBuilders.multipart(getLogoUrlTemplate(community.getID().toString())) + .file(bitstreamFile)) + .andExpect(status().isCreated()) + .andReturn(); + + String postContent = mvcPostResult.getResponse().getContentAsString(); + Map mapPostResult = mapper.readValue(postContent, Map.class); + + getClient(userToken) + .perform(delete(getBitstreamUrlTemplate(String.valueOf(mapPostResult.get("uuid"))))) + .andExpect(status().isNoContent()); + } + private String getLogoUrlTemplate(String uuid) { return "/api/core/communities/" + uuid + "/logo"; } From 61053cab8df8a05881a4290b2fcbd5b4539fecf5 Mon Sep 17 00:00:00 2001 From: Yannick Paulsen Date: Fri, 16 Feb 2024 12:08:00 +0100 Subject: [PATCH 101/939] Update of DataCite crosswalk to metadata schema 4.5 (cherry picked from commit 5db110a19f30f49a2e22c48d0a19044adfc008e1) --- dspace/config/crosswalks/DIM2DataCite.xsl | 111 ++++++++++++++-------- dspace/config/dspace.cfg | 6 +- 2 files changed, 75 insertions(+), 42 deletions(-) diff --git a/dspace/config/crosswalks/DIM2DataCite.xsl b/dspace/config/crosswalks/DIM2DataCite.xsl index e92c371d6a7..5cc3eecdda7 100644 --- a/dspace/config/crosswalks/DIM2DataCite.xsl +++ b/dspace/config/crosswalks/DIM2DataCite.xsl @@ -3,15 +3,14 @@ @@ -46,9 +45,9 @@ properties are in the metadata of the item to export. The classe named above respects this. --> - + - + - + + + + + + + + Other + Other + + + - + - + + + @@ -246,7 +264,7 @@ - @@ -255,6 +273,13 @@ + + - + + @@ -295,9 +328,14 @@ company as well. We have to ensure to use URIs of our prefix as primary identifiers only. --> - + - + + + + + + @@ -313,16 +351,20 @@ + + AlternativeTitle + Subtitle + TranslatedTitle @@ -341,6 +383,7 @@ --> + @@ -436,7 +479,7 @@ dissertations. DataCite uses submitted for the "date the creator submits the resource to the publisher". --> - Issued + Submitted Updated @@ -473,9 +516,9 @@ Audiovisual - Text - Text - Text + JournalArticle + Book + BookChapter Dataset InteractiveResource Image @@ -483,14 +526,14 @@ Model Other Model - Text - Text + Preprint + Other Sound Sound Sound Software - Text - Text + Report + Dissertation Audiovisual Text Other @@ -512,7 +555,7 @@ resolveUrlToHandle(context, altId) until one is recognized or all have been tested. --> - + @@ -561,20 +604,9 @@ Adds Rights information --> - - - - - - - - - - - - - - + + + + Abstract diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index d38ffd64339..b7cc13e508d 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -603,13 +603,13 @@ crosswalk.dissemination.DataCite.stylesheet = crosswalks/DIM2DataCite.xsl ## For DataCite via EZID, comment above and uncomment this: #crosswalk.dissemination.DataCite.stylesheet = crosswalks/DIM2EZID.xsl crosswalk.dissemination.DataCite.schemaLocation = \ - http://datacite.org/schema/kernel-3 \ - http://schema.datacite.org/meta/kernel-3/metadata.xsd + http://datacite.org/schema/kernel-4 \ + http://schema.datacite.org/meta/kernel-4/metadata.xsd crosswalk.dissemination.DataCite.preferList = false crosswalk.dissemination.DataCite.publisher = My University #crosswalk.dissemination.DataCite.dataManager = # defaults to publisher #crosswalk.dissemination.DataCite.hostingInstitution = # defaults to publisher -crosswalk.dissemination.DataCite.namespace = http://datacite.org/schema/kernel-3 +crosswalk.dissemination.DataCite.namespace = http://datacite.org/schema/kernel-4 # Crosswalk Plugin Configuration: # The purpose of Crosswalks is to translate an external metadata format to/from From ab56ad09ab78966b4572121c88dc66d0ce75233b Mon Sep 17 00:00:00 2001 From: Pascal-Nicolas Becker Date: Tue, 20 Feb 2024 12:38:08 +0100 Subject: [PATCH 102/939] Changing a comment on DIM2DataCite.xsl (cherry picked from commit 1a567827df61cff9931d84e8732886457304a7d5) --- dspace/config/crosswalks/DIM2DataCite.xsl | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/dspace/config/crosswalks/DIM2DataCite.xsl b/dspace/config/crosswalks/DIM2DataCite.xsl index 5cc3eecdda7..a97d127694e 100644 --- a/dspace/config/crosswalks/DIM2DataCite.xsl +++ b/dspace/config/crosswalks/DIM2DataCite.xsl @@ -16,10 +16,15 @@ - + 10.5072/dspace- From 2c57c68a8129970b4abdaab50b750b63a34018e0 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 20 Dec 2023 12:25:02 -0600 Subject: [PATCH 103/939] Add more ITs to SWORDv2 to verify basic upload, edit, delete functionality. These all pass prior to any SWORDv2 refactoring (cherry picked from commit eeee0295107ccb2a84e0b2f9adf92536155efb5e) --- .../AbstractWebClientIntegrationTest.java | 14 +- .../java/org/dspace/app/sword2/Swordv2IT.java | 333 +++++++++++++++--- .../org/dspace/app/sword2/example.zip | Bin 0 -> 33777 bytes dspace/config/modules/swordv2-server.cfg | 1 + 4 files changed, 304 insertions(+), 44 deletions(-) create mode 100644 dspace-server-webapp/src/test/resources/org/dspace/app/sword2/example.zip diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java index 6556624c6b1..75b0143e3e6 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java @@ -19,6 +19,7 @@ import org.springframework.boot.web.server.LocalServerPort; import org.springframework.context.ApplicationContext; import org.springframework.http.HttpEntity; +import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestExecutionListeners; @@ -94,6 +95,15 @@ public ResponseEntity getResponseAsString(String path) { return getClient().getForEntity(getURL(path), String.class); } + /** + * Perform a request (defined by RequestEntity) and return response as a String + * @param request RequestEntity object which defines the GET request + * @return ResponseEntity with a String body + */ + public ResponseEntity responseAsString(RequestEntity request) { + return getClient().exchange(request, String.class); + } + /** * Perform an authenticated (via Basic Auth) GET request and return response as a String * @param path path to perform GET against @@ -107,10 +117,10 @@ public ResponseEntity getResponseAsString(String path, String username, /** * Perform an authenticated (via Basic Auth) POST request and return response as a String. - * @param path path to perform GET against + * @param path path to perform POST against * @param username Username (may be null to perform an unauthenticated POST) * @param password Password - * @param requestEntity unknown -- not used. + * @param requestEntity HttpEntity to specify content/headers to POST * @return ResponseEntity with a String body */ public ResponseEntity postResponseAsString(String path, String username, String password, diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/sword2/Swordv2IT.java b/dspace-server-webapp/src/test/java/org/dspace/app/sword2/Swordv2IT.java index 95ec7625141..64e3db7dfc1 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/sword2/Swordv2IT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/sword2/Swordv2IT.java @@ -9,19 +9,36 @@ package org.dspace.app.sword2; import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.junit.Assert.assertThat; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.nio.file.Path; +import java.util.List; import org.dspace.app.rest.test.AbstractWebClientIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Item; import org.dspace.services.ConfigurationService; import org.junit.Assume; import org.junit.Before; -import org.junit.Ignore; +import org.junit.ClassRule; import org.junit.Test; +import org.junit.rules.TemporaryFolder; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.FileSystemResource; +import org.springframework.http.ContentDisposition; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; import org.springframework.test.context.TestPropertySource; +import org.springframework.util.LinkedMultiValueMap; /** * Integration test to verify the /swordv2 endpoint is responding as a valid SWORDv2 endpoint. @@ -41,11 +58,24 @@ public class Swordv2IT extends AbstractWebClientIntegrationTest { private ConfigurationService configurationService; // All SWORD v2 paths that we test against - private final String SERVICE_DOC_PATH = "/swordv2/servicedocument"; - private final String COLLECTION_PATH = "/swordv2/collection"; - private final String MEDIA_RESOURCE_PATH = "/swordv2/edit-media"; - private final String CONTAINER_PATH = "/swordv2/edit"; - private final String STATEMENT_PATH = "/swordv2/statement"; + private final String SWORD_PATH = "/swordv2"; + private final String SERVICE_DOC_PATH = SWORD_PATH + "/servicedocument"; + private final String COLLECTION_PATH = SWORD_PATH + "/collection"; + private final String MEDIA_RESOURCE_PATH = SWORD_PATH + "/edit-media"; + private final String EDIT_PATH = SWORD_PATH + "/edit"; + private final String STATEMENT_PATH = SWORD_PATH + "/statement"; + + // Content Types used + private final String ATOM_SERVICE_CONTENT_TYPE = "application/atomserv+xml;charset=UTF-8"; + private final String ATOM_FEED_CONTENT_TYPE = "application/atom+xml;type=feed;charset=UTF-8"; + private final String ATOM_ENTRY_CONTENT_TYPE = "application/atom+xml;type=entry;charset=UTF-8"; + + /** + * Create a global temporary upload folder which will be cleaned up automatically by JUnit. + * NOTE: As a ClassRule, this temp folder is shared by ALL tests below. + **/ + @ClassRule + public static final TemporaryFolder uploadTempFolder = new TemporaryFolder(); @Before public void onlyRunIfConfigExists() { @@ -60,7 +90,18 @@ public void onlyRunIfConfigExists() { // Ensure SWORDv2 URL configurations are set correctly (based on our integration test server's paths) // SWORDv2 validates requests against these configs, and throws a 404 if they don't match the request path + configurationService.setProperty("swordv2-server.url", getURL(SWORD_PATH)); configurationService.setProperty("swordv2-server.servicedocument.url", getURL(SERVICE_DOC_PATH)); + configurationService.setProperty("swordv2-server.collection.url", getURL(COLLECTION_PATH)); + + // Override default value of SWORD upload directory to point at our JUnit TemporaryFolder (see above). + // This ensures uploaded files are saved in a place where JUnit can clean them up automatically. + configurationService.setProperty("swordv2-server.upload.tempdir", + uploadTempFolder.getRoot().getAbsolutePath()); + + // MUST be set to allow DELETE requests on Items which are in the archive. (This isn't enabled by default) + configurationService.setProperty("plugin.single.org.dspace.sword2.WorkflowManager", + "org.dspace.sword2.WorkflowManagerUnrestricted"); } @Test @@ -68,22 +109,20 @@ public void serviceDocumentUnauthorizedTest() throws Exception { // Attempt to GET the ServiceDocument without first authenticating ResponseEntity response = getResponseAsString(SERVICE_DOC_PATH); // Expect a 401 response code - assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); + assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); } @Test public void serviceDocumentTest() throws Exception { - // Attempt to GET the ServiceDocument as an Admin user. + // Attempt to GET the ServiceDocument as any user account ResponseEntity response = getResponseAsString(SERVICE_DOC_PATH, - admin.getEmail(), password); - // Expect a 200 response code, and an ATOM UTF-8 document - assertThat(response.getStatusCode(), equalTo(HttpStatus.OK)); - assertThat(response.getHeaders().getContentType().toString(), - equalTo("application/atomserv+xml;charset=UTF-8")); + eperson.getEmail(), password); + // Expect a 200 response code, and an ATOM service document + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(ATOM_SERVICE_CONTENT_TYPE, response.getHeaders().getContentType().toString()); // Check for correct SWORD version in response body - assertThat(response.getBody(), - containsString("2.0")); + assertThat(response.getBody(), containsString("2.0")); } @Test @@ -91,44 +130,204 @@ public void collectionUnauthorizedTest() throws Exception { // Attempt to POST to /collection endpoint without sending authentication information ResponseEntity response = postResponseAsString(COLLECTION_PATH, null, null, null); // Expect a 401 response code - assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); + assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); } + /** + * In DSpace the /collections/[handle-prefix]/[handle-suffix] endpoint gives a list of all Items + * which were deposited BY THE AUTHENTICATED USER into the given collection + */ @Test - @Ignore public void collectionTest() throws Exception { - // TODO: Actually test collection endpoint via SWORDv2. - // Currently, we are just ensuring the /collection endpoint exists (see above) and isn't throwing a 404 + context.turnOffAuthorisationSystem(); + // Create all content as the SAME EPERSON we will use to authenticate on this endpoint. + // THIS IS REQUIRED as the /collections endpoint will only show YOUR ITEM SUBMISSIONS. + context.setCurrentUser(eperson); + // Create a top level community and one Collection + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test SWORDv2 Collection") + .build(); + + // Add one Item into that Collection. + String itemTitle = "Test SWORDv2 Item"; + Item item = ItemBuilder.createItem(context, collection) + .withTitle(itemTitle) + .withAuthor("Smith, Sam") + .build(); + + // Above changes MUST be committed to the database for SWORDv2 to see them. + context.commit(); + context.restoreAuthSystemState(); + + // This Collection should exist on the /collection endpoint via its handle. + // Authenticate as the same user we used to create the test content above. + ResponseEntity response = getResponseAsString(COLLECTION_PATH + "/" + collection.getHandle(), + eperson.getEmail(), password); + + // Expect a 200 response code, and an ATOM feed document + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(ATOM_FEED_CONTENT_TYPE, response.getHeaders().getContentType().toString()); + + // Check for response body to include the Item edit link + // NOTE: This endpoint will only list items which were submitted by the authenticated EPerson. + assertThat(response.getBody(), containsString(EDIT_PATH + "/" + item.getID().toString())); + // Check for response body to include the Item title text + assertThat(response.getBody(), containsString("" + itemTitle + "")); } @Test public void mediaResourceUnauthorizedTest() throws Exception { - // Attempt to POST to /mediaresource endpoint without sending authentication information + // Attempt to POST to /edit-media endpoint without sending authentication information ResponseEntity response = postResponseAsString(MEDIA_RESOURCE_PATH, null, null, null); // Expect a 401 response code - assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); + assertEquals(response.getStatusCode(), HttpStatus.UNAUTHORIZED); } + /** + * This tests four different SWORDv2 actions, as these all require starting with a new deposit. + * 1. Depositing a new item via SWORD (via POST /collections/[collection-uuid]) + * 2. Reading the deposited item (via GET /edit/[item-uuid]) + * 3. Updating the deposited item's metadata (via PUT /edit/[item-uuid]) + * 4. Deleting the deposited item (via DELETE /edit/[item-uuid]). + */ @Test - @Ignore - public void mediaResourceTest() throws Exception { - // TODO: Actually test this endpoint via SWORDv2. - // Currently, we are just ensuring the /mediaresource endpoint exists (see above) and isn't throwing a 404 - } + public void depositAndEditViaSwordTest() throws Exception { + context.turnOffAuthorisationSystem(); + // Create a top level community and one Collection + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + // Make sure our Collection allows the "eperson" user to submit into it + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test SWORDv2 Collection") + .withSubmitterGroup(eperson) + .build(); + // Above changes MUST be committed to the database for SWORDv2 to see them. + context.commit(); + context.restoreAuthSystemState(); - @Test - public void containerUnauthorizedTest() throws Exception { - // Attempt to POST to /container endpoint without sending authentication information - ResponseEntity response = postResponseAsString(CONTAINER_PATH, null, null, null); - // Expect a 401 response code - assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); + // Add file + LinkedMultiValueMap multipart = new LinkedMultiValueMap<>(); + multipart.add("file", new FileSystemResource(Path.of("src", "test", "resources", + "org", "dspace", "app", "sword2", "example.zip"))); + // Add required headers + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.MULTIPART_FORM_DATA); + headers.setContentDisposition(ContentDisposition.attachment().filename("example.zip").build()); + headers.set("Packaging", "http://purl.org/net/sword/package/METSDSpaceSIP"); + headers.setAccept(List.of(MediaType.APPLICATION_ATOM_XML)); + + //---- + // STEP 1: Verify upload/submit via SWORDv2 works + //---- + // Send POST to upload Zip file via SWORD + ResponseEntity response = postResponseAsString(COLLECTION_PATH + "/" + collection.getHandle(), + eperson.getEmail(), password, + new HttpEntity<>(multipart, headers)); + + // Expect a 201 CREATED response with ATOM "entry" content returned + assertEquals(HttpStatus.CREATED, response.getStatusCode()); + assertEquals(ATOM_ENTRY_CONTENT_TYPE, response.getHeaders().getContentType().toString()); + // MUST return a "Location" header which is the "/swordv2/edit/[uuid]" URI of the created item + assertNotNull(response.getHeaders().getLocation()); + + String editLink = response.getHeaders().getLocation().toString(); + + // Body should include that link as the rel="edit" URL + assertThat(response.getBody(), containsString("")); + + //---- + // STEP 2: Verify uploaded content can be read via SWORDv2 + //---- + // Edit URI should work when requested by the EPerson who did the deposit + HttpHeaders authHeaders = new HttpHeaders(); + authHeaders.setBasicAuth(eperson.getEmail(), password); + RequestEntity request = RequestEntity.get(editLink) + .accept(MediaType.valueOf("application/atom+xml")) + .headers(authHeaders) + .build(); + response = responseAsString(request); + + // Expect a 200 response with ATOM feed content returned + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(ATOM_FEED_CONTENT_TYPE, response.getHeaders().getContentType().toString()); + // Body should include links to bitstreams from the zip. + // This just verifies at least one /swordv2/edit-media/bitstream/* link exists. + assertThat(response.getBody(), containsString(getURL(MEDIA_RESOURCE_PATH + "/bitstream"))); + // Verify Item title also is returned in the body + assertThat(response.getBody(), containsString("Attempts to detect retrotransposition")); + + //---- + // STEP 3: Verify uploaded content can be UPDATED via SWORDv2 (by an Admin ONLY) + //---- + // Edit URI can be used with PUT to update the metadata of the Item. + // Since we submitted to a collection WITHOUT a workflow, this item is in archive. That means DELETE + // must be done via a user with Admin privileges on the Item. + authHeaders = new HttpHeaders(); + authHeaders.setBasicAuth(admin.getEmail(), password); + // This example simply changes the title. + String newTitle = "This is a new title updated via PUT"; + String newTitleEntry = "" + newTitle + ""; + request = RequestEntity.put(editLink) + .headers(authHeaders) + .contentType(MediaType.APPLICATION_ATOM_XML) + .body(newTitleEntry); + response = responseAsString(request); + // Expect a 200 OK response + assertEquals(HttpStatus.OK, response.getStatusCode()); + + //---- + // STEP 4: Verify content was successfully updated by reading content again + //---- + // Edit URI should work when requested by the EPerson who did the deposit + authHeaders = new HttpHeaders(); + authHeaders.setBasicAuth(eperson.getEmail(), password); + request = RequestEntity.get(editLink) + .accept(MediaType.valueOf("application/atom+xml")) + .headers(authHeaders) + .build(); + response = responseAsString(request); + assertEquals(HttpStatus.OK, response.getStatusCode()); + // Verify the new Item title is now included in the response body + assertThat(response.getBody(), containsString(newTitle)); + + //---- + // STEP 5: Verify uploaded content can be DELETED via SWORDv2 (by an Admin ONLY) + //---- + // Edit URI should also allow user to DELETE the uploaded content + // Since we submitted to a collection WITHOUT a workflow, this item is in archive. That means DELETE + // must be done via a user with Admin privileges on the Item. + authHeaders = new HttpHeaders(); + authHeaders.setBasicAuth(admin.getEmail(), password); + request = RequestEntity.delete(editLink) + .headers(authHeaders) + .build(); + response = responseAsString(request); + + // Expect a 204 No Content response + assertEquals(HttpStatus.NO_CONTENT, response.getStatusCode()); + + // Verify that Edit URI now returns a 404 (using eperson login info) + authHeaders = new HttpHeaders(); + authHeaders.setBasicAuth(eperson.getEmail(), password); + request = RequestEntity.get(editLink) + .accept(MediaType.valueOf("application/atom+xml")) + .headers(authHeaders) + .build(); + response = responseAsString(request); + // Expect a 404 response as content was deleted + assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); } @Test - @Ignore - public void containerTest() throws Exception { - // TODO: Actually test this endpoint via SWORDv2. - // Currently, we are just ensuring the /container endpoint exists (see above) and isn't throwing a 404 + public void editUnauthorizedTest() throws Exception { + // Attempt to POST to /edit endpoint without sending authentication information + ResponseEntity response = postResponseAsString(EDIT_PATH, null, null, null); + // Expect a 401 response code + assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); } @Test @@ -136,14 +335,64 @@ public void statementUnauthorizedTest() throws Exception { // Attempt to GET /statement endpoint without sending authentication information ResponseEntity response = getResponseAsString(STATEMENT_PATH); // Expect a 401 response code - assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); + assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); } + /** + * Statements exist for Items in DSpace (/statements/[item-uuid]) + * https://swordapp.github.io/SWORDv2-Profile/SWORDProfile.html#statement + */ @Test - @Ignore public void statementTest() throws Exception { - // TODO: Actually test this endpoint via SWORDv2. - // Currently, we are just ensuring the /statement endpoint exists (see above) and isn't throwing a 404 + context.turnOffAuthorisationSystem(); + // Create all content as the SAME EPERSON we will use to authenticate on this endpoint. + // THIS IS REQUIRED as the /statements endpoint will only show YOUR ITEM SUBMISSIONS. + context.setCurrentUser(eperson); + // Create a top level community and one Collection + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test SWORDv2 Collection") + .build(); + + // Add one Item into that Collection. + String itemTitle = "Test SWORDv2 Item"; + String itemAuthor = "Smith, Samantha"; + Item item = ItemBuilder.createItem(context, collection) + .withTitle(itemTitle) + .withAuthor(itemAuthor) + .build(); + + // Above changes MUST be committed to the database for SWORDv2 to see them. + context.commit(); + context.restoreAuthSystemState(); + + HttpHeaders authHeaders = new HttpHeaders(); + authHeaders.setBasicAuth(eperson.getEmail(), password); + // GET call to /statement MUST include an "Accept" header that matches one of the formats + // supported by 'SwordStatementDisseminator' (configured in swordv2-server.cfg) + RequestEntity request = RequestEntity.get(getURL(STATEMENT_PATH + "/" + item.getID().toString())) + .accept(MediaType.valueOf("application/atom+xml")) + .headers(authHeaders) + .build(); + ResponseEntity response = responseAsString(request); + + // Expect a 200 response with ATOM feed content returned + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(ATOM_FEED_CONTENT_TYPE, response.getHeaders().getContentType().toString()); + + + // Body should include the statement path of the Item, as well as the title & author information + assertThat(response.getBody(), + containsString(STATEMENT_PATH + "/" + item.getID().toString())); + assertThat(response.getBody(), + containsString("" + itemTitle + "")); + assertThat(response.getBody(), + containsString("" + itemAuthor + "")); + // Also verify Item is in "archived" state + assertThat(response.getBody(), + containsString("f%iAVQZtR0tNJk@EuW{=)X|)00nvm zegvk2 z{Ku}UO~ugt;_)@Zdw74v3aAkdOp~#sFLtiKQ0E;XCZ>u%In^^VJLcyrfs3O=XEr+3 zBke`FjmIWp!FJs|%}bj~x!yGY92X9bU$yffL!&Df?mZcoyWO&mHuXIt(I$fFuYJCVL{n4TSLqiaNPvO{x z&fB${ti9}8c_R4y^u+4WtgShVAh|-FYIRq+i<1ln%6KQ zu@D%!6d83f;5GVvznbNB8y_b%m#eN)xKi;i$7SQr`vy40{dmffSm4A9I2eA#2tE^>-Vtx1o1g@ z5EbXD(2wU2Z|6k}q1{!#PYsJrJHQj&rymC1)i|tH7HEqrJkZI*CYadVsB+LqYQ;Ro zvdF_`5{u=~jJa#uM^Gb&a{tV%@gNautLFHpM5i{bnj)~h`=wfVl+Kr1r>}#@OARr| zOnnUwJ<(+i>)eA1PhDnO1Zkzz`>S&@rW23O02NmiGSP-p8JPx)#gx6h_{O-K5-P(X z+C|~HI&giYQ~Tjrnikp3!O_mzq+?yiz!V5R4TPq#Gx=gicradIBGw@EyweQA$p&b? zCxe;|?h0M_g#}sB43kNMsklUu^Kavl&-i{Ra*k($P@;~O<=_=*63Y^5q?nsh7MOA< z=P|Z`wV0s!4C7T0ToBQac@Cij8SZ_plSUM}WCRJQxkgw`00D0#e?E223-=tWOept& zGMpaaiD{e-e*@Za3?VT|LN0k&U^2}6lGxGQdJ0SgJnJ^TPFyZ6IDAauJ3Tm^UZG6{g|@awcTfW^*+E z280@I2Gt(e1uT4CM$n;Jg_fr>z%$xFUL*t2kH`y5TiDkrrYy1pB38qO)xBhf=xk4J z_u*j-T;*-Ww%Qc(h4azrZEKD5Na0>!jg)&^0}i2h3I_mr%p_eu^G*S=COEv>EuaSQ)C zjsJrV?rb_@3kM6YxXu4vTgzeAEP;@HK=5Dxk1A~g)bIe?Odgw2w&CB{OuwJ0koDTG zzjBFKcE|)M{GAAd$&I<&{z*JdO|+u3uI6|&-e7;r04nRTYc~BIppKvW39EKoL%G<` zJk_f%!TR+cv$$YpUl~)X0vGaoI5$Ki3}8EFMXL2VIlSJ4nor5|TSUA3hHH90a(#g; z-Rcf-jbLbED0MC%1fcb!8fy5buLsuT3Ef`Re4T5PL@&)PI+fqhmP6DM_{ZVth@60q z2Wix@PcY%JB}i&saM9gMg^uvp36J78AHxErg85x=Z8bX}EEWA>6Y37VHLZugZc=^0 zrNLBBk&#T&>TE%|b&@0!pDcB+$!*>`3{x-LTh3IzeJc0E>GUl1_I2d*5L0-XX)NVW%o)gc{ zbLdlgT!h-s*FiBaVg}FspqkH&O&c`C)xJ1Q8-^y&>LP0VF|@uQKe`b%hafw9!u&*K z@dTk_+=knkt>1rvA^oB`LKkp!+_Y!)Hb_Hmv&D7A`dXkjzF+oVjH}3ONfF9^n|coK zQtGt&j)E{I+nJ(ch1%YII0<3Q1A@#`SNpX*uaBpG->izt!QK6J4>U;H!CmNomIZ^`x(t&!w>j@-bG`0-^IF@_ZmO=X%iSO9 ziPO-~|5%tANfO;pT-aS#T*^oSfe8az^RSRHFckhSOsx%q4m<@-{0Wt%!-c&L7((xY z4+{$WSOE{?<&}7WcMI!Elc1+M`p%Ka7r=C|)nhIsB4MplA@FM4Pzc6gVWvP>D{g|ZMM zIar;UO|Td>ff|?a%r3t(^C2eq#;(nE&3HsjU74lz_YE!K}U5lL~6wC4Ui~Qt{LLHola1X*18@#36`1lEdCGptEbfEPu|#j&G9$ zDxpbFatCN&w=^hB4+=_3Y9|HgO%0%49G9qi=50ul(WBX}2l?rz zM^b(@t144+2XE5QDft8n23 z>)z|%CI#FLyHakRx`jI9hnKz=D}?d~-rUt|71SF0xKFSSjY&oB3%Nk%-yQioZCU6d zZdCR@@B%M?vJO&SgF3hexJqIhq;NCJKT!;gb2?CDH`W{K+LV7!d@1#{?C}*jB^phL z$=TEkvFjw1T(k2T#Q_aA)u-}o5!=>h$Z@KGMeD>=S72Lgc?^3h#9ima>7?^}&X^Yc zxBGdNa?hyw1{{+;*Udb`O~Z7f=f8UzQZ~L3NEMOQb+hghvBXVL+7d+-z`x6<;{)aG z&Oa|V{7i8@yOzN4&Ou5OV)H?`2N??E-Q(46HO)sV327gGLY)Ts-6B4uf;?860E(6G z7^cFEosY1sLX;EGE~>Rd;E#vMQ=+&!@88QJ$M18wFZgp0TU7YUDEJ)k7%v_WDo9D~ zf((fL%st5jb7Qze-j@-lNJC3M2=O}h0@%stxAxCjak+d%K@INM%Dl*O_9i zcKJX?zMMAxmPehZ2VLXZmvl}HFR6f28H;C+;FUrgZ-mRh!_Xm|k!;{s zNwO)-?}%~z>CAIYzRhGsrE}fJL5M1*LEQ{JHgnntD5qPSJ28y^O*@x- z9@Bn|qzS4a?oHeQZS=Wvvukmy>v69hk);Dza+v5KJ-?>to8=;j;=0M9fW6mNhD2!+ zmpdy+<8j<&6Y^L8q3t50MyNc{by#M30!qou6n?lkk$i#nd&% zPr@I{24`Wq?%bjtwpg{e;54cRR3q?gAm;4Vdv$EB^cy{ox=HWhdi2+|Mmj@VzCS#( zKkwfMKD&L2y_caeiKd+#TC;9TAsQso9@nn$(8=UcsX}~!_mS0;eew*6-;s|kq#<`$ETCv7(DW!Aaim2(<@P6JQO?h59ttqb&ny>JVd;Ok-i;pe@D*5s% zM1kI)P8vPw!b?mk0Lv(azf)SE)^BbvV|m@-n?))iUeUII-~RZzZf(^4D$2j`N)XJj z$u?JFvP3-e!Y&=5p5iT9#p;xG@!PfFXBk_+G*7r7^i*S$oI0&+y|)fDb*#<5%oH>) zl6am$`L4`PP|ozdUarsRtFk_F7NUR1*Jiv8-w+tGmBzMjt7-_R3}7t(I>W|eV?~W8Q)9q{Hu-l`-xk-p*sFgBMx_~-av`31@+SkmhQ9_ zeFK&u+f&?MV9J1n->EPXG*wM!by3xB_DgCL=Y^Vzj4(F?g)apmVp7yeWVt@k#;7`f zhYo)e0|}Mx$f!~tvQYsYX3ij}-=JahPgk2*3(00ZIV_q<9!FDK+5OKm21avP!1UlC z2-!p%sh0q-3@%?BN9z1S(mPQ%)wYS)p~61e!?BRPq$k}$w+sz^a?Mvf-NAO($K9Uv zv%JS%GLzMkjqgd(`srDSsPyShR^PZZ(|RjvTeKi=YErysb!`Z$bJSZF$Xy-(15ubw zZrLH4uv?uvs|>r5QAFSfqc5tiVG-V#yz~e39F#hhIn?LfIRUiP+lUfusK8ky@nY1{ z3_l|VWmAo?!zrPr-~r=}?l%fk#$3{5W@)n}&waG{vb23m9P_sGiH&-jh&cj0lB&{eZpg?sP%+0k@zSbJ+RdJD zn^7xewlP`utK+kdk?>0>TFHY>+|j6Yx^bO!fA7&6W=qOa*mw?-2n)Deq3zz%OI1n5 z?LA@?b5rSqa%N6Y zkbbZ-E@=gM;!=gBD|c1lxI8(S4QlsLogCaG$!iApQCO(97% zrUtYYOKVa8^diV@c7yL7iK`RGTI%i}P$d<`+)jh*KY=Z`DD4tO>Ss@1FJC3)Qs`(c zzTj5N(7N?xTr7)R#c5`EQGB~w>&LB?7x?~dmTqgz4FwG&GP(ZwITf8U+3$hwM;0bY zCFRC|?SUV9n!fLw)-~s#N`j$cRSI9BlRGCO=?Dk(7j`B!~?g_I~h<3X79w^m36R5%~l^~OwNj~9@b5bOxe*$tXvg(f5Z3?TrW2PVa~ zGxpZ?+MV9)$8Fw@15&q$Rrq9nWN%ZeS zaA=5!9d$}-$;4TMn$GRbWnG__v%Sz!{k0FecV>A!s=Y{qeF~{tT-#O#3+uQ54eSnT z#3^D2zTPe;{ny3~c>Rja-y&`HGdqOHfBj=i*u0E4`WRHAp0^&vKm4vVB{DR44cVo; zqVS@SpM6|n6mbQ@Z za>SXo7E;3F0Z7)AsEE3)73eQ%hGGZo0v7J+rW>VjIND$6KQ%1?1Af4A_e90tziM7R zz`wNlN}uDdrS4S7EW=Wy)zkMbAC8MR;0P!uO=PamJxS31|I~kkL&b4a9_^J+F@>q?;vBbP!8 zPwy!LXWUNI(~w=g)~6R4S@8Xidchs{?5pmrxlA(j|1=an^_wS;eJhUCQEC#OXS7rF z3$V+Oe{;QESAq)N9BCI&|HHc2KD=J)CjE;)$w57L2j(5={^{*yRjjp0CC2syi@it{ z8wvj2<*5IBLU!E%NB8f6fsSj`1zju&L}&+0hDrTF^WS6hUHIN)*c5{OyPk#5SHYL) zNi1{x=a|pIqy@sI^Vj%dcy4fJOcK^t?1^iykaTbDNd4Hfp0*<$HVV-yMHSZ-5^pPY z%o|TCz6apjny3s-x9a3&!=AFcOVq;d2EhwK9HXVZ)mlq3n=zk7-i1%8rQMta(rXAC z4~Jn}aeFufj2Hb_+@*w6nG&SL7}J|1_VaVP?L`_=RAdBiZCk+yFRHq$2u~b8upGZa^?0vqPs{u};avfEf z1RQv_%q}2oEYPy;yEyS8x)^d7nYauqPgJ8S4UW#kIvYmMYWL^63zZkvc1v7fH`ZcE zn(0{dltgt~H(<*{G>hLY0gNeG`#P_*k}k={`nf*P8Un7TT)WoY%xAr~g(R-(o$rcX zX=Q#7oPpgCUOlmyuUxw0{n}-K+Bw6Tb8lNA&CYsNn35fKZTLC$zBcOH1rG8FBS9Y$!j7yPH3jc*GXLLLNuV z{&g{KaeyX0bhM{UI42c}?00Yu>X7_5Y;d+M`QEwAP}yqsM%8Hql!8Y@F!{;b+@d`S z*VS%0)^A9c8Y|6=Qqjki7@$*7SY!q0jw;rLRC0 z>D|_oy{BjykD!&P?I2gSMJJc$+hDifK(an?30xw^u}+_kiubd*cZO+4G`xvVUcMdS zyZTo0;cw5A%w%U`=}01PGIhm?z2^&iG952#(V`-W*PhG%E!A`EMIrZ9a-cd=SJ z(1SZ(Ka@2GuLKrg0zQZl^T*m{?xoGBymBY77Ho%2^`vG4|`Y z%5d1vTuoCZ0l_cnmll1zFnhanBBE@rtjPbfoosk#-W*7%XR!K}X;)sLbw! zmTg+T+eqw0dIFsQb}Rbd#Nex|$8F3KDsqcQnxjyLqOPN&d3KQd(^4%YBp%bT@Q{00@eZ7+h^Y5BOgrMc2x-m%0}u1 zu~)$eU5(wy&?rVqb{iL`6=1C=3(wO3&k{1bUQUva;)eRV7eOd1$O>N}SCTVsLnWJP zsS^p&Hf(1cGb9%5N8rW*Sq^-n;Gy7DC!(y_SyoiY1C8;ALq#)1SQ< z++kw`dU^pKnTvHWQsAj1t~X_cpTV7$ASRY2;H|^rx?Q}Bij0oerUg@OW;r4$Jp3<$ zkuuGp2sMo!FK)vNZ^P12ID2_H+h6h$oC|)`9A*}R;*knwjFLv5kc4kZ&7I>%6xxly~Dtrh9BaUPlLc0VkF-_kT*Cw{XGcTyMka- zRhPfbrc+;hESUy?K5aV(IAbAY(J>Jl$%wi8k>DIqG1Zz^!$$prBzR@NB_p=Xz=wkRgUoNbY)PGvO=r z{`J_&nNZ@y}@kq9Z> zRWy?ArZJh!!phU{I{UAr;-po5w_pN}+ENHE&+lav-T!N#^Vdbb(*E8T?!$qTPC|K? zMtw8V%eL*4CqH3~eNZ(Zwqg`(OL&@Ms5x7pa79xTe1h=)nn^%W&0dx>S5;|5-5&U< zx1*vXqprC^aQI-T7Kd%NTvyGF=?{AyoyYYVY-rYe8EpE9ep#1W=P5q0P}4mk7JTAMX(CiSodpT;!7}*$NM4;sH{HAwR0mdq0c0 zIN1!hsqk`TcW*E&wOyM0t7akQkEpU}6LAB@Yg!Y*ezv`|gir&bKUx%1Ni{Qvg z(;rp+EJ2}%_VUlq5-er0EEwlMq4c#CX%y>Y%;5@g zQV^whT-7^ZCwG?G{|gx(Ki&>oh=N%=B0A_o3TMK95L+DQtecSWxB&$?l%Vz8A#75h zVvRMFi@^6??_bl161B{~hi^akj-=YsuAx0$%-)?3d}_Y$zKs~?gRCX0++-vfgYaF% zhYVrh;^DLqe%+Y>0!cU7LEkmoaN!$5>+qg=JyF^XgntHwpxhi>%X0?cE^F?NePGqTG&N&`9*_Q6+B#C-tjZof#{S(;-1a#p5iF}>+JmC zyE&9pUhuvq_FD`wz)pJy3GX+n!PbKi6{(G+kbVc`^4@N38AB#Rf2flr8jXT~cz)12 zFj`>G;C#0i3&DiEFB#FvAmnbyLq0elog+<8U802LZptJ*@&qqVx zO@u;+^#u*h*MyhpgX4XI6h&D=1_N^X)1>1cMFR1%yHaX?w-}IERVsJ$-Y`%)1IndIN<7?x;JkEWpTB#`NMxhzcKc&JV{vL?MAID@V3^u zTOmQKO#W?KK(XhyGfD%`%j@vD@=qtJ6JbCsr1`5e1Y_C#5}HilMWr9<9T~ehiWro?ha) z{sluv4Nc)Nlgl&NW}tiv717)izaRwHNEEXbOqMiUsy%lqk=_Zd^<>lc#`p3>muiY$3EVV+3Quj z+AXT=ThF!#ckcPS+mvw6a>XkgmChrIcQ#Vr2^Ns0qan}KS$rw;va+~y9JD&LcwmEM zcPo0}?qMIqX|8e-wOi2GB1hTjW&E+(E7Tp9?**Rai<&x0SwywZ?(( z@DkSey8;xtRhzQTa|+VUZ@EaiFuA!ZjE6vBnD|kZf9Z&FR3o>p-jHz1%x2Jswf(g{+Wc&^#BPmO{3eb?nt=rilrBwGDA zjVlwftNrnGFXTl$$g@Y$!Fjs8AH3%yjDnLmJ)EmyB2y+ko>P)N z46oTV<0eU%s!$$%?7G;#jE=lUkt~QtXzPf6sApXbh6F=si!PtnV@!F#T~0H|K*FMGL?MbRazRg?QBt*@Rl9B&>4b6Y-+ppWq!M*GM@j7(Cj;XA=r#;a6d zrV{=W*}V4y{8V-ZCT13!KC&Bjb!PH4iD6dM1!CuVeTW{SCmBG0Ju~iKnNsVxDe2&1 zAz{r8M=x?-eeeI-8Cq3c>p<5g9}eAl-nN$ro?=q%wz@`uN+}kZ<*d$;7Ao_jJt~eX z7Bke<1xwGTp&fnmHX-il=c-}~UD4T?bXZ#_?l}yF3MU>j(v?KjAZ)OzoI$8UPPCh_ zNP9m}d@*C`Sf0>HXfhf2C8l?V6GwQ^jIyhWMH*CX#neignLjv*d z79=YGaI%xT*5!$K`qgDL(@7DI_+Mko#b_ynngr*t(1{Ut;QvCXv>D;}mQBKQ%f64< zu4)r7llT72+YV+wa2ZWjI31+j!qS+5=#MW|2=eOIEKx%v&;vs8E|Q(JefD;npcF}a z0>t2qh@t$S0MkEi;fU)D?$KH}AFS9Vs+3CrLB@Gdxq)o`BG^bSquPhC^la_5v3aU@ z@(x_AZnPS^Fj-4Z>Adpu0`z>5bpRjJY_GLAON~OI0a|NgYFaG*m=;{lW`CBw+y(H1 zW=Rj*ywhpk!$;5M$bCekN{$yRo$vLEdvv41)w(20Bk#QJMg0UFo3;z(xPVMc-sHw- zagy120UKzY`j*f18UXiQxjxCB#gtW5QbK55!%$Q{lu>-3L&NVW?)6v$Kcn4|coGmw zBTc=SCMy9qS2=%>;`s!%QjZt>W4`|BtJ#*(0F)pJH3(uk8Oi~UBkAGeU~22TK#~?b z#n()7%1CAiFr1=1T-*X|R!u9AJ0*0(ZI#ddW?pVb>g&Q^H2KU084 zH+O)+=N+Df^U(iE$0wNheR>O6%lL(B`YE42Ev}9wQdrxV5?ol&+nrk)8{D{Qr>wQM zggiR^d|vQlZDnmqXLm_!Z(C|jQcd`Xc&bL4Si>KPdD2eL-`x<+%|ikKUI@n01d1U< zqzTAHs)0gvUT9@pK5b@hV+0LI2=ICN2vdUxVs{fG-K)R$gJM8OBrOQ2i>)ZEY>XUc z;2|vt3N9=~4uE?q_cm095oX*|H&5b4nD3t-c!`%IV`Q2BE$^0MY1*zq2|@O_3VkY6 zEZ_|bVkxiR0gFc&S_WEMd~@BV4L^jnpPKp#K9+pF2Kl#dpcXe@bqn_xjqystiZj zEPg1~j6uhHt9J!Y3$D;#YYPR5N`51LJ-wqvuiA;vSL~`0$QQXi7d2t~Oi(VIS|+JV zz=kJBIKi%iZ?$4`2A}f807DQMpPW~LWb&aexbdo%*?OmGWK?Tw$}4YgGR0UkLi?UkOkcRGi;WzgwJJyyKXvx3+)tKK>O1 zQReZ607lX6)*nq>`d-ZegQ~Ex+A}b!pPQP3HZTkX(iq&^CCLB-H2@9-%98Lm#@)jp zI!+8As70%l@kfIjc)@v-?!*%Ojr$nQLK^r%02}+oy-ar>u#d(lZ*p7tfDVJd5YQAf z+!GhZfSB#q`liEZ>Kf|@xb;^|esw>}?kwzVEXwUJ;NN*ecow7-23FoMjQu{$56yuX z82c4=MdY>?&qiZ}ro&!nt#5ug72ZOlDtH)nC0F(p_CWG0!0z28>Eh!T3`qMh@T~nA z@F!t=^oJV9KPO-!{HA~$&@XyR)qqm|>JOcWIVs7Mf|LN_mY)vzmo}Vq1@aJoq z4krT-*2oA<*g`NSg%FYKSzpM#k%d(V1Q;_jQQ+4P2oW8f96bPOPd)}|1k$tc?=2e5 zK>su5ZzNMw;BOdHpUAWOmHJYQ|M*&_hT!kW+Hd9f(AWn;%s0W0-p%SSGR)!h?5BH7 z=6!zK46Q#`c5v>^^s&`zbsKbxQPM zeElo)-?eA&XIs-q`T+iAh0{`Ot$hZJF$&trRI%}Ohl$jTWHG9DRe@rY-tXTGJkreXko1MDrs~Hg`D0sS`VtVwbEjewA(+p;RYprpJ&n}s=gE?NZ={3i zWK@+N)pKysIJLK;1s{FE+?A%V5K-HLv_)b{edI(8p=-EL^?dx<4{N+82 zzJ-vlM;rYHJWhOphBqrw69kbZ6%IiCV-7po8;l!8Egil7&z=4UNcr<7QV)}S>4eQ~ z#rhV_0q=fT_+-hzD;p?Tqc%srl3<+*lK9LqS=DrgX zE`N&i#jh{+@#Kkr^gtbNRYR#`F9Ntx+ce`U9dpXz-=YxLmgTA!LnJQc-B-efoWp;) zi_M!f%=W{F+UJWNOTMX!xLMms9p-r`F1Go z6h5)7Wwqb;G#G33H@pGoYoTiZX&lLw1$A1(3+aw(+fKJ1{usjXU;Oiv>>*nJ&MXO9|I%>**5YP*yFtqM zL1CL{I2^Pp_;{wdI^2`=23!$`2Z<#}Ed}5-ot(s0%3Rn$@72&S@i4SDgzhhiNCJ3u z3AE71dLV1;T{YGZlTa3-b;76a-n@Q7ssbz0B4;NZE_pU~{nXyYa+b?*1y*(0h6Y6Z zPfq(h@x7J+5y5YE;?S{&19gw@n+uOzw!P}(xLb3De``^Xp`-d+|nx4+qhgi9zRR^ z$Tg~O1uyB2eRBrD08_FY1Hh*5;CYyhS?Jta^V{1ylhUJiYE|&p#s67yIg#W;x~;^X zcy&=b-lt)1yMJnhyDJ{Lb)RhOR?pJx(@5Pe;{dNR8eGYA|l`TSBk(o(s}OOd)8{w zaS?x`EN^aGs@sES{*h|{nX1v5O9x7pSilu?9!0(R-%Z4EVDL736sNNrC>UX*(7tnW zYD_glQW^_9$AIJ(P3sneg<*nAAO*l0$N^Rv*<25%t7mrF2*_=w9d%ap1J9p!-Y|)R z4g0pe&Lky5wQ>cPptZuqB79UWxl>!kLor>!ydUCSRmaB<-DMTX0C2UI2sVU6T=RoM z!o;p^(}i4qy;OoxGDA{ez%c!xvP}Bz9CDAE=a_J9EPvv)op+xIU1p!uIOwwD8iEk_ z@;67sT#K$X=ambmtV>Lbeg)3uSvJlYj@5Z4JcW~1tU8O+V>6`V9rN4I%*VqL2$nQN z5sE5Rc8hP$Tmk!_rZe)-&FrcIn-+<>ZJVzs{#KtcD+9RK7WQ0S!-AQO>F~n!sc^f7 z0JT6Km?Lgw%R)_&QF^M+k!vTfpr603<6&}|%{!hExb@$%CpUZM&(r1?zKWB%UD-p4 z^Q_riXM;33&F6fk&(zHBH?$%@9T`NQ2o+y=k#|arp1eVcGU{F!;OOMxTjl0TTb^1C*4Gn8h{a7$pr%BBtsS_WGy;S zFHIRO|Mti8rYWi2?OY=0;SEEc*)l#U+K_F(K*wc|MC(aNg2>~EDk6mf)SCm13{+pE z8RF(F7PkA^>DKj=VLi++KC*i187)dJE!DrZ(A7+Bvx z)7pXw+)?zpd1Q=gSswTjM{GRcP`@Qw2Mrpgv8{yWR5t`T)j^E4IMr!y{a9L!^-H~okD1 z#pah=Cp4HpZ4=|oA1gcQE&u&iYLSaiyqks}pzcFe`HFSnKs7LIp8Dz|ultjAQ5>7K zq<9nevSmCewDqY}9J&xw)e%>Et&K{p?o7=#(=Oa*wJJ)m8Np0X@v~d;5M%kK=iP@9 z*;@{n2X;F6j}K0KX;jr%6uN<`f}XTDrb7>e^N|*fF;G!14YCT=ECWz{$i(b0?oI8x zu?Qm%u_i6M+b?uZBf6rV5`edI=KC=#@_ZN|#+UZFU-B!}JfnF?QMjdnxLM}Do1Xq% zbgU-CONl794^1&e>av)ZwDl~jZ4r#}#3*EhKUO9SZ-B5oQco(xTADsUWV`D^8R|3f zf#F1{LKTsB3D6-yOoo4*@(CNM5Yq|KqKH4o$w|WLiBs>G?UIwe4gxPMbhcN+Tbi{)*6x`&doy$NQ24uX*x3zegsrG+ zPWhgGoFgUFqf3r~nGTbymSSSH0m-PgICAhWL0Ogu@>ilYFx=WU6p^1-QTQWHc5w`N zaDEkPA7m!p=pi2tyUQYGKK9F<4S3~TRh8qWG|AHqxR~<$7g6&ks#o*U$JJg}mOK25 zDaWc7EmM^F8rrke#K>%&-b$Ef)Ta`C?Y5h9c*5R$ z25JmU)=&4O4Cm7D>W*?-v@E8X1Zmsuvb;#@{GNT96s)CfJa;o^x>XYPmfw^m2UqqV zV%i(Z${N6vl(aIRoz8TlBrm#AIWc%HA+3QQO?&YZ(bTCPSxt#L`Ho`6ZKDpe&yf@+ zrW@=&HSlIY%T!<5<|qmek_shz_fp$cAX(8F# zycQWF_SRd3hO*<5lkwaPm1%*DMV?v%jXxRI%)|!Oor_gwyV8y>WAjN*+r49N^&#vP zaEtL;uI|cY73rEZ+*zra&abE~!t$(9Ij=w=VwzIIdV8~fpDG;#--;psNupP5XeqSN z!dYTspkO%v7|}5)!5Yz^Od9=JKi5g@?DwkSrX=bWMteu{&*n01wN_hu>@tc^`HFmSBj6Mu(?ZC3jL|C`ZUt4Ihr* za)LW_Qv~we?KuYRRujly9sV7s3+{g4E(NDjbIDriizk5XW;~aU{l_1~N=`Ri<>C4I zq}AE22TI$2>V+?Q9VNl($g(Ix}z21 zPVUAgdTD5AxKgb35lFWO$U|xJH+J#$MnP)Xt}vvg=ep}6#OzF6wnw}qR3BaGFN9X$ z7|TRq^=6f|5ITn>2Ogg#b*)SYzI!Q;*#b zq30%7H%3YuBe~v5yS?GKrE`NylVq8jJ!@ys!FL^fH8T(VWx@1De-zLqT!ZXjV0n`1 z5I!Ahmtke=^jvik2lr#Dd{HjB8g#7JxNXc+!vn(aD1p^Bb_tE{+YB5mTIrqYOQM^f zp3kWB{giu~1S5e-EcN`!hno|jEkg6@O*N)ojj9;2j+TO2v1hjhlU%+UHJTVsmLeR+*2NMz7x%Ioiojg?%7= zwm0aFj@EKJJ2dycXF$VEv3>u|ZDL~Cu6%84@#sWVNP5|w;6W1Va$M9$5tP+02Y7|= z#jVrf!KGEKkBR-N=k;Sb9=B&*s>YOqC%IKnGryt~A0}Oq{rWO$QvB)5>ak$&^h?vO zy_r{apIO{Y-;UHCQoKqp88{{_0KR4_>8;Y1XVeYy#E8jd=gcJW(+&d1$VV5K6LYRh z6HT`luTv3COQM`VB8ml}1g z)_QuiKRibcAc?*|Pn^i<5dM-^U0D7KnxSrn!$oF(!#&Egmy543`4#f&oz z*!S)9TYwoS9g$+7%pU5Kyqd^0>R`InP?DKMx-VTl8+VEd8&#=c<(Sq@Ptvt*7q|MR=fg5gq) z>7mlC=gOaYFW=@}DO-VU2}wq9KwsUzk#6kxJF4Q#7D9yU>_T6voN}9ItOjZorM#2G zzs%X)UO;VF_9|I;dEjTIJ3wc&DN`igbCs!3xY9vbDhLCln-RRFgDQ>%h3MUpC+5oS9)CEKV#Z2)&pl=l=~Y5gHSH6@;bz!-;aUhGw@SvQS56wo0m}c< zCX*p6Qz>CvdP4DzjEf{&p27aZPjN0Oi?~(nBB*%eD^aWBGz>PcL!PBmv(f(Rk>Lw+ zorPOZYS4hkA-#iwU1+zgT<(r#GUn>kXqHl{wI<7Cq%gNNYg0F0mY&0_ZFws@BP@Fw zOK*;JCZ4N0(x+DX&b-Y&)!a{u$d7>JBcAxfDl5vfTy zwWx6<+eE2VdiF&PBEi3#n@9w^_|oTN4^f6QXqV$Fl@+-TYf#eOKsr?>d>wV^?s$4NQHQ@dzMR4~`&SIGwjF5*^~hi@)EE>46-{FG}J zw=^!C4H($BtRE4wGb>A$y(;%0bGIn!GE1-{QpmQqicvpa$z4~lQLloJCjk8ikV|NV&sy?2LNqx`B;R$lHP{@TL2jqp2+%G{7^s-QxMh zP|4rVDy{cRF>@#y@hzKX?X<8Ep9RQCA1<a>2d`lE5o|Eq#_T}(_=?he)2R1+uH++ zxrOX>!vRs6yrfx?xdN}%3B9AC!Pm!CO$72cMV}27OhmPx6@kv*D3M3ZMVvbqCNZ|r ziwUQIerXL3i3xEp{_IsM3QE=(iH&GpT}>0QjSMdkNLmURUCQm-OLeMha@lS!JmW96 z&Ozoy8d+j>t6&{qzE@)9jlNc@q6SC!P^Lr_arYcuv^Dv{e>j1ZIW{YhztF#nu*46U zqkq5LU-a#lwHp|$4BmW`Dk{tEW{j3}H@rmDh5SmHhxhWm&=znKnkxq7c-t}Kb3m<8 zm6PSqRCHbBKLnc$5>iQW70^#d}wrjr@1%+ zzg(qC-MKNym`@pfXCQ(p>2cP@I<}dQrk1W1Zpw5Om1Z7jci3;r6s_}34%PT1C3bi$ z&s^u5J<|xM<_ft_aDiE|P8-rED>F#rZgZsJ?y{dWg`@Skch#oZ=7;4aZlR^sPV>!7 z1H%J~vmhrq`xDgbmRFQV7s1l4GcTAS{dAW!wC^>|SD7>Tm1N(T?*cy+mz0*Wb{7>X z*&lj!N2R}RS5sTu!fklO;HaB#-ADD5zfQJ0s)f$eBioyH@BO0+oSkPMh^HO_b3M%`(wj4tMW~?+p!GKB*I) z#~V8LwtWJTPIC-(eguKgdW;^e+b5ERtE)jN&snC1X7Tea+N}4(8WK2{Xb&s>o$PO8 z4D11Y+371N4buTVtVH}l>tI<|P;X{# zVTNTR)m-WW(qEf>E>?PKAh}8#{kYRt{Cos>^o@$}YEfl5#^3~}kxis1RNbU)7Z5Hf zYDr7#Cb-mY>OVWEN0^4fKWX-)P+dl?8fCP6kH{$eMEvE`_e{7S$+`~92dV1Ru!8zx zSiZUONS;I)6%g{ZmG}TU`^JF;RC#J`B-Er*jiEnwC4a1BT6aZBG!$y%AEEoiYio^% zjtWU|jnm+M+j&4_gFauxDGr__*Rduod)(et(7lF$YrOGWkSClf3&9OZutmW_bTq)kdX`y z2+ugV^bvX!`H{}^c}qL*9xl5vzLUk{!16Us;V&D`!9@6Mm3uh9NRtN%PrNrkurcr| zZlJK%bY;6*pNX!qsGy)eB!|5c+bG`$$hXc#0~lYfdqx8|3oO_kqj|(fKe)BtYY5#r z(@?ckgjBoN)>cCxF159YCkqB$uBB1K6cBI(jvWNTUxxTP1`p-$s+vv0AqC*^a6g~? z*fW;IjbEw6ju##|pY?EN`_et6rwygW1^R4~nncJWrO%v+hHddPyzA;BX)EJ(*un9sN+NG@ ze#wBuL#~?AztJwC!`XJIB@(4>$F*)j>Mu35C9(L;f7$G35Jt<;F*njKpat}7<6;*R zJl1Ltidq}*_<+etU65)HfsnJrm z);gqmbT^XGhKv*&p=r4`-#ChRKgg}~tX1#zUq}7lvHwQgv6z%_UYuFrCXn6ce)n^2 zCqI94ARRAspg4KLgO@Qwl7R7EBBflW?&0n{s?ZqawV4*A{KLok5QXJeYbb65>d!=d zhI7qM-3RDw(MSweov|se+s5@}wQmT0#GdS((hLt2&-2biM3fup-R2+qZfXcNjm8dUDR!~y1@%f`Y}DK>k9UPh^n zOKGScYL9(H2a2y#rggbwv@Y6 z+-ySeS+`NQ_mug9(2eP^=P~FbefztycbTB=z8jp+hrTELy%iAipB8fw(yyv~@S-EG zS;|kIqY|TcJT&)vsEB8N-gb0wsaJ4v@Wl7p%6;)3gQYE)0MedI49+U>56)Q zYYRz4!f*x*S|NaLG??bx7sL`FpYvhSMCpu}Fr>NV30>P%XJw`LZcpgogYM{LP^hj_p#jf?F^N%`M_LiMLc3LVw}{~&mFsZF@n>~ zoPM0L|DX_b7&~g$`tbk7NlIe-$>(^5h zdPb{y_K+q7aHmSy9q7Zr_*za&OCCNQQssYt^x-~ag@F2aRg@@ZgSp7E$4r_+R>7y`SI(0VJv9zbWp9st=YS19f$H}RjYr0 z*2k=mfDG4BZQS92DO?`$?UcU-kW%wz5kAd;-d( zZLTR-?-MIG)?Ul2EU(lj977sr7(+TKUGwxms@A?x=U})&^grr zSCGIheoLZ3bcuLrszJNK)~HbB%f>d+HR80;D_c}Pqu}k#?x}ur=H^MJi7=JOe)+hV zu6_x)@(zPxOy9KpT{bxXdV^T@gtAA)5`Vd;#z9}1zR+Q8x?WJj!Y*Z6nqNj|exCkU zv|R%;=A{q+8U3Qe9kNc?@KM8QPz&O-NGWvB;+nGiKM>RK~r~~!gJ{zJ3#8VVS(Om z%U>DVv%l^$tlSF~b7*Lcen(N4HcYV%t^Zi)O^m~tN;BT3LJ{uvl=CSL=Q^vTLGRd@ zGS{$j9C5^(6Kr^{+?MDlFb5?_N7V6kwV+La%_2dcb@fp_(^H%qak>QI3_bc7OBqf9 ze9{WTa6Whqy+YXgETw5Ze6tDt&OE$fWksE!G{^Ev-;$h^eaSvbm3MCfj5AlAyt*oG z5PplrcbJw)t=sxR`8#*%vM+l*6BKhIJRivBrdYz`K4lc&eaXuFJSre(Fh_M+zwQ`b zm&5#0!_C-yUOpdI2q#D#PBV&A32B5Yxb+RkE`ISn`Q~A<@TS=@W2yM%H9=PoTZK%+ zOna@O(&zE*0GBdIgD%C=Zsr>8Lb1b|?ZosrxG-dDDQbp&U;P;!_1Z`N-o<9YrLERj z)LwSy@A=4RtXI7{Z%Tz?Sxciy%f|{Qf+x@fgU03aW4HWS)bn}7Y6eoBQ-yO>$X-IF z^>HA5_Gf&8W{vV z-8CP7g-GY@D&Br@hxjj)McPp5xe?-8up3yft$(srcca2MP`X1S3PwnbbB#XDZFUJx zXf?wM}x`DzeyNzKokO;B%O5TcIf+V}}Toz>$4HoODSa6(k?B_WsEHsJ3_i?n7 zW>uzid-)mHaG_r52<420OOVr|NY_6f;+&R zRbcKK2nRDqHBDtL!0)XlYiC+Mz#mx&3516M7YG8O<>CQ>X+c0BKdk_W$B0|r#mpXV zA?|2x52FPFxW%0ptTaSL|D6N$A8S`Ue0=|(RWYNOcH1u6C&34zLB0EC<8|_?;;aV5$Sp_s zJUUQNK1infgXGs1!MWP8fFqpaJ6^HQktOF%d-JmKU1|cVQeJ%GRv8?ZSN(z-n6@P< z=Kz0^;^%s2XA7~WKNhzZ63Ra>8fYwTt@K}@P-8SoKN@ccqZS?U{d5Hm4gL~J@Jsi2-1vgrd zp5}+YcmZSD#7Rs-MH-Qa?qBQG$q*O7V^kknFXzW|ZD5OS7aCg(_5ai%tq-ih^&2Jg zVSVPaoO9-u@?R!G;J-AVThrCt)bcOvvCCr>*e>ne-8Gs|5T_kPHkiUDYWcIIn zFo0VZZs}sZ4j-%KLkMklh$wRw0|`2jjACL7Md^@18y}b8E!3@hl|nQwf>_PYG&5I zzmUHtlkd+Kp9ahs;fl0?In(~}puw#SvxJ-d9{At(!2)1fDBu00%OVl3PWM(c?<;8L z=tKl$!Y@NZi5PsJ@=Enxp!j_>}u7yZMBNdHAcApg4Vbs@Ze z@p$fe5Sf43yjQqCfV@0^;k=-GyMNrn5sp&#&o--+AkS~JP#ysuATK`{%mDZe_$XW7zFu; z91jrmpO^rcAN)^D;2*jI`~v^t~s zz14(!!R|}Nt${$?4-C&=Vr)P00T)X*G9GY5Jy#b1OW8`^#O%?by$?$ z*0+F!NQ!hz3pmWc07FWHbT=}-+N!z zJ^Nn2m3ytV_x!Wy6hy^Yfp0j_>Dsc}8rrJb($GOvY*f|;X6Sr;02!E-3H$>U`y-+R z5I41e!|VX!7Em}$6lQ2`1QQTIw};!opqA*)$#g$?(Y@GkkXtQ9G|*la2e| z_ps;oQAcOI@^EG|+2xRkNYYtwqmL{=0tE$;_NgdwD~(W;*E8zRi2-eWPogpUj3|VZ ztflhQ=w{ZmcCsR93n$gH9H=^@w!p&&`4=Potoyx%kBtJwb7j3K6(vM}b*gH)UZzL~Dq|Yjw1D_e^kFp7t zN1x0PqTKqnReNlvc_5^FzI=;Eb{dXp(U8T9@mVEw^#G$5rru+1C5I@<{wiW!BuImu z{cGF3*)AalW?+#^!i_A!dE?aJ>2>mPoS)8Gnj`;tbilg#?V^mEcFXwUnrW^bHx{!_vcQ#0Im* zgx53aH=ev}PeeV#)>&B&`gDlzG|I`d6D)^EZ(q@RXs|v}t3kpNMo6ZUUN+@^rrzp4 zgqC8%Ti~O7h(`N^=n5B&N{F5BiJAc-mKL?|hb1d#%%XrN4Vd3Dh-Q}6Y4NWryeLO9 zIIrJK?Aa)v5-D|v;JIOk;{m3htfLigybH!uUm)avMyx<~Cv~d2>;8O!9(hh**&oml=PPE8u(7@$(@a0ZEVAH<1ODt;Yv2#d91h}ip~iYG!+D$9L1gF$bf;m zK%>vm!(5ro{tU>7R%@qpHP7f$eWH_7tVhTYanYCUG*Cg|9te6gA>B#~xU8RwLKniO zZkG86I{Djn6tGAueyG*pH-*hOBQN-1OCoBDH;G}bp7C8}ZK%c+lDC#TH{8+w-gvW2 z%`F}qm7{|a=RMQo^@McL*=K+mRVnzx8kxE@T7+2}z5G9lt&1?=6hdw(^_d{J~ z(phy^^5&>Pv|bhwZo(N+NuBlOh5z7MhB#BKU*i9|L%L+v^qDNK1=y_)%MPPvT4%Ai zvi(4>LFe?vc5G#ILoK}KxV65-y9x>s=}~UQ=BVc|$*Rt8Yk-sqwLLTSByLe zJf~%<-vim)2rCaMX~|)X)U%SG8-k+_HYwJlooN6^y_e!mAqVT$E7qG2DG#|gI9SgG zCo4>i`k^U&?4T333jTNOm@4{clp}nnux0{LBprobseP1_DVb7 zIZWsI0sgMW*2cEYV>yrH#jr_)d*D47;i*uN+3O>%!s-0l_ct`UrB;<1?}#(K3d@s#2SsbWw4FzP|aPW@JJ#1HoPde2TH_XrzN zUy3v-+S34~3N0i}v6(|H>7rJ18oi`$;|8M<`G7+bhWejz-41K6cFb2K_4}=j^U@Tw z5zA-pc4WnIKS|fP)|}S(u~On!hm#6*aMcIKSLBjvRCQ3RDT;*#0^`!2+wcwxU)W33 z7|t=@zFO#sI|4QPHYYt$Ja9a`c;NEJd*^SLp|?eV;=kjk+$e+vf*Cj#?a^@P_ zh55ewezh>k_R0cXN2_4;fxSRe@Mu}Xb>?S!oo_CvUN^*Jwhy9mc7jXAtHmMr%wwil zpSd_tYc_>m%Grl3VTS%9t>v{Hk%wSc6T14D^-C-XH41*OMLBavcEZzaZKfsjs&)y! zVTVZXw-g-V+2ts-xTogR1p-%7X|8MncAp4dk#KjfuEiOVpt*Q8rxh%}7}mlacx!gJ zb@R-c9&mItBn_kW*CE+n8?Yn2VyGBA>P7p(a)h}1iZA!=Vz9fh{!^MI$09U)t>?zk zgi>@ytts&bi)k)C#uokgiH%z(iyP8Vew7x2eF=rY(j!hi{zKSzztt^YE+J$mLLJh1 zS)KTcNrU?}{X`32yC|<|5E6{E>*Q5Rx&E8EPx<%wClA=bo$6@8Rz_T4nVfLxTL~*% zEjIQu=>2?1-192^gCv`z^Y0sCTU5kPumZD|KlSba^7C~S%iS~`RM$s7*?rW^wUm;w zRm`Y-TNW>-?4f5R=cr~Ze!U`=%hb6>D`Ft*maO+}{PFrpuMjia!=v$c$IVdCYOD)A zrz6Ha<`aD{Dyp(0oES9K)!ptNgOyFj9Wj!jz+=o5tXL3uU8Ynj_JT&xG`8Ke>?)7K z&)PE9wWFIDr5JYTfkKMInPlrT6$Ke%5tmGxdSN;b;TRs74Be&2TS>~McXHEYlIdii ziuY2W6*-oV3S~w^stJv)lfpVhdHFhu6kq}hHlxf%-L{UdN#Vi+_np^{$LA!IW5{+AnD&7x1v;*kUbOh{h zN`M?2zd$C%HgaY8xdtfvml?D`{IB-pWab#fv2@4TJ;!8r7WCF|mkSEr@d|4WS--cQ zW4-(Oqvl%jc{PtZA6>>_?a5iat%CDajaU^IOQ;^ z4#LyrkAjSRra9~BuZ<3Y8r)4qvINFg+GH2NN8&TTJAjxpkzZh*AYAMc`4U0=XC?=K zeNhO`=7a|ZZM{^CqIh;u;iI;X&$g`NZFIR!$IZMV{L%T9t*kp-+_H9t7SxkE{ z_LDt8%2k_iXn{e6+A8W>p#%x?wM}FI#ViBzW?;x>?I*pT+9U zwd3S6w;6`di2D)>F#Xcsqm`i8Ol`kmBzH1E=V;})Sj*yT-(j?gc^j*j5Aj|lJIsE% z((QQc(p^C;SRkn0JaFl8H)DS`0+EntKtE zYHAA)BPPnBtA&Dp`_gQ_%qSd>XB0_CD=Djw`-5eAgTs6ZG9DU-TAhQ>^_X2NFE5a1bOK}4 z3*2E)`aLaf5DyAn&=ZnEs)3_z>H84x% zK_KH=??OMcKS57LTBPwv=AD`4RV;gMWo2$9XOg9<*l0XWbvi8#*Ql0`MwFt47`k2Q z>mDs7u>yz^{*=YjZHtfRZ?z}m+v2T(N!h(mrJJ0Im!N3v8rB8X-j>+YrO1v?C7)D% zX61}3LHp7~`T zq3)nfxcRcM@Rg_QuK!)}!_COs)v_}e5vQW2ru@9fA9iT`2mI9(twxUfn@$ zw(8N*;=;VW$<3>ZZ+?0l0p~JZo0ZDpgLG@OYwpvR;MHF7e!10w4la@dEx6N6Ib?97 z(P+%%YxbLg-c1M7wa(q7<648pi5Mi~kbxLOHfiDw7G+zdDDPh35HEr1CYmpA^MI-{ zg^~{0W=gZPQkmn)7)|e<0@A8-3Uwv zvIbd=Ls70--izy()wm_D@tPGBt=kIgxMe3d_d3pyId(QFe4)fj#zW ztaO^S%eCv}tL2L)JGxkx;T!Gkrb=6ETlR35ugMtYV&)s%pO}W%J^;~1`jwiPz74TV zzYAGC3-$?E4T|}|W)$PCg4WS#P~@EWaTv<_`t{lf`>9yfN?^L*TPd;NtoHY>VsYDDEhf5$go+KfnBS z*Y3Q5t!}Bg^Yj^1`Ftjs;*b{AUzW%(q^lLiVM+8vdZx{Om#O+>%BvrC8G6rg7wd(c%U-E~OqSH;jav?* z8Cgi43g+jT=_y8XeZf3vo!MI6y7ZY7bFxynN~r>0v^N8f2ldZ7sAXL;AJy3GCI z9jp4X^S+JQTYtQCPFCyl*_FHsD}2twq){uiW!e&xrr;#v7zZbe-il8ZP^YLd;c( zEMv@7A8D(4^sF$uG<9f2WRBz`E4KcTWfYvpHuHTA&3ARXDVCO<+j}1-h2@J!tQScg z`~!@^Inu~tYQV<7t{kV+vD6XT`E!Y*v#VWtQkgUd3UkT{cTEiy{x6CUmb=GELY_f2NdANRtRhVNaP$h=KW z)0$~)MHK<|TIFX+7bXvdZ(TFHHlMWzIlTOfZ#i#H)EkMPV&^H5s26xGkyNn=Iyq|xhYz$?_T@1U zdv+ehXmi`!(JPM#&9Iqvf5SGJ^Sz)frMrtgrdP3MZNB*d@oOp%ug;ldCD-e4>0T7d9gS@-*a84o5s;4n?w5M>X`QeZP1#~bPv*>g@G}Jk@xeN|S{y;?`_P!5 zzuFUzF@d3d&b37HBv2X|EHi47q!?q#oM#44PuaEi-bxPp#%&i-ew5pAvZ9|1n!)Nj z2~wo@C3I2NYhw`*eyz?hDuYvuKmc3eOX5i|UUPwnM6!Ox?J)C|kq0I1XzAZjX=@*` zL{4{gq_d4CCT*{ex@Sm^e-1pd83+mT5Wm~@$P{hyKTj%#ib>GqWz&>%CyKSQ>;#+9 z+qHpNeu>9dI)YLuz*zX?Ox%J=q@#H&V!WE?^AJ)zl}QALy_N$1s=@2ItD~H)UZ8B}>-`HmB%ZwRT#UKGVfWcno6c+6W zwiL#rUCpKbcj07)Q7+S=RI|D2XLZ!Z5}LYP=^MOt^;G3<&O)~erl##3M%fEgpkkF> zp^H7E+5uj3XmO&%O{sGN-0)X;8_VSDWbx^4oYjps>7NO5lkRcG)>5mv5s5jhL%sj0Z{LDlgpfrjHicj zbSZMoo39y9MlJh_932M&KR00=Q1&RIo|cst02gWY%qEyjPTRU0$#!shpQ^l>dETnl zsU6HiCx~J^D0#En_b`nwkw9kvv9-2^XYH*q1Qrl8&=3>Fg?pdYmIG{!`-t)=2qim7rWO6D| z?p)7fzewLBHtc6s`{-cjfg1R@P(~l^F?+x37|XGy#hbh)qTW^ClZl+#whoTMWSSoS@V<&vC%!3lR8eXi+zkhw(5R*o6Ks=!_cnB%i4{P7bS^w_;<{T z>*{xgZ<5}eX2{PDCwo?uHopH*JEi%3ZW?)WPaI;J+?PkQIZb7g+O)|%dUrL|m@LgN zz2zQw?DwJx6&V>Yy=uiz z{&lC_Jd*^7)aHLC;(XrDJ+%2vTp?BE6BPLdGwp?+`QcA}WCLa#WpBc}uSPT#`7!w1 zbuT6IRBKSusLiGd)=Mzb7(ZmpJ(QO3H+zCQJ9`F47j|8yz#C53-E!5J=~Lh}g2`)f zH)_#f*(R?=!p|ZUi$Pl#JbMP^WKj=TiVzsR)uBt4>rPA3|j4Q+y63CKWgr7p8>K9#+RH`0fvDlkNE8xrW zbuR`SjNr1xgIHB!CWz&BRZQ3_#pI>9PE8i>#fKrl&Ahjg?Gtq(@?-8mQF%$SFTR<* z1cCh=bex--6I+lANp z{_rk|3#R8mvijOaWtZi%G=aiTQ>G4Qs>lA6^_|6*sGll8LTA6_N{dn0n-8HnnmE&Q z*DWO-!T91ETIh=~s!?2YWVhuX)D?@9q3LP)K3N+&nRw&mjsc$;$+Fpb=MkED zU&baB!(G=SKcnvLDvpIxKCuv$`31BKrD5~q!HzH^GRBhl-f)qC$W(5#7dK}q zj1Vz@;e5}Ow z7rw7MJ{}%cyeY|(=W*RZVnI+7UU(ue4&OrS;e`XW>(z3zK+zEVL=9JaoL z{qsAtVgV8Qfu6LZ1%IAm!~0WjRwRl7tG!)Dl}M9N^-A_wS^3EjT80;@?o-XIcr$n> z{XH!a9(tCM8h&{+Zfr+N8CRduCo+sWkrR{IjxeG~A|}I9-KEqqnIJjF5TWMTZ6U&1 zo6T2dk=K`0NzC;+6eL|l_Xv*kF-mZ+?b!9G6Tf5E0RFsh>^vlKKR!oA=WYVCtzY87 zYZiB&VGTQcj?3JK!lhy;W!EY3@VQjM6*kmgBdWY>`-d6ZjR=i;^@q6Uy>T5-5mc?j z$;giVff`8rDHMDP-?4RS+}{Pp!VQ!2N+q|AFMWKl8?r2u*rLQ&X1O zZzm4IfvpZ}3gmQCH5s_rUsmwP!E^@wBls}RQ2}jCJ1yMhg%O1-1hXTC^i9ou41SskaRd>u9>QXL>((^nR1! z%5z;_AJR#=Nm+Mlus#y zKTC0af|q>^Q;2v<_qFgW`zv68vYI!Xt25XZ-!{BvfIBRLa`WP-AsDbpdWq%1T$~V| znmjVDIxIM=kyDsIlV-z69c2Sru6-&i`cdg7`Ml!1K#j(XxdO}bTb2CI)o(Z^Oql%E-|Z5Yn6#uKx*%Z#R@RT9Sf^7)RgU2$WtaER>PUz4~DCPCWFOB z;lMHI9^-zE`iIXX1(+R}7wGFswUrGuw|YNl6AHc>j5fA5fg=har5j@;&>fP+#3Qg9 zX5xN#OkrkJB>}T#gd7Z}K0(W+H^A>RJN@ZQh{alenVQ%fFV>C)Q^QSzV~z&nhRuLd ze79}L8^%n4Ip7+9Z;LoV?-06zT?RxSwzc}f9g*buA+IPe%NFwjXN1ph44b-&YzKuU zM$4N5dWkmijkrl9N3i`crpJ$tQ{Kxc^?v(lp{xc;iABdsSEje}Exj|+bGxn?zmAA? zTo%M;;$qob`h92v#R|oWGYUEuHK2@BI*E*iI8ej#*HO}vzCX&MI^E^vWLCqKrI+3Z^Fj4w4xWwUCHyM z$wT@BniDWRIZ5x0INf?Jrl6cUR$ou-fB~3u25JmLiE}@GF>C5>NwN{A0*N^jFu?R8 zS>9w+H9Q4I4g&f1A7mFka@0bIS11?`gxKv~DWB1OHO0~p$y7cIo9dGpFK*qKQ@KO1 zxVO){&;QEMy(kHy)_+AQhu;7*lc70?=JY;)D5&y$Wobh+TRw$l@R4#1kGbjrq}m)f zvgmu#9DaA;B__Y!jG1DwAT-i>xTv)0Zl6~{sead~vQ1QU?Z61jUx27y6Bsn}sJgOq zR?B{IEuH3q` z<9`L81TWpp4_Q)6&l>ucyH-y?N>wb;KCrlLfZI5|t9 zDfZCY3baDqG&dFKyfPh~wGZ80ajYo#=3JIv>lBN@l1%wCJ(V+N7fpkaSfd+Pyec3v zBS}juk%5)Kf6H`4Jb1`G)mYRQy%qkaQ=joZ6``*b$x)IkcUmS8@Q2X&$wa-U(`>@UI#fLI4F*ZvK&} zdj`S%FAq)8)Gnet|~hT9>#^N<+g>3P7X=%V-EAD(8F6fZ(VwWrCi{MW3) zYqE`Pl^peqrlbO4xh^k~S#|lNyjP@Nf4GIhVdqHVIBo-7tl0kdDC^MGP2hcMffXGoO1cnC5 zMXLE=g0%Vi%LRYvvx!P`TeR`K95AZEK`C?*`I!CX{=olQ(p|cJcyg}y{u8*sBhi(t z6YuQXFUb2l=ewqB-?NA@^VVfO(Qm0G$z?X$tOC6?u(b8`b=;6xES>q2_^kW%bwL(4 zqQ+pq!_kqx2@Jy!!3t7z)NNE7Zz=*9M0;vtZ1e*H!@}8%rBXdVu~sF27!m>IEui3i z`!Rj2pds!OPeJpibqW><#sI)-uNH{EAKsxyD$A4LwjTKj_J#Rt=@--($2Q{}OU-W; zXJKCwrsTjX9bELQnB3m08V@+VgQd4vy`BcchhN<23@Owas<_aENQI6Nc&W9JEvXG| z0ENVp#s6ifiSvJkn%F_$e}Y{98f#K)J76!YVra8TMr$e42M~& zQE{{VWnSFW0tTY`J&L6yAn?yUaQ|&I3&g?kzl@XVe(p7EXTuP?xJL(k3;8jSpO3?+ zj8eAc`}OWgmzt7@A!&!^+JQOqC_!BbMEK$zVX*YW6fbE9vn&7GwP*(v=ozWm5>4Mk zY35qzFX#NN$q;lI+Jll}+}H)#Cd&k8=|=F%fIN0*giTlbbCS0^#va!-7`YTxI8FKp zgtbPxU6?Ya1JUS%W(Yz>_SQCKye9JDshrNfB1lpblPKwrNoQC*#GhYphoog4?wVILdZ)_+kUl zLf=Ju6w0!Y1+E++$K?Og5s?2rM#^eg?h9JEf{XKDC<3%F7&yPH-?QnzcifYW{c@*|s_Uugojb<%tjgxOf|;4$iN~it?r5sN%v5ib_auSix4dQF z@$Z3O_~!?|(VsQ>v;6zTdg{O9Z7wMH2mg^blNXf}H!HgkZ(;r6;yd~8%dUkj>QA2? zIEDSoZ5?wCrljQOY(++!86|JrkeKmLwVTV}NE7dP{! z!VR|DF3I2P+ z{$s|}{l^dSvHjgL;y^6$fT-`^T)R@!R2jB7zk?%x{*3-;ggId$sB ztXKZ*f7D!7Zdv)`#sLLGzdSQNjuvMhjxz_pMkH}e7YLZqJ=fWTM?+7i;otslo3dx~ zqb0N0{|758NSm9hTglFyvB%=qf5rfBMkaA)5eCrdm@fUk<~GIhe>bu*FfdAhM0kPX zKsFFBX#}xyQ%j2VDspo|Ls%J@6@G%Q`(|K3HUjgWaEK8g%}W~J01Zam8V)o9bZa;^ yBT!H6L^fifKG+E88J>8IKwWo=Y=lxI*a+w Date: Thu, 27 Jul 2023 17:14:23 +0200 Subject: [PATCH 104/939] fix: add default HandleIdentifierProvider for disabled versioning Setting versioning.enabled = false in versioning.cfg is not enough to disable versioning. It is also required to replace the bean class VersionedHandleIdentifierProvider with a HandleIdentifierProvider in identifier-service.xml. I've added one that is commented out as by default versioning is enabled. (cherry picked from commit 92c38de99e0d8dfd0e863812eb307cadd351c1e3) --- dspace/config/spring/api/identifier-service.xml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/dspace/config/spring/api/identifier-service.xml b/dspace/config/spring/api/identifier-service.xml index 0c58cc1de93..79e19e879e8 100644 --- a/dspace/config/spring/api/identifier-service.xml +++ b/dspace/config/spring/api/identifier-service.xml @@ -4,7 +4,7 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> - + + + 11 - 5.3.31 + 5.3.32 2.7.18 5.7.11 5.6.15.Final @@ -1200,6 +1200,12 @@ ${spring.version} + + org.springframework + spring-context-support + ${spring.version} + + spring-tx org.springframework From e28a083ef79998d88fd9fb2e2661a065c63d5bab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Feb 2024 22:00:25 +0000 Subject: [PATCH 109/939] Bump org.apache.james:apache-mime4j-core from 0.8.9 to 0.8.10 Bumps org.apache.james:apache-mime4j-core from 0.8.9 to 0.8.10. --- updated-dependencies: - dependency-name: org.apache.james:apache-mime4j-core dependency-type: direct:production ... Signed-off-by: dependabot[bot] (cherry picked from commit 7f91661f8415a6ffb9c37646f2a2d5e6bb981312) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f940dfb97a3..01830987b38 100644 --- a/pom.xml +++ b/pom.xml @@ -1302,7 +1302,7 @@ org.apache.james apache-mime4j-core - 0.8.9 + 0.8.10 From ae2ee43df83abbd396ba912e2eb1bd69be8227a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Feb 2024 00:08:54 +0000 Subject: [PATCH 110/939] Bump org.postgresql:postgresql from 42.6.0 to 42.7.2 Bumps [org.postgresql:postgresql](https://github.com/pgjdbc/pgjdbc) from 42.6.0 to 42.7.2. - [Release notes](https://github.com/pgjdbc/pgjdbc/releases) - [Changelog](https://github.com/pgjdbc/pgjdbc/blob/master/CHANGELOG.md) - [Commits](https://github.com/pgjdbc/pgjdbc/commits) --- updated-dependencies: - dependency-name: org.postgresql:postgresql dependency-type: direct:production ... Signed-off-by: dependabot[bot] (cherry picked from commit 0b2b81682a29df9df0feb363e9c3812e9a034ee5) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 376557bcdc1..32d4a5428e5 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ 5.7.11 5.6.15.Final 6.2.5.Final - 42.6.0 + 42.7.2 8.11.2 3.10.8 From 678aa9bad5cd00ffad0c9456a26a9be571f8c602 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 20:14:49 +0000 Subject: [PATCH 111/939] Bump org.eclipse.jetty.http2:http2-common Bumps org.eclipse.jetty.http2:http2-common from 9.4.53.v20231009 to 9.4.54.v20240208. --- updated-dependencies: - dependency-name: org.eclipse.jetty.http2:http2-common dependency-type: direct:production ... Signed-off-by: dependabot[bot] (cherry picked from commit a6e3d7a55a849fdb443e5f9df991d4f00d9aea9e) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 22185bbc946..c7a838bf01a 100644 --- a/pom.xml +++ b/pom.xml @@ -37,7 +37,7 @@ 2.3.9 1.1.1 - 9.4.53.v20231009 + 9.4.54.v20240208 2.22.1 2.0.30 1.19.0 From b36613951b0274b39e71cede8a33b9c31762d1b8 Mon Sep 17 00:00:00 2001 From: Martin Walk Date: Thu, 29 Feb 2024 15:21:15 +0100 Subject: [PATCH 112/939] Fix #9383: Set email subject for request copy form --- dspace-api/src/main/java/org/dspace/core/Email.java | 2 +- .../dspace/app/rest/repository/RequestItemRepository.java | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/core/Email.java b/dspace-api/src/main/java/org/dspace/core/Email.java index f6df740a53e..c157d16d496 100644 --- a/dspace-api/src/main/java/org/dspace/core/Email.java +++ b/dspace-api/src/main/java/org/dspace/core/Email.java @@ -395,7 +395,7 @@ public void send() throws MessagingException, IOException { for (String headerName : templateHeaders) { String headerValue = (String) vctx.get(headerName); if ("subject".equalsIgnoreCase(headerName)) { - if (null != headerValue) { + if ((subject == null || subject.isEmpty()) && null != headerValue) { subject = headerValue; } } else if ("charset".equalsIgnoreCase(headerName)) { 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 6eb631cfa56..5945d516600 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 @@ -247,11 +247,15 @@ public RequestItemRest put(Context context, HttpServletRequest request, message = responseMessageNode.asText(); } + JsonNode responseSubjectNode = requestBody.findValue("subject"); + String subject = null; + if (responseSubjectNode != null && !responseSubjectNode.isNull()) { + subject = responseSubjectNode.asText(); + } ri.setDecision_date(new Date()); requestItemService.update(context, ri); // Send the response email - String subject = requestBody.findValue("subject").asText(); try { requestItemEmailNotifier.sendResponse(context, ri, subject, message); } catch (IOException ex) { From 78d8e86369f7495611f4b88c48d3df8ff41c5bec Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Tue, 19 Mar 2024 15:18:28 +0100 Subject: [PATCH 113/939] upgrade to Spring Framework v5.3.33 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 30ee3c3fd78..5bb7b3f3afc 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ 11 - 5.3.32 + 5.3.33 2.7.18 5.7.11 5.6.15.Final From 3817f3ff9d3322e5aca572d49f6204444ed3ecd1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Mar 2024 13:07:21 +0000 Subject: [PATCH 114/939] Bump org.apache.solr:solr-solrj from 8.11.2 to 8.11.3 Bumps org.apache.solr:solr-solrj from 8.11.2 to 8.11.3. --- updated-dependencies: - dependency-name: org.apache.solr:solr-solrj dependency-type: direct:production ... Signed-off-by: dependabot[bot] (cherry picked from commit e93455796623461e4a5f1fb5023ac9fb0f78f0e0) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5bb7b3f3afc..da252ad102c 100644 --- a/pom.xml +++ b/pom.xml @@ -25,7 +25,7 @@ 5.6.15.Final 6.2.5.Final 42.7.2 - 8.11.2 + 8.11.3 3.10.8 2.10.0 From 48079d70a95b820c087a39e1f26c821fe7423f89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Fri, 22 Mar 2024 12:19:43 +0000 Subject: [PATCH 115/939] adding Unicode filtering for sorts --- dspace/solr/search/conf/schema.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/dspace/solr/search/conf/schema.xml b/dspace/solr/search/conf/schema.xml index df21afbc642..66f2f176d2d 100644 --- a/dspace/solr/search/conf/schema.xml +++ b/dspace/solr/search/conf/schema.xml @@ -162,6 +162,7 @@ + From acfe272fbca0d1ffba13a9a0ab391256ad4cf3d1 Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Fri, 19 Jan 2024 21:32:04 +0000 Subject: [PATCH 116/939] Fix generating versioned identifiers if pre-registration is enabled (cherry picked from commit 1844fd28a0df0395192ff2b17c9018b2c46ed72e) --- .../content/WorkspaceItemServiceImpl.java | 15 ++++++++++--- .../dspace/content/packager/PackageUtils.java | 2 +- .../content/service/WorkspaceItemService.java | 21 ++++++++++++++++++- .../DefaultItemVersionProvider.java | 4 +++- .../org/dspace/app/packager/PackagerIT.java | 2 +- 5 files changed, 37 insertions(+), 7 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java index b6e7372af18..b78f6b9f7de 100644 --- a/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java @@ -96,11 +96,18 @@ public WorkspaceItem find(Context context, int id) throws SQLException { @Override public WorkspaceItem create(Context context, Collection collection, boolean template) throws AuthorizeException, SQLException { - return create(context, collection, null, template); + return create(context, collection, null, template, false); } @Override - public WorkspaceItem create(Context context, Collection collection, UUID uuid, boolean template) + public WorkspaceItem create(Context context, Collection collection, boolean template, boolean isNewVersion) + throws AuthorizeException, SQLException { + return create(context, collection, null, template, isNewVersion); + } + + @Override + public WorkspaceItem create(Context context, Collection collection, UUID uuid, boolean template, + boolean isNewVersion) throws AuthorizeException, SQLException { // Check the user has permission to ADD to the collection authorizeService.authorizeAction(context, collection, Constants.ADD); @@ -174,8 +181,10 @@ public WorkspaceItem create(Context context, Collection collection, UUID uuid, b // If configured, register identifiers (eg handle, DOI) now. This is typically used with the Show Identifiers // submission step which previews minted handles and DOIs during the submission process. Default: false + // Additional check needed: if we are creating a new version of an existing item we skip the identifier + // generation here, as this will be performed when the new version is created in VersioningServiceImpl if (DSpaceServicesFactory.getInstance().getConfigurationService() - .getBooleanProperty("identifiers.submission.register", false)) { + .getBooleanProperty("identifiers.submission.register", false) && !isNewVersion) { try { // Get map of filters to use for identifier types, while the item is in progress Map, Filter> filters = FilterUtils.getIdentifierFilters(true); diff --git a/dspace-api/src/main/java/org/dspace/content/packager/PackageUtils.java b/dspace-api/src/main/java/org/dspace/content/packager/PackageUtils.java index 9e7d870076a..9a8ae460648 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/PackageUtils.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/PackageUtils.java @@ -503,7 +503,7 @@ public static DSpaceObject createDSpaceObject(Context context, DSpaceObject pare wsi = workspaceItemService.create(context, (Collection)parent, params.useCollectionTemplate()); } else { wsi = workspaceItemService.create(context, (Collection)parent, - uuid, params.useCollectionTemplate()); + uuid, params.useCollectionTemplate(), false); } // Please note that we are returning an Item which is *NOT* yet in the Archive, diff --git a/dspace-api/src/main/java/org/dspace/content/service/WorkspaceItemService.java b/dspace-api/src/main/java/org/dspace/content/service/WorkspaceItemService.java index c8df68e4349..8559bcc6140 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/WorkspaceItemService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/WorkspaceItemService.java @@ -56,6 +56,23 @@ public interface WorkspaceItemService extends InProgressSubmissionServicetrue, the workspace item starts as a copy + * of the collection's template item + * @param isNewVersion whether we are creating a new workspace item version of an existing item + * @return the newly created workspace item + * @throws SQLException if database error + * @throws AuthorizeException if authorization error + */ + public WorkspaceItem create(Context context, Collection collection, boolean template, boolean isNewVersion) + throws AuthorizeException, SQLException; + /** * Create a new workspace item, with a new ID. An Item is also created. The * submitter is the current user in the context. @@ -65,11 +82,13 @@ public WorkspaceItem create(Context context, Collection collection, boolean temp * @param uuid the preferred uuid of the new item (used if restoring an item and retaining old uuid) * @param template if true, the workspace item starts as a copy * of the collection's template item + * @param isNewVersion whether we are creating a new workspace item version of an existing item * @return the newly created workspace item * @throws SQLException if database error * @throws AuthorizeException if authorization error */ - public WorkspaceItem create(Context context, Collection collection, UUID uuid, boolean template) + public WorkspaceItem create(Context context, Collection collection, UUID uuid, boolean template, + boolean isNewVersion) throws AuthorizeException, SQLException; public WorkspaceItem create(Context c, WorkflowItem wfi) throws SQLException, AuthorizeException; diff --git a/dspace-api/src/main/java/org/dspace/versioning/DefaultItemVersionProvider.java b/dspace-api/src/main/java/org/dspace/versioning/DefaultItemVersionProvider.java index d4590ae24ea..fa89b344140 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/DefaultItemVersionProvider.java +++ b/dspace-api/src/main/java/org/dspace/versioning/DefaultItemVersionProvider.java @@ -52,7 +52,9 @@ public class DefaultItemVersionProvider extends AbstractVersionProvider implemen @Override public Item createNewItemAndAddItInWorkspace(Context context, Item nativeItem) { try { - WorkspaceItem workspaceItem = workspaceItemService.create(context, nativeItem.getOwningCollection(), false); + WorkspaceItem workspaceItem = workspaceItemService.create(context, nativeItem.getOwningCollection(), + false, + true); Item itemNew = workspaceItem.getItem(); itemService.update(context, itemNew); return itemNew; diff --git a/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java b/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java index 7d808ab8715..2cddbb511f9 100644 --- a/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java +++ b/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java @@ -159,7 +159,7 @@ public void packagerUUIDAlreadyExistWithoutForceTest() throws Exception { performExportScript(article.getHandle(), tempFile); UUID id = article.getID(); itemService.delete(context, article); - WorkspaceItem workspaceItem = workspaceItemService.create(context, col1, id, false); + WorkspaceItem workspaceItem = workspaceItemService.create(context, col1, id, false, false); installItemService.installItem(context, workspaceItem, "123456789/0100"); performImportNoForceScript(tempFile); Iterator items = itemService.findByCollection(context, col1); From 9779c175db5fe3e22ce9c8ef3c7d3301d221f082 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Mon, 8 Apr 2024 16:44:43 +0100 Subject: [PATCH 117/939] Replace organisation->organization --- dspace/config/spring/api/discovery.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace/config/spring/api/discovery.xml b/dspace/config/spring/api/discovery.xml index fb25f11598f..c5486ad84e6 100644 --- a/dspace/config/spring/api/discovery.xml +++ b/dspace/config/spring/api/discovery.xml @@ -2889,16 +2889,16 @@ - + - + - + From 5aa32dfd5a2c8867facec798dbc24e56168b3478 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 8 Apr 2024 10:35:20 -0500 Subject: [PATCH 118/939] Update all GitHub Actions. Add newly required CODECOV_TOKEN to codecov action --- .github/workflows/build.yml | 11 ++++++----- .github/workflows/codescan.yml | 2 +- .github/workflows/issue_opened.yml | 2 +- .github/workflows/pull_request_opened.yml | 2 +- .github/workflows/reusable-docker-build.yml | 4 ++-- 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d6913078e47..8b3cb1d017a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -49,7 +49,7 @@ jobs: # https://github.com/actions/setup-java - name: Install JDK ${{ matrix.java }} - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: ${{ matrix.java }} distribution: 'temurin' @@ -65,14 +65,14 @@ jobs: # (This artifact is downloadable at the bottom of any job's summary page) - name: Upload Results of ${{ matrix.type }} to Artifact if: ${{ failure() }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ matrix.type }} results path: ${{ matrix.resultsdir }} # Upload code coverage report to artifact, so that it can be shared with the 'codecov' job (see below) - name: Upload code coverage report to Artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ matrix.type }} coverage report path: 'dspace/target/site/jacoco-aggregate/jacoco.xml' @@ -91,7 +91,7 @@ jobs: # Download artifacts from previous 'tests' job - name: Download coverage artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 # Now attempt upload to Codecov using its action. # NOTE: We use a retry action to retry the Codecov upload if it fails the first time. @@ -101,10 +101,11 @@ jobs: - name: Upload coverage to Codecov.io uses: Wandalen/wretry.action@v1.3.0 with: - action: codecov/codecov-action@v3 + action: codecov/codecov-action@v4 # Ensure codecov-action throws an error when it fails to upload with: | fail_ci_if_error: true + token: ${{ secrets.CODECOV_TOKEN }} # Try re-running action 5 times max attempt_limit: 5 # Run again in 30 seconds diff --git a/.github/workflows/codescan.yml b/.github/workflows/codescan.yml index 13bb0d2278a..1e3d835e271 100644 --- a/.github/workflows/codescan.yml +++ b/.github/workflows/codescan.yml @@ -39,7 +39,7 @@ jobs: # https://github.com/actions/setup-java - name: Install JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: 11 distribution: 'temurin' diff --git a/.github/workflows/issue_opened.yml b/.github/workflows/issue_opened.yml index b4436dca3aa..0a35a6a9504 100644 --- a/.github/workflows/issue_opened.yml +++ b/.github/workflows/issue_opened.yml @@ -16,7 +16,7 @@ jobs: # Only add to project board if issue is flagged as "needs triage" or has no labels # NOTE: By default we flag new issues as "needs triage" in our issue template if: (contains(github.event.issue.labels.*.name, 'needs triage') || join(github.event.issue.labels.*.name) == '') - uses: actions/add-to-project@v0.5.0 + uses: actions/add-to-project@v1.0.0 # Note, the authentication token below is an ORG level Secret. # It must be created/recreated manually via a personal access token with admin:org, project, public_repo permissions # See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token#permissions-for-the-github_token diff --git a/.github/workflows/pull_request_opened.yml b/.github/workflows/pull_request_opened.yml index f16e81c9fd2..bbac52af243 100644 --- a/.github/workflows/pull_request_opened.yml +++ b/.github/workflows/pull_request_opened.yml @@ -21,4 +21,4 @@ jobs: # Assign the PR to whomever created it. This is useful for visualizing assignments on project boards # See https://github.com/toshimaru/auto-author-assign - name: Assign PR to creator - uses: toshimaru/auto-author-assign@v2.0.1 + uses: toshimaru/auto-author-assign@v2.1.0 diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml index aa8327f4d11..2f80ef4ba92 100644 --- a/.github/workflows/reusable-docker-build.yml +++ b/.github/workflows/reusable-docker-build.yml @@ -152,7 +152,7 @@ jobs: # Upload digest to an artifact, so that it can be used in manifest below - name: Upload Docker build digest to artifact if: ${{ ! matrix.isPr }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: digests-${{ inputs.build_id }} path: /tmp/digests/* @@ -192,7 +192,7 @@ jobs: - docker-build steps: - name: Download Docker build digests - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: digests-${{ inputs.build_id }} path: /tmp/digests From a1d7c47f1ede3222cea38505de654113b962eefc Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 9 Apr 2024 09:53:21 -0500 Subject: [PATCH 119/939] Fix Docker build by ensuring all artifacts are named with architecture (amd64 vs arm64) (cherry picked from commit f4edf9286075a8233b6bff4937e820dd279f58b8) --- .github/workflows/reusable-docker-build.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml index 2f80ef4ba92..41a2b4b4fef 100644 --- a/.github/workflows/reusable-docker-build.yml +++ b/.github/workflows/reusable-docker-build.yml @@ -154,7 +154,7 @@ jobs: if: ${{ ! matrix.isPr }} uses: actions/upload-artifact@v4 with: - name: digests-${{ inputs.build_id }} + name: digests-${{ inputs.build_id }}-${{ matrix.arch }} path: /tmp/digests/* if-no-files-found: error retention-days: 1 @@ -172,7 +172,7 @@ jobs: # If this build is NOT a PR and passed in a REDEPLOY_DEMO_URL secret, # Then redeploy https://demo.dspace.org if this build is for our deployment architecture and demo branch. - - name: Redeploy demo.dspace.org (based on maintenace branch) + - name: Redeploy demo.dspace.org (based on maintenance branch) if: | !matrix.isPR && env.REDEPLOY_DEMO_URL != '' && @@ -194,8 +194,10 @@ jobs: - name: Download Docker build digests uses: actions/download-artifact@v4 with: - name: digests-${{ inputs.build_id }} path: /tmp/digests + # Download digests for both AMD64 and ARM64 into same directory + pattern: digests-${{ inputs.build_id }}-* + merge-multiple: true - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 From 49e54eb34fb57380d2d47798e5c0e8270224ca98 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 9 Apr 2024 12:35:05 -0500 Subject: [PATCH 120/939] Ensure build digest name does NOT have slashes by changing arch to use dashes --- .github/workflows/reusable-docker-build.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml index 41a2b4b4fef..687474caed1 100644 --- a/.github/workflows/reusable-docker-build.yml +++ b/.github/workflows/reusable-docker-build.yml @@ -93,6 +93,14 @@ jobs: runs-on: ${{ matrix.os }} steps: + # This step converts the slashes in the "arch" matrix values above into dashes & saves to env.ARCH_NAME + # E.g. "linux/amd64" becomes "linux-amd64" + # This is necessary because all upload artifacts CANNOT have special chars (like slashes) + - name: Prepare + run: | + platform=${{ matrix.arch }} + echo "ARCH_NAME=${platform//\//-}" >> $GITHUB_ENV + # https://github.com/actions/checkout - name: Checkout codebase uses: actions/checkout@v4 @@ -154,7 +162,7 @@ jobs: if: ${{ ! matrix.isPr }} uses: actions/upload-artifact@v4 with: - name: digests-${{ inputs.build_id }}-${{ matrix.arch }} + name: digests-${{ inputs.build_id }}-${{ env.ARCH_NAME }} path: /tmp/digests/* if-no-files-found: error retention-days: 1 From e22bf93a972dffe8279bf2793c43a311229a93ed Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Tue, 9 Apr 2024 17:57:00 +0200 Subject: [PATCH 121/939] Refactor SubmissionConfigConsumer to avoid reload the submission config multiple times during the creation of a collection (cherry picked from commit 00d0a01e1f2c05db67a797d809be3c04dcd215e2) --- .../consumer/SubmissionConfigConsumer.java | 49 ++++++------------- 1 file changed, 16 insertions(+), 33 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/submit/consumer/SubmissionConfigConsumer.java b/dspace-api/src/main/java/org/dspace/submit/consumer/SubmissionConfigConsumer.java index a593fe8ae06..0cf4ae92c2c 100644 --- a/dspace-api/src/main/java/org/dspace/submit/consumer/SubmissionConfigConsumer.java +++ b/dspace-api/src/main/java/org/dspace/submit/consumer/SubmissionConfigConsumer.java @@ -8,15 +8,10 @@ package org.dspace.submit.consumer; import org.apache.logging.log4j.Logger; -import org.dspace.content.Collection; -import org.dspace.content.DSpaceObject; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.dspace.discovery.IndexingService; -import org.dspace.discovery.indexobject.IndexableCollection; import org.dspace.event.Consumer; import org.dspace.event.Event; -import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.submit.factory.SubmissionServiceFactory; /** @@ -28,11 +23,9 @@ public class SubmissionConfigConsumer implements Consumer { /** * log4j logger */ - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(SubmissionConfigConsumer.class); + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SubmissionConfigConsumer.class); - IndexingService indexer = DSpaceServicesFactory.getInstance().getServiceManager() - .getServiceByName(IndexingService.class.getName(), - IndexingService.class); + private boolean reloadNeeded = false; @Override public void initialize() throws Exception { @@ -42,37 +35,27 @@ public void initialize() throws Exception { @Override public void consume(Context ctx, Event event) throws Exception { int st = event.getSubjectType(); - int et = event.getEventType(); + if (st == Constants.COLLECTION) { + // NOTE: IndexEventConsumer ("discovery") should be declared before this consumer + // We don't reindex the collection because it will normally be reindexed by IndexEventConsumer + // before the submission configurations are reloaded - if ( st == Constants.COLLECTION ) { - switch (et) { - case Event.MODIFY_METADATA: - // Submission configuration it's based on solr - // for collection's entity type but, at this point - // that info isn't indexed yet, we need to force it - DSpaceObject subject = event.getSubject(ctx); - Collection collectionFromDSOSubject = (Collection) subject; - indexer.indexContent(ctx, new IndexableCollection (collectionFromDSOSubject), true, false, false); - indexer.commit(); - - log.debug("SubmissionConfigConsumer occured: " + event.toString()); - // reload submission configurations - SubmissionServiceFactory.getInstance().getSubmissionConfigService().reload(); - break; - - default: - log.debug("SubmissionConfigConsumer occured: " + event.toString()); - // reload submission configurations - SubmissionServiceFactory.getInstance().getSubmissionConfigService().reload(); - break; - } + log.debug("SubmissionConfigConsumer occurred: " + event); + // submission configurations should be reloaded + reloadNeeded = true; } } @Override public void end(Context ctx) throws Exception { - // No-op + if (reloadNeeded) { + // reload submission configurations + SubmissionServiceFactory.getInstance().getSubmissionConfigService().reload(); + + // Reset the boolean used + reloadNeeded = false; + } } @Override From 7c505e113ed607cc692ca790943dfb6f1c920aa9 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 9 Apr 2024 14:47:21 -0500 Subject: [PATCH 122/939] Docker build IDs must all be unique to avoid image conflicts. Ensure no builds use a generic name like "dspace". --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 9f1e407cff4..5abfb89a5dc 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -43,7 +43,7 @@ jobs: needs: dspace-dependencies uses: ./.github/workflows/reusable-docker-build.yml with: - build_id: dspace + build_id: dspace-prod image_name: dspace/dspace dockerfile_path: ./Dockerfile secrets: From 2afc9b158eb3b076b6ed4e07d5cfbe939e17fa07 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 10 Apr 2024 09:26:16 -0500 Subject: [PATCH 123/939] Docker build IDs must all be unique to avoid image conflicts. Avoid conflict with "-loadsql" build by appending "-prod" on main build id. --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 5abfb89a5dc..a9ff8760e76 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -120,7 +120,7 @@ jobs: if: github.repository == 'dspace/dspace' uses: ./.github/workflows/reusable-docker-build.yml with: - build_id: dspace-postgres-pgcrypto + build_id: dspace-postgres-pgcrypto-prod image_name: dspace/dspace-postgres-pgcrypto # Must build out of subdirectory to have access to install script for pgcrypto. # NOTE: this context will build the image based on the Dockerfile in the specified directory From 2f7ee4bc769b9fca70fbcb8f59a990cdbe78e7e6 Mon Sep 17 00:00:00 2001 From: Thomas Misilo Date: Fri, 16 Feb 2024 08:45:50 -0600 Subject: [PATCH 124/939] httpd-shibd-foreground.sh needs to be executable In order for the shib container to start, the file that is running needs to be executable (cherry picked from commit 3f9274f23f32e6983f3da81d001d9ce319e7245f) --- .../src/main/docker/dspace-shibboleth/httpd-shibd-foreground.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 dspace/src/main/docker/dspace-shibboleth/httpd-shibd-foreground.sh diff --git a/dspace/src/main/docker/dspace-shibboleth/httpd-shibd-foreground.sh b/dspace/src/main/docker/dspace-shibboleth/httpd-shibd-foreground.sh old mode 100644 new mode 100755 From 8d45c1f94d89d598254c3d97a1e87a4d0713046c Mon Sep 17 00:00:00 2001 From: Tom Misilo <1446856+misilot@users.noreply.github.com> Date: Fri, 12 Apr 2024 15:19:56 -0500 Subject: [PATCH 125/939] Add Space in ePerson's Name for rejection metadata and email (cherry picked from commit ec3089dc4d201201bf607b419b518d06dc1fb4f2) --- .../java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java index bc91a1fd929..faf15d02c6d 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java @@ -1177,7 +1177,7 @@ protected WorkspaceItem returnToWorkspace(Context c, XmlWorkflowItem wfi) public String getEPersonName(EPerson ePerson) { String submitter = ePerson.getFullName(); - submitter = submitter + "(" + ePerson.getEmail() + ")"; + submitter = submitter + " (" + ePerson.getEmail() + ")"; return submitter; } From 9b47b2215cd88c63889266a5950c58461dccd3b6 Mon Sep 17 00:00:00 2001 From: "David P. Steelman" Date: Wed, 17 Apr 2024 13:55:34 -0400 Subject: [PATCH 126/939] Fix OpenSearch NullPointerException for unknown valid UUIDs in scope Fixes a NullPointerException when the "scope" parameter provided to the OpenSearch endpoint is a valid UUID, but is not a UUID associated with a Community or Collection. Instead of throwing a NullPointerException, this change modifies the code to return a null scope (resulting in an "unscoped" OpenSearch request), which is the same behavior that occurs when the UUID is invalid, or otherwise not usable. --- .../dspace/app/rest/utils/ScopeResolver.java | 22 +++++++++++++++ .../opensearch/OpenSearchControllerIT.java | 28 +++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ScopeResolver.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ScopeResolver.java index 658a3e996ae..7a425f5e81a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ScopeResolver.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ScopeResolver.java @@ -35,6 +35,18 @@ public class ScopeResolver { @Autowired CommunityService communityService; + /** + * Returns an IndexableObject corresponding to the community or collection + * of the given scope, or null if the scope is not a valid UUID, or is a + * valid UUID that does not correspond to a community of collection. + * + * @param context the DSpace context + * @param scope a String containing the UUID of the community or collection + * to return. + * @return an IndexableObject corresponding to the community or collection + * of the given scope, or null if the scope is not a valid UUID, or is a + * valid UUID that does not correspond to a community of collection. + */ public IndexableObject resolveScope(Context context, String scope) { IndexableObject scopeObj = null; if (StringUtils.isNotBlank(scope)) { @@ -43,6 +55,16 @@ public IndexableObject resolveScope(Context context, String scope) { scopeObj = new IndexableCommunity(communityService.find(context, uuid)); if (scopeObj.getIndexedObject() == null) { scopeObj = new IndexableCollection(collectionService.find(context, uuid)); + if (scopeObj.getIndexedObject() == null) { + // Can't find the UUID as a community or collection + // so log and return null + log.warn( + "The given scope string " + + StringUtils.trimToEmpty(scope) + + " is not a collection or community UUID." + ); + scopeObj = null; + } } } catch (IllegalArgumentException ex) { log.warn("The given scope string " + StringUtils.trimToEmpty(scope) + " is not a UUID", ex); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/opensearch/OpenSearchControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/opensearch/OpenSearchControllerIT.java index 1ddea619d2f..b533e14568e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/opensearch/OpenSearchControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/opensearch/OpenSearchControllerIT.java @@ -269,4 +269,32 @@ public void emptyDescriptionTest() throws Exception { .andExpect(status().isOk()) .andExpect(xpath("rss/channel/description").string("No Description")); } + + @Test + public void scopeNotCommunityOrCollectionUUIDTest() throws Exception { + // Tests that a OpenSearch response with 1 result (equivalent to an + // unscoped request) is returned if the "scope" UUID is a + // validly-formatted UUID, but not a community or collection UUID. + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection collection1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1") + .build(); + + Item publicItem1 = ItemBuilder.createItem(context, collection1) + .withTitle("Boars at Yellowstone") + .withIssueDate("2017-10-17") + .withAuthor("Ballini, Andreas").withAuthor("Moriarti, Susan") + .build(); + + // UUID is valid, but not a community or collection UUID + String testUUID = "b68f0d1c-7316-41dc-835d-46b79b35642e"; + + getClient().perform(get("/opensearch/search") + .param("scope", testUUID) + .param("query", "*")) + .andExpect(status().isOk()) + .andExpect(xpath("feed/totalResults").string("1")); + } } From e82b94fa435663419a2fd78a9aad9d9e488f7ecc Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Mon, 15 Apr 2024 12:56:58 +0200 Subject: [PATCH 127/939] Add support for LazyDownload of files from S3 --- .../storage/bitstore/S3BitStoreService.java | 87 ++++++++++++++++--- .../storage/bitstore/S3BitStoreServiceIT.java | 21 ++++- 2 files changed, 90 insertions(+), 18 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java index 48f3b9f325f..54333abbf49 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java @@ -10,6 +10,7 @@ import static java.lang.String.valueOf; import java.io.File; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -24,6 +25,7 @@ import javax.validation.constraints.NotNull; import com.amazonaws.AmazonClientException; +import com.amazonaws.AmazonServiceException; import com.amazonaws.auth.AWSCredentials; import com.amazonaws.auth.AWSStaticCredentialsProvider; import com.amazonaws.auth.BasicAWSCredentials; @@ -35,6 +37,7 @@ import com.amazonaws.services.s3.model.GetObjectRequest; import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.transfer.Download; +import com.amazonaws.services.s3.transfer.Transfer.TransferState; import com.amazonaws.services.s3.transfer.TransferManager; import com.amazonaws.services.s3.transfer.TransferManagerBuilder; import com.amazonaws.services.s3.transfer.Upload; @@ -102,6 +105,11 @@ public class S3BitStoreService extends BaseBitStoreService { private String awsRegionName; private boolean useRelativePath; + /** + * The maximum size of individual chunk to download from S3 when a file is accessed. Default 5Mb + */ + private long bufferSize = 5 * 1024 * 1024; + /** * container for all the assets */ @@ -258,20 +266,7 @@ public InputStream get(Bitstream bitstream) throws IOException { if (isRegisteredBitstream(key)) { key = key.substring(REGISTERED_FLAG.length()); } - try { - File tempFile = File.createTempFile("s3-disk-copy-" + UUID.randomUUID(), "temp"); - tempFile.deleteOnExit(); - - GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, key); - - Download download = tm.download(getObjectRequest, tempFile); - download.waitForCompletion(); - - return new DeleteOnCloseFileInputStream(tempFile); - } catch (AmazonClientException | InterruptedException e) { - log.error("get(" + key + ")", e); - throw new IOException(e); - } + return new S3LazyInputStream(key, bufferSize, bitstream.getSizeBytes()); } /** @@ -622,4 +617,68 @@ public boolean isRegisteredBitstream(String internalId) { return internalId.startsWith(REGISTERED_FLAG); } + public void setBufferSize(long bufferSize) { + this.bufferSize = bufferSize; + } + + public class S3LazyInputStream extends InputStream { + private InputStream currentChunkStream; + private String objectKey; + private long endOfChunk = -1; + private long chunkMaxSize; + private long currPos = 0; + private long fileSize; + + public S3LazyInputStream(String objectKey, long chunkMaxSize, long fileSize) throws IOException { + this.objectKey = objectKey; + this.chunkMaxSize = chunkMaxSize; + this.endOfChunk = 0; + this.fileSize = fileSize; + downloadChunk(); + } + + @Override + public int read() throws IOException { + if (currPos == endOfChunk && currPos < fileSize) { + currentChunkStream.close(); + downloadChunk(); + } + + int byteRead = currPos < endOfChunk ? currentChunkStream.read() : -1; + if (byteRead != -1) { + currPos++; + } else { + currentChunkStream.close(); + } + return byteRead; + } + + private void downloadChunk() throws IOException, FileNotFoundException { + // Create a DownloadFileRequest with the desired byte range + long startByte = currPos; // Start byte (inclusive) + long endByte = Long.min(startByte + chunkMaxSize - 1, fileSize - 1); // End byte (inclusive) + GetObjectRequest getRequest = new GetObjectRequest(bucketName, objectKey) + .withRange(startByte, endByte); + + File currentChunkFile = File.createTempFile("s3-disk-copy-" + UUID.randomUUID(), "temp"); + currentChunkFile.deleteOnExit(); + try { + Download download = tm.download(getRequest, currentChunkFile); + download.waitForCompletion(); + currentChunkStream = new DeleteOnCloseFileInputStream(currentChunkFile); + endOfChunk = endOfChunk + download.getProgress().getBytesTransferred(); + } catch (AmazonClientException | InterruptedException e) { + currentChunkFile.delete(); + throw new IOException(e); + } + } + + @Override + public void close() throws IOException { + if (currentChunkStream != null) { + currentChunkStream.close(); + } + } + + } } diff --git a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java index 30eeff2ddc8..dffde0b3a6a 100644 --- a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java +++ b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java @@ -99,6 +99,7 @@ public void setup() throws Exception { s3BitStoreService = new S3BitStoreService(amazonS3Client); s3BitStoreService.setEnabled(BooleanUtils.toBoolean( configurationService.getProperty("assetstore.s3.enabled"))); + s3BitStoreService.setBufferSize(22); context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) @@ -129,12 +130,25 @@ public void testBitstreamPutAndGetWithAlreadyPresentBucket() throws IOException assertThat(amazonS3Client.listBuckets(), contains(bucketNamed(bucketName))); context.turnOffAuthorisationSystem(); - String content = "Test bitstream content"; + String content = "Test bitstream content"; + String contentOverOneSpan = "This content span two chunks"; + String contentExactlyTwoSpans = "Test bitstream contentTest bitstream content"; + String contentOverOneTwoSpans = "Test bitstream contentThis content span three chunks"; Bitstream bitstream = createBitstream(content); + Bitstream bitstreamOverOneSpan = createBitstream(contentOverOneSpan); + Bitstream bitstreamExactlyTwoSpans = createBitstream(contentExactlyTwoSpans); + Bitstream bitstreamOverOneTwoSpans = createBitstream(contentOverOneTwoSpans); context.restoreAuthSystemState(); - s3BitStoreService.put(bitstream, toInputStream(content)); + checkGetPut(bucketName, content, bitstream); + checkGetPut(bucketName, contentOverOneSpan, bitstreamOverOneSpan); + checkGetPut(bucketName, contentExactlyTwoSpans, bitstreamExactlyTwoSpans); + checkGetPut(bucketName, contentOverOneTwoSpans, bitstreamOverOneTwoSpans); + + } + private void checkGetPut(String bucketName, String content, Bitstream bitstream) throws IOException { + s3BitStoreService.put(bitstream, toInputStream(content)); String expectedChecksum = Utils.toHex(generateChecksum(content)); assertThat(bitstream.getSizeBytes(), is((long) content.length())); @@ -147,8 +161,7 @@ public void testBitstreamPutAndGetWithAlreadyPresentBucket() throws IOException String key = s3BitStoreService.getFullKey(bitstream.getInternalId()); ObjectMetadata objectMetadata = amazonS3Client.getObjectMetadata(bucketName, key); assertThat(objectMetadata.getContentMD5(), is(expectedChecksum)); - - } + } @Test public void testBitstreamPutAndGetWithoutSpecifingBucket() throws IOException { From 23ee2fd6cdd63b1fafe1916736a9359df2187348 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Fri, 19 Apr 2024 07:52:32 +0200 Subject: [PATCH 128/939] DURACOM-249 fix checkstyle issues, add javadoc --- .../storage/bitstore/S3BitStoreService.java | 58 ++++++++++++------- .../storage/bitstore/S3BitStoreServiceIT.java | 6 +- 2 files changed, 39 insertions(+), 25 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java index 54333abbf49..e21a101a7e9 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java @@ -25,7 +25,6 @@ import javax.validation.constraints.NotNull; import com.amazonaws.AmazonClientException; -import com.amazonaws.AmazonServiceException; import com.amazonaws.auth.AWSCredentials; import com.amazonaws.auth.AWSStaticCredentialsProvider; import com.amazonaws.auth.BasicAWSCredentials; @@ -37,7 +36,6 @@ import com.amazonaws.services.s3.model.GetObjectRequest; import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.transfer.Download; -import com.amazonaws.services.s3.transfer.Transfer.TransferState; import com.amazonaws.services.s3.transfer.TransferManager; import com.amazonaws.services.s3.transfer.TransferManagerBuilder; import com.amazonaws.services.s3.transfer.Upload; @@ -621,6 +619,14 @@ public void setBufferSize(long bufferSize) { this.bufferSize = bufferSize; } + /** + * This inner class represent an InputStream that uses temporary files to + * represent chunk of the object downloaded from S3. When the input stream is + * read the class look first to the current chunk and download a new one once if + * the current one as been fully read. The class is responsible to close a chunk + * as soon as a new one is retrieved, the last chunk is closed when the input + * stream itself is closed or the last byte is read (the first of the two) + */ public class S3LazyInputStream extends InputStream { private InputStream currentChunkStream; private String objectKey; @@ -639,12 +645,14 @@ public S3LazyInputStream(String objectKey, long chunkMaxSize, long fileSize) thr @Override public int read() throws IOException { + // is the current chunk completely read and other are available? if (currPos == endOfChunk && currPos < fileSize) { currentChunkStream.close(); downloadChunk(); } - int byteRead = currPos < endOfChunk ? currentChunkStream.read() : -1; + int byteRead = currPos < endOfChunk ? currentChunkStream.read() : -1; + // do we get any data or are we at the end of the file? if (byteRead != -1) { currPos++; } else { @@ -653,25 +661,31 @@ public int read() throws IOException { return byteRead; } - private void downloadChunk() throws IOException, FileNotFoundException { - // Create a DownloadFileRequest with the desired byte range - long startByte = currPos; // Start byte (inclusive) - long endByte = Long.min(startByte + chunkMaxSize - 1, fileSize - 1); // End byte (inclusive) - GetObjectRequest getRequest = new GetObjectRequest(bucketName, objectKey) - .withRange(startByte, endByte); - - File currentChunkFile = File.createTempFile("s3-disk-copy-" + UUID.randomUUID(), "temp"); - currentChunkFile.deleteOnExit(); - try { - Download download = tm.download(getRequest, currentChunkFile); - download.waitForCompletion(); - currentChunkStream = new DeleteOnCloseFileInputStream(currentChunkFile); - endOfChunk = endOfChunk + download.getProgress().getBytesTransferred(); - } catch (AmazonClientException | InterruptedException e) { - currentChunkFile.delete(); - throw new IOException(e); - } - } + /** + * This method download the next chunk from S3 + * + * @throws IOException + * @throws FileNotFoundException + */ + private void downloadChunk() throws IOException, FileNotFoundException { + // Create a DownloadFileRequest with the desired byte range + long startByte = currPos; // Start byte (inclusive) + long endByte = Long.min(startByte + chunkMaxSize - 1, fileSize - 1); // End byte (inclusive) + GetObjectRequest getRequest = new GetObjectRequest(bucketName, objectKey) + .withRange(startByte, endByte); + + File currentChunkFile = File.createTempFile("s3-disk-copy-" + UUID.randomUUID(), "temp"); + currentChunkFile.deleteOnExit(); + try { + Download download = tm.download(getRequest, currentChunkFile); + download.waitForCompletion(); + currentChunkStream = new DeleteOnCloseFileInputStream(currentChunkFile); + endOfChunk = endOfChunk + download.getProgress().getBytesTransferred(); + } catch (AmazonClientException | InterruptedException e) { + currentChunkFile.delete(); + throw new IOException(e); + } + } @Override public void close() throws IOException { diff --git a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java index dffde0b3a6a..6ea21eac8d6 100644 --- a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java +++ b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java @@ -147,8 +147,8 @@ public void testBitstreamPutAndGetWithAlreadyPresentBucket() throws IOException } - private void checkGetPut(String bucketName, String content, Bitstream bitstream) throws IOException { - s3BitStoreService.put(bitstream, toInputStream(content)); + private void checkGetPut(String bucketName, String content, Bitstream bitstream) throws IOException { + s3BitStoreService.put(bitstream, toInputStream(content)); String expectedChecksum = Utils.toHex(generateChecksum(content)); assertThat(bitstream.getSizeBytes(), is((long) content.length())); @@ -161,7 +161,7 @@ private void checkGetPut(String bucketName, String content, Bitstream bitstream) String key = s3BitStoreService.getFullKey(bitstream.getInternalId()); ObjectMetadata objectMetadata = amazonS3Client.getObjectMetadata(bucketName, key); assertThat(objectMetadata.getContentMD5(), is(expectedChecksum)); - } + } @Test public void testBitstreamPutAndGetWithoutSpecifingBucket() throws IOException { From 15d9d1c0423c9dc90ef67717043cb5e7dda6ccc8 Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Fri, 19 Apr 2024 21:03:45 +0200 Subject: [PATCH 129/939] Port from #9259 the changes to use a collection object to return the item process submission configured --- .../org/dspace/app/util/DCInputsReader.java | 10 ++++---- .../app/util/SubmissionConfigReader.java | 24 ++++++++++++------- .../main/java/org/dspace/app/util/Util.java | 14 +++-------- .../authority/ChoiceAuthorityServiceImpl.java | 4 ++-- .../ctask/general/RequiredMetadata.java | 9 +++---- .../service/SubmissionConfigService.java | 2 +- .../service/SubmissionConfigServiceImpl.java | 4 ++-- .../dspace/app/util/SubmissionConfigTest.java | 10 +++++++- .../converter/AInprogressItemConverter.java | 2 +- .../SubmissionDefinitionRestRepository.java | 2 +- .../WorkspaceItemRestRepository.java | 2 +- 11 files changed, 45 insertions(+), 38 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/util/DCInputsReader.java b/dspace-api/src/main/java/org/dspace/app/util/DCInputsReader.java index 38692c73a6c..8dc8239ca50 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/DCInputsReader.java +++ b/dspace-api/src/main/java/org/dspace/app/util/DCInputsReader.java @@ -14,7 +14,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; -import javax.servlet.ServletException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.FactoryConfigurationError; @@ -150,17 +149,16 @@ public List getPairs(String name) { * Returns the set of DC inputs used for a particular collection, or the * default set if no inputs defined for the collection * - * @param collectionHandle collection's unique Handle + * @param collection collection for which search the set of DC inputs * @return DC input set * @throws DCInputsReaderException if no default set defined - * @throws ServletException */ - public List getInputsByCollectionHandle(String collectionHandle) + public List getInputsByCollection(Collection collection) throws DCInputsReaderException { SubmissionConfig config; try { config = SubmissionServiceFactory.getInstance().getSubmissionConfigService() - .getSubmissionConfigByCollection(collectionHandle); + .getSubmissionConfigByCollection(collection); String formName = config.getSubmissionName(); if (formName == null) { throw new DCInputsReaderException("No form designated as default"); @@ -691,7 +689,7 @@ private String getValue(Node nd) { public String getInputFormNameByCollectionAndField(Collection collection, String field) throws DCInputsReaderException { - List inputSets = getInputsByCollectionHandle(collection.getHandle()); + List inputSets = getInputsByCollection(collection); for (DCInputSet inputSet : inputSets) { String[] tokenized = Utils.tokenize(field); String schema = tokenized[0]; diff --git a/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java b/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java index 0f144fd69f4..82bb1c9ca4a 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java +++ b/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java @@ -210,18 +210,26 @@ public int countSubmissionConfigs() { * Returns the Item Submission process config used for a particular * collection, or the default if none is defined for the collection * - * @param collectionHandle collection's unique Handle + * @param col collection for which search Submission process config * @return the SubmissionConfig representing the item submission config - * @throws SubmissionConfigReaderException if no default submission process configuration defined + * @throws IllegalStateException if no default submission process configuration defined */ - public SubmissionConfig getSubmissionConfigByCollection(String collectionHandle) { - // get the name of the submission process config for this collection - String submitName = collectionToSubmissionConfig - .get(collectionHandle); - if (submitName == null) { + public SubmissionConfig getSubmissionConfigByCollection(Collection col) { + + String submitName; + + if (col != null) { + + // get the name of the submission process config for this collection submitName = collectionToSubmissionConfig - .get(DEFAULT_COLLECTION); + .get(col.getHandle()); + if (submitName != null) { + return getSubmissionConfigByName(submitName); + } } + + submitName = collectionToSubmissionConfig.get(DEFAULT_COLLECTION); + if (submitName == null) { throw new IllegalStateException( "No item submission process configuration designated as 'default' in 'submission-map' section of " + diff --git a/dspace-api/src/main/java/org/dspace/app/util/Util.java b/dspace-api/src/main/java/org/dspace/app/util/Util.java index f8ef3b1731f..3bc828d6c49 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/Util.java +++ b/dspace-api/src/main/java/org/dspace/app/util/Util.java @@ -405,21 +405,13 @@ public static List getControlledVocabulariesDisplayValueLocalized( DCInput myInputs = null; boolean myInputsFound = false; String formFileName = I18nUtil.getInputFormsFileName(locale); - String col_handle = ""; Collection collection = item.getOwningCollection(); - if (collection == null) { - // set an empty handle so to get the default input set - col_handle = ""; - } else { - col_handle = collection.getHandle(); - } - // Read the input form file for the specific collection DCInputsReader inputsReader = new DCInputsReader(formFileName); - List inputSets = inputsReader.getInputsByCollectionHandle(col_handle); + List inputSets = inputsReader.getInputsByCollection(collection); // Replace the values of Metadatum[] with the correct ones in case // of @@ -500,8 +492,8 @@ public static List[] splitList(List idsList, int i) { public static List differenceInSubmissionFields(Collection fromCollection, Collection toCollection) throws DCInputsReaderException { DCInputsReader reader = new DCInputsReader(); - List from = reader.getInputsByCollectionHandle(fromCollection.getHandle()); - List to = reader.getInputsByCollectionHandle(toCollection.getHandle()); + List from = reader.getInputsByCollection(fromCollection); + List to = reader.getInputsByCollection(toCollection); Set fromFieldName = new HashSet<>(); Set toFieldName = new HashSet<>(); diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java index 34ba9e8c455..f4d1f02710e 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java @@ -242,7 +242,7 @@ public String getChoiceAuthorityName(String schema, String element, String quali // check if it is the requested collection Map controllerFormDef = controllerFormDefinitions.get(fieldKey); SubmissionConfig submissionConfig = submissionConfigService - .getSubmissionConfigByCollection(collection.getHandle()); + .getSubmissionConfigByCollection(collection); String submissionName = submissionConfig.getSubmissionName(); // check if the requested collection has a submission definition that use an authority for the metadata if (controllerFormDef.containsKey(submissionName)) { @@ -495,7 +495,7 @@ private ChoiceAuthority getAuthorityByFieldKeyCollection(String fieldKey, Collec try { configReaderService = SubmissionServiceFactory.getInstance().getSubmissionConfigService(); SubmissionConfig submissionName = configReaderService - .getSubmissionConfigByCollection(collection.getHandle()); + .getSubmissionConfigByCollection(collection); ma = controllerFormDefinitions.get(fieldKey).get(submissionName.getSubmissionName()); } catch (SubmissionConfigReaderException e) { // the system is in an illegal state as the submission definition is not valid diff --git a/dspace-api/src/main/java/org/dspace/ctask/general/RequiredMetadata.java b/dspace-api/src/main/java/org/dspace/ctask/general/RequiredMetadata.java index 07bfed5fe57..2899e3f6bdd 100644 --- a/dspace-api/src/main/java/org/dspace/ctask/general/RequiredMetadata.java +++ b/dspace-api/src/main/java/org/dspace/ctask/general/RequiredMetadata.java @@ -17,6 +17,7 @@ import org.dspace.app.util.DCInputSet; import org.dspace.app.util.DCInputsReader; import org.dspace.app.util.DCInputsReaderException; +import org.dspace.content.Collection; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.MetadataValue; @@ -69,7 +70,7 @@ public int perform(DSpaceObject dso) throws IOException { handle = "in workflow"; } sb.append("Item: ").append(handle); - for (String req : getReqList(item.getOwningCollection().getHandle())) { + for (String req : getReqList(item.getOwningCollection())) { List vals = itemService.getMetadataByMetadataString(item, req); if (vals.size() == 0) { sb.append(" missing required field: ").append(req); @@ -91,14 +92,14 @@ public int perform(DSpaceObject dso) throws IOException { } } - protected List getReqList(String handle) throws DCInputsReaderException { - List reqList = reqMap.get(handle); + protected List getReqList(Collection collection) throws DCInputsReaderException { + List reqList = reqMap.get(collection.getHandle()); if (reqList == null) { reqList = reqMap.get("default"); } if (reqList == null) { reqList = new ArrayList(); - List inputSet = reader.getInputsByCollectionHandle(handle); + List inputSet = reader.getInputsByCollection(collection); for (DCInputSet inputs : inputSet) { for (DCInput[] row : inputs.getFields()) { for (DCInput input : row) { diff --git a/dspace-api/src/main/java/org/dspace/submit/service/SubmissionConfigService.java b/dspace-api/src/main/java/org/dspace/submit/service/SubmissionConfigService.java index c4b111a38f7..36ba82f60d5 100644 --- a/dspace-api/src/main/java/org/dspace/submit/service/SubmissionConfigService.java +++ b/dspace-api/src/main/java/org/dspace/submit/service/SubmissionConfigService.java @@ -34,7 +34,7 @@ public interface SubmissionConfigService { public int countSubmissionConfigs(); - public SubmissionConfig getSubmissionConfigByCollection(String collectionHandle); + public SubmissionConfig getSubmissionConfigByCollection(Collection collection); public SubmissionConfig getSubmissionConfigByName(String submitName); diff --git a/dspace-api/src/main/java/org/dspace/submit/service/SubmissionConfigServiceImpl.java b/dspace-api/src/main/java/org/dspace/submit/service/SubmissionConfigServiceImpl.java index a72bcc2c3bf..fe063954a1d 100644 --- a/dspace-api/src/main/java/org/dspace/submit/service/SubmissionConfigServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/submit/service/SubmissionConfigServiceImpl.java @@ -57,8 +57,8 @@ public int countSubmissionConfigs() { } @Override - public SubmissionConfig getSubmissionConfigByCollection(String collectionHandle) { - return submissionConfigReader.getSubmissionConfigByCollection(collectionHandle); + public SubmissionConfig getSubmissionConfigByCollection(Collection collection) { + return submissionConfigReader.getSubmissionConfigByCollection(collection); } @Override diff --git a/dspace-api/src/test/java/org/dspace/app/util/SubmissionConfigTest.java b/dspace-api/src/test/java/org/dspace/app/util/SubmissionConfigTest.java index cb1f828b93c..4ac19310987 100644 --- a/dspace-api/src/test/java/org/dspace/app/util/SubmissionConfigTest.java +++ b/dspace-api/src/test/java/org/dspace/app/util/SubmissionConfigTest.java @@ -9,17 +9,20 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.when; import java.util.ArrayList; import java.util.List; import org.dspace.AbstractUnitTest; +import org.dspace.content.Collection; import org.dspace.submit.factory.SubmissionServiceFactory; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import org.mockito.Mock; /** * Tests for parsing and utilities on submission config forms / readers @@ -30,6 +33,9 @@ public class SubmissionConfigTest extends AbstractUnitTest { DCInputsReader inputReader; + @Mock + private Collection col1; + @BeforeClass public static void setUpClass() { } @@ -56,6 +62,8 @@ public void testReadAndProcessTypeBindSubmissionConfig() String typeBindSubmissionName = "typebindtest"; String typeBindSubmissionStepName = "typebindtest"; + when(col1.getHandle()).thenReturn(typeBindHandle); + // Expected field lists from typebindtest form List allConfiguredFields = new ArrayList<>(); allConfiguredFields.add("dc.title"); @@ -67,7 +75,7 @@ public void testReadAndProcessTypeBindSubmissionConfig() // Get submission configuration SubmissionConfig submissionConfig = SubmissionServiceFactory.getInstance().getSubmissionConfigService() - .getSubmissionConfigByCollection(typeBindHandle); + .getSubmissionConfigByCollection(col1); // Submission name should match name defined in item-submission.xml assertEquals(typeBindSubmissionName, submissionConfig.getSubmissionName()); // Step 0 - our process only has one step. It should not be null and have the ID typebindtest diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java index a5431d90004..a80c8bd948b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java @@ -82,7 +82,7 @@ protected void fillFromModel(T obj, R witem, Projection projection) { if (collection != null) { SubmissionDefinitionRest def = converter.toRest( - submissionConfigService.getSubmissionConfigByCollection(collection.getHandle()), projection); + submissionConfigService.getSubmissionConfigByCollection(collection), projection); witem.setSubmissionDefinition(def); for (SubmissionSectionRest sections : def.getPanels()) { SubmissionStepConfig stepConfig = submissionSectionConverter.toModel(sections); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionDefinitionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionDefinitionRestRepository.java index d964994928e..17eb90b7901 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionDefinitionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionDefinitionRestRepository.java @@ -70,7 +70,7 @@ public SubmissionDefinitionRest findByCollection(@Parameter(value = "uuid", requ return null; } SubmissionDefinitionRest def = converter - .toRest(submissionConfigService.getSubmissionConfigByCollection(col.getHandle()), + .toRest(submissionConfigService.getSubmissionConfigByCollection(col), utils.obtainProjection()); return def; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java index b4d04e59e32..5f4bb0dfe92 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java @@ -251,7 +251,7 @@ public Iterable upload(Context context, HttpServletRequest re } SubmissionConfig submissionConfig = - submissionConfigService.getSubmissionConfigByCollection(collection.getHandle()); + submissionConfigService.getSubmissionConfigByCollection(collection); List result = null; List records = new ArrayList<>(); try { From cf6008271a731f7664a003bd797cb42b568848eb Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Fri, 19 Apr 2024 21:38:31 +0200 Subject: [PATCH 130/939] Refactor SubmissionConfigReader to use a map for the collections configured through the entityType value --- .../app/util/SubmissionConfigReader.java | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java b/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java index 82bb1c9ca4a..7342cc4283f 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java +++ b/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java @@ -22,10 +22,10 @@ import org.apache.logging.log4j.Logger; import org.dspace.content.Collection; import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.CollectionService; import org.dspace.core.Context; -import org.dspace.discovery.SearchServiceException; import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.services.factory.DSpaceServicesFactory; import org.w3c.dom.Document; @@ -96,6 +96,13 @@ public class SubmissionConfigReader { */ private Map> stepDefns = null; + /** + * Hashmap which stores which submission process configuration is used by + * which entityType, computed from the item submission config file + * (specifically, the 'submission-map' tag) + */ + private Map entityTypeToSubmissionConfig = null; + /** * Reference to the item submission definitions defined in the * "submission-definitions" section @@ -127,6 +134,7 @@ public SubmissionConfigReader() throws SubmissionConfigReaderException { public void reload() throws SubmissionConfigReaderException { collectionToSubmissionConfig = null; + entityTypeToSubmissionConfig = null; stepDefns = null; submitDefns = null; buildInputs(configDir + SUBMIT_DEF_FILE_PREFIX + SUBMIT_DEF_FILE_SUFFIX); @@ -145,6 +153,7 @@ public void reload() throws SubmissionConfigReaderException { */ private void buildInputs(String fileName) throws SubmissionConfigReaderException { collectionToSubmissionConfig = new HashMap(); + entityTypeToSubmissionConfig = new HashMap(); submitDefns = new HashMap>>(); String uri = "file:" + new File(fileName).getAbsolutePath(); @@ -162,9 +171,6 @@ private void buildInputs(String fileName) throws SubmissionConfigReaderException } catch (FactoryConfigurationError fe) { throw new SubmissionConfigReaderException( "Cannot create Item Submission Configuration parser", fe); - } catch (SearchServiceException se) { - throw new SubmissionConfigReaderException( - "Cannot perform a discovery search for Item Submission Configuration", se); } catch (Exception e) { throw new SubmissionConfigReaderException( "Error creating Item Submission Configuration: " + e); @@ -228,6 +234,16 @@ public SubmissionConfig getSubmissionConfigByCollection(Collection col) { } } + // get the name of the submission process based on the entity type of this collections + if (!entityTypeToSubmissionConfig.isEmpty()) { + String entityType = collectionService.getMetadataFirstValue(col, "dspace", "entity", "type", Item.ANY); + submitName = entityTypeToSubmissionConfig + .get(entityType); + if (submitName != null) { + return getSubmissionConfigByName(submitName); + } + } + submitName = collectionToSubmissionConfig.get(DEFAULT_COLLECTION); if (submitName == null) { @@ -308,7 +324,7 @@ public SubmissionStepConfig getStepConfig(String stepID) * should correspond to the collection-form maps, the form definitions, and * the display/storage word pairs. */ - private void doNodes(Node n) throws SAXException, SearchServiceException, SubmissionConfigReaderException { + private void doNodes(Node n) throws SAXException, SubmissionConfigReaderException { if (n == null) { return; } @@ -355,9 +371,7 @@ private void doNodes(Node n) throws SAXException, SearchServiceException, Submis * the collection handle and item submission name, put name in hashmap keyed * by the collection handle. */ - private void processMap(Node e) throws SAXException, SearchServiceException { - // create a context - Context context = new Context(); + private void processMap(Node e) throws SAXException { NodeList nl = e.getChildNodes(); int len = nl.getLength(); @@ -377,7 +391,7 @@ private void processMap(Node e) throws SAXException, SearchServiceException { throw new SAXException( "name-map element is missing submission-name attribute in 'item-submission.xml'"); } - if (content != null && content.length() > 0) { + if (content != null && !content.isEmpty()) { throw new SAXException( "name-map element has content in 'item-submission.xml', it should be empty."); } @@ -385,12 +399,7 @@ private void processMap(Node e) throws SAXException, SearchServiceException { collectionToSubmissionConfig.put(id, value); } else { - // get all collections for this entity-type - List collections = collectionService.findAllCollectionsByEntityType( context, - entityType); - for (Collection collection : collections) { - collectionToSubmissionConfig.putIfAbsent(collection.getHandle(), value); - } + entityTypeToSubmissionConfig.put(entityType, value); } } // ignore any child node that isn't a "name-map" } From 1a65dfb15b021f5e58bdb821ee8d85330c416dd1 Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Fri, 19 Apr 2024 21:39:19 +0200 Subject: [PATCH 131/939] Remove the consumer submissionconfig from default configuration because it is not needed to reload forms if a collection entitytype is changed --- dspace/config/dspace.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index b7cc13e508d..60a7506d876 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -780,7 +780,7 @@ event.dispatcher.default.class = org.dspace.event.BasicDispatcher # Add rdf here, if you are using dspace-rdf to export your repository content as RDF. # Add iiif here, if you are using dspace-iiif. # Add orcidqueue here, if the integration with ORCID is configured and wish to enable the synchronization queue functionality -event.dispatcher.default.consumers = versioning, discovery, eperson, submissionconfig +event.dispatcher.default.consumers = versioning, discovery, eperson # The noindex dispatcher will not create search or browse indexes (useful for batch item imports) event.dispatcher.noindex.class = org.dspace.event.BasicDispatcher From 770b38c7934ba58cea1bcf23d819332128e2a956 Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Fri, 19 Apr 2024 22:35:55 +0200 Subject: [PATCH 132/939] Add test for item process submission forms mapped by entityType --- .../app/util/SubmissionConfigReader.java | 3 +- .../dspaceFolder/config/item-submission.xml | 10 + .../dspace/app/util/SubmissionConfigIT.java | 60 ++++ .../SubmissionDefinitionsControllerIT.java | 299 ++++++++++++------ 4 files changed, 275 insertions(+), 97 deletions(-) create mode 100644 dspace-api/src/test/java/org/dspace/app/util/SubmissionConfigIT.java diff --git a/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java b/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java index 7342cc4283f..70c5092602a 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java +++ b/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java @@ -11,6 +11,7 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -154,7 +155,7 @@ public void reload() throws SubmissionConfigReaderException { private void buildInputs(String fileName) throws SubmissionConfigReaderException { collectionToSubmissionConfig = new HashMap(); entityTypeToSubmissionConfig = new HashMap(); - submitDefns = new HashMap>>(); + submitDefns = new LinkedHashMap>>(); String uri = "file:" + new File(fileName).getAbsolutePath(); diff --git a/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml b/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml index 452460501a5..2b4cee04491 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml @@ -24,6 +24,8 @@ + + @@ -257,6 +259,14 @@ + + + + + + + + diff --git a/dspace-api/src/test/java/org/dspace/app/util/SubmissionConfigIT.java b/dspace-api/src/test/java/org/dspace/app/util/SubmissionConfigIT.java new file mode 100644 index 00000000000..f171c45328a --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/util/SubmissionConfigIT.java @@ -0,0 +1,60 @@ +/** + * 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.app.util; + +import static org.junit.Assert.assertEquals; + +import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.submit.factory.SubmissionServiceFactory; +import org.dspace.submit.service.SubmissionConfigService; +import org.junit.Test; + +/** + * Integration Tests for parsing and utilities on submission config forms / readers + * + * @author Toni Prieto + */ +public class SubmissionConfigIT extends AbstractIntegrationTestWithDatabase { + + @Test + public void testSubmissionConfigMapByCollectionOrEntityType() + throws SubmissionConfigReaderException { + + context.turnOffAuthorisationSystem(); + // Sep up a structure with one top community and two collections + Community topcom = CommunityBuilder.createCommunity(context, "123456789/topcommunity-test") + .withName("Parent Community") + .build(); + // col1 should use the item submission form directly mapped for this collection + Collection col1 = CollectionBuilder.createCollection(context, topcom, "123456789/collection-test") + .withName("Collection 1") + .withEntityType("CustomEntityType") + .build(); + // col2 should use the item submission form mapped for the entity type CustomEntityType + Collection col2 = CollectionBuilder.createCollection(context, topcom, "123456789/not-mapped1") + .withName("Collection 2") + .withEntityType("CustomEntityType") + .build(); + context.restoreAuthSystemState(); + + SubmissionConfigService submissionConfigService = SubmissionServiceFactory.getInstance() + .getSubmissionConfigService(); + + // for col1, it should return the item submission form defined directly for the collection + SubmissionConfig submissionConfig1 = submissionConfigService.getSubmissionConfigByCollection(col1); + assertEquals("collectiontest", submissionConfig1.getSubmissionName()); + + // for col2, it should return the item submission form defined for the entitytype CustomEntityType + SubmissionConfig submissionConfig2 = submissionConfigService.getSubmissionConfigByCollection(col2); + assertEquals("entitytypetest", submissionConfig2.getSubmissionName()); + } +} \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java index babb1fac232..1a1a4576dc8 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java @@ -32,6 +32,11 @@ */ public class SubmissionDefinitionsControllerIT extends AbstractControllerIntegrationTest { + // The total number of expected submission definitions is referred to in multiple tests and assertions as + // is the last page (totalDefinitions - 1) + // This integer should be maintained along with any changes to item-submissions.xml + private static final int totalDefinitions = 9; + @Test public void findAll() throws Exception { //When we call the root endpoint as anonymous user @@ -243,110 +248,110 @@ public void findAllPaginationTest() throws Exception { getClient(tokenAdmin).perform(get("/api/config/submissiondefinitions") .param("size", "1") .param("page", "0")) - .andExpect(status().isOk()) - .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("traditional"))) - .andExpect(jsonPath("$._links.first.href", Matchers.allOf( - Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=0"), Matchers.containsString("size=1")))) - .andExpect(jsonPath("$._links.self.href", Matchers.allOf( - Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=0"), Matchers.containsString("size=1")))) - .andExpect(jsonPath("$._links.next.href", Matchers.allOf( - Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=1"), Matchers.containsString("size=1")))) - .andExpect(jsonPath("$._links.last.href", Matchers.allOf( - Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=6"), Matchers.containsString("size=1")))) - .andExpect(jsonPath("$.page.size", is(1))) - .andExpect(jsonPath("$.page.totalElements", is(7))) - .andExpect(jsonPath("$.page.totalPages", is(7))) - .andExpect(jsonPath("$.page.number", is(0))); + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("traditional"))) + .andExpect(jsonPath("$._links.first.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=0"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=0"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.next.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=1"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.last.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=" + (totalDefinitions - 1)), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$.page.size", is(1))) + .andExpect(jsonPath("$.page.totalElements", is(totalDefinitions))) + .andExpect(jsonPath("$.page.totalPages", is(totalDefinitions))) + .andExpect(jsonPath("$.page.number", is(0))); getClient(tokenAdmin).perform(get("/api/config/submissiondefinitions") .param("size", "1") .param("page", "1")) - .andExpect(status().isOk()) - .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("test-hidden"))) - .andExpect(jsonPath("$._links.first.href", Matchers.allOf( - Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=0"), Matchers.containsString("size=1")))) - .andExpect(jsonPath("$._links.prev.href", Matchers.allOf( - Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=0"), Matchers.containsString("size=1")))) - .andExpect(jsonPath("$._links.next.href", Matchers.allOf( - Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=2"), Matchers.containsString("size=1")))) - .andExpect(jsonPath("$._links.self.href", Matchers.allOf( - Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=1"), Matchers.containsString("size=1")))) - .andExpect(jsonPath("$._links.last.href", Matchers.allOf( - Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page="), Matchers.containsString("size=1")))) - .andExpect(jsonPath("$.page.size", is(1))) - .andExpect(jsonPath("$.page.totalElements", is(7))) - .andExpect(jsonPath("$.page.totalPages", is(7))) - .andExpect(jsonPath("$.page.number", is(1))); + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("languagetestprocess"))) + .andExpect(jsonPath("$._links.first.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=0"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.prev.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=0"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.next.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=2"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=1"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.last.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=" + (totalDefinitions - 1)), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$.page.size", is(1))) + .andExpect(jsonPath("$.page.totalElements", is(totalDefinitions))) + .andExpect(jsonPath("$.page.totalPages", is(totalDefinitions))) + .andExpect(jsonPath("$.page.number", is(1))); getClient(tokenAdmin).perform(get("/api/config/submissiondefinitions") .param("size", "1") .param("page", "2")) - .andExpect(status().isOk()) - .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("accessConditionNotDiscoverable"))) - .andExpect(jsonPath("$._links.first.href", Matchers.allOf( - Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=0"), Matchers.containsString("size=1")))) - .andExpect(jsonPath("$._links.prev.href", Matchers.allOf( - Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=1"), Matchers.containsString("size=1")))) - .andExpect(jsonPath("$._links.next.href", Matchers.allOf( - Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=3"), Matchers.containsString("size=1")))) - .andExpect(jsonPath("$._links.self.href", Matchers.allOf( - Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=2"), Matchers.containsString("size=1")))) - .andExpect(jsonPath("$._links.last.href", Matchers.allOf( - Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=6"), Matchers.containsString("size=1")))) - .andExpect(jsonPath("$.page.size", is(1))) - .andExpect(jsonPath("$.page.totalElements", is(7))) - .andExpect(jsonPath("$.page.totalPages", is(7))) - .andExpect(jsonPath("$.page.number", is(2))); + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("extractiontestprocess"))) + .andExpect(jsonPath("$._links.first.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=0"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.prev.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=1"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.next.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=3"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=2"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.last.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=" + (totalDefinitions - 1)), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$.page.size", is(1))) + .andExpect(jsonPath("$.page.totalElements", is(totalDefinitions))) + .andExpect(jsonPath("$.page.totalPages", is(totalDefinitions))) + .andExpect(jsonPath("$.page.number", is(2))); getClient(tokenAdmin).perform(get("/api/config/submissiondefinitions") .param("size", "1") .param("page", "3")) - .andExpect(status().isOk()) - .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("languagetestprocess"))) - .andExpect(jsonPath("$._links.first.href", Matchers.allOf( - Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=0"), Matchers.containsString("size=1")))) - .andExpect(jsonPath("$._links.prev.href", Matchers.allOf( - Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=2"), Matchers.containsString("size=1")))) - .andExpect(jsonPath("$._links.next.href", Matchers.allOf( - Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=4"), Matchers.containsString("size=1")))) - .andExpect(jsonPath("$._links.self.href", Matchers.allOf( - Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=3"), Matchers.containsString("size=1")))) - .andExpect(jsonPath("$._links.last.href", Matchers.allOf( - Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=6"), Matchers.containsString("size=1")))) - .andExpect(jsonPath("$.page.size", is(1))) - .andExpect(jsonPath("$.page.totalElements", is(7))) - .andExpect(jsonPath("$.page.totalPages", is(7))) - .andExpect(jsonPath("$.page.number", is(3))); + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("qualdroptest"))) + .andExpect(jsonPath("$._links.first.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=0"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.prev.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=2"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.next.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=4"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=3"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.last.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=" + (totalDefinitions - 1)), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$.page.size", is(1))) + .andExpect(jsonPath("$.page.totalElements", is(totalDefinitions))) + .andExpect(jsonPath("$.page.totalPages", is(totalDefinitions))) + .andExpect(jsonPath("$.page.number", is(3))); getClient(tokenAdmin).perform(get("/api/config/submissiondefinitions") - .param("size", "1") - .param("page", "4")) + .param("size", "1") + .param("page", "4")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("qualdroptest"))) + .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("typebindtest"))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), Matchers.containsString("page=0"), Matchers.containsString("size=1")))) @@ -361,18 +366,18 @@ public void findAllPaginationTest() throws Exception { Matchers.containsString("page=4"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=6"), Matchers.containsString("size=1")))) + Matchers.containsString("page=" + (totalDefinitions - 1)), Matchers.containsString("size=1")))) .andExpect(jsonPath("$.page.size", is(1))) - .andExpect(jsonPath("$.page.totalElements", is(7))) - .andExpect(jsonPath("$.page.totalPages", is(7))) + .andExpect(jsonPath("$.page.totalElements", is(totalDefinitions))) + .andExpect(jsonPath("$.page.totalPages", is(totalDefinitions))) .andExpect(jsonPath("$.page.number", is(4))); getClient(tokenAdmin).perform(get("/api/config/submissiondefinitions") - .param("size", "1") - .param("page", "5")) + .param("size", "1") + .param("page", "5")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("extractiontestprocess"))) + .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("accessConditionNotDiscoverable"))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), Matchers.containsString("page=0"), Matchers.containsString("size=1")))) @@ -386,12 +391,114 @@ public void findAllPaginationTest() throws Exception { Matchers.containsString("/api/config/submissiondefinitions?"), Matchers.containsString("page=5"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=" + (totalDefinitions - 1)), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$.page.size", is(1))) + .andExpect(jsonPath("$.page.totalElements", is(totalDefinitions))) + .andExpect(jsonPath("$.page.totalPages", is(totalDefinitions))) + .andExpect(jsonPath("$.page.number", is(5))); + + getClient(tokenAdmin).perform(get("/api/config/submissiondefinitions") + .param("size", "1") + .param("page", "5")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("accessConditionNotDiscoverable"))) + .andExpect(jsonPath("$._links.first.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=0"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.prev.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=4"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.next.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), Matchers.containsString("page=6"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=5"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.last.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=" + (totalDefinitions - 1)), Matchers.containsString("size=1")))) .andExpect(jsonPath("$.page.size", is(1))) - .andExpect(jsonPath("$.page.totalElements", is(7))) - .andExpect(jsonPath("$.page.totalPages", is(7))) + .andExpect(jsonPath("$.page.totalElements", is(totalDefinitions))) + .andExpect(jsonPath("$.page.totalPages", is(totalDefinitions))) .andExpect(jsonPath("$.page.number", is(5))); + + getClient(tokenAdmin).perform(get("/api/config/submissiondefinitions") + .param("size", "1") + .param("page", "6")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("test-hidden"))) + .andExpect(jsonPath("$._links.first.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=0"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.prev.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=5"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.next.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=7"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=6"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.last.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=" + (totalDefinitions - 1)), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$.page.size", is(1))) + .andExpect(jsonPath("$.page.totalElements", is(totalDefinitions))) + .andExpect(jsonPath("$.page.totalPages", is(totalDefinitions))) + .andExpect(jsonPath("$.page.number", is(6))); + + getClient(tokenAdmin).perform(get("/api/config/submissiondefinitions") + .param("size", "1") + .param("page", "7")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("collectiontest"))) + .andExpect(jsonPath("$._links.first.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=0"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.prev.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=6"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.next.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=8"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=7"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.last.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=" + (totalDefinitions - 1)), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$.page.size", is(1))) + .andExpect(jsonPath("$.page.totalElements", is(totalDefinitions))) + .andExpect(jsonPath("$.page.totalPages", is(totalDefinitions))) + .andExpect(jsonPath("$.page.number", is(7))); + + getClient(tokenAdmin).perform(get("/api/config/submissiondefinitions") + .param("size", "1") + .param("page", "8")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("entitytypetest"))) + .andExpect(jsonPath("$._links.first.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=0"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.prev.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=7"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=8"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.last.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=" + (totalDefinitions - 1)), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$.page.size", is(1))) + .andExpect(jsonPath("$.page.totalElements", is(totalDefinitions))) + .andExpect(jsonPath("$.page.totalPages", is(totalDefinitions))) + .andExpect(jsonPath("$.page.number", is(8))); + } } From d5e2c71da40ae60c578557dda697b5d2824a5141 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 22 Apr 2024 10:42:21 -0500 Subject: [PATCH 133/939] Minor fixes to Entities import. MUST run "ignored" migrations. Also modifying submission configs no longer needed --- .../src/main/docker-compose/db.entities.yml | 21 +++---------------- 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/dspace/src/main/docker-compose/db.entities.yml b/dspace/src/main/docker-compose/db.entities.yml index 32c54a5d0bd..809cf52df0b 100644 --- a/dspace/src/main/docker-compose/db.entities.yml +++ b/dspace/src/main/docker-compose/db.entities.yml @@ -18,27 +18,12 @@ services: ### OVERRIDE default 'entrypoint' in 'docker-compose.yml #### # Ensure that the database is ready BEFORE starting tomcat # 1. While a TCP connection to dspacedb port 5432 is not available, continue to sleep - # 2. Then, run database migration to init database tables - # 3. (Custom for Entities) enable Entity-specific collection submission mappings in item-submission.xml - # This 'sed' command inserts the sample configurations specific to the Entities data set, see: - # https://github.com/DSpace/DSpace/blob/main/dspace/config/item-submission.xml#L36-L49 - # 4. Finally, start Tomcat + # 2. Then, run migration latest version of database tables (run with "ignored" in case this SQL data is outdated) + # 3. Finally, start Tomcat entrypoint: - /bin/bash - '-c' - | while (! /dev/null 2>&1; do sleep 1; done; - /dspace/bin/dspace database migrate - sed -i '/name-map collection-handle="default".*/a \\n \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - ' /dspace/config/item-submission.xml + /dspace/bin/dspace database migrate ignored catalina.sh run From 69f7f85defd40d1d97a48d0a4a27ccee6de91bfb Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 22 Apr 2024 10:43:06 -0500 Subject: [PATCH 134/939] Minor fixes to DB scripts. Use Postgres 15. Don't error out if pgcrypto already installed. --- .../src/main/docker/dspace-postgres-pgcrypto-curl/Dockerfile | 2 +- .../docker/dspace-postgres-pgcrypto-curl/install-pgcrypto.sh | 4 ++-- dspace/src/main/docker/dspace-postgres-pgcrypto/Dockerfile | 2 +- .../main/docker/dspace-postgres-pgcrypto/install-pgcrypto.sh | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/Dockerfile b/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/Dockerfile index b2131a74026..aabf87df3ed 100644 --- a/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/Dockerfile +++ b/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/Dockerfile @@ -10,7 +10,7 @@ # docker build --build-arg POSTGRES_VERSION=13 --build-arg POSTGRES_PASSWORD=mypass ./dspace/src/main/docker/dspace-postgres-pgcrypto-curl/ # This will be published as dspace/dspace-postgres-pgcrypto:$DSPACE_VERSION-loadsql -ARG POSTGRES_VERSION=13 +ARG POSTGRES_VERSION=15 ARG POSTGRES_PASSWORD=dspace FROM postgres:${POSTGRES_VERSION} diff --git a/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/install-pgcrypto.sh b/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/install-pgcrypto.sh index 3f8e95e1044..ff042ec1bba 100644 --- a/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/install-pgcrypto.sh +++ b/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/install-pgcrypto.sh @@ -40,9 +40,9 @@ fi # Then, setup pgcrypto on this database psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL -- Create a new schema in this database named "extensions" (or whatever you want to name it) - CREATE SCHEMA extensions; + CREATE SCHEMA IF NOT EXISTS extensions; -- Enable this extension in this new schema - CREATE EXTENSION pgcrypto SCHEMA extensions; + CREATE EXTENSION IF NOT EXISTS pgcrypto WITH SCHEMA extensions; -- Update your database's "search_path" to also search the new "extensions" schema. -- You are just appending it on the end of the existing comma-separated list. ALTER DATABASE dspace SET search_path TO "\$user",public,extensions; diff --git a/dspace/src/main/docker/dspace-postgres-pgcrypto/Dockerfile b/dspace/src/main/docker/dspace-postgres-pgcrypto/Dockerfile index 7dde1a6bfd1..2298cd4e76e 100644 --- a/dspace/src/main/docker/dspace-postgres-pgcrypto/Dockerfile +++ b/dspace/src/main/docker/dspace-postgres-pgcrypto/Dockerfile @@ -10,7 +10,7 @@ # docker build --build-arg POSTGRES_VERSION=13 --build-arg POSTGRES_PASSWORD=mypass ./dspace/src/main/docker/dspace-postgres-pgcrypto/ # This will be published as dspace/dspace-postgres-pgcrypto:$DSPACE_VERSION -ARG POSTGRES_VERSION=13 +ARG POSTGRES_VERSION=15 ARG POSTGRES_PASSWORD=dspace FROM postgres:${POSTGRES_VERSION} diff --git a/dspace/src/main/docker/dspace-postgres-pgcrypto/install-pgcrypto.sh b/dspace/src/main/docker/dspace-postgres-pgcrypto/install-pgcrypto.sh index 65405aa7bdb..67c4539b5a4 100644 --- a/dspace/src/main/docker/dspace-postgres-pgcrypto/install-pgcrypto.sh +++ b/dspace/src/main/docker/dspace-postgres-pgcrypto/install-pgcrypto.sh @@ -11,9 +11,9 @@ set -e psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL -- Create a new schema in this database named "extensions" (or whatever you want to name it) - CREATE SCHEMA extensions; + CREATE SCHEMA IF NOT EXISTS extensions; -- Enable this extension in this new schema - CREATE EXTENSION pgcrypto SCHEMA extensions; + CREATE EXTENSION IF NOT EXISTS pgcrypto WITH SCHEMA extensions; -- Update your database's "search_path" to also search the new "extensions" schema. -- You are just appending it on the end of the existing comma-separated list. ALTER DATABASE dspace SET search_path TO "\$user",public,extensions; From b1fb884aec4f407be205ceaf90416f6e8497d2da Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 22 Apr 2024 14:15:13 -0500 Subject: [PATCH 135/939] Remove unnecessary "exit" statements which stop running container (cherry picked from commit 11158ae525a68be445099072168c324197697b96) --- .../docker/dspace-postgres-pgcrypto-curl/install-pgcrypto.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/install-pgcrypto.sh b/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/install-pgcrypto.sh index ff042ec1bba..d8e0382010d 100644 --- a/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/install-pgcrypto.sh +++ b/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/install-pgcrypto.sh @@ -23,7 +23,6 @@ then rm /tmp/dspace-db-init.sql touch $CHECKFILE - exit fi # If $LOCALSQL environment variable set, then simply run it in PostgreSQL @@ -34,7 +33,6 @@ then psql -U $POSTGRES_USER < ${LOCALSQL} touch $CHECKFILE - exit fi # Then, setup pgcrypto on this database From 362c80136bfde04e3801796afd2d7e3d8485d392 Mon Sep 17 00:00:00 2001 From: Thomas Misilo Date: Sat, 9 Mar 2024 13:48:42 -0600 Subject: [PATCH 136/939] Change from openjdk to eclipse-temurin base images Since the opendjk image has been deprecated, it was suggested to change to eclipse-temurin Fixes #9277 (cherry picked from commit 86ca5aabf9ab71b3041562f0ac600f872ccd443f) --- Dockerfile | 2 +- Dockerfile.cli | 4 ++-- Dockerfile.dependencies | 2 +- Dockerfile.test | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3c536b31762..ee48dec5083 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,7 +28,7 @@ RUN mvn --no-transfer-progress package ${MAVEN_FLAGS} && \ mvn clean # Step 2 - Run Ant Deploy -FROM openjdk:${JDK_VERSION}-slim as ant_build +FROM eclipse-temurin:${JDK_VERSION} as ant_build ARG TARGET_DIR=dspace-installer # COPY the /install directory from 'build' container to /dspace-src in this container COPY --from=build /install /dspace-src diff --git a/Dockerfile.cli b/Dockerfile.cli index 62e83b79ef0..96a84bc5626 100644 --- a/Dockerfile.cli +++ b/Dockerfile.cli @@ -24,7 +24,7 @@ RUN mvn --no-transfer-progress package && \ mvn clean # Step 2 - Run Ant Deploy -FROM openjdk:${JDK_VERSION}-slim as ant_build +FROM eclipse-temurin:${JDK_VERSION} as ant_build ARG TARGET_DIR=dspace-installer # COPY the /install directory from 'build' container to /dspace-src in this container COPY --from=build /install /dspace-src @@ -45,7 +45,7 @@ RUN mkdir $ANT_HOME && \ RUN ant init_installation update_configs update_code # Step 3 - Run jdk -FROM openjdk:${JDK_VERSION} +FROM eclipse-temurin:${JDK_VERSION} # NOTE: DSPACE_INSTALL must align with the "dspace.dir" default configuration. ENV DSPACE_INSTALL=/dspace # Copy the /dspace directory from 'ant_build' container to /dspace in this container diff --git a/Dockerfile.dependencies b/Dockerfile.dependencies index 6f72ab05853..1400b356d41 100644 --- a/Dockerfile.dependencies +++ b/Dockerfile.dependencies @@ -7,7 +7,7 @@ ARG JDK_VERSION=11 # Step 1 - Run Maven Build -FROM maven:3-openjdk-${JDK_VERSION}-slim as build +FROM maven:3-eclipse-temurin-${JDK_VERSION} as build ARG TARGET_DIR=dspace-installer WORKDIR /app # Create the 'dspace' user account & home directory diff --git a/Dockerfile.test b/Dockerfile.test index 4e9b2b5b434..f6f8c1a290f 100644 --- a/Dockerfile.test +++ b/Dockerfile.test @@ -27,7 +27,7 @@ RUN mvn --no-transfer-progress package -Pdspace-rest && \ mvn clean # Step 2 - Run Ant Deploy -FROM openjdk:${JDK_VERSION}-slim as ant_build +FROM eclipse-temurin:${JDK_VERSION} as ant_build ARG TARGET_DIR=dspace-installer # COPY the /install directory from 'build' container to /dspace-src in this container COPY --from=build /install /dspace-src From 1cfa041a315d023a9ded6766571d4e8dc0280b2f Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Fri, 26 Apr 2024 10:07:10 +0200 Subject: [PATCH 137/939] Remove unused function findAllCollectionsByEntityType of CollectionService --- .../dspace/content/CollectionServiceImpl.java | 20 ------------------- .../content/service/CollectionService.java | 15 -------------- 2 files changed, 35 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java index 0d9507e3aa3..a0ac5b1c639 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -1054,26 +1054,6 @@ public int countCollectionsWithSubmit(String q, Context context, Community commu return (int) resp.getTotalSearchResults(); } - @Override - @SuppressWarnings("rawtypes") - public List findAllCollectionsByEntityType(Context context, String entityType) - throws SearchServiceException { - List collectionList = new ArrayList<>(); - - DiscoverQuery discoverQuery = new DiscoverQuery(); - discoverQuery.setDSpaceObjectFilter(IndexableCollection.TYPE); - discoverQuery.addFilterQueries("dspace.entity.type:" + entityType); - - DiscoverResult discoverResult = searchService.search(context, discoverQuery); - List solrIndexableObjects = discoverResult.getIndexableObjects(); - - for (IndexableObject solrCollection : solrIndexableObjects) { - Collection c = ((IndexableCollection) solrCollection).getIndexedObject(); - collectionList.add(c); - } - return collectionList; - } - /** * Returns total collection archived items * diff --git a/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java b/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java index 90db5c73140..b923549b199 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java @@ -456,21 +456,6 @@ public int countCollectionsWithSubmit(String q, Context context, Community commu public int countCollectionsWithSubmit(String q, Context context, Community community, String entityType) throws SQLException, SearchServiceException; - /** - * Returns a list of all collections for a specific entity type. - * NOTE: for better performance, this method retrieves its results from an index (cache) - * and does not query the database directly. - * This means that results may be stale or outdated until - * https://github.com/DSpace/DSpace/issues/2853 is resolved." - * - * @param context DSpace Context - * @param entityType limit the returned collection to those related to given entity type - * @return list of collections found - * @throws SearchServiceException if search error - */ - public List findAllCollectionsByEntityType(Context context, String entityType) - throws SearchServiceException; - /** * Returns total collection archived items * From 10aa3189198e3c6023625acb8b94037239d5cc51 Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Fri, 26 Apr 2024 14:48:08 +0200 Subject: [PATCH 138/939] Add comment to clarify in which use case the consumer submissionconfig is useful --- dspace/config/dspace.cfg | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 60a7506d876..0b7cd6ee44a 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -823,6 +823,11 @@ event.consumer.orcidqueue.class = org.dspace.orcid.consumer.OrcidQueueConsumer event.consumer.orcidqueue.filters = Item+Install|Modify|Modify_Metadata|Delete|Remove # item submission config reload consumer +# This consumer can be useful for reloading changes made in the item-submission.xml config file, +# without restarting Tomcat, primarily for adding new collection mappings. +# With this consumer, configuration reloading is triggered after a collection is updated. +# It is disabled by default. To enable it, add 'submissionconfig' to the list of +# activated consumers (event.dispatcher.default.consumers). event.consumer.submissionconfig.class = org.dspace.submit.consumer.SubmissionConfigConsumer event.consumer.submissionconfig.filters = Collection+Modify_Metadata From 9d12600d13c6f5783d2c9afc2902ff448aaac8e8 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Tue, 18 Apr 2023 15:47:14 +0200 Subject: [PATCH 139/939] [Ticket 2124] Slow response times --- .../authorization/impl/DeleteFeature.java | 6 +- .../converter/AInprogressItemConverter.java | 10 +- .../rest/model/AInprogressSubmissionRest.java | 29 +-- .../app/rest/model/WorkflowItemRest.java | 25 +- .../app/rest/model/WorkspaceItemRest.java | 15 ++ .../WorkflowItemCollectionLinkRepository.java | 60 +++++ .../WorkflowItemItemLinkRepository.java | 60 +++++ .../WorkflowItemSubmitterLinkRepository.java | 60 +++++ ...WorkspaceItemCollectionLinkRepository.java | 60 +++++ .../WorkspaceItemItemLinkRepository.java | 60 +++++ .../WorkspaceItemSubmitterLinkRepository.java | 60 +++++ .../jwt/ShortLivedJWTTokenHandler.java | 1 - .../WorkflowItemRestLinkRepositoryIT.java | 226 ++++++++++++++++++ .../WorkspaceItemRestLinkRepositoryIT.java | 222 +++++++++++++++++ 14 files changed, 854 insertions(+), 40 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemCollectionLinkRepository.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemItemLinkRepository.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemSubmitterLinkRepository.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemCollectionLinkRepository.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemItemLinkRepository.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemSubmitterLinkRepository.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestLinkRepositoryIT.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestLinkRepositoryIT.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/DeleteFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/DeleteFeature.java index 02ca816290d..0f5378125d8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/DeleteFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/DeleteFeature.java @@ -65,11 +65,13 @@ public class DeleteFeature implements AuthorizationFeature { @Override public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException { if (object instanceof BaseObjectRest) { + DSpaceObject dSpaceObject = (DSpaceObject) utils.getDSpaceAPIObjectFromRest(context, object); + if (object.getType().equals(WorkspaceItemRest.NAME)) { - object = ((WorkspaceItemRest)object).getItem(); + WorkspaceItem workspaceItem = (WorkspaceItem) utils.getDSpaceAPIObjectFromRest(context, object); + dSpaceObject = workspaceItem.getItem(); } - DSpaceObject dSpaceObject = (DSpaceObject) utils.getDSpaceAPIObjectFromRest(context, object); DSpaceObject parentObject = getParentObject(context, dSpaceObject); switch (object.getType()) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java index a5431d90004..f40022f19f7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java @@ -70,11 +70,11 @@ protected void fillFromModel(T obj, R witem, Projection projection) { submitter = obj.getSubmitter(); witem.setId(obj.getID()); - witem.setCollection(collection != null ? converter.toRest(collection, projection) : null); - witem.setItem(converter.toRest(item, projection)); - if (submitter != null) { - witem.setSubmitter(converter.toRest(submitter, projection)); - } +// witem.setCollection(collection != null ? converter.toRest(collection, projection) : null); +// witem.setItem(converter.toRest(item, projection)); +// if (submitter != null) { +// witem.setSubmitter(converter.toRest(submitter, projection)); +// } // 1. retrieve the submission definition // 2. iterate over the submission section to allow to plugin additional diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AInprogressSubmissionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AInprogressSubmissionRest.java index 903e2866c85..79cf007ba19 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AInprogressSubmissionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AInprogressSubmissionRest.java @@ -22,16 +22,11 @@ */ public abstract class AInprogressSubmissionRest extends BaseObjectRest { + private Date lastModified = new Date(); private Map sections; @JsonIgnore - private CollectionRest collection; - @JsonIgnore - private ItemRest item; - @JsonIgnore private SubmissionDefinitionRest submissionDefinition; - @JsonIgnore - private EPersonRest submitter; public Date getLastModified() { return lastModified; @@ -41,14 +36,6 @@ public void setLastModified(Date lastModified) { this.lastModified = lastModified; } - public ItemRest getItem() { - return item; - } - - public void setItem(ItemRest item) { - this.item = item; - } - public SubmissionDefinitionRest getSubmissionDefinition() { return submissionDefinition; } @@ -57,14 +44,6 @@ public void setSubmissionDefinition(SubmissionDefinitionRest submissionDefinitio this.submissionDefinition = submissionDefinition; } - public EPersonRest getSubmitter() { - return submitter; - } - - public void setSubmitter(EPersonRest submitter) { - this.submitter = submitter; - } - public Map getSections() { if (sections == null) { sections = new HashMap(); @@ -76,12 +55,6 @@ public void setSections(Map sections) { this.sections = sections; } - public CollectionRest getCollection() { - return collection; - } - public void setCollection(CollectionRest collection) { - this.collection = collection; - } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowItemRest.java index 8f580f44147..4a840a0bb77 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowItemRest.java @@ -15,10 +15,22 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ @LinksRest(links = { - @LinkRest( - name = WorkflowItemRest.STEP, - method = "getStep" - ) + @LinkRest( + name = WorkflowItemRest.STEP, + method = "getStep" + ), + @LinkRest( + name = WorkflowItemRest.SUBMITTER, + method = "getWorkflowItemSubmitter" + ), + @LinkRest( + name = WorkflowItemRest.ITEM, + method = "getWorkflowItemItem" + ), + @LinkRest( + name = WorkflowItemRest.COLLECTION, + method = "getWorkflowItemCollection" + ) }) public class WorkflowItemRest extends AInprogressSubmissionRest { public static final String NAME = "workflowitem"; @@ -26,6 +38,11 @@ public class WorkflowItemRest extends AInprogressSubmissionRest { public static final String STEP = "step"; + public static final String SUBMITTER = "submitter"; + public static final String ITEM = "item"; + public static final String COLLECTION = "collection"; + + @Override public String getCategory() { return CATEGORY; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkspaceItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkspaceItemRest.java index 57a5ab5c7f0..b40d1a4494f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkspaceItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkspaceItemRest.java @@ -18,6 +18,18 @@ @LinkRest( name = WorkspaceItemRest.SUPERVISION_ORDERS, method = "getSupervisionOrders" + ), + @LinkRest( + name = WorkspaceItemRest.SUBMITTER, + method = "getWorkspaceItemSubmitter" + ), + @LinkRest( + name = WorkspaceItemRest.ITEM, + method = "getWorkspaceItemItem" + ), + @LinkRest( + name = WorkspaceItemRest.COLLECTION, + method = "getWorkspaceItemCollection" ) }) public class WorkspaceItemRest extends AInprogressSubmissionRest { @@ -25,6 +37,9 @@ public class WorkspaceItemRest extends AInprogressSubmissionRest { public static final String CATEGORY = RestAddressableModel.SUBMISSION; public static final String SUPERVISION_ORDERS = "supervisionOrders"; + public static final String SUBMITTER = "submitter"; + public static final String ITEM = "item"; + public static final String COLLECTION = "collection"; @Override public String getCategory() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemCollectionLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemCollectionLinkRepository.java new file mode 100644 index 00000000000..fa92a69e77d --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemCollectionLinkRepository.java @@ -0,0 +1,60 @@ +/** + * 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.app.rest.repository; + +import java.sql.SQLException; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.model.CollectionRest; +import org.dspace.app.rest.model.WorkflowItemRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.core.Context; +import org.dspace.workflow.WorkflowItem; +import org.dspace.xmlworkflow.storedcomponents.service.XmlWorkflowItemService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Link repository for "collection" subresource of a workflow item. + */ +@Component(WorkflowItemRest.CATEGORY + "." + WorkflowItemRest.NAME + "." + WorkflowItemRest.COLLECTION) +public class WorkflowItemCollectionLinkRepository extends AbstractDSpaceRestRepository + implements LinkRestRepository { + + @Autowired + XmlWorkflowItemService wis; + + /** + * Retrieve the item for a workflow collection. + * + * @param request - The current request + * @param id - The workflow item ID for which to retrieve the collection + * @param optionalPageable - optional pageable object + * @param projection - the current projection + * @return the item for the workflow collection + */ + @PreAuthorize("hasPermission(#id, 'WORKFLOWITEM', 'READ')") + public CollectionRest getWorkflowItemCollection(@Nullable HttpServletRequest request, Integer id, + @Nullable Pageable optionalPageable, Projection projection) { + try { + Context context = obtainContext(); + WorkflowItem witem = wis.find(context, id); + if (witem == null) { + throw new ResourceNotFoundException("No such workflow item: " + id); + } + + return converter.toRest(witem.getCollection(), projection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemItemLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemItemLinkRepository.java new file mode 100644 index 00000000000..40624799bff --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemItemLinkRepository.java @@ -0,0 +1,60 @@ +/** + * 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.app.rest.repository; + +import java.sql.SQLException; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.model.WorkflowItemRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.core.Context; +import org.dspace.workflow.WorkflowItem; +import org.dspace.xmlworkflow.storedcomponents.service.XmlWorkflowItemService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Link repository for "item" subresource of a workflow item. + */ +@Component(WorkflowItemRest.CATEGORY + "." + WorkflowItemRest.NAME + "." + WorkflowItemRest.ITEM) +public class WorkflowItemItemLinkRepository extends AbstractDSpaceRestRepository + implements LinkRestRepository { + + @Autowired + XmlWorkflowItemService wis; + + /** + * Retrieve the item for a workflow item. + * + * @param request - The current request + * @param id - The workflow item ID for which to retrieve the item + * @param optionalPageable - optional pageable object + * @param projection - the current projection + * @return the item for the workflow item + */ + @PreAuthorize("hasPermission(#id, 'WORKFLOWITEM', 'READ')") + public ItemRest getWorkflowItemItem(@Nullable HttpServletRequest request, Integer id, + @Nullable Pageable optionalPageable, Projection projection) { + try { + Context context = obtainContext(); + WorkflowItem witem = wis.find(context, id); + if (witem == null) { + throw new ResourceNotFoundException("No such workflow item: " + id); + } + + return converter.toRest(witem.getItem(), projection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemSubmitterLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemSubmitterLinkRepository.java new file mode 100644 index 00000000000..b4b0ec0adff --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemSubmitterLinkRepository.java @@ -0,0 +1,60 @@ +/** + * 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.app.rest.repository; + +import java.sql.SQLException; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.model.EPersonRest; +import org.dspace.app.rest.model.WorkflowItemRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.core.Context; +import org.dspace.workflow.WorkflowItem; +import org.dspace.xmlworkflow.storedcomponents.service.XmlWorkflowItemService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Link repository for "submitter" subresource of a workflow item. + */ +@Component(WorkflowItemRest.CATEGORY + "." + WorkflowItemRest.NAME + "." + WorkflowItemRest.SUBMITTER) +public class WorkflowItemSubmitterLinkRepository extends AbstractDSpaceRestRepository + implements LinkRestRepository { + + @Autowired + XmlWorkflowItemService wis; + + /** + * Retrieve the submitter for a workflow item. + * + * @param request - The current request + * @param id - The workflow item ID for which to retrieve the submitter + * @param optionalPageable - optional pageable object + * @param projection - the current projection + * @return the submitter for the workflow item + */ + @PreAuthorize("hasPermission(#id, 'WORKFLOWITEM', 'READ')") + public EPersonRest getWorkflowItemSubmitter(@Nullable HttpServletRequest request, Integer id, + @Nullable Pageable optionalPageable, Projection projection) { + try { + Context context = obtainContext(); + WorkflowItem witem = wis.find(context, id); + if (witem == null) { + throw new ResourceNotFoundException("No such workflow item: " + id); + } + + return converter.toRest(witem.getSubmitter(), projection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemCollectionLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemCollectionLinkRepository.java new file mode 100644 index 00000000000..c8f9373b07c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemCollectionLinkRepository.java @@ -0,0 +1,60 @@ +/** + * 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.app.rest.repository; + +import java.sql.SQLException; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.model.CollectionRest; +import org.dspace.app.rest.model.WorkspaceItemRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.WorkspaceItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Link repository for "collection" subresource of a workspace item. + */ +@Component(WorkspaceItemRest.CATEGORY + "." + WorkspaceItemRest.NAME + "." + WorkspaceItemRest.COLLECTION) +public class WorkspaceItemCollectionLinkRepository extends AbstractDSpaceRestRepository + implements LinkRestRepository { + + @Autowired + WorkspaceItemService wis; + + /** + * Retrieve the collection for a workspace item. + * + * @param request - The current request + * @param id - The workspace item ID for which to retrieve the collection + * @param optionalPageable - optional pageable object + * @param projection - the current projection + * @return the collection for the workspace item + */ + @PreAuthorize("hasPermission(#id, 'WORKSPACEITEM', 'READ')") + public CollectionRest getWorkspaceItemCollection(@Nullable HttpServletRequest request, Integer id, + @Nullable Pageable optionalPageable, Projection projection) { + try { + Context context = obtainContext(); + WorkspaceItem witem = wis.find(context, id); + if (witem == null) { + throw new ResourceNotFoundException("No such workspace item: " + id); + } + + return converter.toRest(witem.getCollection(), projection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemItemLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemItemLinkRepository.java new file mode 100644 index 00000000000..48052fe5371 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemItemLinkRepository.java @@ -0,0 +1,60 @@ +/** + * 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.app.rest.repository; + +import java.sql.SQLException; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.model.WorkspaceItemRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.WorkspaceItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Link repository for "item" subresource of a workspace item. + */ +@Component(WorkspaceItemRest.CATEGORY + "." + WorkspaceItemRest.NAME + "." + WorkspaceItemRest.ITEM) +public class WorkspaceItemItemLinkRepository extends AbstractDSpaceRestRepository + implements LinkRestRepository { + + @Autowired + WorkspaceItemService wis; + + /** + * Retrieve the item for a workspace item. + * + * @param request - The current request + * @param id - The workspace item ID for which to retrieve the item + * @param optionalPageable - optional pageable object + * @param projection - the current projection + * @return the item for the workspace item + */ + @PreAuthorize("hasPermission(#id, 'WORKSPACEITEM', 'READ')") + public ItemRest getWorkspaceItemItem(@Nullable HttpServletRequest request, Integer id, + @Nullable Pageable optionalPageable, Projection projection) { + try { + Context context = obtainContext(); + WorkspaceItem witem = wis.find(context, id); + if (witem == null) { + throw new ResourceNotFoundException("No such workspace item: " + id); + } + + return converter.toRest(witem.getItem(), projection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemSubmitterLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemSubmitterLinkRepository.java new file mode 100644 index 00000000000..a98de6a0b3a --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemSubmitterLinkRepository.java @@ -0,0 +1,60 @@ +/** + * 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.app.rest.repository; + +import java.sql.SQLException; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.model.EPersonRest; +import org.dspace.app.rest.model.WorkspaceItemRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.WorkspaceItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Link repository for "submitter" subresource of a workspace item. + */ +@Component(WorkspaceItemRest.CATEGORY + "." + WorkspaceItemRest.NAME + "." + WorkspaceItemRest.SUBMITTER) +public class WorkspaceItemSubmitterLinkRepository extends AbstractDSpaceRestRepository + implements LinkRestRepository { + + @Autowired + WorkspaceItemService wis; + + /** + * Retrieve the submitter for a workspace item. + * + * @param request - The current request + * @param id - The workspace item ID for which to retrieve the submitter + * @param optionalPageable - optional pageable object + * @param projection - the current projection + * @return the submitter for the workspace item + */ + @PreAuthorize("hasPermission(#id, 'WORKSPACEITEM', 'READ')") + public EPersonRest getWorkspaceItemSubmitter(@Nullable HttpServletRequest request, Integer id, + @Nullable Pageable optionalPageable, Projection projection) { + try { + Context context = obtainContext(); + WorkspaceItem witem = wis.find(context, id); + if (witem == null) { + throw new ResourceNotFoundException("No such workspace item: " + id); + } + + return converter.toRest(witem.getSubmitter(), projection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java index fc4ab39407a..72c305b5d12 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java @@ -16,7 +16,6 @@ import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; import com.nimbusds.jwt.util.DateUtils; -import org.apache.commons.lang3.StringUtils; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.springframework.stereotype.Component; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestLinkRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestLinkRepositoryIT.java new file mode 100644 index 00000000000..7228a7ba0b9 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestLinkRepositoryIT.java @@ -0,0 +1,226 @@ +/** + * 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.app.rest; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.jayway.jsonpath.matchers.JsonPathMatchers; +import org.dspace.app.rest.matcher.CollectionMatcher; +import org.dspace.app.rest.matcher.EPersonMatcher; +import org.dspace.app.rest.matcher.ItemMatcher; +import org.dspace.app.rest.matcher.WorkflowItemMatcher; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.WorkflowItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.eperson.EPerson; +import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; +import org.hamcrest.Matchers; +import org.junit.Test; + +/** + * Test suite for the WorkflowItem Link repositories + */ +public class WorkflowItemRestLinkRepositoryIT extends AbstractControllerIntegrationTest { + + @Test + /** + * The workflowitem resource endpoint must have an embeddable submitter + * + * @throws Exception + */ + public void findOneEmbedSubmitterTest() throws Exception { + context.turnOffAuthorisationSystem(); + + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("submitter@dspace.org") + .withNameInMetadata("Sub", "Mitter") + .build(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .withWorkflowGroup("reviewer", admin) + .build(); + + XmlWorkflowItem witem = WorkflowItemBuilder.createWorkflowItem(context, col1) + .withSubmitter(submitter) + .withTitle("Workflow Item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + context.restoreAuthSystemState(); + + String authToken = getAuthToken(admin.getEmail(), password); + + getClient(authToken).perform(get("/api/workflow/workflowitems/" + witem.getID())).andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject(witem, + "Workflow Item 1", + "2017-10-17", + "ExtraEntry")))) + .andExpect(jsonPath("$", JsonPathMatchers.hasNoJsonPath("$._embedded.submitter"))); + + getClient(authToken).perform(get("/api/workflow/workflowitems/" + witem.getID()).param("embed", "submitter")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject(witem, + "Workflow Item 1", + "2017-10-17", + "ExtraEntry")))) + .andExpect(jsonPath("$._embedded.submitter", Matchers.is( + EPersonMatcher.matchEPersonEntry(submitter)))); + + getClient(authToken).perform(get("/api/workflow/workflowitems/" + witem.getID() + "/submitter")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", EPersonMatcher.matchEPersonEntry(submitter))); + + } + + @Test + /** + * The workflowitem resource endpoint must have an embeddable collection + * + * @throws Exception + */ + public void findOneEmbedCollectionTest() throws Exception { + context.turnOffAuthorisationSystem(); + + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("submitter@dspace.org") + .withNameInMetadata("Sub", "Mitter") + .build(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .withWorkflowGroup("reviewer", admin) + .build(); + + XmlWorkflowItem witem = WorkflowItemBuilder.createWorkflowItem(context, col1) + .withSubmitter(submitter) + .withTitle("Workflow Item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + context.restoreAuthSystemState(); + + String authToken = getAuthToken(admin.getEmail(), password); + + getClient(authToken).perform(get("/api/workflow/workflowitems/" + witem.getID())).andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject(witem, + "Workflow Item 1", + "2017-10-17", + "ExtraEntry")))) + .andExpect(jsonPath("$", JsonPathMatchers.hasNoJsonPath("$._embedded.collection"))); + + getClient(authToken).perform(get("/api/workflow/workflowitems/" + witem.getID()).param("embed", "collection")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject(witem, + "Workflow Item 1", + "2017-10-17", + "ExtraEntry")))) + .andExpect(jsonPath("$._embedded.collection", Matchers.is( + CollectionMatcher.matchCollection(col1)))); + + getClient(authToken).perform(get("/api/workflow/workflowitems/" + witem.getID() + "/collection")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", CollectionMatcher.matchCollection(col1))); + + } + + @Test + /** + * The workflowitem resource endpoint must have an embeddable item + * + * @throws Exception + */ + public void findOneEmbedItemTest() throws Exception { + context.turnOffAuthorisationSystem(); + + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("submitter@dspace.org") + .withNameInMetadata("Sub", "Mitter") + .build(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .withWorkflowGroup("reviewer", admin) + .build(); + + XmlWorkflowItem witem = WorkflowItemBuilder.createWorkflowItem(context, col1) + .withSubmitter(submitter) + .withTitle("Workflow Item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + context.restoreAuthSystemState(); + + String authToken = getAuthToken(admin.getEmail(), password); + + getClient(authToken).perform(get("/api/workflow/workflowitems/" + witem.getID())).andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject(witem, + "Workflow Item 1", + "2017-10-17", + "ExtraEntry")))) + .andExpect(jsonPath("$", JsonPathMatchers.hasNoJsonPath("$._embedded.item"))); + + getClient(authToken).perform(get("/api/workflow/workflowitems/" + witem.getID()).param("embed", "item")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject(witem, + "Workflow Item 1", + "2017-10-17", + "ExtraEntry")))) + .andExpect(jsonPath("$._embedded.item", Matchers.is( + ItemMatcher.matchItemProperties(witem.getItem())))); + + getClient(authToken).perform(get("/api/workflow/workflowitems/" + witem.getID() + "/item")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", ItemMatcher.matchItemProperties(witem.getItem()))); + + } + + +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestLinkRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestLinkRepositoryIT.java new file mode 100644 index 00000000000..709480e8cd5 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestLinkRepositoryIT.java @@ -0,0 +1,222 @@ +/** + * 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.app.rest; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.jayway.jsonpath.matchers.JsonPathMatchers; +import org.dspace.app.rest.matcher.CollectionMatcher; +import org.dspace.app.rest.matcher.EPersonMatcher; +import org.dspace.app.rest.matcher.ItemMatcher; +import org.dspace.app.rest.matcher.WorkspaceItemMatcher; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.WorkspaceItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.WorkspaceItem; +import org.dspace.eperson.EPerson; +import org.hamcrest.Matchers; +import org.junit.Test; + +/** + * Test suite for the WorkspaceItem Link repositories + */ +public class WorkspaceItemRestLinkRepositoryIT extends AbstractControllerIntegrationTest { + + + @Test + /** + * The workspaceitem resource endpoint must have an embeddable submitter + * + * @throws Exception + */ + public void findOneEmbedSubmitterTest() throws Exception { + context.turnOffAuthorisationSystem(); + + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("submitter@dspace.org") + .withNameInMetadata("Sub", "Mitter") + .build(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + + //2. a workspace item + WorkspaceItem witem = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withSubmitter(submitter) + .withTitle("Workspace Item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + context.restoreAuthSystemState(); + + String authToken = getAuthToken(admin.getEmail(), password); + + getClient(authToken).perform(get("/api/submission/workspaceitems/" + witem.getID())).andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + WorkspaceItemMatcher.matchItemWithTitleAndDateIssuedAndSubject(witem, + "Workspace Item 1", + "2017-10-17", + "ExtraEntry")))) + .andExpect(jsonPath("$", JsonPathMatchers.hasNoJsonPath("$._embedded.submitter"))); + + getClient(authToken).perform(get("/api/submission/workspaceitems/" + witem.getID()).param("embed", "submitter")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + WorkspaceItemMatcher.matchItemWithTitleAndDateIssuedAndSubject(witem, + "Workspace Item 1", + "2017-10-17", + "ExtraEntry")))) + .andExpect(jsonPath("$._embedded.submitter", Matchers.is( + EPersonMatcher.matchEPersonEntry(submitter)))); + + getClient(authToken).perform(get("/api/submission/workspaceitems/" + witem.getID() + "/submitter")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", EPersonMatcher.matchEPersonEntry(submitter))); + + } + + @Test + /** + * The workspaceitem resource endpoint must have an embeddable collection + * + * @throws Exception + */ + public void findOneEmbedCollectionTest() throws Exception { + context.turnOffAuthorisationSystem(); + + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("submitter@dspace.org") + .withNameInMetadata("Sub", "Mitter") + .build(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + + //2. a workspace item + WorkspaceItem witem = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withSubmitter(submitter) + .withTitle("Workspace Item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + context.restoreAuthSystemState(); + + String authToken = getAuthToken(admin.getEmail(), password); + + getClient(authToken).perform(get("/api/submission/workspaceitems/" + witem.getID())).andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + WorkspaceItemMatcher.matchItemWithTitleAndDateIssuedAndSubject(witem, + "Workspace Item 1", + "2017-10-17", + "ExtraEntry")))) + .andExpect(jsonPath("$", JsonPathMatchers.hasNoJsonPath("$._embedded.collection"))); + + getClient(authToken).perform( + get("/api/submission/workspaceitems/" + witem.getID()).param("embed", "collection")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + WorkspaceItemMatcher.matchItemWithTitleAndDateIssuedAndSubject(witem, + "Workspace Item 1", + "2017-10-17", + "ExtraEntry")))) + .andExpect(jsonPath("$._embedded.collection", Matchers.is( + CollectionMatcher.matchCollection(col1)))); + + getClient(authToken).perform(get("/api/submission/workspaceitems/" + witem.getID() + "/collection")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", CollectionMatcher.matchCollection(col1))); + + } + + @Test + /** + * The workspaceitem resource endpoint must have an embeddable item + * + * @throws Exception + */ + public void findOneEmbedItemTest() throws Exception { + context.turnOffAuthorisationSystem(); + + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("submitter@dspace.org") + .withNameInMetadata("Sub", "Mitter") + .build(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + + //2. a workspace item + WorkspaceItem witem = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withSubmitter(submitter) + .withTitle("Workspace Item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + context.restoreAuthSystemState(); + + String authToken = getAuthToken(admin.getEmail(), password); + + getClient(authToken).perform(get("/api/submission/workspaceitems/" + witem.getID())).andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + WorkspaceItemMatcher.matchItemWithTitleAndDateIssuedAndSubject(witem, + "Workspace Item 1", + "2017-10-17", + "ExtraEntry")))) + .andExpect(jsonPath("$", JsonPathMatchers.hasNoJsonPath("$._embedded.item"))); + + getClient(authToken).perform(get("/api/submission/workspaceitems/" + witem.getID()).param("embed", "item")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + WorkspaceItemMatcher.matchItemWithTitleAndDateIssuedAndSubject(witem, + "Workspace Item 1", + "2017-10-17", + "ExtraEntry")))) + .andExpect(jsonPath("$._embedded.item", Matchers.is( + ItemMatcher.matchItemProperties(witem.getItem())))); + + getClient(authToken).perform(get("/api/submission/workspaceitems/" + witem.getID() + "/item")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", ItemMatcher.matchItemProperties(witem.getItem()))); + + } + + +} From d139d06c58e95b6585c46987b716c2ecdfbf8d3b Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 29 Apr 2024 12:58:20 +0200 Subject: [PATCH 140/939] 105866: test fixes --- .../jwt/ShortLivedJWTTokenHandler.java | 1 + .../app/rest/DiscoveryRestControllerIT.java | 18 ------- .../org/dspace/app/rest/LoginAsEPersonIT.java | 4 +- .../rest/WorkspaceItemRestRepositoryIT.java | 53 ++++++++++++++----- 4 files changed, 43 insertions(+), 33 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java index 72c305b5d12..fc4ab39407a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java @@ -16,6 +16,7 @@ import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; import com.nimbusds.jwt.util.DateUtils; +import org.apache.commons.lang3.StringUtils; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.springframework.stereotype.Component; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java index 80d8ab2df42..cd84567d649 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java @@ -5822,10 +5822,6 @@ public void discoverSearchPoolTaskObjectsTest() throws Exception { .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.contains( SearchResultMatcher.match("workflow", "pooltask", "pooltasks") ))) - .andExpect(jsonPath("$._embedded.searchResult._embedded.objects",Matchers.contains( - allOf(hasJsonPath("$._embedded.indexableObject._embedded.workflowitem._embedded.item", - is(SearchResultMatcher.matchEmbeddedObjectOnItemName("item", "Mathematical Theory")))) - ))) .andExpect(jsonPath("$._embedded.searchResult.page.totalElements", is(1))); getClient(adminToken).perform(get("/api/discover/search/objects") @@ -5839,12 +5835,6 @@ public void discoverSearchPoolTaskObjectsTest() throws Exception { SearchResultMatcher.match("workflow", "pooltask", "pooltasks"), SearchResultMatcher.match("workflow", "pooltask", "pooltasks") ))) - .andExpect(jsonPath("$._embedded.searchResult._embedded.objects",Matchers.containsInAnyOrder( - allOf(hasJsonPath("$._embedded.indexableObject._embedded.workflowitem._embedded.item", - is(SearchResultMatcher.matchEmbeddedObjectOnItemName("item", "Metaphysics")))), - allOf(hasJsonPath("$._embedded.indexableObject._embedded.workflowitem._embedded.item", - is(SearchResultMatcher.matchEmbeddedObjectOnItemName("item", "Test Metaphysics")))) - ))) .andExpect(jsonPath("$._embedded.searchResult.page.totalElements", is(2))); } @@ -5909,14 +5899,6 @@ public void discoverSearchPoolTaskObjectsEmptyQueryTest() throws Exception { SearchResultMatcher.match("workflow", "pooltask", "pooltasks"), SearchResultMatcher.match("workflow", "pooltask", "pooltasks") ))) - .andExpect(jsonPath("$._embedded.searchResult._embedded.objects",Matchers.containsInAnyOrder( - allOf(hasJsonPath("$._embedded.indexableObject._embedded.workflowitem._embedded.item", - is(SearchResultMatcher.matchEmbeddedObjectOnItemName("item", "Mathematical Theory")))), - allOf(hasJsonPath("$._embedded.indexableObject._embedded.workflowitem._embedded.item", - is(SearchResultMatcher.matchEmbeddedObjectOnItemName("item", "Metaphysics")))), - allOf(hasJsonPath("$._embedded.indexableObject._embedded.workflowitem._embedded.item", - is(SearchResultMatcher.matchEmbeddedObjectOnItemName("item", "Test Metaphysics")))) - ))) .andExpect(jsonPath("$._embedded.searchResult.page.totalElements", is(3))); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/LoginAsEPersonIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/LoginAsEPersonIT.java index bf3b54c9224..60d0e1a2a56 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/LoginAsEPersonIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/LoginAsEPersonIT.java @@ -191,6 +191,7 @@ public void createEmptyWorkspaceItemLoginOnBehalfOfCheckSubmitterTest() throws E // create a workspaceitem explicitly in the col1 MvcResult mvcResult = getClient(authToken).perform(post("/api/submission/workspaceitems") .param("owningCollection", col1.getID().toString()) + .param("embed", "collection") .header("X-On-Behalf-Of", eperson.getID()) .contentType(org.springframework .http.MediaType.APPLICATION_JSON)) @@ -204,7 +205,8 @@ public void createEmptyWorkspaceItemLoginOnBehalfOfCheckSubmitterTest() throws E Map map = mapper.readValue(content, Map.class); String workspaceItemId = String.valueOf(map.get("id")); - getClient(authToken).perform(get("/api/submission/workspaceitems/" + workspaceItemId)) + getClient(authToken).perform(get("/api/submission/workspaceitems/" + workspaceItemId) + .param("embed", "submitter")) .andExpect(jsonPath("$._embedded.submitter", EPersonMatcher.matchProperties(eperson))); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java index 8b2f3f093a3..82108c4bbeb 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java @@ -878,6 +878,7 @@ public void createEmptyWorkspateItemTest() throws Exception { // create a workspaceitem explicitly in the col1 getClient(authToken).perform(post("/api/submission/workspaceitems") .param("owningCollection", col1.getID().toString()) + .param("embed", "collection") .contentType(org.springframework.http.MediaType.APPLICATION_JSON)) .andExpect(status().isCreated()) .andExpect(jsonPath("$._embedded.collection.id", is(col1.getID().toString()))) @@ -886,6 +887,7 @@ public void createEmptyWorkspateItemTest() throws Exception { // create a workspaceitem explicitly in the col2 getClient(authToken).perform(post("/api/submission/workspaceitems") .param("owningCollection", col2.getID().toString()) + .param("embed", "collection") .contentType(org.springframework.http.MediaType.APPLICATION_JSON)) .andExpect(status().isCreated()) .andExpect(jsonPath("$._embedded.collection.id", is(col2.getID().toString()))) @@ -894,10 +896,10 @@ public void createEmptyWorkspateItemTest() throws Exception { // create a workspaceitem without an explicit collection, this will go in the first valid collection for the // user: the col1 getClient(authToken).perform(post("/api/submission/workspaceitems") + .param("embed", "collection") .contentType(org.springframework.http.MediaType.APPLICATION_JSON)) .andExpect(status().isCreated()) .andExpect(jsonPath("$._embedded.collection.id", is(col1.getID().toString()))) - .andExpect(jsonPath("$", WorkspaceItemMatcher.matchFullEmbeds())) .andDo(result -> idRef3.set(read(result.getResponse().getContentAsString(), "$.id"))); @@ -945,7 +947,8 @@ public void createSingleWorkspaceItemFromBibtexFileWithOneEntryTest() throws Exc try { // create a workspaceitem from a single bibliographic entry file explicitly in the default collection (col1) getClient(authToken).perform(multipart("/api/submission/workspaceitems") - .file(bibtexFile)) + .file(bibtexFile) + .param("embed", "collection")) // create should return 200, 201 (created) is better for single resource .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone['dc.title'][0].value", @@ -974,6 +977,7 @@ public void createSingleWorkspaceItemFromBibtexFileWithOneEntryTest() throws Exc try { getClient(authToken).perform(multipart("/api/submission/workspaceitems") .file(bibtexFile) + .param("embed", "collection") .param("owningCollection", col2.getID().toString())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone['dc.title'][0].value", @@ -1037,7 +1041,8 @@ public void createSingleWorkspaceItemFromBibtexArticleFileWithOneEntryTest() thr try { // create a workspaceitem from a single bibliographic entry file explicitly in the default collection (col1) getClient(authToken).perform(multipart("/api/submission/workspaceitems") - .file(bibtexFile)) + .file(bibtexFile) + .param("embed", "collection")) // create should return 200, 201 (created) is better for single resource .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.workspaceitems[0]" + @@ -1071,6 +1076,7 @@ public void createSingleWorkspaceItemFromBibtexArticleFileWithOneEntryTest() thr try { getClient(authToken).perform(multipart("/api/submission/workspaceitems") .file(bibtexFile) + .param("embed", "collection") .param("owningCollection", col2.getID().toString())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.workspaceitems[0]" + @@ -1134,7 +1140,8 @@ public void createSingleWorkspaceItemFromBibtexFileWithDiacriticsTest() throws E try { // create a workspaceitem from a single bibliographic entry file explicitly in the default collection (col1) getClient(authToken).perform(multipart("/api/submission/workspaceitems") - .file(bibtexFile)) + .file(bibtexFile) + .param("embed", "collection")) // create should return 200, 201 (created) is better for single resource .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.workspaceitems[0].sections." + @@ -1165,6 +1172,7 @@ public void createSingleWorkspaceItemFromBibtexFileWithDiacriticsTest() throws E try { getClient(authToken).perform(multipart("/api/submission/workspaceitems") .file(bibtexFile) + .param("embed", "collection") .param("owningCollection", col2.getID().toString())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.workspaceitems[0].sections." + @@ -1231,7 +1239,8 @@ public void createSingleWorkspaceItemFromBibtexFileWithMultipleAuthorsTest() thr try { // create a workspaceitem from a single bibliographic entry file explicitly in the default collection (col1) getClient(authToken).perform(multipart("/api/submission/workspaceitems") - .file(bibtexFile)) + .file(bibtexFile) + .param("embed", "collection")) // create should return 200, 201 (created) is better for single resource .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.workspaceitems[0]" + @@ -1271,6 +1280,7 @@ public void createSingleWorkspaceItemFromBibtexFileWithMultipleAuthorsTest() thr try { getClient(authToken).perform(multipart("/api/submission/workspaceitems") .file(bibtexFile) + .param("embed", "collection") .param("owningCollection", col2.getID().toString())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.workspaceitems[0]" + @@ -1336,7 +1346,8 @@ public void createSingleWorkspaceItemFromCSVWithOneEntryTest() throws Exception AtomicReference> idRef = new AtomicReference<>(); try { getClient(authToken).perform(multipart("/api/submission/workspaceitems") - .file(csvFile)) + .file(csvFile) + .param("embed", "collection")) // create should return 200, 201 (created) is better for single resource .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone['dc.title'][0].value", @@ -1376,6 +1387,7 @@ public void createSingleWorkspaceItemFromCSVWithOneEntryTest() throws Exception try { getClient(authToken).perform(multipart("/api/submission/workspaceitems") .file(csvFile) + .param("embed", "collection") .param("owningCollection", col2.getID().toString())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone" @@ -1453,7 +1465,8 @@ public void createSingleWorkspaceItemFromCSVWithOneEntryAndMissingDataTest() thr try { getClient(authToken).perform(multipart("/api/submission/workspaceitems") - .file(csvFile)) + .file(csvFile) + .param("embed", "collection")) // create should return 200, 201 (created) is better for single resource .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone['dc.title'][0].value", @@ -1531,7 +1544,8 @@ public void createSingleWorkspaceItemFromTSVWithOneEntryTest() throws Exception // create workspaceitems in the default collection (col1) try { getClient(authToken).perform(multipart("/api/submission/workspaceitems") - .file(tsvFile)) + .file(tsvFile) + .param("embed", "collection")) // create should return 200, 201 (created) is better for single resource .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone['dc.title'][0].value", @@ -1607,7 +1621,8 @@ public void createSingleWorkspaceItemFromRISWithOneEntryTest() throws Exception // create workspaceitems in the default collection (col1) try { getClient(authToken).perform(multipart("/api/submission/workspaceitems") - .file(tsvFile)) + .file(tsvFile) + .param("embed", "collection")) // create should return 200, 201 (created) is better for single resource .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone['dc.title'][0].value", @@ -1684,7 +1699,8 @@ public void createSingleWorkspaceItemFromEndnoteWithOneEntryTest() throws Except // create workspaceitems in the default collection (col1) try { getClient(authToken).perform(multipart("/api/submission/workspaceitems") - .file(endnoteFile)) + .file(endnoteFile) + .param("embed", "collection")) // create should return 200, 201 (created) is better for single resource .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone['dc.title'][0].value", @@ -1763,7 +1779,8 @@ public void createSingleWorkspaceItemFromTSVWithOneEntryAndMissingDataTest() thr // create workspaceitems in the default collection (col1) try { getClient(authToken).perform(multipart("/api/submission/workspaceitems") - .file(csvFile)) + .file(csvFile) + .param("embed", "collection")) // create should return 200, 201 (created) is better for single resource .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone['dc.title'][0].value", @@ -1844,7 +1861,9 @@ public void createSingleWorkspaceItemFromMultipleFilesWithOneEntryTest() throws // create a workspaceitem from a single bibliographic entry file explicitly in the default collection (col1) try { getClient(authToken).perform(multipart("/api/submission/workspaceitems") - .file(bibtexFile).file(pubmedFile)) + .file(bibtexFile) + .file(pubmedFile) + .param("embed", "collection")) // create should return 200, 201 (created) is better for single resource .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone['dc.title'][0].value", @@ -1879,6 +1898,7 @@ public void createSingleWorkspaceItemFromMultipleFilesWithOneEntryTest() throws try { getClient(authToken).perform(multipart("/api/submission/workspaceitems") .file(bibtexFile).file(pubmedFile) + .param("embed", "collection") .param("owningCollection", col2.getID().toString())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone['dc.title'][0].value", @@ -4323,6 +4343,7 @@ public void createWorkspaceItemFromExternalSources() throws Exception { String token = getAuthToken(admin.getEmail(), password); MvcResult mvcResult = getClient(token).perform(post("/api/submission/workspaceitems?owningCollection=" + col1.getID().toString()) + .param("embed", "item") .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) .content("https://localhost:8080/server/api/integration/" + "externalsources/mock/entryValues/one")) @@ -4333,7 +4354,8 @@ public void createWorkspaceItemFromExternalSources() throws Exception { workspaceItemId = (Integer) map.get("id"); String itemUuidString = String.valueOf(((Map) ((Map) map.get("_embedded")).get("item")).get("uuid")); - getClient(token).perform(get("/api/submission/workspaceitems/" + workspaceItemId)) + getClient(token).perform(get("/api/submission/workspaceitems/" + workspaceItemId) + .param("embed", "item")) .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.allOf( hasJsonPath("$.id", is(workspaceItemId)), @@ -4528,6 +4550,7 @@ public void createWorkspaceItemFromExternalSourcesNonAdminWithPermission() throw String token = getAuthToken(eperson.getEmail(), password); getClient(token).perform(post("/api/submission/workspaceitems") + .param("embed", "collection") .param("owningCollection", col1.getID().toString()) .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) .content("https://localhost:8080/server/api/integration/externalsources/" + @@ -4537,7 +4560,9 @@ public void createWorkspaceItemFromExternalSourcesNonAdminWithPermission() throw .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); workspaceItemId = idRef.get(); - getClient(token).perform(get("/api/submission/workspaceitems/" + workspaceItemId)) + getClient(token).perform(get("/api/submission/workspaceitems/" + workspaceItemId) + .param("embed", "collection") + .param("embed", "item")) .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.allOf( hasJsonPath("$.id", is(workspaceItemId)), From 75c688daef432348e916aca0335dbf5b368d8586 Mon Sep 17 00:00:00 2001 From: DSpace Bot <68393067+dspace-bot@users.noreply.github.com> Date: Mon, 29 Apr 2024 14:31:20 -0500 Subject: [PATCH 141/939] [Port dspace-7_x] Drop not null for EPerson in Process table (#9510) * Set EPerson nullable in ProcessTable (cherry picked from commit 9ed997d2ddcce046b6affc4f2f8edbe62f03cc51) * chore: Remove nullable = false in EPerson Entity (cherry picked from commit f1a38c2485715613115734f887567fdfcf412d2c) * chore: Avoid NPE when getEPerson is called (cherry picked from commit 2f9ad722986ae2997856e88ce125cbd1e8500037) * test: Add test for insert nullable EPerson in Process' table (cherry picked from commit 8fe264ee6930f278def5f54df964ea8c8f4a5b60) * Revert "Set EPerson nullable in ProcessTable" This reverts commit aea7b9385b6c6972b0ac27e061d082bb92eaf4a6. (cherry picked from commit 7de931c7be8ab885a20030115ad730fc40e73832) * feat: Drop NOT NULL for user_id in Process table (cherry picked from commit 3b73786c554e8fe768cedf04baa86ac6e447fd6e) * chore: add endline (cherry picked from commit 27ed14c38ca57cb6fcf5185c0517c3aef27fd589) * fix: add headers file (cherry picked from commit a3ea7cbaaa89fe34094f110071aef4acfa811d35) * Rename file sql (cherry picked from commit eb22285924752aec13ea6aa16fbba7bff5c54daf) * Rename sql script to 7.6.x (cherry picked from commit d2ef87eab6467524e73bd8c5edb5f97ed314a327) * fix: Set fetch lazy in EPerson Process (cherry picked from commit d6340403ffade379a4c2745b52c37b28940b063f) * fix: set the user attached to a process to null if none is found in the DB (cherry picked from commit afb734df5fa20f9e814397efa6ab7aef93b1a3b7) --------- Co-authored-by: Roy Bruschini Co-authored-by: Jens Vannerum --- .../src/main/java/org/dspace/scripts/Process.java | 4 ++-- .../org/dspace/scripts/ProcessServiceImpl.java | 15 +++++++++++---- ...6_2024.03.07__set_eperson_process_nullable.sql | 9 +++++++++ ...6_2024.03.07__set_eperson_process_nullable.sql | 9 +++++++++ .../test/java/org/dspace/process/ProcessIT.java | 11 +++++++++++ .../app/rest/converter/ProcessConverter.java | 7 ++++++- 6 files changed, 48 insertions(+), 7 deletions(-) create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2024.03.07__set_eperson_process_nullable.sql create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2024.03.07__set_eperson_process_nullable.sql diff --git a/dspace-api/src/main/java/org/dspace/scripts/Process.java b/dspace-api/src/main/java/org/dspace/scripts/Process.java index eab3ba460c0..9834c78c155 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/Process.java +++ b/dspace-api/src/main/java/org/dspace/scripts/Process.java @@ -51,8 +51,8 @@ public class Process implements ReloadableEntity { @Column(name = "process_id", unique = true, nullable = false) private Integer processId; - @ManyToOne - @JoinColumn(name = "user_id", nullable = false) + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") private EPerson ePerson; @Column(name = "start_time") diff --git a/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java b/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java index 2e14aeaa36c..f242ec7acda 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java @@ -92,10 +92,17 @@ public Process create(Context context, EPerson ePerson, String scriptName, }); Process createdProcess = processDAO.create(context, process); - log.info(LogHelper.getHeader(context, "process_create", - "Process has been created for eperson with email " + ePerson.getEmail() - + " with ID " + createdProcess.getID() + " and scriptName " + - scriptName + " and parameters " + parameters)); + + if (ePerson != null) { + log.info(LogHelper.getHeader(context, "process_create", + "Process has been created for eperson with email " + ePerson.getEmail() + + " with ID " + createdProcess.getID() + " and scriptName " + + scriptName + " and parameters " + parameters)); + } else { + log.info(LogHelper.getHeader(context, "process_create", + "Process has been created for command-line user with ID " + createdProcess.getID() + + " and scriptName " + scriptName + " and parameters " + parameters)); + } return createdProcess; } diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2024.03.07__set_eperson_process_nullable.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2024.03.07__set_eperson_process_nullable.sql new file mode 100644 index 00000000000..39dc1115be4 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2024.03.07__set_eperson_process_nullable.sql @@ -0,0 +1,9 @@ +-- +-- 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 ALTER COLUMN user_id DROP NOT NULL; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2024.03.07__set_eperson_process_nullable.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2024.03.07__set_eperson_process_nullable.sql new file mode 100644 index 00000000000..39dc1115be4 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2024.03.07__set_eperson_process_nullable.sql @@ -0,0 +1,9 @@ +-- +-- 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 ALTER COLUMN user_id DROP NOT NULL; diff --git a/dspace-api/src/test/java/org/dspace/process/ProcessIT.java b/dspace-api/src/test/java/org/dspace/process/ProcessIT.java index d6640652121..6cf840ea6ab 100644 --- a/dspace-api/src/test/java/org/dspace/process/ProcessIT.java +++ b/dspace-api/src/test/java/org/dspace/process/ProcessIT.java @@ -9,6 +9,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.util.HashSet; import java.util.LinkedList; @@ -87,4 +88,14 @@ public void removeOneGroupTest() throws Exception { assertFalse(isPresent); } + + @Test + public void addProcessWithNullEPersonTest() throws Exception { + try { + ProcessBuilder.createProcess(context, null, "mock-script", new LinkedList<>(), + new HashSet<>()).build(); + } catch (NullPointerException e) { + fail("Should not have thrown NullPointerException"); + } + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ProcessConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ProcessConverter.java index de03d606301..38eaf420479 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ProcessConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ProcessConverter.java @@ -14,6 +14,7 @@ import org.dspace.app.rest.projection.Projection; import org.dspace.scripts.Process; import org.dspace.scripts.service.ProcessService; +import org.hibernate.ObjectNotFoundException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; @@ -39,7 +40,11 @@ public ProcessRest convert(Process process, Projection projection) { processRest.setId(process.getID()); processRest.setScriptName(process.getName()); processRest.setProcessId(process.getID()); - processRest.setUserId(process.getEPerson().getID()); + try { + processRest.setUserId(process.getEPerson().getID()); + } catch (ObjectNotFoundException e ) { + processRest.setUserId(null); + } processRest.setProcessStatus(process.getProcessStatus()); processRest.setStartTime(process.getStartTime()); processRest.setEndTime(process.getFinishedTime()); From 2f99646417117fc54eb0b2ed301af88ea306c71f Mon Sep 17 00:00:00 2001 From: haoueclf Date: Mon, 11 Dec 2023 13:42:50 +0100 Subject: [PATCH 142/939] [DS-3439] Copy collection template item specified metadata during Sword v2 METS deposit ingestion. (cherry picked from commit 7ead4ae7f005f79f8c6a9fe704d60511259bdaee) --- .../org/dspace/content/ItemServiceImpl.java | 50 +++++++++++++++++++ .../content/WorkspaceItemServiceImpl.java | 48 +----------------- .../packager/AbstractMETSIngester.java | 3 ++ .../dspace/content/service/ItemService.java | 12 +++++ 4 files changed, 66 insertions(+), 47 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java index 26a61877fe7..285b3b6940f 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.UUID; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -276,6 +277,55 @@ public Item createTemplateItem(Context context, Collection collection) throws SQ } } + @Override + public void populateWithTemplateItemMetadata(Context context, Collection collection, boolean template, Item item) + throws SQLException { + + Item templateItem = collection.getTemplateItem(); + + Optional colEntityType = getDSpaceEntityType(collection); + Optional templateItemEntityType = getDSpaceEntityType(templateItem); + + if (template && colEntityType.isPresent() && templateItemEntityType.isPresent() && + !StringUtils.equals(colEntityType.get().getValue(), templateItemEntityType.get().getValue())) { + throw new IllegalStateException("The template item has entity type : (" + + templateItemEntityType.get().getValue() + ") different than collection entity type : " + + colEntityType.get().getValue()); + } + + if (template && colEntityType.isPresent() && templateItemEntityType.isEmpty()) { + MetadataValue original = colEntityType.get(); + MetadataField metadataField = original.getMetadataField(); + MetadataSchema metadataSchema = metadataField.getMetadataSchema(); + // NOTE: dspace.entity.type = does not make sense + // the collection entity type is by default blank when a collection is first created + if (StringUtils.isNotBlank(original.getValue())) { + addMetadata(context, item, metadataSchema.getName(), metadataField.getElement(), + metadataField.getQualifier(), original.getLanguage(), original.getValue()); + } + } + + if (template && (templateItem != null)) { + List md = getMetadata(templateItem, Item.ANY, Item.ANY, Item.ANY, Item.ANY); + + for (MetadataValue aMd : md) { + MetadataField metadataField = aMd.getMetadataField(); + MetadataSchema metadataSchema = metadataField.getMetadataSchema(); + addMetadata(context, item, metadataSchema.getName(), metadataField.getElement(), + metadataField.getQualifier(), aMd.getLanguage(), aMd.getValue()); + } + } + } + + private Optional getDSpaceEntityType(DSpaceObject dSpaceObject) { + return Objects.nonNull(dSpaceObject) ? dSpaceObject.getMetadata() + .stream() + .filter(x -> x.getMetadataField().toString('.') + .equalsIgnoreCase("dspace.entity.type")) + .findFirst() + : Optional.empty(); + } + @Override public Iterator findAll(Context context) throws SQLException { return itemDAO.findAll(context, true); diff --git a/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java index b78f6b9f7de..1da9e6e44a6 100644 --- a/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java @@ -12,11 +12,8 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Objects; -import java.util.Optional; import java.util.UUID; -import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.app.util.DCInputsReaderException; import org.dspace.app.util.Util; @@ -141,41 +138,7 @@ public WorkspaceItem create(Context context, Collection collection, UUID uuid, b .addPolicy(context, item, Constants.DELETE, item.getSubmitter(), ResourcePolicy.TYPE_SUBMISSION); // Copy template if appropriate - Item templateItem = collection.getTemplateItem(); - - Optional colEntityType = getDSpaceEntityType(collection); - Optional templateItemEntityType = getDSpaceEntityType(templateItem); - - if (template && colEntityType.isPresent() && templateItemEntityType.isPresent() && - !StringUtils.equals(colEntityType.get().getValue(), templateItemEntityType.get().getValue())) { - throw new IllegalStateException("The template item has entity type : (" + - templateItemEntityType.get().getValue() + ") different than collection entity type : " + - colEntityType.get().getValue()); - } - - if (template && colEntityType.isPresent() && templateItemEntityType.isEmpty()) { - MetadataValue original = colEntityType.get(); - MetadataField metadataField = original.getMetadataField(); - MetadataSchema metadataSchema = metadataField.getMetadataSchema(); - // NOTE: dspace.entity.type = does not make sense - // the collection entity type is by default blank when a collection is first created - if (StringUtils.isNotBlank(original.getValue())) { - itemService.addMetadata(context, item, metadataSchema.getName(), metadataField.getElement(), - metadataField.getQualifier(), original.getLanguage(), original.getValue()); - } - } - - if (template && (templateItem != null)) { - List md = itemService.getMetadata(templateItem, Item.ANY, Item.ANY, Item.ANY, Item.ANY); - - for (MetadataValue aMd : md) { - MetadataField metadataField = aMd.getMetadataField(); - MetadataSchema metadataSchema = metadataField.getMetadataSchema(); - itemService.addMetadata(context, item, metadataSchema.getName(), metadataField.getElement(), - metadataField.getQualifier(), aMd.getLanguage(), - aMd.getValue()); - } - } + itemService.populateWithTemplateItemMetadata(context, collection, template, item); itemService.update(context, item); @@ -213,15 +176,6 @@ public WorkspaceItem create(Context context, Collection collection, UUID uuid, b return workspaceItem; } - private Optional getDSpaceEntityType(DSpaceObject dSpaceObject) { - return Objects.nonNull(dSpaceObject) ? dSpaceObject.getMetadata() - .stream() - .filter(x -> x.getMetadataField().toString('.') - .equalsIgnoreCase("dspace.entity.type")) - .findFirst() - : Optional.empty(); - } - @Override public WorkspaceItem create(Context c, WorkflowItem workflowItem) throws SQLException, AuthorizeException { WorkspaceItem workspaceItem = workspaceItemDAO.create(c, new WorkspaceItem()); diff --git a/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java index 98277c4f9c0..0ed0abe2182 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java @@ -636,6 +636,9 @@ protected DSpaceObject replaceObject(Context context, DSpaceObject dso, owningCollection = inProgressSubmission.getCollection(); } + itemService.populateWithTemplateItemMetadata(context, owningCollection, params.useCollectionTemplate(), + item); + addLicense(context, item, license, owningCollection , params); diff --git a/dspace-api/src/main/java/org/dspace/content/service/ItemService.java b/dspace-api/src/main/java/org/dspace/content/service/ItemService.java index de7644af83f..12867ad18c3 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/ItemService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/ItemService.java @@ -83,6 +83,18 @@ public interface ItemService */ public Item createTemplateItem(Context context, Collection collection) throws SQLException, AuthorizeException; + /** + * Populate the given item with all template item specified metadata. + * + * @param context DSpace context object + * @param collection Collection (parent) + * @param template if true, the item inherits all collection's template item metadata + * @param item item to populate with template item specified metadata + * @throws SQLException if database error + */ + public void populateWithTemplateItemMetadata (Context context, Collection collection, boolean template, Item item) + throws SQLException; + /** * Get all the items in the archive. Only items with the "in archive" flag * set are included. The order of the list is indeterminate. From 849a252fa85e79c7669648bcd317e18b8c66e8ad Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Wed, 24 Jan 2024 21:13:52 +0000 Subject: [PATCH 143/939] Update ContainerManagerDSpace.java Remove duplicate item.delete call that causes hibernate errors as item is already deleted (cherry picked from commit 57032050b97e264a2a875d4bae49f092e06df481) --- .../main/java/org/dspace/sword2/ContainerManagerDSpace.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/dspace-swordv2/src/main/java/org/dspace/sword2/ContainerManagerDSpace.java b/dspace-swordv2/src/main/java/org/dspace/sword2/ContainerManagerDSpace.java index 454afd80dc1..81b48d22c08 100644 --- a/dspace-swordv2/src/main/java/org/dspace/sword2/ContainerManagerDSpace.java +++ b/dspace-swordv2/src/main/java/org/dspace/sword2/ContainerManagerDSpace.java @@ -760,9 +760,6 @@ protected void doContainerDelete(SwordContext swordContext, Item item, WorkflowItem wfi = wft.getWorkflowItem(context, item); workflowItemService.deleteWrapper(context, wfi); } - - // then delete the item - itemService.delete(context, item); } catch (SQLException | IOException e) { throw new DSpaceSwordException(e); } catch (AuthorizeException e) { From 435988ca00f00fa9c116792defe0925c05d65cd3 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Tue, 30 Apr 2024 16:40:47 +0200 Subject: [PATCH 144/939] Swap the delete and constraint db lines --- ...023.09.28__enforce_group_or_eperson_for_resourcepolicy.sql | 4 ++-- ...023.09.28__enforce_group_or_eperson_for_resourcepolicy.sql | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.09.28__enforce_group_or_eperson_for_resourcepolicy.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.09.28__enforce_group_or_eperson_for_resourcepolicy.sql index 2e352e2ee7c..61f361625dd 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.09.28__enforce_group_or_eperson_for_resourcepolicy.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.09.28__enforce_group_or_eperson_for_resourcepolicy.sql @@ -6,7 +6,7 @@ -- http://www.dspace.org/license/ -- +DELETE FROM ResourcePolicy WHERE eperson_id is null and epersongroup_id is null; + ALTER TABLE ResourcePolicy ADD CONSTRAINT resourcepolicy_eperson_and_epersongroup_not_nullobject_chk CHECK (eperson_id is not null or epersongroup_id is not null) ; - -DELETE FROM ResourcePolicy WHERE eperson_id is null and epersongroup_id is null; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.09.28__enforce_group_or_eperson_for_resourcepolicy.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.09.28__enforce_group_or_eperson_for_resourcepolicy.sql index 2e352e2ee7c..61f361625dd 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.09.28__enforce_group_or_eperson_for_resourcepolicy.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.09.28__enforce_group_or_eperson_for_resourcepolicy.sql @@ -6,7 +6,7 @@ -- http://www.dspace.org/license/ -- +DELETE FROM ResourcePolicy WHERE eperson_id is null and epersongroup_id is null; + ALTER TABLE ResourcePolicy ADD CONSTRAINT resourcepolicy_eperson_and_epersongroup_not_nullobject_chk CHECK (eperson_id is not null or epersongroup_id is not null) ; - -DELETE FROM ResourcePolicy WHERE eperson_id is null and epersongroup_id is null; From 07377d12c8ba249bb1cf17c094e84c2f2f8eda30 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 30 Apr 2024 09:48:55 -0500 Subject: [PATCH 145/939] Add ITs which test SWORD deletion for workspace vs workflow items (we already have a test for archived items) (cherry picked from commit 70b0a2874964682ff5efbff9452351fae534f1f6) --- .../java/org/dspace/app/sword2/Swordv2IT.java | 137 +++++++++++++++++- 1 file changed, 135 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/sword2/Swordv2IT.java b/dspace-server-webapp/src/test/java/org/dspace/app/sword2/Swordv2IT.java index 64e3db7dfc1..3ff79f78055 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/sword2/Swordv2IT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/sword2/Swordv2IT.java @@ -20,9 +20,13 @@ import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.ItemBuilder; +import org.dspace.builder.WorkflowItemBuilder; +import org.dspace.builder.WorkspaceItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Item; +import org.dspace.content.WorkspaceItem; import org.dspace.services.ConfigurationService; +import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; import org.junit.Assume; import org.junit.Before; import org.junit.ClassRule; @@ -295,7 +299,7 @@ public void depositAndEditViaSwordTest() throws Exception { assertThat(response.getBody(), containsString(newTitle)); //---- - // STEP 5: Verify uploaded content can be DELETED via SWORDv2 (by an Admin ONLY) + // STEP 5: Verify archived Item can be DELETED via SWORDv2 (by an Admin ONLY) //---- // Edit URI should also allow user to DELETE the uploaded content // Since we submitted to a collection WITHOUT a workflow, this item is in archive. That means DELETE @@ -306,7 +310,6 @@ public void depositAndEditViaSwordTest() throws Exception { .headers(authHeaders) .build(); response = responseAsString(request); - // Expect a 204 No Content response assertEquals(HttpStatus.NO_CONTENT, response.getStatusCode()); @@ -322,6 +325,136 @@ public void depositAndEditViaSwordTest() throws Exception { assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); } + @Test + public void deleteWorkspaceItemViaSwordTest() throws Exception { + context.turnOffAuthorisationSystem(); + // Create a top level community and one Collection + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test SWORDv2 Collection") + .withSubmitterGroup(eperson) + .build(); + + String titleOfItem = "This is a test SWORD workspace item"; + WorkspaceItem wsi = WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withSubmitter(eperson) + .withTitle(titleOfItem) + .build(); + + // Above changes MUST be committed to the database for SWORDv2 to see them. + context.commit(); + context.restoreAuthSystemState(); + + // Edit link of WorkspaceItem is the Item UUID + String editLink = "/swordv2/edit/" + wsi.getItem().getID(); + + //---- + // STEP 1: Verify WorkspaceItem is found via SWORDv2 when logged in as the submitter + //---- + HttpHeaders authHeaders = new HttpHeaders(); + authHeaders.setBasicAuth(eperson.getEmail(), password); + RequestEntity request = RequestEntity.get(editLink) + .accept(MediaType.valueOf("application/atom+xml")) + .headers(authHeaders) + .build(); + ResponseEntity response = responseAsString(request); + assertEquals(HttpStatus.OK, response.getStatusCode()); + // Verify the new Item title is now included in the response body + assertThat(response.getBody(), containsString(titleOfItem)); + + //---- + // STEP 2: Verify WorkspaceItem can be deleted by submitter + //---- + authHeaders = new HttpHeaders(); + authHeaders.setBasicAuth(eperson.getEmail(), password); + request = RequestEntity.delete(editLink) + .headers(authHeaders) + .build(); + response = responseAsString(request); + // Expect a 204 No Content response + assertEquals(HttpStatus.NO_CONTENT, response.getStatusCode()); + + // Verify that Edit URI now returns a 404 (deleted successfully) + authHeaders = new HttpHeaders(); + authHeaders.setBasicAuth(eperson.getEmail(), password); + request = RequestEntity.get(editLink) + .accept(MediaType.valueOf("application/atom+xml")) + .headers(authHeaders) + .build(); + response = responseAsString(request); + // Expect a 404 response as content was deleted + assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); + } + + + @Test + public void deleteWorkflowItemViaSwordTest() throws Exception { + context.turnOffAuthorisationSystem(); + // Create a top level community and one Collection + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + // Create a Collection with a workflow step enabled + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test SWORDv2 Workflow Collection") + .withSubmitterGroup(eperson) + .withWorkflowGroup(1, admin) + .build(); + + String titleOfItem = "This is a test SWORD workflow item"; + XmlWorkflowItem workflowItem = WorkflowItemBuilder.createWorkflowItem(context, collection) + .withSubmitter(eperson) + .withTitle(titleOfItem) + .withIssueDate("2017-10-17") + .build(); + // Above changes MUST be committed to the database for SWORDv2 to see them. + context.commit(); + context.restoreAuthSystemState(); + + // Edit link of WorkflowItem is the Item UUID + String editLink = "/swordv2/edit/" + workflowItem.getItem().getID(); + + //---- + // STEP 1: Verify WorkflowItem is found via SWORDv2 when logged in as the submitter + //---- + HttpHeaders authHeaders = new HttpHeaders(); + authHeaders.setBasicAuth(eperson.getEmail(), password); + RequestEntity request = RequestEntity.get(editLink) + .accept(MediaType.valueOf("application/atom+xml")) + .headers(authHeaders) + .build(); + ResponseEntity response = responseAsString(request); + assertEquals(HttpStatus.OK, response.getStatusCode()); + // Verify the new Item title is now included in the response body + assertThat(response.getBody(), containsString(titleOfItem)); + + //---- + // STEP 2: Verify WorkflowItem can be deleted by ADMIN only + //---- + // NOTE: Once Item is in Workflow, deletion requires ADMIN permissions + authHeaders = new HttpHeaders(); + authHeaders.setBasicAuth(admin.getEmail(), password); + request = RequestEntity.delete(editLink) + .headers(authHeaders) + .build(); + response = responseAsString(request); + // Expect a 204 No Content response + assertEquals(HttpStatus.NO_CONTENT, response.getStatusCode()); + + // Verify that Edit URI now returns a 404 (deleted successfully) + authHeaders = new HttpHeaders(); + authHeaders.setBasicAuth(eperson.getEmail(), password); + request = RequestEntity.get(editLink) + .accept(MediaType.valueOf("application/atom+xml")) + .headers(authHeaders) + .build(); + response = responseAsString(request); + // Expect a 404 response as content was deleted + assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); + } + @Test public void editUnauthorizedTest() throws Exception { // Attempt to POST to /edit endpoint without sending authentication information From 67d7c0bf3bb646ba94f2d7196f7ed20a41d0e701 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 30 Apr 2024 10:30:12 -0500 Subject: [PATCH 146/939] Fix deletion by only cleaning up wrapper for Workspace/workflow items (cherry picked from commit 5c13569d0fc6709d43fd8824e875220b4ccad302) --- .../main/java/org/dspace/sword2/ContainerManagerDSpace.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dspace-swordv2/src/main/java/org/dspace/sword2/ContainerManagerDSpace.java b/dspace-swordv2/src/main/java/org/dspace/sword2/ContainerManagerDSpace.java index 81b48d22c08..c0e8ef6bdc4 100644 --- a/dspace-swordv2/src/main/java/org/dspace/sword2/ContainerManagerDSpace.java +++ b/dspace-swordv2/src/main/java/org/dspace/sword2/ContainerManagerDSpace.java @@ -755,11 +755,14 @@ protected void doContainerDelete(SwordContext swordContext, Item item, WorkflowTools wft = new WorkflowTools(); if (wft.isItemInWorkspace(swordContext.getContext(), item)) { WorkspaceItem wsi = wft.getWorkspaceItem(context, item); - workspaceItemService.deleteAll(context, wsi); + workspaceItemService.deleteWrapper(context, wsi); } else if (wft.isItemInWorkflow(context, item)) { WorkflowItem wfi = wft.getWorkflowItem(context, item); workflowItemService.deleteWrapper(context, wfi); } + + // then delete the item + itemService.delete(context, item); } catch (SQLException | IOException e) { throw new DSpaceSwordException(e); } catch (AuthorizeException e) { From f9d3efd1e7efc7ac6f96d5ed48f6bc16b2f2da0d Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Fri, 26 Jan 2024 09:58:57 +0100 Subject: [PATCH 147/939] added validation of page url (cherry picked from commit 771f37a1facf97d29850db835490b6c2bfa07adb) --- .../app/rest/repository/FeedbackRestRepository.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/FeedbackRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/FeedbackRestRepository.java index 8fd28bbe94b..8188237114e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/FeedbackRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/FeedbackRestRepository.java @@ -79,8 +79,14 @@ protected FeedbackRest createAndReturn(Context context) throws AuthorizeExceptio throw new DSpaceBadRequestException("e-mail and message fields are mandatory!"); } + String pageUrl = feedbackRest.getPage(); + String urlPrefix = configurationService.getProperty("dspace.ui.url"); + if (! StringUtils.startsWith(pageUrl, urlPrefix)) { + throw new DSpaceBadRequestException("unexpected page url was submitted"); + } + try { - feedbackService.sendEmail(context, req, recipientEmail, senderEmail, message, feedbackRest.getPage()); + feedbackService.sendEmail(context, req, recipientEmail, senderEmail, message, pageUrl); } catch (IOException | MessagingException e) { throw new RuntimeException(e.getMessage(), e); } @@ -100,4 +106,4 @@ public void setFeedbackService(FeedbackService feedbackService) { this.feedbackService = feedbackService; } -} \ No newline at end of file +} From 0cfe4aec73db1681c80cbc914898c3c050c3cc11 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Fri, 26 Jan 2024 10:40:48 +0100 Subject: [PATCH 148/939] allow empty feedback page url (cherry picked from commit c4cc56a4291106beb8b3250e4e4058d13ab962e2) --- .../org/dspace/app/rest/repository/FeedbackRestRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/FeedbackRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/FeedbackRestRepository.java index 8188237114e..42672062175 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/FeedbackRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/FeedbackRestRepository.java @@ -81,7 +81,7 @@ protected FeedbackRest createAndReturn(Context context) throws AuthorizeExceptio String pageUrl = feedbackRest.getPage(); String urlPrefix = configurationService.getProperty("dspace.ui.url"); - if (! StringUtils.startsWith(pageUrl, urlPrefix)) { + if (StringUtils.isNotBlank(pageUrl) && ! StringUtils.startsWith(pageUrl, urlPrefix)) { throw new DSpaceBadRequestException("unexpected page url was submitted"); } From 30e3cde30d2b996a151626fd3068c482ef198772 Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Mon, 29 Jan 2024 16:33:46 +0000 Subject: [PATCH 149/939] Update ItemImportServiceImpl.java expand setPermission to take the rpType string (cherry picked from commit 679df52bbdcb3f9b85da2d3128417f99b2133b80) --- .../org/dspace/app/itemimport/ItemImportServiceImpl.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 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 255f4bdcbb1..c1357aff9eb 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 @@ -1743,7 +1743,8 @@ protected void processOptions(Context c, Item myItem, List options) } else { logInfo("\tSetting special permissions for " + bitstreamName); - setPermission(c, myGroup, actionID, bs); + String rpType = useWorkflow ? ResourcePolicy.TYPE_SUBMISSION : ResourcePolicy.TYPE_INHERITED; + setPermission(c, myGroup, rpType, actionID, bs); } } @@ -1801,13 +1802,14 @@ protected void processOptions(Context c, Item myItem, List options) * * @param c DSpace Context * @param g Dspace Group + * @param rpType resource policy type string * @param actionID action identifier * @param bs Bitstream * @throws SQLException if database error * @throws AuthorizeException if authorization error * @see org.dspace.core.Constants */ - protected void setPermission(Context c, Group g, int actionID, Bitstream bs) + protected void setPermission(Context c, Group g, String rpType, int actionID, Bitstream bs) throws SQLException, AuthorizeException { if (!isTest) { // remove the default policy @@ -1819,6 +1821,7 @@ protected void setPermission(Context c, Group g, int actionID, Bitstream bs) rp.setdSpaceObject(bs); rp.setAction(actionID); rp.setGroup(g); + rp.setRpType(rpType); resourcePolicyService.update(c, rp); } else { From a97d72f993ce6cb6edf2e3991707895fc4288626 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 1 May 2024 11:50:22 -0500 Subject: [PATCH 150/939] Minor update to latest Spring version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index da252ad102c..9f8f8f24702 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ 11 - 5.3.33 + 5.3.34 2.7.18 5.7.11 5.6.15.Final From 735906c42921f1e79b7ee843b9d836049f051afb Mon Sep 17 00:00:00 2001 From: John Abrahams Date: Wed, 1 May 2024 11:54:44 -0400 Subject: [PATCH 151/939] Install unzip util in final built image (cherry picked from commit 3eae430beca18f18d1396e614f8aa1f5b85169a7) --- Dockerfile.cli | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Dockerfile.cli b/Dockerfile.cli index 96a84bc5626..53040a2fad8 100644 --- a/Dockerfile.cli +++ b/Dockerfile.cli @@ -33,9 +33,9 @@ WORKDIR /dspace-src ENV ANT_VERSION 1.10.13 ENV ANT_HOME /tmp/ant-$ANT_VERSION ENV PATH $ANT_HOME/bin:$PATH -# Need wget to install ant, and unzip for managing AIPs +# Need wget to install ant RUN apt-get update \ - && apt-get install -y --no-install-recommends wget unzip \ + && apt-get install -y --no-install-recommends wget \ && apt-get purge -y --auto-remove \ && rm -rf /var/lib/apt/lists/* # Download and install 'ant' @@ -52,3 +52,8 @@ ENV DSPACE_INSTALL=/dspace COPY --from=ant_build /dspace $DSPACE_INSTALL # Give java extra memory (1GB) ENV JAVA_OPTS=-Xmx1000m +# Install unzip for AIPs +RUN apt-get update \ + && apt-get install -y --no-install-recommends unzip \ + && apt-get purge -y --auto-remove \ + && rm -rf /var/lib/apt/lists/* From e1dc6951028b468e8676341ad1ccc28a95d0dd28 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 1 May 2024 11:37:05 -0500 Subject: [PATCH 152/939] Remove obsolete "version" tag from compose files (cherry picked from commit 9ca0ad5579348e4e092307e7688bda1c5c9a178f) --- docker-compose-cli.yml | 1 - docker-compose.yml | 1 - dspace/src/main/docker-compose/cli.assetstore.yml | 2 -- dspace/src/main/docker-compose/cli.ingest.yml | 2 -- dspace/src/main/docker-compose/db.entities.yml | 2 -- dspace/src/main/docker-compose/db.restore.yml | 2 -- dspace/src/main/docker-compose/docker-compose-angular.yml | 1 - dspace/src/main/docker-compose/docker-compose-iiif.yml | 1 - dspace/src/main/docker-compose/docker-compose-shibboleth.yml | 1 - 9 files changed, 13 deletions(-) diff --git a/docker-compose-cli.yml b/docker-compose-cli.yml index 4f4d4a18916..d6a194617e0 100644 --- a/docker-compose-cli.yml +++ b/docker-compose-cli.yml @@ -1,4 +1,3 @@ -version: "3.7" networks: # Default to using network named 'dspacenet' from docker-compose.yml. # Its full name will be prepended with the project name (e.g. "-p d7" means it will be named "d7_dspacenet") diff --git a/docker-compose.yml b/docker-compose.yml index a7894135c6a..23fce37db24 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,3 @@ -version: '3.7' networks: dspacenet: ipam: diff --git a/dspace/src/main/docker-compose/cli.assetstore.yml b/dspace/src/main/docker-compose/cli.assetstore.yml index d6a6a4395f4..6563aa081eb 100644 --- a/dspace/src/main/docker-compose/cli.assetstore.yml +++ b/dspace/src/main/docker-compose/cli.assetstore.yml @@ -6,8 +6,6 @@ # http://www.dspace.org/license/ # -version: "3.7" - services: dspace-cli: environment: diff --git a/dspace/src/main/docker-compose/cli.ingest.yml b/dspace/src/main/docker-compose/cli.ingest.yml index d22a235d4fd..6172147f195 100644 --- a/dspace/src/main/docker-compose/cli.ingest.yml +++ b/dspace/src/main/docker-compose/cli.ingest.yml @@ -6,8 +6,6 @@ # http://www.dspace.org/license/ # -version: "3.7" - services: dspace-cli: environment: diff --git a/dspace/src/main/docker-compose/db.entities.yml b/dspace/src/main/docker-compose/db.entities.yml index 809cf52df0b..3480c0df870 100644 --- a/dspace/src/main/docker-compose/db.entities.yml +++ b/dspace/src/main/docker-compose/db.entities.yml @@ -6,8 +6,6 @@ # http://www.dspace.org/license/ # -version: "3.7" - services: dspacedb: image: dspace/dspace-postgres-pgcrypto:dspace-7_x-loadsql diff --git a/dspace/src/main/docker-compose/db.restore.yml b/dspace/src/main/docker-compose/db.restore.yml index fc2f30b9d8e..09990e67561 100644 --- a/dspace/src/main/docker-compose/db.restore.yml +++ b/dspace/src/main/docker-compose/db.restore.yml @@ -6,8 +6,6 @@ # http://www.dspace.org/license/ # -version: "3.7" - # # Overrides the default "dspacedb" container behavior to load a local SQL file into PostgreSQL. # diff --git a/dspace/src/main/docker-compose/docker-compose-angular.yml b/dspace/src/main/docker-compose/docker-compose-angular.yml index 8f9bdf94b38..c9b87c904f1 100644 --- a/dspace/src/main/docker-compose/docker-compose-angular.yml +++ b/dspace/src/main/docker-compose/docker-compose-angular.yml @@ -6,7 +6,6 @@ # http://www.dspace.org/license/ # -version: '3.7' networks: # Default to using network named 'dspacenet' from docker-compose.yml. # Its full name will be prepended with the project name (e.g. "-p d7" means it will be named "d7_dspacenet") diff --git a/dspace/src/main/docker-compose/docker-compose-iiif.yml b/dspace/src/main/docker-compose/docker-compose-iiif.yml index d795192f13c..6f5470e5d15 100644 --- a/dspace/src/main/docker-compose/docker-compose-iiif.yml +++ b/dspace/src/main/docker-compose/docker-compose-iiif.yml @@ -10,7 +10,6 @@ # Test environment for DSpace + Cantaloupe for IIIF support. See README for instructions. # This should NEVER be used in production scenarios. # -version: '3.7' networks: # Default to using network named 'dspacenet' from docker-compose.yml. # Its full name will be prepended with the project name (e.g. "-p d7" means it will be named "d7_dspacenet") diff --git a/dspace/src/main/docker-compose/docker-compose-shibboleth.yml b/dspace/src/main/docker-compose/docker-compose-shibboleth.yml index 33eadcb142d..f7fb2dcbd1a 100644 --- a/dspace/src/main/docker-compose/docker-compose-shibboleth.yml +++ b/dspace/src/main/docker-compose/docker-compose-shibboleth.yml @@ -10,7 +10,6 @@ # Test environment for DSpace + Shibboleth (running via mod_shib in Apache). See README for instructions. # This should NEVER be used in production scenarios. # -version: '3.7' networks: # Default to using network named 'dspacenet' from docker-compose.yml. # Its full name will be prepended with the project name (e.g. "-p d7" means it will be named "d7_dspacenet") From 08c46bd0a1dcbfd238f52b454c0b6eb33c97f651 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 May 2024 16:41:54 +0000 Subject: [PATCH 153/939] Bump org.xmlunit:xmlunit-core from 2.9.1 to 2.10.0 in /dspace-api Bumps [org.xmlunit:xmlunit-core](https://github.com/xmlunit/xmlunit) from 2.9.1 to 2.10.0. - [Release notes](https://github.com/xmlunit/xmlunit/releases) - [Changelog](https://github.com/xmlunit/xmlunit/blob/main/RELEASE_NOTES.md) - [Commits](https://github.com/xmlunit/xmlunit/compare/v2.9.1...v2.10.0) --- updated-dependencies: - dependency-name: org.xmlunit:xmlunit-core dependency-type: direct:production ... Signed-off-by: dependabot[bot] (cherry picked from commit 1fdc3ce220b8bb7e13410176537cf06122bb5ea7) --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 5b578fa49d5..4c881cbd246 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -900,7 +900,7 @@ org.xmlunit xmlunit-core - 2.9.1 + 2.10.0 test From 097d2530630fd862e6af13ed734374bdb1fb1046 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Thu, 22 Feb 2024 20:58:00 +0100 Subject: [PATCH 154/939] Make sure vocab init is run for vocabulary returned in /api/submission/vocabularies (cherry picked from commit 42437dd65be8400d96d1abf36d7abce58426b118) --- .../dspace/content/authority/DSpaceControlledVocabulary.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java index 16632ee5466..94c9452356b 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java @@ -224,6 +224,7 @@ public Choice getChoice(String authKey, String locale) { @Override public boolean isHierarchical() { + init(); return true; } @@ -256,6 +257,7 @@ public Choice getParentChoice(String authorityName, String childId, String local @Override public Integer getPreloadLevel() { + init(); return preloadLevel; } From 62b42ff2d1f13ebdca1efbb47f63f05796717405 Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Thu, 21 Mar 2024 11:12:59 +0000 Subject: [PATCH 155/939] Update RoleDisseminator.java Fix NPE if empty password hash string (cherry picked from commit 04824a2e9826d03f83f77a36361f0f42e23b9ec0) --- .../main/java/org/dspace/content/packager/RoleDisseminator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/content/packager/RoleDisseminator.java b/dspace-api/src/main/java/org/dspace/content/packager/RoleDisseminator.java index f627779af8d..15e9f1b1449 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/RoleDisseminator.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/RoleDisseminator.java @@ -431,7 +431,7 @@ protected void writeEPerson(EPerson eperson, XMLStreamWriter writer, if (emitPassword) { PasswordHash password = ePersonService.getPasswordHash(eperson); - if (null != password) { + if (null != password && password.getHashString() != null) { writer.writeStartElement(PASSWORD_HASH); String algorithm = password.getAlgorithm(); From ce9129c961a65ef9027872e27946d656606227f9 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 3 May 2024 14:20:58 +0200 Subject: [PATCH 156/939] 105866: Remove commented code --- .../dspace/app/rest/converter/AInprogressItemConverter.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java index f40022f19f7..4cd3d2bfb2e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java @@ -70,11 +70,6 @@ protected void fillFromModel(T obj, R witem, Projection projection) { submitter = obj.getSubmitter(); witem.setId(obj.getID()); -// witem.setCollection(collection != null ? converter.toRest(collection, projection) : null); -// witem.setItem(converter.toRest(item, projection)); -// if (submitter != null) { -// witem.setSubmitter(converter.toRest(submitter, projection)); -// } // 1. retrieve the submission definition // 2. iterate over the submission section to allow to plugin additional From 24f2bca4b3619691fb27f40d7869e79fb83bae78 Mon Sep 17 00:00:00 2001 From: Xiqinger <163422483+Xiqinger@users.noreply.github.com> Date: Tue, 26 Mar 2024 22:42:47 +0800 Subject: [PATCH 157/939] check path before mkdirs (cherry picked from commit fda62bc10154e9e3e84fcadabd46f6915321c4c6) --- .../app/itemimport/ItemImportServiceImpl.java | 83 +++++++++---------- 1 file changed, 41 insertions(+), 42 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 6531b7d0c19..cd55e97a635 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 @@ -1959,58 +1959,57 @@ public String unzip(File zipfile, String destDir) throws IOException { try { while (entries.hasMoreElements()) { entry = entries.nextElement(); + String entryName = entry.getName(); + File outFile = new File(zipDir + entryName); + // Verify that this file/directory will be extracted into our zipDir (and not somewhere else!) + if (!outFile.toPath().normalize().startsWith(zipDir)) { + throw new IOException("Bad zip entry: '" + entryName + + "' in file '" + zipfile.getAbsolutePath() + "'!" + + " Cannot process this file or directory."); + } if (entry.isDirectory()) { - if (!new File(zipDir + entry.getName()).mkdirs()) { + if (!outFile.mkdirs()) { logError("Unable to create contents directory: " + zipDir + entry.getName()); } } else { - String entryName = entry.getName(); - File outFile = new File(zipDir + entryName); - // Verify that this file will be extracted into our zipDir (and not somewhere else!) - if (!outFile.toPath().normalize().startsWith(zipDir)) { - throw new IOException("Bad zip entry: '" + entryName - + "' in file '" + zipfile.getAbsolutePath() + "'!" - + " Cannot process this file."); - } else { - logInfo("Extracting file: " + entryName); + logInfo("Extracting file: " + entryName); - int index = entryName.lastIndexOf('/'); - if (index == -1) { - // Was it created on Windows instead? - index = entryName.lastIndexOf('\\'); + int index = entryName.lastIndexOf('/'); + if (index == -1) { + // Was it created on Windows instead? + index = entryName.lastIndexOf('\\'); + } + if (index > 0) { + File dir = new File(zipDir + entryName.substring(0, index)); + if (!dir.exists() && !dir.mkdirs()) { + logError("Unable to create directory: " + dir.getAbsolutePath()); } - if (index > 0) { - File dir = new File(zipDir + entryName.substring(0, index)); - if (!dir.exists() && !dir.mkdirs()) { - logError("Unable to create directory: " + dir.getAbsolutePath()); - } - //Entries could have too many directories, and we need to adjust the sourcedir - // file1.zip (SimpleArchiveFormat / item1 / contents|dublin_core|... - // SimpleArchiveFormat / item2 / contents|dublin_core|... - // or - // file2.zip (item1 / contents|dublin_core|... - // item2 / contents|dublin_core|... - - //regex supports either windows or *nix file paths - String[] entryChunks = entryName.split("/|\\\\"); - if (entryChunks.length > 2) { - if (StringUtils.equals(sourceDirForZip, sourcedir)) { - sourceDirForZip = sourcedir + "/" + entryChunks[0]; - } + //Entries could have too many directories, and we need to adjust the sourcedir + // file1.zip (SimpleArchiveFormat / item1 / contents|dublin_core|... + // SimpleArchiveFormat / item2 / contents|dublin_core|... + // or + // file2.zip (item1 / contents|dublin_core|... + // item2 / contents|dublin_core|... + + //regex supports either windows or *nix file paths + String[] entryChunks = entryName.split("/|\\\\"); + if (entryChunks.length > 2) { + if (StringUtils.equals(sourceDirForZip, sourcedir)) { + sourceDirForZip = sourcedir + "/" + entryChunks[0]; } } - byte[] buffer = new byte[1024]; - int len; - InputStream in = zf.getInputStream(entry); - BufferedOutputStream out = new BufferedOutputStream( - new FileOutputStream(outFile)); - while ((len = in.read(buffer)) >= 0) { - out.write(buffer, 0, len); - } - in.close(); - out.close(); } + byte[] buffer = new byte[1024]; + int len; + InputStream in = zf.getInputStream(entry); + BufferedOutputStream out = new BufferedOutputStream( + new FileOutputStream(outFile)); + while ((len = in.read(buffer)) >= 0) { + out.write(buffer, 0, len); + } + in.close(); + out.close(); } } } finally { From cbfe0beede1b969698db89b7fccca60412930946 Mon Sep 17 00:00:00 2001 From: Xiqinger <163422483+Xiqinger@users.noreply.github.com> Date: Tue, 26 Mar 2024 22:54:00 +0800 Subject: [PATCH 158/939] Add empty check before call iterator().next() (cherry picked from commit b2663c810c2372c7ab841fc223d3ecd0d6823ba0) --- .../app/itemupdate/DeleteBitstreamsAction.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/itemupdate/DeleteBitstreamsAction.java b/dspace-api/src/main/java/org/dspace/app/itemupdate/DeleteBitstreamsAction.java index cb5dcfb75dc..65157f5207e 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemupdate/DeleteBitstreamsAction.java +++ b/dspace-api/src/main/java/org/dspace/app/itemupdate/DeleteBitstreamsAction.java @@ -70,16 +70,19 @@ public void execute(Context context, ItemArchive itarch, boolean isTest, } } - if (alterProvenance) { + if (alterProvenance && !bundles.isEmpty()) { DtoMetadata dtom = DtoMetadata.create("dc.description.provenance", "en", ""); String append = "Bitstream " + bs.getName() + " deleted on " + DCDate .getCurrent() + "; "; - Item item = bundles.iterator().next().getItems().iterator().next(); - ItemUpdate.pr("Append provenance with: " + append); + List items = bundles.iterator().next().getItems(); + if (!items.isEmpty()) { + Item item = items.iterator().next(); + ItemUpdate.pr("Append provenance with: " + append); - if (!isTest) { - MetadataUtilities.appendMetadata(context, item, dtom, false, append); + if (!isTest) { + MetadataUtilities.appendMetadata(context, item, dtom, false, append); + } } } } From 1920101273933a3b0310ae696c3a5ef369ce70e0 Mon Sep 17 00:00:00 2001 From: Xiqinger <163422483+Xiqinger@users.noreply.github.com> Date: Tue, 26 Mar 2024 23:08:13 +0800 Subject: [PATCH 159/939] avoid outputStream write after close (cherry picked from commit 214568d480782a4792aebb916403930d61d13052) --- .../CreativeCommonsRDFStreamDisseminationCrosswalk.java | 1 - .../CreativeCommonsTextStreamDisseminationCrosswalk.java | 1 - .../content/crosswalk/LicenseStreamDisseminationCrosswalk.java | 1 - .../org/dspace/content/packager/AbstractMETSDisseminator.java | 1 + .../src/main/java/org/dspace/subscriptions/ContentGenerator.java | 1 + 5 files changed, 2 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/CreativeCommonsRDFStreamDisseminationCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/CreativeCommonsRDFStreamDisseminationCrosswalk.java index 9042a3a7f52..0528c0c2057 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/CreativeCommonsRDFStreamDisseminationCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/CreativeCommonsRDFStreamDisseminationCrosswalk.java @@ -59,7 +59,6 @@ public void disseminate(Context context, DSpaceObject dso, OutputStream out) Bitstream cc = creativeCommonsService.getLicenseRdfBitstream((Item) dso); if (cc != null) { Utils.copy(bitstreamService.retrieve(context, cc), out); - out.close(); } } } diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/CreativeCommonsTextStreamDisseminationCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/CreativeCommonsTextStreamDisseminationCrosswalk.java index edb9d60a623..cd26b4d19ce 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/CreativeCommonsTextStreamDisseminationCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/CreativeCommonsTextStreamDisseminationCrosswalk.java @@ -65,7 +65,6 @@ public void disseminate(Context context, DSpaceObject dso, OutputStream out) Bitstream cc = creativeCommonsService.getLicenseTextBitstream((Item) dso); if (cc != null) { Utils.copy(bitstreamService.retrieve(context, cc), out); - out.close(); } } } diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/LicenseStreamDisseminationCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/LicenseStreamDisseminationCrosswalk.java index 75b884613db..46858747870 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/LicenseStreamDisseminationCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/LicenseStreamDisseminationCrosswalk.java @@ -57,7 +57,6 @@ public void disseminate(Context context, DSpaceObject dso, OutputStream out) if (licenseBs != null) { Utils.copy(bitstreamService.retrieve(context, licenseBs), out); - out.close(); } } } diff --git a/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSDisseminator.java b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSDisseminator.java index 685fd9000da..fd50ec8023e 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSDisseminator.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSDisseminator.java @@ -628,6 +628,7 @@ protected MdSec makeMdSec(Context context, DSpaceObject dso, Class mdSecClass, // Disseminate crosswalk output to an outputstream ByteArrayOutputStream disseminateOutput = new ByteArrayOutputStream(); sxwalk.disseminate(context, dso, disseminateOutput); + disseminateOutput.close(); // Convert output to an inputstream, so we can write to manifest or Zip file ByteArrayInputStream crosswalkedStream = new ByteArrayInputStream( disseminateOutput.toByteArray()); diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java b/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java index c3035614343..e8f6996cbde 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java @@ -89,6 +89,7 @@ private String generateBodyMail(Context context, List indexable .orElseGet(() -> entityType2Disseminator.get("Item")) .disseminate(context, item, out); } + out.close(); return out.toString(); } catch (Exception e) { log.error(e.getMessage(), e); From d6f6444f5891761626695b732721a2de8ae85e40 Mon Sep 17 00:00:00 2001 From: nwoodward Date: Fri, 5 Apr 2024 14:22:23 -0500 Subject: [PATCH 160/939] updated query to get collections referenced in the subscribers table (cherry picked from commit c5989c65bbfa587c173a222e71966ec073a9222c) --- .../java/org/dspace/content/dao/impl/CollectionDAOImpl.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java index c0ef6ea42fc..e07da9f4449 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java @@ -159,7 +159,8 @@ public List findAuthorizedByGroup(Context context, EPerson ePerson, @Override public List findCollectionsWithSubscribers(Context context) throws SQLException { - return list(createQuery(context, "SELECT DISTINCT col FROM Subscription s join s.collection col")); + return list(createQuery(context, "SELECT c FROM Collection c JOIN Subscription s ON c.id = " + + "s.dSpaceObject")); } @Override From 1da7c6b93317ae68203cc8c1c854ca85519e0588 Mon Sep 17 00:00:00 2001 From: nwoodward Date: Thu, 11 Apr 2024 11:01:21 -0500 Subject: [PATCH 161/939] make sure collections list is unique (cherry picked from commit d27331a0b3ab4fba393f697e9cb48fc8b9760894) --- .../java/org/dspace/content/dao/impl/CollectionDAOImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java index e07da9f4449..befa1397a8e 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java @@ -159,7 +159,7 @@ public List findAuthorizedByGroup(Context context, EPerson ePerson, @Override public List findCollectionsWithSubscribers(Context context) throws SQLException { - return list(createQuery(context, "SELECT c FROM Collection c JOIN Subscription s ON c.id = " + + return list(createQuery(context, "SELECT DISTINCT c FROM Collection c JOIN Subscription s ON c.id = " + "s.dSpaceObject")); } From 4d54f74ae139bd6c837648c67c9dca2dfad0acff Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Thu, 29 Feb 2024 14:06:54 +0100 Subject: [PATCH 162/939] trim trailing whitespaces in search query (cherry picked from commit 43e2a1d402139f32b2e239172e5636b62798eae1) --- .../CrossRefImportMetadataSourceServiceImpl.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefImportMetadataSourceServiceImpl.java index 71b088ff162..f4cdb4e6160 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefImportMetadataSourceServiceImpl.java @@ -192,8 +192,9 @@ private SearchByIdCallable(String id) { @Override public List call() throws Exception { List results = new ArrayList<>(); + URIBuilder uriBuilder = new URIBuilder(url); String ID = URLDecoder.decode(query.getParameterAsClass("id", String.class), "UTF-8"); - URIBuilder uriBuilder = new URIBuilder(url + "/" + ID); + uriBuilder.setPath(uriBuilder.getPath() + "/" + ID); Map> params = new HashMap>(); String responseString = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); JsonNode jsonNode = convertStringJsonToJsonNode(responseString); @@ -277,7 +278,7 @@ private class CountByQueryCallable implements Callable { private CountByQueryCallable(String queryString) { query = new Query(); - query.addParameter("query", queryString); + query.addParameter("query", StringUtils.trim(queryString)); } private CountByQueryCallable(Query query) { @@ -308,7 +309,7 @@ private class DoiCheckCallable implements Callable { private DoiCheckCallable(final String id) { final Query query = new Query(); - query.addParameter("id", id); + query.addParameter("id", StringUtils.trim(id)); this.query = query; } @@ -319,7 +320,8 @@ private DoiCheckCallable(final Query query) { @Override public Integer call() throws Exception { Map> params = new HashMap>(); - URIBuilder uriBuilder = new URIBuilder(url + "/" + query.getParameterAsClass("id", String.class)); + URIBuilder uriBuilder = new URIBuilder(url); + uriBuilder.setPath(uriBuilder.getPath() + "/" + query.getParameterAsClass("id", String.class)); String responseString = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); JsonNode jsonNode = convertStringJsonToJsonNode(responseString); return StringUtils.equals(jsonNode.at("/status").toString(), "ok") ? 1 : 0; From cef170ad121f82047ad61b7d4bf5910a04be6632 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Thu, 29 Feb 2024 14:44:51 +0100 Subject: [PATCH 163/939] extend trimming (cherry picked from commit 2c6f43d48f4527f41e54d4c41fa8ddd232960bd0) --- .../crossref/CrossRefImportMetadataSourceServiceImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefImportMetadataSourceServiceImpl.java index f4cdb4e6160..419f6ca8a08 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefImportMetadataSourceServiceImpl.java @@ -133,7 +133,7 @@ private class SearchByQueryCallable implements Callable> { private SearchByQueryCallable(String queryString, Integer maxResult, Integer start) { query = new Query(); - query.addParameter("query", queryString); + query.addParameter("query", StringUtils.trim(queryString)); query.addParameter("count", maxResult); query.addParameter("start", start); } @@ -186,7 +186,7 @@ private SearchByIdCallable(Query query) { private SearchByIdCallable(String id) { this.query = new Query(); - query.addParameter("id", id); + query.addParameter("id", StringUtils.trim(id)); } @Override From 1606862aa38cbf2a387dfeade1055b71fc0fc255 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Thu, 29 Feb 2024 14:19:25 +0100 Subject: [PATCH 164/939] add processor to handle CrossRef abstracts (cherry picked from commit 498d1379701e670c276b3daf104d2069c965c9c0) --- dspace/config/spring/api/crossref-integration.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dspace/config/spring/api/crossref-integration.xml b/dspace/config/spring/api/crossref-integration.xml index d1e416d2b0c..4786c44a786 100644 --- a/dspace/config/spring/api/crossref-integration.xml +++ b/dspace/config/spring/api/crossref-integration.xml @@ -132,8 +132,11 @@ - + + + + From a000f1430a4a150c0ac46667ea60a97c5265f781 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Thu, 29 Feb 2024 14:22:49 +0100 Subject: [PATCH 165/939] add CrossRefAbstractProcessor (cherry picked from commit fa3f10e0b632b0e6bcc344193cbefeead3edb0f7) --- .../crossref/CrossRefAbstractProcessor.java | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAbstractProcessor.java diff --git a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAbstractProcessor.java b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAbstractProcessor.java new file mode 100644 index 00000000000..689c4e58702 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAbstractProcessor.java @@ -0,0 +1,117 @@ +package org.dspace.importer.external.crossref; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.importer.external.metadatamapping.contributor.JsonPathMetadataProcessor; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Collection; + +public class CrossRefAbstractProcessor implements JsonPathMetadataProcessor { + + private final static Logger log = LogManager.getLogger(); + + private String path; + + @Override + public Collection processMetadata(String json) { + JsonNode rootNode = convertStringJsonToJsonNode(json); + JsonNode abstractNode = rootNode.at(path); + Collection values = new ArrayList<>(); + if (!abstractNode.isMissingNode()) { + String abstractValue = abstractNode.textValue(); + if (StringUtils.isNotEmpty(abstractValue)) { + abstractValue = prettifyAbstract(abstractValue); + if (abstractValue != null) { + values.add(abstractValue); + } + } + } + return values; + } + + /** + * remove JATS markup from abstract + * + * @param abstractValue abstract with JATS markup + * @return abstract without JATS markup + */ + private String prettifyAbstract(String abstractValue) { + if (!abstractValue.contains(""; + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + Document xmlDoc; + try { + DocumentBuilder builder = factory.newDocumentBuilder(); + InputSource is = new InputSource(new StringReader(xmlString)); + xmlDoc = builder.parse(is); + } catch (SAXException | IOException | ParserConfigurationException e) { + log.warn("unable to parse XML markup in CrossRef abstract field: " + e.getMessage()); + return null; + } + + StringBuilder sb = new StringBuilder(); + + NodeList rootElements = xmlDoc.getElementsByTagName("root"); + Node rootElement = rootElements.item(0); + NodeList childElements = rootElement.getChildNodes(); + for (int i = 0; i < childElements.getLength(); i++) { + Node childElement = childElements.item(i); + String nodeName = childElement.getNodeName(); + if (StringUtils.equals(nodeName, "jats:title")) { + if (! StringUtils.equals(childElement.getTextContent(), "Abstract")) { + sb.append(childElement.getTextContent()); + sb.append("\n"); + } + } + else if (StringUtils.equals(nodeName, "jats:sec")) { + NodeList secElements = childElement.getChildNodes(); + for (int j = 0; j < secElements.getLength(); j++) { + Node secChildElement = secElements.item(j); + sb.append(secChildElement.getTextContent()); + sb.append("\n"); + } + sb.append("\n"); + } + } + + return sb.toString().trim(); + } + + private JsonNode convertStringJsonToJsonNode(String json) { + ObjectMapper mapper = new ObjectMapper(); + JsonNode body = null; + try { + body = mapper.readTree(json); + } catch (JsonProcessingException e) { + log.error("Unable to process json response.", e); + } + return body; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } +} From 6b57079569abddf8e7de7726fac850f5608925b7 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Thu, 29 Feb 2024 14:52:07 +0100 Subject: [PATCH 166/939] add header in class file (cherry picked from commit b7720043974fbbadf104b37798fefaf619e58e5e) --- .../external/crossref/CrossRefAbstractProcessor.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAbstractProcessor.java b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAbstractProcessor.java index 689c4e58702..b774e07fc86 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAbstractProcessor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAbstractProcessor.java @@ -1,3 +1,10 @@ +/** + * 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.importer.external.crossref; import com.fasterxml.jackson.core.JsonProcessingException; From 71798f0f743353a7bcc1a8828a1b1b215e338e79 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Thu, 29 Feb 2024 20:52:24 +0100 Subject: [PATCH 167/939] fix code style violations (cherry picked from commit 23b8b696a078000c06c56a9608defdaef36318c5) --- .../crossref/CrossRefAbstractProcessor.java | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAbstractProcessor.java b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAbstractProcessor.java index b774e07fc86..21c702aeaba 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAbstractProcessor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAbstractProcessor.java @@ -7,6 +7,14 @@ */ package org.dspace.importer.external.crossref; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Collection; + import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -20,14 +28,6 @@ import org.xml.sax.InputSource; import org.xml.sax.SAXException; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import java.io.IOException; -import java.io.StringReader; -import java.util.ArrayList; -import java.util.Collection; - public class CrossRefAbstractProcessor implements JsonPathMetadataProcessor { private final static Logger log = LogManager.getLogger(); @@ -88,8 +88,7 @@ private String prettifyAbstract(String abstractValue) { sb.append(childElement.getTextContent()); sb.append("\n"); } - } - else if (StringUtils.equals(nodeName, "jats:sec")) { + } else if (StringUtils.equals(nodeName, "jats:sec")) { NodeList secElements = childElement.getChildNodes(); for (int j = 0; j < secElements.getLength(); j++) { Node secChildElement = secElements.item(j); From 263b45046cc8068221b668c64f3722576e823498 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Thu, 29 Feb 2024 21:07:02 +0100 Subject: [PATCH 168/939] sort imports lexicographically (cherry picked from commit 2867b36ebb0aeb67f8fda70cc72bd9a7dc88a6c5) --- .../external/crossref/CrossRefAbstractProcessor.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAbstractProcessor.java b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAbstractProcessor.java index 21c702aeaba..1b6da9d37b1 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAbstractProcessor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAbstractProcessor.java @@ -7,13 +7,13 @@ */ package org.dspace.importer.external.crossref; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; import java.util.Collection; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; From e22f9a211f789d4cd218c70a78fc4dae2255bf15 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 6 May 2024 10:09:23 -0500 Subject: [PATCH 169/939] Upgrade Tika and bcprov-jdk18on to latest versions. Synced other dependencies with versions used on `main` (where possible to do so) --- pom.xml | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 9f8f8f24702..82718c0f3e9 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ 5.7.11 5.6.15.Final 6.2.5.Final - 42.7.2 + 42.7.3 8.11.3 3.10.8 @@ -38,13 +38,13 @@ 1.1.1 9.4.54.v20240208 - 2.22.1 - 2.0.30 + 2.23.1 + 2.0.31 1.19.0 1.7.36 - 2.5.0 + 2.9.2 - 1.70 + 1.78.1 @@ -1295,13 +1295,19 @@ org.bouncycastle - bcpkix-jdk15on + bcpkix-jdk18on ${bouncycastle.version} org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on + ${bouncycastle.version} + + + + org.bouncycastle + bcutil-jdk18on ${bouncycastle.version} @@ -1502,13 +1508,18 @@ commons-lang3 3.14.0 - + commons-logging commons-logging 1.3.0 + + org.apache.commons + commons-compress + 1.26.0 + org.apache.commons commons-pool2 From dbf063dfb9aff359f0ec153497f2a58b5d189258 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 6 May 2024 12:24:13 -0500 Subject: [PATCH 170/939] Ensure potentially large Solr query uses POST, not GET (cherry picked from commit 0a21a11c9a88f0b87b05ea2fe4ba619a15daaf10) --- .../src/main/java/org/dspace/content/EntityTypeServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/content/EntityTypeServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/EntityTypeServiceImpl.java index 0e0c6d51e50..b5b066d9c36 100644 --- a/dspace-api/src/main/java/org/dspace/content/EntityTypeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/EntityTypeServiceImpl.java @@ -148,7 +148,7 @@ public List getSubmitAuthorizedTypes(Context context) sQuery.setFacetMinCount(1); sQuery.setFacetLimit(Integer.MAX_VALUE); sQuery.setFacetSort(FacetParams.FACET_SORT_INDEX); - QueryResponse qResp = solrSearchCore.getSolr().query(sQuery); + QueryResponse qResp = solrSearchCore.getSolr().query(sQuery, solrSearchCore.REQUEST_METHOD); FacetField facetField = qResp.getFacetField("search.entitytype"); if (Objects.nonNull(facetField)) { for (Count c : facetField.getValues()) { From 606b10ccb76ef7307164142adce5bcf59829ac9e Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 7 May 2024 17:16:33 -0500 Subject: [PATCH 171/939] Add migration to set user_id to null if EPerson no longer exists & enforce it for future (cherry picked from commit ef24645cbf31ed1572f98669e883033d65fc6c4d) --- .../V7.6_2024.05.07__process_eperson_constraint.sql | 13 +++++++++++++ .../V7.6_2024.05.07__process_eperson_constraint.sql | 13 +++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2024.05.07__process_eperson_constraint.sql create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2024.05.07__process_eperson_constraint.sql diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2024.05.07__process_eperson_constraint.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2024.05.07__process_eperson_constraint.sql new file mode 100644 index 00000000000..26de16f466e --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2024.05.07__process_eperson_constraint.sql @@ -0,0 +1,13 @@ +-- +-- 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/ +-- + +-- If Process references an EPerson that no longer exists, set the "user_id" to null. +UPDATE process SET user_id = null WHERE NOT EXISTS (SELECT * FROM EPerson where uuid = process.user_id); + +-- Add new constraint where process.user_id is nullified if referenced EPerson is deleted. +ALTER TABLE process ADD CONSTRAINT process_eperson FOREIGN KEY (user_id) REFERENCES EPerson(uuid) ON DELETE SET NULL; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2024.05.07__process_eperson_constraint.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2024.05.07__process_eperson_constraint.sql new file mode 100644 index 00000000000..26de16f466e --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2024.05.07__process_eperson_constraint.sql @@ -0,0 +1,13 @@ +-- +-- 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/ +-- + +-- If Process references an EPerson that no longer exists, set the "user_id" to null. +UPDATE process SET user_id = null WHERE NOT EXISTS (SELECT * FROM EPerson where uuid = process.user_id); + +-- Add new constraint where process.user_id is nullified if referenced EPerson is deleted. +ALTER TABLE process ADD CONSTRAINT process_eperson FOREIGN KEY (user_id) REFERENCES EPerson(uuid) ON DELETE SET NULL; From 448a69483bf0bce10b58cfc3d3bdb37dda6d3e6c Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 7 May 2024 17:17:00 -0500 Subject: [PATCH 172/939] Fix NullPointerException. Only return userid if EPerson not null (cherry picked from commit f34c592352bf64383721d489afcb6846000e290e) --- .../java/org/dspace/app/rest/converter/ProcessConverter.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ProcessConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ProcessConverter.java index 38eaf420479..54231132d76 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ProcessConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ProcessConverter.java @@ -14,7 +14,6 @@ import org.dspace.app.rest.projection.Projection; import org.dspace.scripts.Process; import org.dspace.scripts.service.ProcessService; -import org.hibernate.ObjectNotFoundException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; @@ -40,10 +39,8 @@ public ProcessRest convert(Process process, Projection projection) { processRest.setId(process.getID()); processRest.setScriptName(process.getName()); processRest.setProcessId(process.getID()); - try { + if (process.getEPerson() != null) { processRest.setUserId(process.getEPerson().getID()); - } catch (ObjectNotFoundException e ) { - processRest.setUserId(null); } processRest.setProcessStatus(process.getProcessStatus()); processRest.setStartTime(process.getStartTime()); From 1b091a033e6ce2b043cd5482a97108d2f87316c7 Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Tue, 12 Mar 2024 15:50:11 +0100 Subject: [PATCH 173/939] Fix OAIHarvester#extractHandle not handling config properly - defaults for oai.harvester.acceptedHandleServer and oai.harvester.rejectedHandlePrefix are now set - rejected handles no longer pass if multiple prefixes were configured for oai.harvester.rejectedHandlePrefix (cherry picked from commit 862a6c5be6f6abf7f91c50ce387fbc468fa29081) --- .../java/org/dspace/harvest/OAIHarvester.java | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/harvest/OAIHarvester.java b/dspace-api/src/main/java/org/dspace/harvest/OAIHarvester.java index 5aeb40bdd91..1b8d0002ce2 100644 --- a/dspace-api/src/main/java/org/dspace/harvest/OAIHarvester.java +++ b/dspace-api/src/main/java/org/dspace/harvest/OAIHarvester.java @@ -16,6 +16,7 @@ import java.sql.SQLException; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.Date; @@ -683,15 +684,11 @@ protected void processRecord(Element record, String OREPrefix, final long curren * @return null or the handle to be used. */ protected String extractHandle(Item item) { - String[] acceptedHandleServers = configurationService.getArrayProperty("oai.harvester.acceptedHandleServer"); - if (acceptedHandleServers == null) { - acceptedHandleServers = new String[] {"hdl.handle.net"}; - } + String[] acceptedHandleServers = configurationService + .getArrayProperty("oai.harvester.acceptedHandleServer", new String[] {"hdl.handle.net"}); - String[] rejectedHandlePrefixes = configurationService.getArrayProperty("oai.harvester.rejectedHandlePrefix"); - if (rejectedHandlePrefixes == null) { - rejectedHandlePrefixes = new String[] {"123456789"}; - } + String[] rejectedHandlePrefixes = configurationService + .getArrayProperty("oai.harvester.rejectedHandlePrefix", new String[] {"123456789"}); List values = itemService.getMetadata(item, "dc", "identifier", Item.ANY, Item.ANY); @@ -706,12 +703,9 @@ protected String extractHandle(Item item) { for (String server : acceptedHandleServers) { if (urlPieces[2].equals(server)) { - for (String prefix : rejectedHandlePrefixes) { - if (!urlPieces[3].equals(prefix)) { - return urlPieces[3] + "/" + urlPieces[4]; - } + if (Arrays.stream(rejectedHandlePrefixes).noneMatch(prefix -> prefix.equals(urlPieces[3]))) { + return urlPieces[3] + "/" + urlPieces[4]; } - } } } From 532e21ffb9072178b3e299b66327a425a3a4f06b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Morin?= Date: Wed, 8 May 2024 15:22:56 -0400 Subject: [PATCH 174/939] Updated CheckStyle so that new Java features such as string blocks are supported, but without requiring to change anything else (cherry picked from commit ef8c621a0fdf0ced3c13d8a1b93522dee5b4f421) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 82718c0f3e9..f7ce30b30df 100644 --- a/pom.xml +++ b/pom.xml @@ -287,7 +287,7 @@ com.puppycrawl.tools checkstyle - 8.30 + 8.38 From 2fdc422b249993045e8b905a9f2ea0826a355b2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Thu, 9 May 2024 14:39:51 +0100 Subject: [PATCH 175/939] change IT from organisation to organization --- .../java/org/dspace/app/rest/DiscoveryRestControllerIT.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java index 80d8ab2df42..c70b214731f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java @@ -1277,11 +1277,11 @@ public void checkSortOrderInPersonOrOrgunitConfigurationTest() throws Exception DiscoverySortFieldConfiguration.SORT_ORDER.desc.name()), SortOptionMatcher.sortOptionMatcher("organization.legalName", DiscoverySortFieldConfiguration.SORT_ORDER.asc.name()), - SortOptionMatcher.sortOptionMatcher("organisation.address.addressCountry", + SortOptionMatcher.sortOptionMatcher("organization.address.addressCountry", DiscoverySortFieldConfiguration.SORT_ORDER.asc.name()), - SortOptionMatcher.sortOptionMatcher("organisation.address.addressLocality", + SortOptionMatcher.sortOptionMatcher("organization.address.addressLocality", DiscoverySortFieldConfiguration.SORT_ORDER.asc.name()), - SortOptionMatcher.sortOptionMatcher("organisation.foundingDate", + SortOptionMatcher.sortOptionMatcher("organization.foundingDate", DiscoverySortFieldConfiguration.SORT_ORDER.desc.name()), SortOptionMatcher.sortOptionMatcher("dc.date.accessioned", DiscoverySortFieldConfiguration.SORT_ORDER.desc.name()), From 31020d1d4ff0d2af6baf9adb32b4bb2fef753b1e Mon Sep 17 00:00:00 2001 From: Roy Bruschini Date: Mon, 25 Mar 2024 16:52:55 +0100 Subject: [PATCH 176/939] Create templates mail and renamed name in getEmailFileName (cherry picked from commit 7a637e6871134a3d81cc9abbbe158090bdcec6f0) --- .../app/itemimport/ItemImportServiceImpl.java | 4 ++-- dspace/config/emails/batch_import_error | 19 +++++++++++++++++++ dspace/config/emails/batch_import_success | 16 ++++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 dspace/config/emails/batch_import_error create mode 100644 dspace/config/emails/batch_import_success 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 cd55e97a635..1e219ee6314 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 @@ -2234,7 +2234,7 @@ public void emailSuccessMessage(Context context, EPerson eperson, String fileName) throws MessagingException { try { Locale supportedLocale = I18nUtil.getEPersonLocale(eperson); - Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "bte_batch_import_success")); + Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "batch_import_success")); email.addRecipient(eperson.getEmail()); email.addArgument(fileName); @@ -2250,7 +2250,7 @@ public void emailErrorMessage(EPerson eperson, String error) logError("An error occurred during item import, the user will be notified. " + error); try { Locale supportedLocale = I18nUtil.getEPersonLocale(eperson); - Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "bte_batch_import_error")); + Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "batch_import_error")); email.addRecipient(eperson.getEmail()); email.addArgument(error); email.addArgument(configurationService.getProperty("dspace.ui.url") + "/feedback"); diff --git a/dspace/config/emails/batch_import_error b/dspace/config/emails/batch_import_error new file mode 100644 index 00000000000..2e2a2b74272 --- /dev/null +++ b/dspace/config/emails/batch_import_error @@ -0,0 +1,19 @@ +## Email sent to DSpace users when they batch import fails. +## +## Parameters: {0} the export error +## {1} the URL to the feedback page +## +## +## See org.dspace.core.Email for information on the format of this file. +## +#set($subject = 'DSpace - The batch import was not completed.') +The batch import you initiated from the DSpace UI was not completed, due to the following reason: + ${params[0]} + +For more information you may contact your system administrator: + ${params[1]} + + + +The DSpace Team + diff --git a/dspace/config/emails/batch_import_success b/dspace/config/emails/batch_import_success new file mode 100644 index 00000000000..7e9fdbf7416 --- /dev/null +++ b/dspace/config/emails/batch_import_success @@ -0,0 +1,16 @@ + +## Email sent to DSpace users when they successfully batch import items. +## +## Parameters: {0} the filepath to the mapfile created by the batch import +## +## +## See org.dspace.core.Email for information on the format of this file. +## +#set($subject = 'DSpace - Batch import successfully completed') +The batch item import you initiated from the DSpace UI has completed successfully. + +You may find the mapfile for the import in the following path: ${params[0]} + + +The DSpace Team + From d90cf24a21a47268e710392985994e9a385ac23a Mon Sep 17 00:00:00 2001 From: Roy Bruschini Date: Mon, 25 Mar 2024 16:55:43 +0100 Subject: [PATCH 177/939] Fix typo (cherry picked from commit 74c15e8d55c88e68bb7d7f716deffe5409922868) --- dspace/config/emails/batch_import_error | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace/config/emails/batch_import_error b/dspace/config/emails/batch_import_error index 2e2a2b74272..2c62d72bf9c 100644 --- a/dspace/config/emails/batch_import_error +++ b/dspace/config/emails/batch_import_error @@ -1,4 +1,4 @@ -## Email sent to DSpace users when they batch import fails. +## Email sent to DSpace users when their batch import fails. ## ## Parameters: {0} the export error ## {1} the URL to the feedback page From ca27ea5fc2b071b6bfe76ba6de6715e6577f237d Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 9 May 2024 17:42:21 +0200 Subject: [PATCH 178/939] Remove not unique id from inner beans to avoid xml validation issues (cherry picked from commit 844b73ff4903ef59b2a69f438b5bec207a63ead4) --- .../data/dspaceFolder/config/spring/api/item-filters.xml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/item-filters.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/item-filters.xml index 836d4f08967..4107202be56 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/item-filters.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/item-filters.xml @@ -295,8 +295,7 @@ - + @@ -352,8 +351,7 @@ - + From 2976ae397167a5969481c109894d6482b8e90f93 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Fri, 10 May 2024 16:41:09 +0200 Subject: [PATCH 179/939] bugfix: array index can be out of bound (cherry picked from commit 4fc6b07466a7b131fe61f62b8585087c3697afb5) --- .../crosswalk/SubscriptionDsoMetadataForEmailCompose.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/SubscriptionDsoMetadataForEmailCompose.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/SubscriptionDsoMetadataForEmailCompose.java index 05fda2b9747..ad92018b222 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/SubscriptionDsoMetadataForEmailCompose.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/SubscriptionDsoMetadataForEmailCompose.java @@ -49,7 +49,7 @@ public void disseminate(Context context, DSpaceObject dso, OutputStream out) thr for (String actualMetadata : metadata) { String[] splitted = actualMetadata.split("\\."); String qualifier = null; - if (splitted.length == 1) { + if (splitted.length == 3) { qualifier = splitted[2]; } var metadataValue = itemService.getMetadataFirstValue(item, splitted[0], splitted[1], qualifier, ANY); From d14f1130241fc312d94f9df1cedc98bf32e207ad Mon Sep 17 00:00:00 2001 From: "Gantner, Florian Klaus" Date: Thu, 7 Mar 2024 15:31:03 +0100 Subject: [PATCH 180/939] use datacite api response for recordscount and paginated calls. update test file for response as expected from the api. (cherry picked from commit 65ac01a4a655176351b0a74a8daf5b701a418d35) --- ...taCiteImportMetadataSourceServiceImpl.java | 39 ++++++++++++++++++- .../org/dspace/app/rest/dataCite-test.json | 2 +- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java index a11f2bc2471..298c30ec39a 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java @@ -73,8 +73,32 @@ public ImportRecord getRecord(String recordId) throws MetadataSourceException { @Override public int getRecordsCount(String query) throws MetadataSourceException { - Collection records = getRecords(query, 0, -1); - return records == null ? 0 : records.size(); + String id = getID(query); + Map> params = new HashMap<>(); + Map uriParameters = new HashMap<>(); + params.put("uriParameters", uriParameters); + if (StringUtils.isBlank(id)) { + id = query; + } + uriParameters.put("query", id); + uriParameters.put("page[size]", "1"); + int timeoutMs = configurationService.getIntProperty("datacite.timeout", 180000); + String url = configurationService.getProperty("datacite.url", "https://api.datacite.org/dois/"); + String responseString = liveImportClient.executeHttpGetRequest(timeoutMs, url, params); + JsonNode jsonNode = convertStringJsonToJsonNode(responseString); + if (jsonNode == null) { + log.warn("DataCite returned invalid JSON"); + throw new MetadataSourceException("Could not read datacite source"); + } + JsonNode dataNode = jsonNode.at("/meta/total"); + if (dataNode != null) { + try { + return Integer.valueOf(dataNode.toString()); + } catch (Exception e) { + log.debug("Could not read integer value" + dataNode.toString()); + } + } + return 0; } @Override @@ -95,6 +119,17 @@ public Collection getRecords(String query, int start, int count) t id = query; } uriParameters.put("query", id); + // start = current dspace page / datacite page number starting with 1 + // dspace rounds up/down to the next configured pagination settings. + if (start > 0 && count > 0) { + uriParameters.put("page[number]", Integer.toString((start / count) + 1)); + } + + // count = dspace page size / default datacite page size is currently 25 https://support.datacite.org/docs/pagination + if (count > 0) { + uriParameters.put("page[size]", Integer.toString(count)); + } + int timeoutMs = configurationService.getIntProperty("datacite.timeout", 180000); String url = configurationService.getProperty("datacite.url", "https://api.datacite.org/dois/"); String responseString = liveImportClient.executeHttpGetRequest(timeoutMs, url, params); diff --git a/dspace-server-webapp/src/test/resources/org/dspace/app/rest/dataCite-test.json b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/dataCite-test.json index 8ede6f29a08..1b7f63cd89c 100644 --- a/dspace-server-webapp/src/test/resources/org/dspace/app/rest/dataCite-test.json +++ b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/dataCite-test.json @@ -1 +1 @@ -{"data":{"id":"10.48550/arxiv.2207.04779","type":"dois","attributes":{"doi":"10.48550/arxiv.2207.04779","prefix":"10.48550","suffix":"arxiv.2207.04779","identifiers":[{"identifier":"2207.04779","identifierType":"arXiv"}],"alternateIdentifiers":[{"alternateIdentifierType":"arXiv","alternateIdentifier":"2207.04779"}],"creators":[{"name":"Bayer, Jonas","nameType":"Personal","givenName":"Jonas","familyName":"Bayer","affiliation":[],"nameIdentifiers":[]},{"name":"Benzmüller, Christoph","nameType":"Personal","givenName":"Christoph","familyName":"Benzmüller","affiliation":[],"nameIdentifiers":[]},{"name":"Buzzard, Kevin","nameType":"Personal","givenName":"Kevin","familyName":"Buzzard","affiliation":[],"nameIdentifiers":[]},{"name":"David, Marco","nameType":"Personal","givenName":"Marco","familyName":"David","affiliation":[],"nameIdentifiers":[]},{"name":"Lamport, Leslie","nameType":"Personal","givenName":"Leslie","familyName":"Lamport","affiliation":[],"nameIdentifiers":[]},{"name":"Matiyasevich, Yuri","nameType":"Personal","givenName":"Yuri","familyName":"Matiyasevich","affiliation":[],"nameIdentifiers":[]},{"name":"Paulson, Lawrence","nameType":"Personal","givenName":"Lawrence","familyName":"Paulson","affiliation":[],"nameIdentifiers":[]},{"name":"Schleicher, Dierk","nameType":"Personal","givenName":"Dierk","familyName":"Schleicher","affiliation":[],"nameIdentifiers":[]},{"name":"Stock, Benedikt","nameType":"Personal","givenName":"Benedikt","familyName":"Stock","affiliation":[],"nameIdentifiers":[]},{"name":"Zelmanov, Efim","nameType":"Personal","givenName":"Efim","familyName":"Zelmanov","affiliation":[],"nameIdentifiers":[]}],"titles":[{"title":"Mathematical Proof Between Generations"}],"publisher":"arXiv","container":{},"publicationYear":2022,"subjects":[{"lang":"en","subject":"History and Overview (math.HO)","subjectScheme":"arXiv"},{"lang":"en","subject":"Logic in Computer Science (cs.LO)","subjectScheme":"arXiv"},{"subject":"FOS: Mathematics","subjectScheme":"Fields of Science and Technology (FOS)"},{"subject":"FOS: Mathematics","schemeUri":"http://www.oecd.org/science/inno/38235147.pdf","subjectScheme":"Fields of Science and Technology (FOS)"},{"subject":"FOS: Computer and information sciences","subjectScheme":"Fields of Science and Technology (FOS)"},{"subject":"FOS: Computer and information sciences","schemeUri":"http://www.oecd.org/science/inno/38235147.pdf","subjectScheme":"Fields of Science and Technology (FOS)"}],"contributors":[],"dates":[{"date":"2022-07-08T14:42:33Z","dateType":"Submitted","dateInformation":"v1"},{"date":"2022-07-13T00:14:24Z","dateType":"Updated","dateInformation":"v1"},{"date":"2022-07","dateType":"Available","dateInformation":"v1"},{"date":"2022","dateType":"Issued"}],"language":null,"types":{"ris":"GEN","bibtex":"misc","citeproc":"article","schemaOrg":"CreativeWork","resourceType":"Article","resourceTypeGeneral":"Preprint"},"relatedIdentifiers":[],"relatedItems":[],"sizes":[],"formats":[],"version":"1","rightsList":[{"rights":"arXiv.org perpetual, non-exclusive license","rightsUri":"http://arxiv.org/licenses/nonexclusive-distrib/1.0/"}],"descriptions":[{"description":"A proof is one of the most important concepts of mathematics. However, there is a striking difference between how a proof is defined in theory and how it is used in practice. This puts the unique status of mathematics as exact science into peril. Now may be the time to reconcile theory and practice, i.e. precision and intuition, through the advent of computer proof assistants. For the most time this has been a topic for experts in specialized communities. However, mathematical proofs have become increasingly sophisticated, stretching the boundaries of what is humanly comprehensible, so that leading mathematicians have asked for formal verification of their proofs. At the same time, major theorems in mathematics have recently been computer-verified by people from outside of these communities, even by beginning students. This article investigates the gap between the different definitions of a proof and possibilities to build bridges. It is written as a polemic or a collage by different members of the communities in mathematics and computer science at different stages of their careers, challenging well-known preconceptions and exploring new perspectives.","descriptionType":"Abstract"},{"description":"17 pages, 1 figure","descriptionType":"Other"}],"geoLocations":[],"fundingReferences":[],"xml":"PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHJlc291cmNlIHhtbG5zPSJodHRwOi8vZGF0YWNpdGUub3JnL3NjaGVtYS9rZXJuZWwtNCIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnNjaGVtYUxvY2F0aW9uPSJodHRwOi8vZGF0YWNpdGUub3JnL3NjaGVtYS9rZXJuZWwtNCBodHRwOi8vc2NoZW1hLmRhdGFjaXRlLm9yZy9tZXRhL2tlcm5lbC00LjMvbWV0YWRhdGEueHNkIj4KICA8aWRlbnRpZmllciBpZGVudGlmaWVyVHlwZT0iRE9JIj4xMC40ODU1MC9BUlhJVi4yMjA3LjA0Nzc5PC9pZGVudGlmaWVyPgogIDxhbHRlcm5hdGVJZGVudGlmaWVycz4KICAgIDxhbHRlcm5hdGVJZGVudGlmaWVyIGFsdGVybmF0ZUlkZW50aWZpZXJUeXBlPSJhclhpdiI+MjIwNy4wNDc3OTwvYWx0ZXJuYXRlSWRlbnRpZmllcj4KICA8L2FsdGVybmF0ZUlkZW50aWZpZXJzPgogIDxjcmVhdG9ycz4KICAgIDxjcmVhdG9yPgogICAgICA8Y3JlYXRvck5hbWUgbmFtZVR5cGU9IlBlcnNvbmFsIj5CYXllciwgSm9uYXM8L2NyZWF0b3JOYW1lPgogICAgICA8Z2l2ZW5OYW1lPkpvbmFzPC9naXZlbk5hbWU+CiAgICAgIDxmYW1pbHlOYW1lPkJheWVyPC9mYW1pbHlOYW1lPgogICAgPC9jcmVhdG9yPgogICAgPGNyZWF0b3I+CiAgICAgIDxjcmVhdG9yTmFtZSBuYW1lVHlwZT0iUGVyc29uYWwiPkJlbnptw7xsbGVyLCBDaHJpc3RvcGg8L2NyZWF0b3JOYW1lPgogICAgICA8Z2l2ZW5OYW1lPkNocmlzdG9waDwvZ2l2ZW5OYW1lPgogICAgICA8ZmFtaWx5TmFtZT5CZW56bcO8bGxlcjwvZmFtaWx5TmFtZT4KICAgIDwvY3JlYXRvcj4KICAgIDxjcmVhdG9yPgogICAgICA8Y3JlYXRvck5hbWUgbmFtZVR5cGU9IlBlcnNvbmFsIj5CdXp6YXJkLCBLZXZpbjwvY3JlYXRvck5hbWU+CiAgICAgIDxnaXZlbk5hbWU+S2V2aW48L2dpdmVuTmFtZT4KICAgICAgPGZhbWlseU5hbWU+QnV6emFyZDwvZmFtaWx5TmFtZT4KICAgIDwvY3JlYXRvcj4KICAgIDxjcmVhdG9yPgogICAgICA8Y3JlYXRvck5hbWUgbmFtZVR5cGU9IlBlcnNvbmFsIj5EYXZpZCwgTWFyY288L2NyZWF0b3JOYW1lPgogICAgICA8Z2l2ZW5OYW1lPk1hcmNvPC9naXZlbk5hbWU+CiAgICAgIDxmYW1pbHlOYW1lPkRhdmlkPC9mYW1pbHlOYW1lPgogICAgPC9jcmVhdG9yPgogICAgPGNyZWF0b3I+CiAgICAgIDxjcmVhdG9yTmFtZSBuYW1lVHlwZT0iUGVyc29uYWwiPkxhbXBvcnQsIExlc2xpZTwvY3JlYXRvck5hbWU+CiAgICAgIDxnaXZlbk5hbWU+TGVzbGllPC9naXZlbk5hbWU+CiAgICAgIDxmYW1pbHlOYW1lPkxhbXBvcnQ8L2ZhbWlseU5hbWU+CiAgICA8L2NyZWF0b3I+CiAgICA8Y3JlYXRvcj4KICAgICAgPGNyZWF0b3JOYW1lIG5hbWVUeXBlPSJQZXJzb25hbCI+TWF0aXlhc2V2aWNoLCBZdXJpPC9jcmVhdG9yTmFtZT4KICAgICAgPGdpdmVuTmFtZT5ZdXJpPC9naXZlbk5hbWU+CiAgICAgIDxmYW1pbHlOYW1lPk1hdGl5YXNldmljaDwvZmFtaWx5TmFtZT4KICAgIDwvY3JlYXRvcj4KICAgIDxjcmVhdG9yPgogICAgICA8Y3JlYXRvck5hbWUgbmFtZVR5cGU9IlBlcnNvbmFsIj5QYXVsc29uLCBMYXdyZW5jZTwvY3JlYXRvck5hbWU+CiAgICAgIDxnaXZlbk5hbWU+TGF3cmVuY2U8L2dpdmVuTmFtZT4KICAgICAgPGZhbWlseU5hbWU+UGF1bHNvbjwvZmFtaWx5TmFtZT4KICAgIDwvY3JlYXRvcj4KICAgIDxjcmVhdG9yPgogICAgICA8Y3JlYXRvck5hbWUgbmFtZVR5cGU9IlBlcnNvbmFsIj5TY2hsZWljaGVyLCBEaWVyazwvY3JlYXRvck5hbWU+CiAgICAgIDxnaXZlbk5hbWU+RGllcms8L2dpdmVuTmFtZT4KICAgICAgPGZhbWlseU5hbWU+U2NobGVpY2hlcjwvZmFtaWx5TmFtZT4KICAgIDwvY3JlYXRvcj4KICAgIDxjcmVhdG9yPgogICAgICA8Y3JlYXRvck5hbWUgbmFtZVR5cGU9IlBlcnNvbmFsIj5TdG9jaywgQmVuZWRpa3Q8L2NyZWF0b3JOYW1lPgogICAgICA8Z2l2ZW5OYW1lPkJlbmVkaWt0PC9naXZlbk5hbWU+CiAgICAgIDxmYW1pbHlOYW1lPlN0b2NrPC9mYW1pbHlOYW1lPgogICAgPC9jcmVhdG9yPgogICAgPGNyZWF0b3I+CiAgICAgIDxjcmVhdG9yTmFtZSBuYW1lVHlwZT0iUGVyc29uYWwiPlplbG1hbm92LCBFZmltPC9jcmVhdG9yTmFtZT4KICAgICAgPGdpdmVuTmFtZT5FZmltPC9naXZlbk5hbWU+CiAgICAgIDxmYW1pbHlOYW1lPlplbG1hbm92PC9mYW1pbHlOYW1lPgogICAgPC9jcmVhdG9yPgogIDwvY3JlYXRvcnM+CiAgPHRpdGxlcz4KICAgIDx0aXRsZT5NYXRoZW1hdGljYWwgUHJvb2YgQmV0d2VlbiBHZW5lcmF0aW9uczwvdGl0bGU+CiAgPC90aXRsZXM+CiAgPHB1Ymxpc2hlcj5hclhpdjwvcHVibGlzaGVyPgogIDxwdWJsaWNhdGlvblllYXI+MjAyMjwvcHVibGljYXRpb25ZZWFyPgogIDxzdWJqZWN0cz4KICAgIDxzdWJqZWN0IHhtbDpsYW5nPSJlbiIgc3ViamVjdFNjaGVtZT0iYXJYaXYiPkhpc3RvcnkgYW5kIE92ZXJ2aWV3IChtYXRoLkhPKTwvc3ViamVjdD4KICAgIDxzdWJqZWN0IHhtbDpsYW5nPSJlbiIgc3ViamVjdFNjaGVtZT0iYXJYaXYiPkxvZ2ljIGluIENvbXB1dGVyIFNjaWVuY2UgKGNzLkxPKTwvc3ViamVjdD4KICAgIDxzdWJqZWN0IHN1YmplY3RTY2hlbWU9IkZpZWxkcyBvZiBTY2llbmNlIGFuZCBUZWNobm9sb2d5IChGT1MpIj5GT1M6IE1hdGhlbWF0aWNzPC9zdWJqZWN0PgogICAgPHN1YmplY3Qgc3ViamVjdFNjaGVtZT0iRmllbGRzIG9mIFNjaWVuY2UgYW5kIFRlY2hub2xvZ3kgKEZPUykiPkZPUzogQ29tcHV0ZXIgYW5kIGluZm9ybWF0aW9uIHNjaWVuY2VzPC9zdWJqZWN0PgogIDwvc3ViamVjdHM+CiAgPGRhdGVzPgogICAgPGRhdGUgZGF0ZVR5cGU9IlN1Ym1pdHRlZCIgZGF0ZUluZm9ybWF0aW9uPSJ2MSI+MjAyMi0wNy0wOFQxNDo0MjozM1o8L2RhdGU+CiAgICA8ZGF0ZSBkYXRlVHlwZT0iVXBkYXRlZCIgZGF0ZUluZm9ybWF0aW9uPSJ2MSI+MjAyMi0wNy0xM1QwMDoxNDoyNFo8L2RhdGU+CiAgICA8ZGF0ZSBkYXRlVHlwZT0iQXZhaWxhYmxlIiBkYXRlSW5mb3JtYXRpb249InYxIj4yMDIyLTA3PC9kYXRlPgogIDwvZGF0ZXM+CiAgPHJlc291cmNlVHlwZSByZXNvdXJjZVR5cGVHZW5lcmFsPSJQcmVwcmludCI+QXJ0aWNsZTwvcmVzb3VyY2VUeXBlPgogIDx2ZXJzaW9uPjE8L3ZlcnNpb24+CiAgPHJpZ2h0c0xpc3Q+CiAgICA8cmlnaHRzIHJpZ2h0c1VSST0iaHR0cDovL2FyeGl2Lm9yZy9saWNlbnNlcy9ub25leGNsdXNpdmUtZGlzdHJpYi8xLjAvIj5hclhpdi5vcmcgcGVycGV0dWFsLCBub24tZXhjbHVzaXZlIGxpY2Vuc2U8L3JpZ2h0cz4KICA8L3JpZ2h0c0xpc3Q+CiAgPGRlc2NyaXB0aW9ucz4KICAgIDxkZXNjcmlwdGlvbiBkZXNjcmlwdGlvblR5cGU9IkFic3RyYWN0Ij5BIHByb29mIGlzIG9uZSBvZiB0aGUgbW9zdCBpbXBvcnRhbnQgY29uY2VwdHMgb2YgbWF0aGVtYXRpY3MuIEhvd2V2ZXIsIHRoZXJlIGlzIGEgc3RyaWtpbmcgZGlmZmVyZW5jZSBiZXR3ZWVuIGhvdyBhIHByb29mIGlzIGRlZmluZWQgaW4gdGhlb3J5IGFuZCBob3cgaXQgaXMgdXNlZCBpbiBwcmFjdGljZS4gVGhpcyBwdXRzIHRoZSB1bmlxdWUgc3RhdHVzIG9mIG1hdGhlbWF0aWNzIGFzIGV4YWN0IHNjaWVuY2UgaW50byBwZXJpbC4gTm93IG1heSBiZSB0aGUgdGltZSB0byByZWNvbmNpbGUgdGhlb3J5IGFuZCBwcmFjdGljZSwgaS5lLiBwcmVjaXNpb24gYW5kIGludHVpdGlvbiwgdGhyb3VnaCB0aGUgYWR2ZW50IG9mIGNvbXB1dGVyIHByb29mIGFzc2lzdGFudHMuIEZvciB0aGUgbW9zdCB0aW1lIHRoaXMgaGFzIGJlZW4gYSB0b3BpYyBmb3IgZXhwZXJ0cyBpbiBzcGVjaWFsaXplZCBjb21tdW5pdGllcy4gSG93ZXZlciwgbWF0aGVtYXRpY2FsIHByb29mcyBoYXZlIGJlY29tZSBpbmNyZWFzaW5nbHkgc29waGlzdGljYXRlZCwgc3RyZXRjaGluZyB0aGUgYm91bmRhcmllcyBvZiB3aGF0IGlzIGh1bWFubHkgY29tcHJlaGVuc2libGUsIHNvIHRoYXQgbGVhZGluZyBtYXRoZW1hdGljaWFucyBoYXZlIGFza2VkIGZvciBmb3JtYWwgdmVyaWZpY2F0aW9uIG9mIHRoZWlyIHByb29mcy4gQXQgdGhlIHNhbWUgdGltZSwgbWFqb3IgdGhlb3JlbXMgaW4gbWF0aGVtYXRpY3MgaGF2ZSByZWNlbnRseSBiZWVuIGNvbXB1dGVyLXZlcmlmaWVkIGJ5IHBlb3BsZSBmcm9tIG91dHNpZGUgb2YgdGhlc2UgY29tbXVuaXRpZXMsIGV2ZW4gYnkgYmVnaW5uaW5nIHN0dWRlbnRzLiBUaGlzIGFydGljbGUgaW52ZXN0aWdhdGVzIHRoZSBnYXAgYmV0d2VlbiB0aGUgZGlmZmVyZW50IGRlZmluaXRpb25zIG9mIGEgcHJvb2YgYW5kIHBvc3NpYmlsaXRpZXMgdG8gYnVpbGQgYnJpZGdlcy4gSXQgaXMgd3JpdHRlbiBhcyBhIHBvbGVtaWMgb3IgYSBjb2xsYWdlIGJ5IGRpZmZlcmVudCBtZW1iZXJzIG9mIHRoZSBjb21tdW5pdGllcyBpbiBtYXRoZW1hdGljcyBhbmQgY29tcHV0ZXIgc2NpZW5jZSBhdCBkaWZmZXJlbnQgc3RhZ2VzIG9mIHRoZWlyIGNhcmVlcnMsIGNoYWxsZW5naW5nIHdlbGwta25vd24gcHJlY29uY2VwdGlvbnMgYW5kIGV4cGxvcmluZyBuZXcgcGVyc3BlY3RpdmVzLjwvZGVzY3JpcHRpb24+CiAgICA8ZGVzY3JpcHRpb24gZGVzY3JpcHRpb25UeXBlPSJPdGhlciI+MTcgcGFnZXMsIDEgZmlndXJlPC9kZXNjcmlwdGlvbj4KICA8L2Rlc2NyaXB0aW9ucz4KPC9yZXNvdXJjZT4=","url":"https://arxiv.org/abs/2207.04779","contentUrl":null,"metadataVersion":1,"schemaVersion":"http://datacite.org/schema/kernel-4","source":"mds","isActive":true,"state":"findable","reason":null,"viewCount":0,"viewsOverTime":[],"downloadCount":0,"downloadsOverTime":[],"referenceCount":0,"citationCount":0,"citationsOverTime":[],"partCount":0,"partOfCount":0,"versionCount":0,"versionOfCount":0,"created":"2022-07-12T01:41:56.000Z","registered":"2022-07-12T01:41:57.000Z","published":"2022","updated":"2022-07-13T01:24:20.000Z"},"relationships":{"client":{"data":{"id":"arxiv.content","type":"clients"}},"provider":{"data":{"id":"arxiv","type":"providers"}},"media":{"data":{"id":"10.48550/arxiv.2207.04779","type":"media"}},"references":{"data":[]},"citations":{"data":[]},"parts":{"data":[]},"partOf":{"data":[]},"versions":{"data":[]},"versionOf":{"data":[]}}}} +{"data":[{"id":"10.48550/arxiv.2207.04779","type":"dois","attributes":{"doi":"10.48550/arxiv.2207.04779","identifiers":[{"identifier":"2207.04779","identifierType":"arXiv"}],"creators":[{"name":"Bayer, Jonas","nameType":"Personal","givenName":"Jonas","familyName":"Bayer","affiliation":[],"nameIdentifiers":[]},{"name":"Benzmüller, Christoph","nameType":"Personal","givenName":"Christoph","familyName":"Benzmüller","affiliation":[],"nameIdentifiers":[]},{"name":"Buzzard, Kevin","nameType":"Personal","givenName":"Kevin","familyName":"Buzzard","affiliation":[],"nameIdentifiers":[]},{"name":"David, Marco","nameType":"Personal","givenName":"Marco","familyName":"David","affiliation":[],"nameIdentifiers":[]},{"name":"Lamport, Leslie","nameType":"Personal","givenName":"Leslie","familyName":"Lamport","affiliation":[],"nameIdentifiers":[]},{"name":"Matiyasevich, Yuri","nameType":"Personal","givenName":"Yuri","familyName":"Matiyasevich","affiliation":[],"nameIdentifiers":[]},{"name":"Paulson, Lawrence","nameType":"Personal","givenName":"Lawrence","familyName":"Paulson","affiliation":[],"nameIdentifiers":[]},{"name":"Schleicher, Dierk","nameType":"Personal","givenName":"Dierk","familyName":"Schleicher","affiliation":[],"nameIdentifiers":[]},{"name":"Stock, Benedikt","nameType":"Personal","givenName":"Benedikt","familyName":"Stock","affiliation":[],"nameIdentifiers":[]},{"name":"Zelmanov, Efim","nameType":"Personal","givenName":"Efim","familyName":"Zelmanov","affiliation":[],"nameIdentifiers":[]}],"titles":[{"title":"Mathematical Proof Between Generations"}],"publisher":"arXiv","container":{},"publicationYear":2022,"subjects":[{"lang":"en","subject":"History and Overview (math.HO)","subjectScheme":"arXiv"},{"lang":"en","subject":"Logic in Computer Science (cs.LO)","subjectScheme":"arXiv"},{"subject":"FOS: Mathematics","subjectScheme":"Fields of Science and Technology (FOS)"},{"subject":"FOS: Mathematics","schemeUri":"http://www.oecd.org/science/inno/38235147.pdf","subjectScheme":"Fields of Science and Technology (FOS)"},{"subject":"FOS: Computer and information sciences","subjectScheme":"Fields of Science and Technology (FOS)"},{"subject":"FOS: Computer and information sciences","schemeUri":"http://www.oecd.org/science/inno/38235147.pdf","subjectScheme":"Fields of Science and Technology (FOS)"}],"contributors":[],"dates":[{"date":"2022-07-08T14:42:33Z","dateType":"Submitted","dateInformation":"v1"},{"date":"2024-03-05T01:15:18Z","dateType":"Updated","dateInformation":"v1"},{"date":"2022-07","dateType":"Available","dateInformation":"v1"},{"date":"2022","dateType":"Issued"}],"language":null,"types":{"ris":"RPRT","bibtex":"article","citeproc":"article-journal","schemaOrg":"ScholarlyArticle","resourceType":"Article","resourceTypeGeneral":"Text"},"relatedIdentifiers":[{"relationType":"IsVersionOf","relatedIdentifier":"10.1090/noti2860","relatedIdentifierType":"DOI"}],"relatedItems":[],"sizes":[],"formats":[],"version":"1","rightsList":[{"rights":"arXiv.org perpetual, non-exclusive license","rightsUri":"http://arxiv.org/licenses/nonexclusive-distrib/1.0/"}],"descriptions":[{"description":"A proof is one of the most important concepts of mathematics. However, there is a striking difference between how a proof is defined in theory and how it is used in practice. This puts the unique status of mathematics as exact science into peril. Now may be the time to reconcile theory and practice, i.e. precision and intuition, through the advent of computer proof assistants. For the most time this has been a topic for experts in specialized communities. However, mathematical proofs have become increasingly sophisticated, stretching the boundaries of what is humanly comprehensible, so that leading mathematicians have asked for formal verification of their proofs. At the same time, major theorems in mathematics have recently been computer-verified by people from outside of these communities, even by beginning students. This article investigates the gap between the different definitions of a proof and possibilities to build bridges. It is written as a polemic or a collage by different members of the communities in mathematics and computer science at different stages of their careers, challenging well-known preconceptions and exploring new perspectives.","descriptionType":"Abstract"},{"description":"17 pages, 1 figure","descriptionType":"Other"}],"geoLocations":[],"fundingReferences":[],"url":"https://arxiv.org/abs/2207.04779","contentUrl":null,"metadataVersion":2,"schemaVersion":"http://datacite.org/schema/kernel-4","source":"mds","isActive":true,"state":"findable","reason":null,"viewCount":0,"downloadCount":0,"referenceCount":0,"citationCount":0,"partCount":0,"partOfCount":0,"versionCount":0,"versionOfCount":1,"created":"2022-07-12T01:41:56Z","registered":"2022-07-12T01:41:57Z","published":null,"updated":"2024-03-05T11:33:47Z"},"relationships":{"client":{"data":{"id":"arxiv.content","type":"clients"}}}}],"meta":{"total":1,"totalPages":1,"page":1,"states":[{"id":"findable","title":"Findable","count":1}],"resourceTypes":[{"id":"text","title":"Text","count":1}],"created":[{"id":"2022","title":"2022","count":1}],"published":[{"id":"2022","title":"2022","count":1}],"registered":[{"id":"2022","title":"2022","count":1}],"providers":[{"id":"arxiv","title":"arXiv","count":1}],"clients":[{"id":"arxiv.content","title":"arXiv","count":1}],"affiliations":[],"prefixes":[{"id":"10.48550","title":"10.48550","count":1}],"certificates":[],"licenses":[],"schemaVersions":[{"id":"4","title":"Schema 4","count":1}],"linkChecksStatus":[],"subjects":[{"id":"FOS: Computer and information sciences","title":"Fos: Computer And Information Sciences","count":1},{"id":"FOS: Mathematics","title":"Fos: Mathematics","count":1},{"id":"History and Overview (math.HO)","title":"History And Overview (Math.Ho)","count":1},{"id":"Logic in Computer Science (cs.LO)","title":"Logic In Computer Science (Cs.Lo)","count":1}],"fieldsOfScience":[{"id":"computer_and_information_sciences","title":"Computer and information sciences","count":1},{"id":"mathematics","title":"Mathematics","count":1}],"citations":[],"views":[],"downloads":[]},"links":{"self":"https://api.datacite.org/dois?query=10.48550/arxiv.2207.04779"}} From e35f859e06def64f44de426be01a12f74e9b363f Mon Sep 17 00:00:00 2001 From: "Gantner, Florian Klaus" Date: Tue, 16 Apr 2024 12:02:48 +0200 Subject: [PATCH 181/939] datacite api test for no results count (cherry picked from commit 9cd1a1093d08d74da6773112645dfd79b69dfdd0) --- ...DataCiteImportMetadataSourceServiceIT.java | 19 +++++++++++++++++++ .../dspace/app/rest/dataCite-noResults.json | 1 + 2 files changed, 20 insertions(+) create mode 100644 dspace-server-webapp/src/test/resources/org/dspace/app/rest/dataCite-noResults.json diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DataCiteImportMetadataSourceServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DataCiteImportMetadataSourceServiceIT.java index 83ebc40c796..c1481f0573c 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DataCiteImportMetadataSourceServiceIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DataCiteImportMetadataSourceServiceIT.java @@ -146,4 +146,23 @@ private ArrayList getRecords() { return records; } + @Test + public void dataCiteImportMetadataNoResultsTest() throws Exception { + context.turnOffAuthorisationSystem(); + CloseableHttpClient originalHttpClient = liveImportClientImpl.getHttpClient(); + CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class); + try (InputStream dataciteResp = getClass().getResourceAsStream("dataCite-noResults.json")) { + String dataciteTextResp = IOUtils.toString(dataciteResp, Charset.defaultCharset()); + liveImportClientImpl.setHttpClient(httpClient); + CloseableHttpResponse response = mockResponse(dataciteTextResp, 200, "OK"); + when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response); + context.restoreAuthSystemState(); + int tot = dataCiteServiceImpl.getRecordsCount("nocontent"); + assertEquals(0, tot); + Collection importRecords = dataCiteServiceImpl.getRecords("nocontent", 0 , -1); + assertEquals(0, importRecords.size()); + } finally { + liveImportClientImpl.setHttpClient(originalHttpClient); + } + } } diff --git a/dspace-server-webapp/src/test/resources/org/dspace/app/rest/dataCite-noResults.json b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/dataCite-noResults.json new file mode 100644 index 00000000000..c54fbe3636d --- /dev/null +++ b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/dataCite-noResults.json @@ -0,0 +1 @@ +{"data":[],"meta":{"total":0,"totalPages":0,"page":1},"links":{"self":"https://api.datacite.org/dois?query=nocontent"}} From 17c68323dbffea126add945ea18a1e2ad65cfe73 Mon Sep 17 00:00:00 2001 From: "Gantner, Florian Klaus" Date: Fri, 10 May 2024 18:08:19 +0200 Subject: [PATCH 182/939] avoid empty data to create phantom records for datacite import (cherry picked from commit 9667a3374d0aab18c91f53f59f8d6dc05e2df429) --- .../DataCiteImportMetadataSourceServiceImpl.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java index 298c30ec39a..6c65d96b375 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java @@ -143,12 +143,16 @@ public Collection getRecords(String query, int start, int count) t Iterator iterator = dataNode.iterator(); while (iterator.hasNext()) { JsonNode singleDoiNode = iterator.next(); - String json = singleDoiNode.at("/attributes").toString(); - records.add(transformSourceRecords(json)); + JsonNode singleDoiNodeAttribute = singleDoiNode.at("/attributes"); + if (!singleDoiNodeAttribute.isMissingNode()) { + records.add(transformSourceRecords(singleDoiNodeAttribute.toString())); + } } } else { - String json = dataNode.at("/attributes").toString(); - records.add(transformSourceRecords(json)); + JsonNode singleDoiNodeAttribute = dataNode.at("/attributes"); + if (!singleDoiNodeAttribute.isMissingNode()) { + records.add(transformSourceRecords(singleDoiNodeAttribute.toString())); + } } return records; From 1128b0eb10aba8170b1e9987eded3e75746eb0f5 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Mon, 13 May 2024 16:20:29 +0200 Subject: [PATCH 183/939] remove outdated comment as RSS feeds are supported in DS 7.3+ --- dspace/config/dspace.cfg | 2 -- 1 file changed, 2 deletions(-) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 0b7cd6ee44a..3063572514a 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1276,8 +1276,6 @@ plugin.named.org.dspace.content.license.LicenseArgumentFormatter = \ org.dspace.content.license.SimpleDSpaceObjectLicenseFormatter = eperson #### Syndication Feed (RSS) Settings ###### -# TODO: UNSUPPORTED in DSpace 7.0. Will be added in a later release - # URLs returned by the feed will point at the global handle server # (e.g. https://hdl.handle.net/123456789/1). Set to true to use local server # URLs (i.e. https://myserver.myorg/handle/123456789/1) From 2309496f67f940ccdabd6a4b46b05bff3935004c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eike=20Martin=20L=C3=B6hden?= Date: Fri, 19 Apr 2024 09:13:57 +0200 Subject: [PATCH 184/939] Correct relationship direction for persons in the project submission step. (cherry picked from commit ee2abfaf8801a252fd7033de7849220fb79ddab7) --- dspace/config/submission-forms.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace/config/submission-forms.xml b/dspace/config/submission-forms.xml index 39a4778356c..07ebdbd1c28 100644 --- a/dspace/config/submission-forms.xml +++ b/dspace/config/submission-forms.xml @@ -516,7 +516,7 @@ - isProjectOfPerson + isPersonOfProject person true From b0995cdf6c7aa1df38cd9b230966240acc2c785d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eike=20Martin=20L=C3=B6hden?= Date: Fri, 19 Apr 2024 09:15:43 +0200 Subject: [PATCH 185/939] Set correct metadata field for virtual metadata of persons in projects. (cherry picked from commit ff7d96b82ca832cc3a5342350eeccc29943e83b7) --- dspace/config/spring/api/virtual-metadata.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace/config/spring/api/virtual-metadata.xml b/dspace/config/spring/api/virtual-metadata.xml index efab52c1b7c..dee96eca7c0 100644 --- a/dspace/config/spring/api/virtual-metadata.xml +++ b/dspace/config/spring/api/virtual-metadata.xml @@ -77,7 +77,7 @@ - + - + + on the filters defined in item-filters.xml, eg. + Of course, you can use a filter on the VersionedDOIIdentifierProvider as well. + --> - From 2d150cb86cefbc84ed7e3de9a9c5230abe5c934a Mon Sep 17 00:00:00 2001 From: Pascal-Nicolas Becker Date: Tue, 4 Jun 2024 00:03:47 +0200 Subject: [PATCH 206/939] Cleanup doi filters We can set filters in identifier-service.xml. Setting them in modules/identifiers.cfg is just overidding the other one. To keep things simple, we should avoid having two different filters for the same issue. The filter configured in spring is working for any new DOI, while the one we take out here, is just working for items being run through the install item service. (cherry picked from commit 59aaf3e57cceca6cf6e5e878a94c2dc3889a7929) --- dspace/config/modules/identifiers.cfg | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/dspace/config/modules/identifiers.cfg b/dspace/config/modules/identifiers.cfg index 2660646af39..aff37c89c56 100644 --- a/dspace/config/modules/identifiers.cfg +++ b/dspace/config/modules/identifiers.cfg @@ -15,17 +15,12 @@ # Default: false #identifiers.submission.register = true -# This configuration property can be set to a filter name to determine if a PENDING DOI for an item -# should be queued for registration. If the filter doesn't match, the DOI will stay in PENDING or MINTED status -# so that the identifier itself persists in case it is considered for registration in the future. -# See doi-filter and other example filters in item-filters.xml. -# Default (always_true_filter) -#identifiers.submission.filter.install = doi-filter - # This optional configuration property can be set to a filter name, in case there are some initial rules to apply # when first deciding whether a DOI should be be created for a new workspace item with a PENDING status. # This filter is only applied if identifiers.submission.register is true. # This filter is updated as submission data is saved. +# If you're looking for the filter that decides whether a DOI of an installed item should be queued for registration +# at DataCite, please check the filter in the identifier service spring configuration. # Default: (always_true_filter) #identifiers.submission.filter.workspace = always_true_filter From fe76579d04f0e88d0c6e3516066e81df133e0e34 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 May 2024 19:51:43 +0000 Subject: [PATCH 207/939] Bump org.apache.commons:commons-configuration2 from 2.9.0 to 2.10.1 Bumps org.apache.commons:commons-configuration2 from 2.9.0 to 2.10.1. --- updated-dependencies: - dependency-name: org.apache.commons:commons-configuration2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] (cherry picked from commit df7220bd9814e2e31f1000ebc6074395c27a196c) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f7ce30b30df..8f213db2a55 100644 --- a/pom.xml +++ b/pom.xml @@ -1486,7 +1486,7 @@ org.apache.commons commons-configuration2 - 2.9.0 + 2.10.1 org.apache.commons From 0d4d065d457155ab6a3c7bfd479504af75c10a8f Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 23 May 2024 16:32:56 +0200 Subject: [PATCH 208/939] Create a temporary version of the ConfigurationPlaceholderConfigurer to overcome bu in 2.10 (cherry picked from commit d44d76ea03aca7490a11e9a1d9c52fdc669feb1c) --- .../util/DSpaceConfigurationInitializer.java | 6 +-- ...aceConfigurationPlaceholderConfigurer.java | 5 +- .../DSpaceConfigurationPropertySource.java | 54 +++++++++++++++++++ 3 files changed, 59 insertions(+), 6 deletions(-) create mode 100644 dspace-services/src/main/java/org/dspace/servicemanager/config/DSpaceConfigurationPropertySource.java diff --git a/dspace-api/src/test/java/org/dspace/util/DSpaceConfigurationInitializer.java b/dspace-api/src/test/java/org/dspace/util/DSpaceConfigurationInitializer.java index e2e0355f123..e5a8adb2fdf 100644 --- a/dspace-api/src/test/java/org/dspace/util/DSpaceConfigurationInitializer.java +++ b/dspace-api/src/test/java/org/dspace/util/DSpaceConfigurationInitializer.java @@ -8,7 +8,7 @@ package org.dspace.util; import org.apache.commons.configuration2.Configuration; -import org.apache.commons.configuration2.spring.ConfigurationPropertySource; +import org.dspace.servicemanager.config.DSpaceConfigurationPropertySource; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.springframework.context.ApplicationContextInitializer; @@ -38,8 +38,8 @@ public void initialize(final ConfigurableApplicationContext applicationContext) Configuration configuration = configurationService.getConfiguration(); // Create an Apache Commons Configuration Property Source from our configuration - ConfigurationPropertySource apacheCommonsConfigPropertySource = - new ConfigurationPropertySource(configuration.getClass().getName(), configuration); + DSpaceConfigurationPropertySource apacheCommonsConfigPropertySource = + new DSpaceConfigurationPropertySource(configuration.getClass().getName(), configuration); // Prepend it to the Environment's list of PropertySources // NOTE: This is added *first* in the list so that settings in DSpace's diff --git a/dspace-services/src/main/java/org/dspace/servicemanager/config/DSpaceConfigurationPlaceholderConfigurer.java b/dspace-services/src/main/java/org/dspace/servicemanager/config/DSpaceConfigurationPlaceholderConfigurer.java index b85450dcd03..caa715e21bf 100644 --- a/dspace-services/src/main/java/org/dspace/servicemanager/config/DSpaceConfigurationPlaceholderConfigurer.java +++ b/dspace-services/src/main/java/org/dspace/servicemanager/config/DSpaceConfigurationPlaceholderConfigurer.java @@ -8,7 +8,6 @@ package org.dspace.servicemanager.config; import org.apache.commons.configuration2.Configuration; -import org.apache.commons.configuration2.spring.ConfigurationPropertySource; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.env.MutablePropertySources; @@ -27,8 +26,8 @@ public class DSpaceConfigurationPlaceholderConfigurer extends PropertySourcesPlaceholderConfigurer { public DSpaceConfigurationPlaceholderConfigurer(Configuration configuration) { - ConfigurationPropertySource apacheCommonsConfigPropertySource = - new ConfigurationPropertySource(configuration.getClass().getName(), configuration); + DSpaceConfigurationPropertySource apacheCommonsConfigPropertySource = + new DSpaceConfigurationPropertySource(configuration.getClass().getName(), configuration); MutablePropertySources propertySources = new MutablePropertySources(); propertySources.addLast(apacheCommonsConfigPropertySource); setPropertySources(propertySources); diff --git a/dspace-services/src/main/java/org/dspace/servicemanager/config/DSpaceConfigurationPropertySource.java b/dspace-services/src/main/java/org/dspace/servicemanager/config/DSpaceConfigurationPropertySource.java new file mode 100644 index 00000000000..9bbf821766d --- /dev/null +++ b/dspace-services/src/main/java/org/dspace/servicemanager/config/DSpaceConfigurationPropertySource.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dspace.servicemanager.config; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.configuration2.Configuration; +import org.apache.commons.lang3.ArrayUtils; +import org.springframework.core.env.EnumerablePropertySource; + +/** + * Allow use of Apache Commons Configuration Objects as Spring PropertySources. + * This class is a copy of the ConfigurationPropertySource class in the Apache Commons Configuration + * project needed until to fix the issue https://issues.apache.org/jira/browse/CONFIGURATION-846 + */ +public class DSpaceConfigurationPropertySource extends EnumerablePropertySource { + + protected DSpaceConfigurationPropertySource(final String name) { + super(name); + } + + public DSpaceConfigurationPropertySource(final String name, final Configuration source) { + super(name, source); + } + + @Override + public Object getProperty(final String name) { + final String[] propValue = source.getStringArray(name); + return propValue != null && propValue.length == 1 ? propValue[0] : propValue; + } + + @Override + public String[] getPropertyNames() { + final List keys = new ArrayList<>(); + source.getKeys().forEachRemaining(keys::add); + return keys.toArray(ArrayUtils.EMPTY_STRING_ARRAY); + } +} From 4b5248fe1515fc39a5f42d206d3faf4c801ab340 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Mon, 27 May 2024 11:49:00 +0200 Subject: [PATCH 209/939] DURACOM-267 update commons-configuration2 fix according to the community feedback (cherry picked from commit f2d4ffc49c457a4f919a4bf2ef4171ea49246103) --- .../config/DSpaceConfigurationPropertySource.java | 10 ++++++++-- pom.xml | 2 ++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/dspace-services/src/main/java/org/dspace/servicemanager/config/DSpaceConfigurationPropertySource.java b/dspace-services/src/main/java/org/dspace/servicemanager/config/DSpaceConfigurationPropertySource.java index 9bbf821766d..a904e04c5a0 100644 --- a/dspace-services/src/main/java/org/dspace/servicemanager/config/DSpaceConfigurationPropertySource.java +++ b/dspace-services/src/main/java/org/dspace/servicemanager/config/DSpaceConfigurationPropertySource.java @@ -26,7 +26,7 @@ /** * Allow use of Apache Commons Configuration Objects as Spring PropertySources. - * This class is a copy of the ConfigurationPropertySource class in the Apache Commons Configuration + * This class is a temporary copy of the ConfigurationPropertySource class in the Apache Commons Configuration * project needed until to fix the issue https://issues.apache.org/jira/browse/CONFIGURATION-846 */ public class DSpaceConfigurationPropertySource extends EnumerablePropertySource { @@ -42,7 +42,13 @@ public DSpaceConfigurationPropertySource(final String name, final Configuration @Override public Object getProperty(final String name) { final String[] propValue = source.getStringArray(name); - return propValue != null && propValue.length == 1 ? propValue[0] : propValue; + if (propValue == null || propValue.length == 0) { + return null; + } else if (propValue.length == 1) { + return propValue[0]; + } else { + return propValue; + } } @Override diff --git a/pom.xml b/pom.xml index 8f213db2a55..ae83442235e 100644 --- a/pom.xml +++ b/pom.xml @@ -432,6 +432,8 @@ + + **/src/main/java/org/dspace/servicemanager/config/DSpaceConfigurationPropertySource.java **/src/test/resources/** **/src/test/data/** **/src/main/license/** From b9e6af675878c489b27a8171e18ea431d1d26170 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Fri, 31 May 2024 19:47:12 +0200 Subject: [PATCH 210/939] DURACOM-267 improve handling of empty configuration property (cherry picked from commit 0422b8786f9fb9d39f51403da94f69a77c20044c) --- .../DSpaceConfigurationPropertySource.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/dspace-services/src/main/java/org/dspace/servicemanager/config/DSpaceConfigurationPropertySource.java b/dspace-services/src/main/java/org/dspace/servicemanager/config/DSpaceConfigurationPropertySource.java index a904e04c5a0..d3394399301 100644 --- a/dspace-services/src/main/java/org/dspace/servicemanager/config/DSpaceConfigurationPropertySource.java +++ b/dspace-services/src/main/java/org/dspace/servicemanager/config/DSpaceConfigurationPropertySource.java @@ -41,13 +41,17 @@ public DSpaceConfigurationPropertySource(final String name, final Configuration @Override public Object getProperty(final String name) { - final String[] propValue = source.getStringArray(name); - if (propValue == null || propValue.length == 0) { - return null; - } else if (propValue.length == 1) { - return propValue[0]; + if (source.getProperty(name) != null) { + final String[] propValue = source.getStringArray(name); + if (propValue == null || propValue.length == 0) { + return ""; + } else if (propValue.length == 1) { + return propValue[0]; + } else { + return propValue; + } } else { - return propValue; + return null; } } From 7fc74bdc567c91137484255979ee4f6f4387db22 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Mon, 3 Jun 2024 10:07:06 +0200 Subject: [PATCH 211/939] DURACOM-267 assure that our custom DSpaceConfigurationPropertySource is used also for the spring context created by the DSpace kernel (cherry picked from commit 4fcf995b699b287f7e773ad8ca420cd68cae1e12) --- .../app/rest/utils/DSpaceConfigurationInitializer.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/DSpaceConfigurationInitializer.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/DSpaceConfigurationInitializer.java index 56b8ae32dce..9291a436c78 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/DSpaceConfigurationInitializer.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/DSpaceConfigurationInitializer.java @@ -8,7 +8,7 @@ package org.dspace.app.rest.utils; import org.apache.commons.configuration2.Configuration; -import org.apache.commons.configuration2.spring.ConfigurationPropertySource; +import org.dspace.servicemanager.config.DSpaceConfigurationPropertySource; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.slf4j.Logger; @@ -38,8 +38,8 @@ public void initialize(final ConfigurableApplicationContext applicationContext) Configuration configuration = configurationService.getConfiguration(); // Create an Apache Commons Configuration Property Source from our configuration - ConfigurationPropertySource apacheCommonsConfigPropertySource = - new ConfigurationPropertySource(configuration.getClass().getName(), configuration); + DSpaceConfigurationPropertySource apacheCommonsConfigPropertySource = + new DSpaceConfigurationPropertySource(configuration.getClass().getName(), configuration); // Prepend it to the Environment's list of PropertySources // NOTE: This is added *first* in the list so that settings in DSpace's ConfigurationService *override* From c5d139c9e1dc71482d1a9112b01ad718c317bfae Mon Sep 17 00:00:00 2001 From: nwoodward Date: Mon, 3 Jun 2024 12:53:44 -0500 Subject: [PATCH 212/939] updates creativecommons.org links to https --- .../impl/CCLicenseAddPatchOperation.java | 2 +- .../rest/CCLicenseAddPatchOperationIT.java | 4 +-- .../rest/CCLicenseRemovePatchOperationIT.java | 4 +-- .../org/dspace/app/rest/cinii-second.xml | 4 +-- .../org/dspace/license/cc-license-rdf.xml | 36 +++++++++---------- dspace/config/dspace.cfg | 2 +- dspace/config/submission-forms.xml | 12 +++---- 7 files changed, 32 insertions(+), 32 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/CCLicenseAddPatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/CCLicenseAddPatchOperation.java index ec4dff9f6c5..602ae5e9690 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/CCLicenseAddPatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/CCLicenseAddPatchOperation.java @@ -26,7 +26,7 @@ * Example: * curl -X PATCH http://${dspace.server.url}/api/submission/workspaceitems/31599 -H "Content-Type: * application/json" -d '[{ "op": "add", "path": "/sections/cclicense/uri", - * "value":"http://creativecommons.org/licenses/by-nc-sa/3.0/us/"}]' + * "value":"https://creativecommons.org/licenses/by-nc-sa/3.0/us/"}]' * */ public class CCLicenseAddPatchOperation extends AddPatchOperation { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CCLicenseAddPatchOperationIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CCLicenseAddPatchOperationIT.java index f07c816b9c9..eb49661259c 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CCLicenseAddPatchOperationIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CCLicenseAddPatchOperationIT.java @@ -63,7 +63,7 @@ public void patchSubmissionCCLicense() throws Exception { List ops = new ArrayList<>(); AddOperation addOperation = new AddOperation("/sections/cclicense/uri", - "http://creativecommons.org/licenses/by-nc-sa/4.0/"); + "https://creativecommons.org/licenses/by-nc-sa/4.0/"); ops.add(addOperation); String patchBody = getPatchContent(ops); @@ -74,7 +74,7 @@ public void patchSubmissionCCLicense() throws Exception { .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$.sections.cclicense", allOf( - hasJsonPath("$.uri", is("http://creativecommons.org/licenses/by-nc-sa/4.0/")), + hasJsonPath("$.uri", is("https://creativecommons.org/licenses/by-nc-sa/4.0/")), hasJsonPath("$.rights", is("Attribution-NonCommercial-ShareAlike 4.0 International")), hasJsonPath("$.file.name", is("license_rdf")) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CCLicenseRemovePatchOperationIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CCLicenseRemovePatchOperationIT.java index 8e01678899a..dd2b99d158b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CCLicenseRemovePatchOperationIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CCLicenseRemovePatchOperationIT.java @@ -64,7 +64,7 @@ public void patchRemoveSubmissionCCLicense() throws Exception { // First add a license and verify it is added List ops = new ArrayList<>(); AddOperation addOperation = new AddOperation("/sections/cclicense/uri", - "http://creativecommons.org/licenses/by-nc-sa/4.0/"); + "https://creativecommons.org/licenses/by-nc-sa/4.0/"); ops.add(addOperation); String patchBody = getPatchContent(ops); @@ -75,7 +75,7 @@ public void patchRemoveSubmissionCCLicense() throws Exception { .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$.sections.cclicense", allOf( - hasJsonPath("$.uri", is("http://creativecommons.org/licenses/by-nc-sa/4.0/")), + hasJsonPath("$.uri", is("https://creativecommons.org/licenses/by-nc-sa/4.0/")), hasJsonPath("$.rights", is("Attribution-NonCommercial-ShareAlike 4.0 International")), hasJsonPath("$.file.name", is("license_rdf")) diff --git a/dspace-server-webapp/src/test/resources/org/dspace/app/rest/cinii-second.xml b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/cinii-second.xml index 8eb68ff047f..cf1845ca563 100644 --- a/dspace-server-webapp/src/test/resources/org/dspace/app/rest/cinii-second.xml +++ b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/cinii-second.xml @@ -57,7 +57,7 @@ 322 331 - © 2022 The Author(s). Published by National Institute for Materials Science in partnership with Taylor & Francis Group. This is an Open Access article distributed under the terms of the Creative Commons Attribution License (http://creativecommons.org/licenses/by/4.0/), which permits unrestricted use, distribution, and reproduction in any medium, provided the original work is properly cited. + © 2022 The Author(s). Published by National Institute for Materials Science in partnership with Taylor & Francis Group. This is an Open Access article distributed under the terms of the Creative Commons Attribution License (https://creativecommons.org/licenses/by/4.0/), which permits unrestricted use, distribution, and reproduction in any medium, provided the original work is properly cited. @@ -74,4 +74,4 @@ oai:irdb.nii.ac.jp:01257:0005348137 - \ No newline at end of file + diff --git a/dspace-server-webapp/src/test/resources/org/dspace/license/cc-license-rdf.xml b/dspace-server-webapp/src/test/resources/org/dspace/license/cc-license-rdf.xml index 5ff75ee4c74..2051406b550 100644 --- a/dspace-server-webapp/src/test/resources/org/dspace/license/cc-license-rdf.xml +++ b/dspace-server-webapp/src/test/resources/org/dspace/license/cc-license-rdf.xml @@ -1,31 +1,31 @@ - http://creativecommons.org/licenses/by-nc-sa/4.0/ + https://creativecommons.org/licenses/by-nc-sa/4.0/ Attribution-NonCommercial-ShareAlike 4.0 International false - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + - Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. + Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index fc0f516c3aa..fc840ef0035 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1011,7 +1011,7 @@ cc.license.classfilter = publicdomain, recombo, mark # Jurisdiction of the creative commons license -- is it ported or not? # Use the key from the url seen in the response from the api call, -# http://api.creativecommons.org/rest/1.5/support/jurisdictions +# https://api.creativecommons.org/rest/1.5/support/jurisdictions # Commented out means the license is unported. # (e.g. nz = New Zealand, uk = England and Wales, jp = Japan) # or set value none for user-selected jurisdiction diff --git a/dspace/config/submission-forms.xml b/dspace/config/submission-forms.xml index 07ebdbd1c28..0b1e049c81c 100644 --- a/dspace/config/submission-forms.xml +++ b/dspace/config/submission-forms.xml @@ -1549,27 +1549,27 @@ Attribution (CC-BY) - http://creativecommons.org/licenses/by/4.0/ + https://creativecommons.org/licenses/by/4.0/ Attribution, No Derivative Works (CC-BY-ND) - http://creativecommons.org/licenses/by-nd/4.0/ + https://creativecommons.org/licenses/by-nd/4.0/ Attribution, Share-alike (CC-BY-SA) - http://creativecommons.org/licenses/by-sa/4.0/ + https://creativecommons.org/licenses/by-sa/4.0/ Attribution, Non-commercial (CC-BY-NC) - http://creativecommons.org/licenses/by-nc/4.0/ + https://creativecommons.org/licenses/by-nc/4.0/ Attribution, Non-commercial, No Derivative Works (CC-BY-NC-ND) - http://creativecommons.org/licenses/by-nc-nd/4.0/ + https://creativecommons.org/licenses/by-nc-nd/4.0/ Attribution, Non-commercial, Share-alike (CC-BY-NC-SA) - http://creativecommons.org/licenses/by-nc-sa/4.0/ + https://creativecommons.org/licenses/by-nc-sa/4.0/ From 4cd3b75e67e04aae1dd25be13a4d532e74940642 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Morin?= Date: Fri, 3 May 2024 14:08:50 -0400 Subject: [PATCH 213/939] Fixed classpath issue in test_database target (cherry picked from commit 6937c19973f0810a4b1eb646bb0eaefdded9cae6) --- dspace/src/main/config/build.xml | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/dspace/src/main/config/build.xml b/dspace/src/main/config/build.xml index 8b33522d2b1..940287c9d16 100644 --- a/dspace/src/main/config/build.xml +++ b/dspace/src/main/config/build.xml @@ -764,8 +764,25 @@ Common usage: + + + + + + + + + + + + + + + + + Date: Tue, 4 Jun 2024 09:43:10 -0500 Subject: [PATCH 214/939] Ensure work directory is cleaned up whether zip file is created successfully or an error occurs. (cherry picked from commit b7f764746c8a88d8db3ec3cf989b6aa3eda8e087) --- .../app/itemexport/ItemExportServiceImpl.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExportServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExportServiceImpl.java index a884f9b0756..7c80e1ea7dc 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExportServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExportServiceImpl.java @@ -490,7 +490,7 @@ public void exportAsZip(Context context, Iterator items, File wkDir = new File(workDir); if (!wkDir.exists() && !wkDir.mkdirs()) { - logError("Unable to create working direcory"); + logError("Unable to create working directory"); } File dnDir = new File(destDirName); @@ -498,11 +498,18 @@ public void exportAsZip(Context context, Iterator items, logError("Unable to create destination directory"); } - // export the items using normal export method - exportItem(context, items, workDir, seqStart, migrate, excludeBitstreams); + try { + // export the items using normal export method (this exports items to our workDir) + exportItem(context, items, workDir, seqStart, migrate, excludeBitstreams); - // now zip up the export directory created above - zip(workDir, destDirName + System.getProperty("file.separator") + zipFileName); + // now zip up the workDir directory created above + zip(workDir, destDirName + System.getProperty("file.separator") + zipFileName); + } finally { + // Cleanup workDir created above, if it still exists + if (wkDir.exists()) { + deleteDirectory(wkDir); + } + } } @Override From 26658b666517f9da71de5e1ff706dce692da6350 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Thu, 16 May 2024 16:36:35 +0200 Subject: [PATCH 215/939] evaluate surname and given-name if authname is not present (cherry picked from commit 8b5f0445373e5ce60be2b12fd1d487f54f74e350) --- .../contributor/AuthorMetadataContributor.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/AuthorMetadataContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/AuthorMetadataContributor.java index 26063dc7441..62b7fe81e39 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/AuthorMetadataContributor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/AuthorMetadataContributor.java @@ -71,7 +71,7 @@ public Collection contributeMetadata(Element element) { } /** - * Retrieve the the ScopusID, orcid, author name and affiliationID + * Retrieve the ScopusID, orcid, author name and affiliationID * metadata associated with the given element object. * If the value retrieved from the element is empty * it is set PLACEHOLDER_PARENT_METADATA_VALUE @@ -82,11 +82,19 @@ public Collection contributeMetadata(Element element) { private List getMetadataOfAuthors(Element element) throws JaxenException { List metadatums = new ArrayList(); Element authname = element.getChild("authname", NAMESPACE); + Element surname = element.getChild("surname", NAMESPACE); + Element givenName = element.getChild("given-name", NAMESPACE); Element scopusId = element.getChild("authid", NAMESPACE); Element orcid = element.getChild("orcid", NAMESPACE); Element afid = element.getChild("afid", NAMESPACE); - addMetadatum(metadatums, getMetadata(getElementValue(authname), this.authname)); + if (authname != null) { + addMetadatum(metadatums, getMetadata(getElementValue(authname), this.authname)); + } else { + addMetadatum(metadatums, getMetadata(getElementValue(surname) + ", " + + getElementValue(givenName), this.authname)); + } + addMetadatum(metadatums, getMetadata(getElementValue(scopusId), this.scopusId)); addMetadatum(metadatums, getMetadata(getElementValue(orcid), this.orcid)); addMetadatum(metadatums, getMetadata(StringUtils.isNotBlank(afid.getValue()) @@ -170,4 +178,4 @@ public void setAffiliation(MetadataFieldConfig affiliation) { this.affiliation = affiliation; } -} \ No newline at end of file +} From 6e5c5089abe776b0e3b8b364ec10393855275429 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Thu, 16 May 2024 16:37:48 +0200 Subject: [PATCH 216/939] Update scopus-empty-resp.xml (cherry picked from commit 4b2ea66f19324d204058084434ff99a71fd6588e) --- .../resources/org/dspace/app/rest/scopus-empty-resp.xml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/test/resources/org/dspace/app/rest/scopus-empty-resp.xml b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/scopus-empty-resp.xml index b2b4264b5c3..3e0ecbead35 100644 --- a/dspace-server-webapp/src/test/resources/org/dspace/app/rest/scopus-empty-resp.xml +++ b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/scopus-empty-resp.xml @@ -3,9 +3,11 @@ 0 0 0 - - + + Result set was empty - \ No newline at end of file + From 2b1270197e2d4af1d7051a2d75c501d00d3fc318 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Thu, 16 May 2024 16:40:04 +0200 Subject: [PATCH 217/939] improve handling of 0 hits responses of Scopus API (cherry picked from commit b12bd6ce565bb95fcb470a4ea156abd0a6ca531f) --- ...ScopusImportMetadataSourceServiceImpl.java | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/ScopusImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/ScopusImportMetadataSourceServiceImpl.java index 944d467e315..e61ca052868 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/ScopusImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/ScopusImportMetadataSourceServiceImpl.java @@ -14,6 +14,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -24,6 +25,8 @@ import javax.el.MethodNotFoundException; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.content.Item; import org.dspace.importer.external.datamodel.ImportRecord; import org.dspace.importer.external.datamodel.Query; @@ -62,6 +65,8 @@ public class ScopusImportMetadataSourceServiceImpl extends AbstractImportMetadat @Autowired private LiveImportClient liveImportClient; + private final static Logger log = LogManager.getLogger(); + public LiveImportClient getLiveImportClient() { return liveImportClient; } @@ -200,6 +205,9 @@ public Integer call() throws Exception { Map requestParams = getRequestParameters(query, null, null, null); params.put(URI_PARAMETERS, requestParams); String response = liveImportClient.executeHttpGetRequest(timeout, url, params); + if (StringUtils.isEmpty(response)) { + return 0; + } SAXBuilder saxBuilder = new SAXBuilder(); // disallow DTD parsing to ensure no XXE attacks can occur @@ -245,6 +253,10 @@ public List call() throws Exception { Map requestParams = getRequestParameters(queryString, viewMode, null, null); params.put(URI_PARAMETERS, requestParams); String response = liveImportClient.executeHttpGetRequest(timeout, url, params); + if (StringUtils.isEmpty(response)) { + return results; + } + List elements = splitToRecords(response); for (Element record : elements) { results.add(transformSourceRecords(record)); @@ -304,6 +316,10 @@ public List call() throws Exception { Map requestParams = getRequestParameters(queryString, viewMode, start, count); params.put(URI_PARAMETERS, requestParams); String response = liveImportClient.executeHttpGetRequest(timeout, url, params); + if (StringUtils.isEmpty(response)) { + return results; + } + List elements = splitToRecords(response); for (Element record : elements) { results.add(transformSourceRecords(record)); @@ -349,6 +365,10 @@ public List call() throws Exception { Map requestParams = getRequestParameters(queryString, viewMode, start, count); params.put(URI_PARAMETERS, requestParams); String response = liveImportClient.executeHttpGetRequest(timeout, url, params); + if (StringUtils.isEmpty(response)) { + return results; + } + List elements = splitToRecords(response); for (Element record : elements) { results.add(transformSourceRecords(record)); @@ -383,10 +403,16 @@ private List splitToRecords(String recordsSrc) { saxBuilder.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true); Document document = saxBuilder.build(new StringReader(recordsSrc)); Element root = document.getRootElement(); - List records = root.getChildren("entry",Namespace.getNamespace("http://www.w3.org/2005/Atom")); + String totalResults = root.getChildText("totalResults", Namespace.getNamespace("http://a9.com/-/spec/opensearch/1.1/")); + if (totalResults != null && "0".equals(totalResults)) { + log.debug("got Scopus API with empty response"); + return Collections.emptyList(); + } + List records = root.getChildren("entry", Namespace.getNamespace("http://www.w3.org/2005/Atom")); return records; } catch (JDOMException | IOException e) { - return new ArrayList(); + log.warn("got unexpected XML response from Scopus API: " + e.getMessage()); + return Collections.emptyList(); } } @@ -422,4 +448,4 @@ public void setInstKey(String instKey) { this.instKey = instKey; } -} \ No newline at end of file +} From 8e56fdd95b2054a49606103554f5bea1366fce21 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Thu, 16 May 2024 16:41:31 +0200 Subject: [PATCH 218/939] fixed failed test (cherry picked from commit 6989cb6f15761ac453ada2b5ce69b6d2dbbd3e78) --- .../app/rest/ScopusImportMetadataSourceServiceIT.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScopusImportMetadataSourceServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScopusImportMetadataSourceServiceIT.java index b9310e3adba..7f6cb53ce40 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScopusImportMetadataSourceServiceIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScopusImportMetadataSourceServiceIT.java @@ -96,7 +96,7 @@ public void scopusImportMetadataGetRecordsCountTest() throws Exception { } @Test - public void scopusImportMetadataGetRecordsEmptyResponceTest() throws Exception { + public void scopusImportMetadataGetRecordsEmptyResponseTest() throws Exception { context.turnOffAuthorisationSystem(); String originApiKey = scopusServiceImpl.getApiKey(); if (StringUtils.isBlank(originApiKey)) { @@ -113,8 +113,7 @@ public void scopusImportMetadataGetRecordsEmptyResponceTest() throws Exception { context.restoreAuthSystemState(); Collection recordsImported = scopusServiceImpl.getRecords("roma", 0, 20); - ImportRecord importedRecord = recordsImported.iterator().next(); - assertTrue(importedRecord.getValueList().isEmpty()); + assertTrue(recordsImported.isEmpty()); } finally { liveImportClientImpl.setHttpClient(originalHttpClient); scopusServiceImpl.setApiKey(originApiKey); @@ -229,4 +228,4 @@ private ArrayList getRecords() { return records; } -} \ No newline at end of file +} From 8d82457eb7b3f5f96fa164c0ca5905bde34a4649 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Mon, 27 May 2024 00:17:53 +0200 Subject: [PATCH 219/939] 115434: Added relatedEntityType parameter to byLabel endpoint to differentiate relationships with same label and different entity types (cherry picked from commit 8512fab392c99d774197c026abbdfc941764ae9d) --- .../RelationshipRestRepository.java | 35 +++++++++--- .../rest/RelationshipRestRepositoryIT.java | 55 +++++++++++++++++++ 2 files changed, 83 insertions(+), 7 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipRestRepository.java index 21f43ddacd6..6be4f390daa 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipRestRepository.java @@ -29,9 +29,11 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.DSpaceObject; +import org.dspace.content.EntityType; import org.dspace.content.Item; import org.dspace.content.Relationship; import org.dspace.content.RelationshipType; +import org.dspace.content.service.EntityTypeService; import org.dspace.content.service.ItemService; import org.dspace.content.service.RelationshipService; import org.dspace.content.service.RelationshipTypeService; @@ -60,6 +62,9 @@ public class RelationshipRestRepository extends DSpaceRestRepository findByLabel(@Parameter(value = "label", required = true) String label, @Parameter(value = "dso", required = false) UUID dsoId, + @Parameter(value = "relatedEntityType") String relatedEntityType, Pageable pageable) throws SQLException { Context context = obtainContext(); @@ -352,14 +359,28 @@ public Page findByLabel(@Parameter(value = "label", required = if (item == null) { throw new ResourceNotFoundException("The request DSO with id: " + dsoId + " was not found"); } + + EntityType dsoEntityType = itemService.getEntityType(context, item); + + if (dsoEntityType == null) { + throw new UnprocessableEntityException(String.format( + "The request DSO with id: %s doesn't have an entity type", dsoId)); + } + for (RelationshipType relationshipType : relationshipTypeList) { - boolean isLeft = false; - if (relationshipType.getLeftwardType().equalsIgnoreCase(label)) { - isLeft = true; + if (relatedEntityType == null || + relationshipType.getRightType().getLabel().equals(dsoEntityType.getLabel()) && + relationshipType.getLeftType().getLabel().equals(relatedEntityType) || + relationshipType.getRightType().getLabel().equals(relatedEntityType) && + relationshipType.getLeftType().getLabel().equals(dsoEntityType.getLabel())) { + boolean isLeft = relationshipType.getLeftwardType().equalsIgnoreCase(label); + total += + relationshipService.countByItemAndRelationshipType(context, item, relationshipType, isLeft); + relationships.addAll( + relationshipService.findByItemAndRelationshipType(context, item, relationshipType, + isLeft, pageable.getPageSize(), + Math.toIntExact(pageable.getOffset()))); } - total += relationshipService.countByItemAndRelationshipType(context, item, relationshipType, isLeft); - relationships.addAll(relationshipService.findByItemAndRelationshipType(context, item, relationshipType, - isLeft, pageable.getPageSize(), Math.toIntExact(pageable.getOffset()))); } } else { for (RelationshipType relationshipType : relationshipTypeList) { @@ -377,7 +398,7 @@ public Page findByLabel(@Parameter(value = "label", required = * of potentially related items we need to know which of these other items * are already in a specific relationship with the focus item and, * by exclusion which ones are not yet related. - * + * * @param typeId The relationship type id to apply as a filter to the returned relationships * @param label The name of the relation as defined from the side of the 'focusItem' * @param focusUUID The uuid of the item to be checked on the side defined by 'relationshipLabel' diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java index d8e53c770c7..d126c0352d0 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java @@ -2323,6 +2323,61 @@ public void findRelationshipByLabelTest() throws Exception { ; } + @Test + public void findRelationshipByLabelWithRelatedEntityTypeTest() throws Exception { + context.turnOffAuthorisationSystem(); + RelationshipType isAuthorOfPublicationRelationshipTypePublication = relationshipTypeService + .findbyTypesAndTypeName(context, entityTypeService.findByEntityType(context, "Publication"), + entityTypeService.findByEntityType(context, "Person"), + "isAuthorOfPublication", "isPublicationOfAuthor"); + RelationshipType isAuthorOfPublicationRelationshipTypeOrgUnit = relationshipTypeService + .findbyTypesAndTypeName(context, entityTypeService.findByEntityType(context, "Publication"), + entityTypeService.findByEntityType(context, "OrgUnit"), + "isAuthorOfPublication", "isPublicationOfAuthor"); + + // We're creating a Relationship of type isAuthorOfPublication between a Publication and a Person + Relationship relationship1 = RelationshipBuilder + .createRelationshipBuilder(context, publication1, author1, isAuthorOfPublicationRelationshipTypePublication) + .build(); + + // We're creating a Relationship of type isAuthorOfPublication between a Publication and an OrgUnit + Relationship relationship2 = RelationshipBuilder + .createRelationshipBuilder(context, publication1, orgUnit1, isAuthorOfPublicationRelationshipTypeOrgUnit) + .build(); + context.restoreAuthSystemState(); + + // Perform a GET request to the searchByLabel endpoint, asking for Relationships of type isAuthorOfPublication + // With an extra parameter namely DSO which resolves to the publication used by both relationships. + // Both relationships should be returned if we don't specify the DSO's related entity type + getClient().perform(get("/api/core/relationships/search/byLabel") + .param("label", "isAuthorOfPublication") + .param("dso", publication1.getID().toString()) + .param("projection", "full")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page", is(PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 2)))) + .andExpect(jsonPath("$._embedded.relationships", containsInAnyOrder( + RelationshipMatcher.matchRelationship(relationship1), + RelationshipMatcher.matchRelationship(relationship2) + ))) + ; + + // Perform a GET request to the searchByLabel endpoint, asking for Relationships of type isAuthorOfPublication + // With an extra parameter namely DSO which resolves to the publication used by both relationships. + // Only the Person relationship should be returned if we specify the DSO's related entity type + getClient().perform(get("/api/core/relationships/search/byLabel") + .param("label", "isAuthorOfPublication") + .param("dso", publication1.getID().toString()) + .param("relatedEntityType", "Person") + .param("projection", "full")) + + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page", is(PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 1)))) + .andExpect(jsonPath("$._embedded.relationships", containsInAnyOrder( + RelationshipMatcher.matchRelationship(relationship1) + ))) + ; + } + @Test public void putRelationshipWithNonexistentID() throws Exception { context.turnOffAuthorisationSystem(); From 7789304ebe531fe16ddad7af798998249f0197ba Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 7 Jun 2024 20:53:48 +0200 Subject: [PATCH 220/939] 115434: Added test proving that different values for relatedEntityType return different results (cherry picked from commit bbae1fb0d110e2c1e7ad63bc01ac1084590ad62c) --- .../app/rest/RelationshipRestRepositoryIT.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java index d126c0352d0..aad26ba2c11 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java @@ -2376,6 +2376,22 @@ public void findRelationshipByLabelWithRelatedEntityTypeTest() throws Exception RelationshipMatcher.matchRelationship(relationship1) ))) ; + + // Perform a GET request to the searchByLabel endpoint, asking for Relationships of type isAuthorOfPublication + // With an extra parameter namely DSO which resolves to the publication used by both relationships. + // Only the OrgUnit relationship should be returned if we specify the DSO's related entity type + getClient().perform(get("/api/core/relationships/search/byLabel") + .param("label", "isAuthorOfPublication") + .param("dso", publication1.getID().toString()) + .param("relatedEntityType", "OrgUnit") + .param("projection", "full")) + + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page", is(PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 1)))) + .andExpect(jsonPath("$._embedded.relationships", containsInAnyOrder( + RelationshipMatcher.matchRelationship(relationship2) + ))) + ; } @Test From 928dad1c5cbbbe95b69cf4b3d6f533e72a25d221 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 29 Apr 2024 11:21:08 -0500 Subject: [PATCH 221/939] Ensure List is not empty before returning first value (cherry picked from commit 068bcdf3af2a74218cfb77fdb944e7384de3db75) --- .../dspace/content/DSpaceObjectServiceImpl.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java index 2119959073f..f47ed47d4c0 100644 --- a/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java @@ -314,20 +314,26 @@ public List addMetadata(Context context, T dso, MetadataField met @Override public MetadataValue addMetadata(Context context, T dso, MetadataField metadataField, String language, String value, String authority, int confidence) throws SQLException { - return addMetadata(context, dso, metadataField, language, Arrays.asList(value), Arrays.asList(authority), - Arrays.asList(confidence)).get(0); + List metadataValues = + addMetadata(context, dso, metadataField, language, Arrays.asList(value), Arrays.asList(authority), + Arrays.asList(confidence)); + return CollectionUtils.isNotEmpty(metadataValues) ? metadataValues.get(0) : null; } @Override public MetadataValue addMetadata(Context context, T dso, String schema, String element, String qualifier, String lang, String value) throws SQLException { - return addMetadata(context, dso, schema, element, qualifier, lang, Arrays.asList(value)).get(0); + List metadataValues = + addMetadata(context, dso, schema, element, qualifier, lang, Arrays.asList(value)); + return CollectionUtils.isNotEmpty(metadataValues) ? metadataValues.get(0) : null; } @Override public MetadataValue addMetadata(Context context, T dso, MetadataField metadataField, String language, String value) throws SQLException { - return addMetadata(context, dso, metadataField, language, Arrays.asList(value)).get(0); + List metadataValues = + addMetadata(context, dso, metadataField, language, Arrays.asList(value)); + return CollectionUtils.isNotEmpty(metadataValues) ? metadataValues.get(0) : null; } @Override From 8407c571db341d7620cfd1bf8a73dce1ed1be229 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 24 May 2024 09:19:25 -0500 Subject: [PATCH 222/939] Throw IllegalArgumentException if addMetadata() called without values. Add unit tests to prove it works (cherry picked from commit ed918a8d0c51e9c2071991b876f832fe85d1b8f1) --- .../content/DSpaceObjectServiceImpl.java | 21 ++++++++++++++++ .../java/org/dspace/content/ItemTest.java | 24 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java index f47ed47d4c0..4e3fa42162c 100644 --- a/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java @@ -242,10 +242,31 @@ public List addMetadata(Context context, T dso, MetadataField met } + /** + * Add metadata value(s) to a MetadataField of a DSpace Object + * @param context current DSpace context + * @param dso DSpaceObject to modify + * @param metadataField MetadataField to add values to + * @param lang Language code to add + * @param values One or more metadata values to add + * @param authorities One or more authorities to add + * @param confidences One or more confidences to add (for authorities) + * @param placeSupplier Supplier of "place" for new metadata values + * @return List of newly added metadata values + * @throws SQLException if database error occurs + * @throws IllegalArgumentException for an empty list of values + */ public List addMetadata(Context context, T dso, MetadataField metadataField, String lang, List values, List authorities, List confidences, Supplier placeSupplier) throws SQLException { + // Throw an error if we are attempting to add empty values + if (values == null || values.isEmpty()) { + throw new IllegalArgumentException("Cannot add empty values to a new metadata field " + + metadataField.toString() + " on object with uuid = " + + dso.getID().toString() + " and type = " + getTypeText(dso)); + } + boolean authorityControlled = metadataAuthorityService.isAuthorityControlled(metadataField); boolean authorityRequired = metadataAuthorityService.isAuthorityRequired(metadataField); List newMetadata = new ArrayList<>(); diff --git a/dspace-api/src/test/java/org/dspace/content/ItemTest.java b/dspace-api/src/test/java/org/dspace/content/ItemTest.java index ada9bbc1590..de9919f4e3d 100644 --- a/dspace-api/src/test/java/org/dspace/content/ItemTest.java +++ b/dspace-api/src/test/java/org/dspace/content/ItemTest.java @@ -537,6 +537,17 @@ public void testAddMetadata_5args_1() throws SQLException { assertThat("testAddMetadata_5args_1 11", dc.get(1).getValue(), equalTo(values[1])); } + @Test(expected = IllegalArgumentException.class) + public void testAddMetadata_5args_no_values() throws Exception { + String schema = "dc"; + String element = "contributor"; + String qualifier = "author"; + String lang = Item.ANY; + String[] values = {}; + itemService.addMetadata(context, it, schema, element, qualifier, lang, Arrays.asList(values)); + fail("IllegalArgumentException expected"); + } + /** * Test of addMetadata method, of class Item. */ @@ -614,6 +625,19 @@ public void testAddMetadata_7args_1_noauthority() throws SQLException { assertThat("testAddMetadata_7args_1 15", dc.get(1).getConfidence(), equalTo(-1)); } + @Test(expected = IllegalArgumentException.class) + public void testAddMetadata_7args_no_values() throws Exception { + String schema = "dc"; + String element = "contributor"; + String qualifier = "author"; + String lang = Item.ANY; + List values = new ArrayList(); + List authorities = new ArrayList(); + List confidences = new ArrayList(); + itemService.addMetadata(context, it, schema, element, qualifier, lang, values, authorities, confidences); + fail("IllegalArgumentException expected"); + } + /** * Test of addMetadata method, of class Item. */ From 30dafa09d0da90d55fa8898abdfb948cfcd74fa9 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 24 May 2024 09:54:42 -0500 Subject: [PATCH 223/939] Fix testAddMetadata_5args_2 to no longer be *identical* to testAddMetadata_5args_1. It appears this second test was meant to test a different addMetadata() method which accepts a single Value instead of a List (cherry picked from commit 25f722ed980fe13a7244f90f3799efcaa9ba6f7c) --- .../test/java/org/dspace/content/ItemTest.java | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/content/ItemTest.java b/dspace-api/src/test/java/org/dspace/content/ItemTest.java index de9919f4e3d..e3337d9d92a 100644 --- a/dspace-api/src/test/java/org/dspace/content/ItemTest.java +++ b/dspace-api/src/test/java/org/dspace/content/ItemTest.java @@ -639,7 +639,7 @@ public void testAddMetadata_7args_no_values() throws Exception { } /** - * Test of addMetadata method, of class Item. + * This is the same as testAddMetadata_5args_1 except it is adding a *single* value as a String, not a List. */ @Test public void testAddMetadata_5args_2() throws SQLException { @@ -647,24 +647,18 @@ public void testAddMetadata_5args_2() throws SQLException { String element = "contributor"; String qualifier = "author"; String lang = Item.ANY; - List values = Arrays.asList("value0", "value1"); - itemService.addMetadata(context, it, schema, element, qualifier, lang, values); + String value = "value0"; + itemService.addMetadata(context, it, schema, element, qualifier, lang, value); List dc = itemService.getMetadata(it, schema, element, qualifier, lang); assertThat("testAddMetadata_5args_2 0", dc, notNullValue()); - assertTrue("testAddMetadata_5args_2 1", dc.size() == 2); + assertTrue("testAddMetadata_5args_2 1", dc.size() == 1); assertThat("testAddMetadata_5args_2 2", dc.get(0).getMetadataField().getMetadataSchema().getName(), equalTo(schema)); assertThat("testAddMetadata_5args_2 3", dc.get(0).getMetadataField().getElement(), equalTo(element)); assertThat("testAddMetadata_5args_2 4", dc.get(0).getMetadataField().getQualifier(), equalTo(qualifier)); assertThat("testAddMetadata_5args_2 5", dc.get(0).getLanguage(), equalTo(lang)); - assertThat("testAddMetadata_5args_2 6", dc.get(0).getValue(), equalTo(values.get(0))); - assertThat("testAddMetadata_5args_2 7", dc.get(1).getMetadataField().getMetadataSchema().getName(), - equalTo(schema)); - assertThat("testAddMetadata_5args_2 8", dc.get(1).getMetadataField().getElement(), equalTo(element)); - assertThat("testAddMetadata_5args_2 9", dc.get(1).getMetadataField().getQualifier(), equalTo(qualifier)); - assertThat("testAddMetadata_5args_2 10", dc.get(1).getLanguage(), equalTo(lang)); - assertThat("testAddMetadata_5args_2 11", dc.get(1).getValue(), equalTo(values.get(1))); + assertThat("testAddMetadata_5args_2 6", dc.get(0).getValue(), equalTo(value)); } /** From 872af3315590a70ae72466ba8bdef7741b365489 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 24 May 2024 12:01:03 -0500 Subject: [PATCH 224/939] Fix bug in MetadataImport where it could call addMetadata() with empty values. Minor refactors to MetadataImportIT to make findItemByName more efficient. (cherry picked from commit f8ac8edc4970086c74e5d5f7b492199bd9bf3ba7) --- .../dspace/app/bulkedit/MetadataImport.java | 10 ++++--- .../dspace/app/bulkedit/MetadataImportIT.java | 27 ++++++++++--------- 2 files changed, 21 insertions(+), 16 deletions(-) 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 af6976acb14..ad46cb95c35 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 @@ -825,8 +825,10 @@ protected void compareAndUpdate(Context c, Item item, String[] fromCSV, boolean addRelationships(c, item, element, values); } else { itemService.clearMetadata(c, item, schema, element, qualifier, language); - itemService.addMetadata(c, item, schema, element, qualifier, - language, values, authorities, confidences); + if (!values.isEmpty()) { + itemService.addMetadata(c, item, schema, element, qualifier, + language, values, authorities, confidences); + } itemService.update(c, item); } } @@ -1121,8 +1123,8 @@ protected void add(Context c, String[] fromCSV, String md, BulkEditChange change .getAuthoritySeparator() + dcv.getConfidence(); } - // Add it - if ((value != null) && (!"".equals(value))) { + // Add it, if value is not blank + if (value != null && StringUtils.isNotBlank(value)) { changes.registerAdd(dcv); } } diff --git a/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java index e50f7913ad7..ae079df560e 100644 --- a/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java +++ b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java @@ -9,12 +9,12 @@ import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertTrue; +import static junit.framework.TestCase.fail; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStreamWriter; -import java.sql.SQLException; import java.util.List; import org.apache.commons.cli.ParseException; @@ -218,9 +218,10 @@ public void personMetadataImportTest() throws Exception { @Test public void metadataImportRemovingValueTest() throws Exception { - context.turnOffAuthorisationSystem(); - Item item = ItemBuilder.createItem(context,personCollection).withAuthor("TestAuthorToRemove").withTitle("title") + String itemTitle = "Testing removing author"; + Item item = ItemBuilder.createItem(context,personCollection).withAuthor("TestAuthorToRemove") + .withTitle(itemTitle) .build(); context.restoreAuthSystemState(); @@ -232,19 +233,21 @@ public void metadataImportRemovingValueTest() throws Exception { String[] csv = {"id,collection,dc.title,dc.contributor.author[*]", item.getID().toString() + "," + personCollection.getHandle() + "," + item.getName() + ","}; performImportScript(csv); - item = findItemByName("title"); + item = findItemByName(itemTitle); assertEquals(0, itemService.getMetadata(item, "dc", "contributor", "author", Item.ANY).size()); } - private Item findItemByName(String name) throws SQLException { - Item importedItem = null; - List allItems = IteratorUtils.toList(itemService.findAll(context)); - for (Item item : allItems) { - if (item.getName().equals(name)) { - importedItem = item; - } + private Item findItemByName(String name) throws Exception { + List items = + IteratorUtils.toList(itemService.findByMetadataField(context, "dc", "title", null, name)); + + if (items != null && !items.isEmpty()) { + // Just return first matching Item. Tests should ensure name/title is unique. + return items.get(0); + } else { + fail("Could not find expected Item with dc.title = '" + name + "'"); + return null; } - return importedItem; } public void performImportScript(String[] csv) throws Exception { From c890199cfe314b2192fe098ad6ccb3ada7570687 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 24 May 2024 13:51:46 -0500 Subject: [PATCH 225/939] Add tests to verify behavior of addMetadata() when encountering virtual metadata (cherry picked from commit 2eb7dbca6ba349da70ccbf52f65a3b3ad70acbb2) --- .../java/org/dspace/content/ItemTest.java | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/dspace-api/src/test/java/org/dspace/content/ItemTest.java b/dspace-api/src/test/java/org/dspace/content/ItemTest.java index e3337d9d92a..aaa28769dca 100644 --- a/dspace-api/src/test/java/org/dspace/content/ItemTest.java +++ b/dspace-api/src/test/java/org/dspace/content/ItemTest.java @@ -13,6 +13,8 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; @@ -638,6 +640,51 @@ public void testAddMetadata_7args_no_values() throws Exception { fail("IllegalArgumentException expected"); } + @Test + public void testAddMetadata_list_with_virtual_metadata() throws Exception { + String schema = "dc"; + String element = "contributor"; + String qualifier = "author"; + String lang = Item.ANY; + // Create two fake virtual metadata ("virtual::[relationship-id]") values + List values = new ArrayList<>(Arrays.asList("uuid-1", "uuid-2")); + List authorities = new ArrayList<>(Arrays.asList(Constants.VIRTUAL_AUTHORITY_PREFIX + "relationship-1", + Constants.VIRTUAL_AUTHORITY_PREFIX + "relationship-2")); + List confidences = new ArrayList<>(Arrays.asList(-1, -1)); + + // Virtual metadata values will be IGNORED. No metadata should be added as we are calling addMetadata() + // with two virtual metadata values. + List valuesAdded = itemService.addMetadata(context, it, schema, element, qualifier, lang, + values, authorities, confidences); + assertNotNull(valuesAdded); + assertTrue(valuesAdded.isEmpty()); + + // Now, update tests values to append a third value which is NOT virtual metadata + String newValue = "new-metadata-value"; + String newAuthority = "auth0"; + Integer newConfidence = 0; + values.add(newValue); + authorities.add(newAuthority); + confidences.add(newConfidence); + + // Call addMetadata again, and this time only one value (the new, non-virtual metadata) should be added + valuesAdded = itemService.addMetadata(context, it, schema, element, qualifier, lang, + values, authorities, confidences); + assertNotNull(valuesAdded); + assertEquals(1, valuesAdded.size()); + + // Get metadata and ensure new value is the ONLY ONE for this metadata field + List dc = itemService.getMetadata(it, schema, element, qualifier, lang); + assertNotNull(dc); + assertEquals(1, dc.size()); + assertEquals(schema, dc.get(0).getMetadataField().getMetadataSchema().getName()); + assertEquals(element, dc.get(0).getMetadataField().getElement()); + assertEquals(qualifier, dc.get(0).getMetadataField().getQualifier()); + assertEquals(newValue, dc.get(0).getValue()); + assertNull(dc.get(0).getAuthority()); + assertEquals(-1, dc.get(0).getConfidence()); + } + /** * This is the same as testAddMetadata_5args_1 except it is adding a *single* value as a String, not a List. */ @@ -720,6 +767,42 @@ public void testAddMetadata_7args_2_noauthority() throws SQLException { assertThat("testAddMetadata_7args_2 8", dc.get(0).getConfidence(), equalTo(-1)); } + @Test + public void testAddMetadata_single_virtual_metadata() throws Exception { + String schema = "dc"; + String element = "contributor"; + String qualifier = "author"; + String lang = Item.ANY; + // Create a single fake virtual metadata ("virtual::[relationship-id]") value + String value = "uuid-1"; + String authority = Constants.VIRTUAL_AUTHORITY_PREFIX + "relationship-1"; + Integer confidence = -1; + + // Virtual metadata values will be IGNORED. No metadata should be added as we are calling addMetadata() + // with a virtual metadata value. + MetadataValue valuesAdded = itemService.addMetadata(context, it, schema, element, qualifier, lang, + value, authority, confidence); + // Returned object will be null when no metadata was added + assertNull(valuesAdded); + + // Verify this metadata field does NOT exist on the item + List mv = itemService.getMetadata(it, schema, element, qualifier, lang); + assertNotNull(mv); + assertTrue(mv.isEmpty()); + + // Also try calling addMetadata() with MetadataField object + MetadataField metadataField = metadataFieldService.findByElement(context, schema, element, qualifier); + valuesAdded = itemService.addMetadata(context, it, metadataField, lang, value, authority, confidence); + // Returned object should still be null + assertNull(valuesAdded); + + // Verify this metadata field does NOT exist on the item + mv = itemService.getMetadata(it, schema, element, qualifier, lang); + assertNotNull(mv); + assertTrue(mv.isEmpty()); + } + + /** * Test of clearMetadata method, of class Item. */ From 0f74cb22bf1e5eedec766cb9e8b85ff9d35d2f2e Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 6 Jun 2024 11:44:28 -0500 Subject: [PATCH 226/939] Avoid inline display of HTML/JS bitstreams. Add JS to list of known formats so that it can be recognized by DSpace. (cherry picked from commit 356a0281867989cd1bf6345f9d369f9c992791c8) --- .../org/dspace/app/rest/BitstreamRestController.java | 8 +++++++- dspace/config/dspace.cfg | 5 +++++ dspace/config/registries/bitstream-formats.xml | 9 +++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java index 9b5ede37c85..e3d6076844f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java @@ -209,7 +209,13 @@ private boolean checkFormatForContentDisposition(BitstreamFormat format) { if (format == null) { return false; } - List formats = List.of((configurationService.getArrayProperty("webui.content_disposition_format"))); + // Default to always downloading HTML/JavaScript files. These formats can embed JavaScript which would be run + // in the user's browser when loaded inline. This could be the basis for an XSS attack. + // RTF is also added because most browsers attempt to display it as plain text. + String [] defaultFormats = { "text/html", "text/javascript", "text/richtext" }; + + List formats = List.of(configurationService.getArrayProperty("webui.content_disposition_format", + defaultFormats)); boolean download = formats.contains(format.getMIMEType()); if (!download) { for (String ext : format.getExtensions()) { diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index fc840ef0035..1d108d5c334 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1373,6 +1373,11 @@ webui.content_disposition_threshold = 8388608 # Set which mimetypes, file extensions will NOT be opened inline # Files with these mimetypes/extensions will always be downloaded, # regardless of the threshold above +# We HIGHLY RECOMMEND forcing HTML / Javascript to always download. +# If a bitstream contained malicious Javascript, it would be executed in a user's browser when opened inline. +webui.content_disposition_format = text/html +webui.content_disposition_format = text/javascript +# RTF is always downloaded because most browsers attempt to display it as plain text. webui.content_disposition_format = text/richtext #### Multi-file HTML document/site settings ##### diff --git a/dspace/config/registries/bitstream-formats.xml b/dspace/config/registries/bitstream-formats.xml index 3515773fd74..fe0943e015e 100644 --- a/dspace/config/registries/bitstream-formats.xml +++ b/dspace/config/registries/bitstream-formats.xml @@ -827,4 +827,13 @@ avif + + text/javascript + JavaScript + JavaScript + 1 + false + js + + From 7143c97248e89416d8b491593a546879394875a2 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 6 Jun 2024 15:55:01 -0500 Subject: [PATCH 227/939] Fix failing IT by increasing number of formats by one (cherry picked from commit c61b7033f2ce63ceec6d867679216cccdaf10aec) --- .../org/dspace/app/rest/BitstreamFormatRestRepositoryIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamFormatRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamFormatRestRepositoryIT.java index fd128269308..d28202af659 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamFormatRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamFormatRestRepositoryIT.java @@ -56,7 +56,7 @@ public class BitstreamFormatRestRepositoryIT extends AbstractControllerIntegrati @Autowired private BitstreamFormatConverter bitstreamFormatConverter; - private final int DEFAULT_AMOUNT_FORMATS = 85; + private final int DEFAULT_AMOUNT_FORMATS = 86; @Test public void findAllPaginationTest() throws Exception { From 7ba150f4fc8121b44587c642be71e7c37a553cc0 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 10 Jun 2024 09:58:52 -0500 Subject: [PATCH 228/939] Add XML to the list of formats to always download (cherry picked from commit 39975e45cfff8aa70c7c0896c6f1cdff8d3e6fbf) --- .../java/org/dspace/app/rest/BitstreamRestController.java | 4 ++-- dspace/config/dspace.cfg | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java index e3d6076844f..d70c0904b3c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java @@ -209,10 +209,10 @@ private boolean checkFormatForContentDisposition(BitstreamFormat format) { if (format == null) { return false; } - // Default to always downloading HTML/JavaScript files. These formats can embed JavaScript which would be run + // Default to always downloading HTML/JavaScript/XML files. These formats can embed JavaScript which may be run // in the user's browser when loaded inline. This could be the basis for an XSS attack. // RTF is also added because most browsers attempt to display it as plain text. - String [] defaultFormats = { "text/html", "text/javascript", "text/richtext" }; + String [] defaultFormats = { "text/html", "text/javascript", "text/xml", "text/richtext" }; List formats = List.of(configurationService.getArrayProperty("webui.content_disposition_format", defaultFormats)); diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 1d108d5c334..0554d76866f 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1373,10 +1373,11 @@ webui.content_disposition_threshold = 8388608 # Set which mimetypes, file extensions will NOT be opened inline # Files with these mimetypes/extensions will always be downloaded, # regardless of the threshold above -# We HIGHLY RECOMMEND forcing HTML / Javascript to always download. -# If a bitstream contained malicious Javascript, it would be executed in a user's browser when opened inline. +# We HIGHLY RECOMMEND forcing HTML / Javascript / XML to always download. +# If one of these bitstreams contains malicious Javascript, it may be executed in a user's browser when opened inline. webui.content_disposition_format = text/html webui.content_disposition_format = text/javascript +webui.content_disposition_format = text/xml # RTF is always downloaded because most browsers attempt to display it as plain text. webui.content_disposition_format = text/richtext From 7951c8e428819972a3c3d80fd8f1fb0ae2cd3284 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 10 Jun 2024 10:09:33 -0500 Subject: [PATCH 229/939] Add a test to prove the default settings are to always download these formats (cherry picked from commit e6bfb833eed8228aa8f9ab66ec3277284d747b0d) --- .../app/rest/BitstreamRestControllerIT.java | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java index ae1b62c1b1b..cef087c363f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java @@ -1247,7 +1247,6 @@ public void closeInputStreamsDownloadWithCoverPage() throws Exception { Mockito.verify(inputStreamSpy, times(1)).close(); } - @Test public void checkContentDispositionOfFormats() throws Exception { configurationService.setProperty("webui.content_disposition_format", new String[] { @@ -1285,6 +1284,35 @@ public void checkContentDispositionOfFormats() throws Exception { verifyBitstreamDownload(html, "text/html;charset=UTF-8", false); } + @Test + public void checkDefaultContentDispositionFormats() throws Exception { + // This test is similar to the above test, but it verifies that our *default settings* for + // webui.content_disposition_format are protecting us from loading specific formats *inline*. + context.turnOffAuthorisationSystem(); + Community community = CommunityBuilder.createCommunity(context).build(); + Collection collection = CollectionBuilder.createCollection(context, community).build(); + Item item = ItemBuilder.createItem(context, collection).build(); + String content = "Test Content"; + Bitstream html; + Bitstream js; + Bitstream xml; + try (InputStream is = IOUtils.toInputStream(content, CharEncoding.UTF_8)) { + html = BitstreamBuilder.createBitstream(context, item, is) + .withMimeType("text/html").build(); + js = BitstreamBuilder.createBitstream(context, item, is) + .withMimeType("text/javascript").build(); + xml = BitstreamBuilder.createBitstream(context, item, is) + .withMimeType("text/xml").build(); + } + context.restoreAuthSystemState(); + + // By default, HTML, JS & XML should all download. This protects us from possible XSS attacks, as + // each of these formats can embed JavaScript which may execute when the file is loaded *inline*. + verifyBitstreamDownload(html, "text/html;charset=UTF-8", true); + verifyBitstreamDownload(js, "text/javascript;charset=UTF-8", true); + verifyBitstreamDownload(xml, "text/xml;charset=UTF-8", true); + } + private void verifyBitstreamDownload(Bitstream file, String contentType, boolean shouldDownload) throws Exception { String token = getAuthToken(admin.getEmail(), password); String header = getClient(token).perform(get("/api/core/bitstreams/" + file.getID() + "/content") From c30ff35448fe2da77d922f3b9db20500066b343d Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 10 Jun 2024 11:51:11 -0500 Subject: [PATCH 230/939] For additional security, ensure "unknown" formats are always downloaded. Update test to prove behavior. (cherry picked from commit 6da072de9e43d52e43eb9b312446a9359638f3c7) --- .../app/rest/BitstreamRestController.java | 15 ++++++++++++--- .../app/rest/BitstreamRestControllerIT.java | 17 +++++++++++++---- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java index d70c0904b3c..b85c009c7be 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java @@ -204,10 +204,19 @@ private boolean isNotAnErrorResponse(HttpServletResponse response) { || responseCode.equals(Response.Status.Family.REDIRECTION); } + /** + * Check if a Bitstream of the specified format should always be downloaded (i.e. "content-disposition: attachment") + * or can be served inline (i.e. "content-disposition: inline"). + *

+ * NOTE that downloading via "attachment" is more secure, as the user's browser will not attempt to process or + * display the file. But, downloading via "inline" may be seen as more user-friendly for common formats. + * @param format BitstreamFormat + * @return true if always download ("attachment"). false if can be served inline ("inline") + */ private boolean checkFormatForContentDisposition(BitstreamFormat format) { - // never automatically download undefined formats - if (format == null) { - return false; + // Undefined or Unknown formats should ALWAYS be downloaded for additional security. + if (format == null || format.getSupportLevel() == BitstreamFormat.UNKNOWN) { + return true; } // Default to always downloading HTML/JavaScript/XML files. These formats can embed JavaScript which may be run // in the user's browser when loaded inline. This could be the basis for an XSS attack. diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java index cef087c363f..502429679f6 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java @@ -1296,6 +1296,7 @@ public void checkDefaultContentDispositionFormats() throws Exception { Bitstream html; Bitstream js; Bitstream xml; + Bitstream unknown; try (InputStream is = IOUtils.toInputStream(content, CharEncoding.UTF_8)) { html = BitstreamBuilder.createBitstream(context, item, is) .withMimeType("text/html").build(); @@ -1303,6 +1304,8 @@ public void checkDefaultContentDispositionFormats() throws Exception { .withMimeType("text/javascript").build(); xml = BitstreamBuilder.createBitstream(context, item, is) .withMimeType("text/xml").build(); + unknown = BitstreamBuilder.createBitstream(context, item, is) + .withMimeType("application/octet-stream").build(); } context.restoreAuthSystemState(); @@ -1311,6 +1314,8 @@ public void checkDefaultContentDispositionFormats() throws Exception { verifyBitstreamDownload(html, "text/html;charset=UTF-8", true); verifyBitstreamDownload(js, "text/javascript;charset=UTF-8", true); verifyBitstreamDownload(xml, "text/xml;charset=UTF-8", true); + // Unknown file formats should also always download + verifyBitstreamDownload(unknown, "application/octet-stream;charset=UTF-8", true); } private void verifyBitstreamDownload(Bitstream file, String contentType, boolean shouldDownload) throws Exception { @@ -1321,11 +1326,15 @@ private void verifyBitstreamDownload(Bitstream file, String contentType, boolean .andExpect(content().contentType(contentType)) .andReturn().getResponse().getHeader("content-disposition"); if (shouldDownload) { - assertTrue(header.contains("attachment")); - assertFalse(header.contains("inline")); + assertTrue("Content-Disposition should contain 'attachment' for " + contentType, + header.contains("attachment")); + assertFalse("Content-Disposition should NOT contain 'inline' for " + contentType, + header.contains("inline")); } else { - assertTrue(header.contains("inline")); - assertFalse(header.contains("attachment")); + assertTrue("Content-Disposition should contain 'inline' for " + contentType, + header.contains("inline")); + assertFalse("Content-Disposition should NOT contain 'attachment' for " + contentType, + header.contains("attachment")); } } } From 3bcd33d92a8f3e1dab958d332b2030fc1b272d8a Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 10 Jun 2024 14:52:21 -0500 Subject: [PATCH 231/939] To avoid misconfiguration, hardcode HTML, XML, RDF, JS to download only. Add a new wildcard setting to allow sites to force all files to download only. (cherry picked from commit a091d343b9c1e491ca0fc005fd18bc1c75f8e830) --- .../app/rest/BitstreamRestController.java | 29 ++++++--- .../app/rest/BitstreamRestControllerIT.java | 60 ++++++++++++++++--- dspace/config/dspace.cfg | 17 +++--- 3 files changed, 82 insertions(+), 24 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java index b85c009c7be..22b18724b90 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java @@ -20,6 +20,7 @@ import javax.ws.rs.core.Response; import org.apache.catalina.connector.ClientAbortException; +import org.apache.commons.collections4.ListUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.converter.ConverterService; @@ -206,25 +207,37 @@ private boolean isNotAnErrorResponse(HttpServletResponse response) { /** * Check if a Bitstream of the specified format should always be downloaded (i.e. "content-disposition: attachment") - * or can be served inline (i.e. "content-disposition: inline"). + * or can be opened inline (i.e. "content-disposition: inline"). *

* NOTE that downloading via "attachment" is more secure, as the user's browser will not attempt to process or * display the file. But, downloading via "inline" may be seen as more user-friendly for common formats. * @param format BitstreamFormat - * @return true if always download ("attachment"). false if can be served inline ("inline") + * @return true if always download ("attachment"). false if can be opened inline ("inline") */ private boolean checkFormatForContentDisposition(BitstreamFormat format) { // Undefined or Unknown formats should ALWAYS be downloaded for additional security. if (format == null || format.getSupportLevel() == BitstreamFormat.UNKNOWN) { return true; } - // Default to always downloading HTML/JavaScript/XML files. These formats can embed JavaScript which may be run - // in the user's browser when loaded inline. This could be the basis for an XSS attack. - // RTF is also added because most browsers attempt to display it as plain text. - String [] defaultFormats = { "text/html", "text/javascript", "text/xml", "text/richtext" }; - List formats = List.of(configurationService.getArrayProperty("webui.content_disposition_format", - defaultFormats)); + // Load additional formats configured to require download + List configuredFormats = List.of(configurationService. + getArrayProperty("webui.content_disposition_format")); + + // If configuration includes "*", then all formats will always be downloaded. + if (configuredFormats.contains("*")) { + return true; + } + + // Define a download list of formats which DSpace forces to ALWAYS be downloaded. + // These formats can embed JavaScript which may be run in the user's browser if the file is opened inline. + // Therefore, DSpace blocks opening these formats inline as it could be used for an XSS attack. + List downloadOnlyFormats = List.of("text/html", "text/javascript", "text/xml", "rdf"); + + // Combine our two lists + List formats = ListUtils.union(downloadOnlyFormats, configuredFormats); + + // See if the passed in format's MIME type or file extension is listed. boolean download = formats.contains(format.getMIMEType()); if (!download) { for (String ext : format.getExtensions()) { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java index 502429679f6..855fedbaa2a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java @@ -1263,7 +1263,7 @@ public void checkContentDispositionOfFormats() throws Exception { Bitstream rtf; Bitstream xml; Bitstream txt; - Bitstream html; + Bitstream csv; try (InputStream is = IOUtils.toInputStream(content, CharEncoding.UTF_8)) { rtf = BitstreamBuilder.createBitstream(context, item, is) .withMimeType("text/richtext").build(); @@ -1271,8 +1271,8 @@ public void checkContentDispositionOfFormats() throws Exception { .withMimeType("text/xml").build(); txt = BitstreamBuilder.createBitstream(context, item, is) .withMimeType("text/plain").build(); - html = BitstreamBuilder.createBitstream(context, item, is) - .withMimeType("text/html").build(); + csv = BitstreamBuilder.createBitstream(context, item, is) + .withMimeType("text/csv").build(); } context.restoreAuthSystemState(); @@ -1281,12 +1281,12 @@ public void checkContentDispositionOfFormats() throws Exception { verifyBitstreamDownload(xml, "text/xml;charset=UTF-8", true); verifyBitstreamDownload(txt, "text/plain;charset=UTF-8", true); // this format is not configured and should open inline - verifyBitstreamDownload(html, "text/html;charset=UTF-8", false); + verifyBitstreamDownload(csv, "text/csv;charset=UTF-8", false); } @Test - public void checkDefaultContentDispositionFormats() throws Exception { - // This test is similar to the above test, but it verifies that our *default settings* for + public void checkHardcodedContentDispositionFormats() throws Exception { + // This test is similar to the above test, but it verifies that our *hardcoded settings* for // webui.content_disposition_format are protecting us from loading specific formats *inline*. context.turnOffAuthorisationSystem(); Community community = CommunityBuilder.createCommunity(context).build(); @@ -1295,17 +1295,25 @@ public void checkDefaultContentDispositionFormats() throws Exception { String content = "Test Content"; Bitstream html; Bitstream js; + Bitstream rdf; Bitstream xml; Bitstream unknown; + Bitstream svg; try (InputStream is = IOUtils.toInputStream(content, CharEncoding.UTF_8)) { html = BitstreamBuilder.createBitstream(context, item, is) .withMimeType("text/html").build(); js = BitstreamBuilder.createBitstream(context, item, is) - .withMimeType("text/javascript").build(); + .withMimeType("text/javascript").build(); + // NOTE: RDF has a MIME Type with a "charset" in our bitstream-formats.xml + rdf = BitstreamBuilder.createBitstream(context, item, is) + .withMimeType("application/rdf+xml; charset=utf-8").build(); xml = BitstreamBuilder.createBitstream(context, item, is) .withMimeType("text/xml").build(); unknown = BitstreamBuilder.createBitstream(context, item, is) .withMimeType("application/octet-stream").build(); + svg = BitstreamBuilder.createBitstream(context, item, is) + .withMimeType("image/svg+xml").build(); + } context.restoreAuthSystemState(); @@ -1313,11 +1321,49 @@ public void checkDefaultContentDispositionFormats() throws Exception { // each of these formats can embed JavaScript which may execute when the file is loaded *inline*. verifyBitstreamDownload(html, "text/html;charset=UTF-8", true); verifyBitstreamDownload(js, "text/javascript;charset=UTF-8", true); + verifyBitstreamDownload(rdf, "application/rdf+xml;charset=UTF-8", true); verifyBitstreamDownload(xml, "text/xml;charset=UTF-8", true); // Unknown file formats should also always download verifyBitstreamDownload(unknown, "application/octet-stream;charset=UTF-8", true); + // SVG does NOT currently exist as a recognized format in DSpace's bitstream-formats.xml, but it's another + // format that allows embedded JavaScript. This test is a reminder that SVGs should NOT be opened inline. + verifyBitstreamDownload(svg, "application/octet-stream;charset=UTF-8", true); + } + + @Test + public void checkWildcardContentDispositionFormats() throws Exception { + // Setting "*" should result in all formats being downloaded (nothing will be opened inline) + configurationService.setProperty("webui.content_disposition_format", "*"); + + context.turnOffAuthorisationSystem(); + Community community = CommunityBuilder.createCommunity(context).build(); + Collection collection = CollectionBuilder.createCollection(context, community).build(); + Item item = ItemBuilder.createItem(context, collection).build(); + String content = "Test Content"; + Bitstream csv; + Bitstream jpg; + Bitstream mpg; + Bitstream pdf; + try (InputStream is = IOUtils.toInputStream(content, CharEncoding.UTF_8)) { + csv = BitstreamBuilder.createBitstream(context, item, is) + .withMimeType("text/csv").build(); + jpg = BitstreamBuilder.createBitstream(context, item, is) + .withMimeType("image/jpeg").build(); + mpg = BitstreamBuilder.createBitstream(context, item, is) + .withMimeType("video/mpeg").build(); + pdf = BitstreamBuilder.createBitstream(context, item, is) + .withMimeType("application/pdf").build(); + } + context.restoreAuthSystemState(); + + // All formats should be download only + verifyBitstreamDownload(csv, "text/csv;charset=UTF-8", true); + verifyBitstreamDownload(jpg, "image/jpeg;charset=UTF-8", true); + verifyBitstreamDownload(mpg, "video/mpeg;charset=UTF-8", true); + verifyBitstreamDownload(pdf, "application/pdf;charset=UTF-8", true); } + private void verifyBitstreamDownload(Bitstream file, String contentType, boolean shouldDownload) throws Exception { String token = getAuthToken(admin.getEmail(), password); String header = getClient(token).perform(get("/api/core/bitstreams/" + file.getID() + "/content") diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 0554d76866f..c21fb54655b 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1370,15 +1370,14 @@ webui.content_disposition_threshold = 8388608 #### Content Attachment Disposition Formats #### # -# Set which mimetypes, file extensions will NOT be opened inline -# Files with these mimetypes/extensions will always be downloaded, -# regardless of the threshold above -# We HIGHLY RECOMMEND forcing HTML / Javascript / XML to always download. -# If one of these bitstreams contains malicious Javascript, it may be executed in a user's browser when opened inline. -webui.content_disposition_format = text/html -webui.content_disposition_format = text/javascript -webui.content_disposition_format = text/xml -# RTF is always downloaded because most browsers attempt to display it as plain text. +# Set which mimetypes or file extensions will NOT be opened inline. +# Files with these mimetypes/extensions will always be downloaded, regardless of the threshold above. +# NOTE: For security reasons, some file formats (e.g. HTML, XML, RDF, JS) will always be downloaded regardless +# of the settings here. This blocks these formats from executing embedded JavaScript when opened inline. +# For additional security, you may choose to set this to "*" to force all formats to always be downloaded +# (i.e. disables all formats from opening inline within the user's browser). +# +# By default, RTF is always downloaded because most browsers attempt to display it as plain text. webui.content_disposition_format = text/richtext #### Multi-file HTML document/site settings ##### From 2cb8204d3c474ce9a7cb6946c43a0beab0ba92a2 Mon Sep 17 00:00:00 2001 From: nwoodward Date: Mon, 8 Jul 2024 12:33:07 -0500 Subject: [PATCH 232/939] updated creative commons licenses version from 3.0 to 4.0 --- .../rest/submit/factory/impl/CCLicenseAddPatchOperation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/CCLicenseAddPatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/CCLicenseAddPatchOperation.java index 602ae5e9690..80624257e6c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/CCLicenseAddPatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/CCLicenseAddPatchOperation.java @@ -26,7 +26,7 @@ * Example: * curl -X PATCH http://${dspace.server.url}/api/submission/workspaceitems/31599 -H "Content-Type: * application/json" -d '[{ "op": "add", "path": "/sections/cclicense/uri", - * "value":"https://creativecommons.org/licenses/by-nc-sa/3.0/us/"}]' + * "value":"https://creativecommons.org/licenses/by-nc-sa/4.0/us/"}]' * */ public class CCLicenseAddPatchOperation extends AddPatchOperation { From 12c3cc5b3eb6c4d0339595b0efa236e16075b0dd Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 9 Jul 2024 13:35:16 -0500 Subject: [PATCH 233/939] Update LICENSES_THIRD_PARTY (and related configs) for 7.6.2 release --- LICENSES_THIRD_PARTY | 610 +++++++++--------- pom.xml | 6 +- .../license/LICENSES_THIRD_PARTY.properties | 85 +-- 3 files changed, 326 insertions(+), 375 deletions(-) diff --git a/LICENSES_THIRD_PARTY b/LICENSES_THIRD_PARTY index e494c80c5d6..791009e4c3a 100644 --- a/LICENSES_THIRD_PARTY +++ b/LICENSES_THIRD_PARTY @@ -26,24 +26,23 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * AWS Java SDK for Amazon S3 (com.amazonaws:aws-java-sdk-s3:1.12.261 - https://aws.amazon.com/sdkforjava) * JMES Path Query library (com.amazonaws:jmespath-java:1.12.261 - https://aws.amazon.com/sdkforjava) * HPPC Collections (com.carrotsearch:hppc:0.8.1 - http://labs.carrotsearch.com/hppc.html/hppc) - * com.drewnoakes:metadata-extractor (com.drewnoakes:metadata-extractor:2.18.0 - https://drewnoakes.com/code/exif/) + * com.drewnoakes:metadata-extractor (com.drewnoakes:metadata-extractor:2.19.0 - https://drewnoakes.com/code/exif/) * parso (com.epam:parso:2.0.14 - https://github.com/epam/parso) - * Esri Geometry API for Java (com.esri.geometry:esri-geometry-api:2.2.0 - https://github.com/Esri/geometry-api-java) - * ClassMate (com.fasterxml:classmate:1.3.0 - http://github.com/cowtowncoder/java-classmate) - * Jackson-annotations (com.fasterxml.jackson.core:jackson-annotations:2.13.4 - http://github.com/FasterXML/jackson) - * Jackson-core (com.fasterxml.jackson.core:jackson-core:2.13.4 - https://github.com/FasterXML/jackson-core) - * jackson-databind (com.fasterxml.jackson.core:jackson-databind:2.13.4.2 - http://github.com/FasterXML/jackson) + * ClassMate (com.fasterxml:classmate:1.6.0 - https://github.com/FasterXML/java-classmate) + * Jackson-annotations (com.fasterxml.jackson.core:jackson-annotations:2.16.0 - https://github.com/FasterXML/jackson) + * Jackson-core (com.fasterxml.jackson.core:jackson-core:2.16.0 - https://github.com/FasterXML/jackson-core) + * jackson-databind (com.fasterxml.jackson.core:jackson-databind:2.16.0 - https://github.com/FasterXML/jackson) * Jackson dataformat: CBOR (com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.12.6 - http://github.com/FasterXML/jackson-dataformats-binary) - * Jackson dataformat: Smile (com.fasterxml.jackson.dataformat:jackson-dataformat-smile:2.13.3 - http://github.com/FasterXML/jackson-dataformats-binary) + * Jackson dataformat: Smile (com.fasterxml.jackson.dataformat:jackson-dataformat-smile:2.15.2 - https://github.com/FasterXML/jackson-dataformats-binary) * Jackson-dataformat-YAML (com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.11.1 - https://github.com/FasterXML/jackson-dataformats-text) * Jackson datatype: jdk8 (com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.13.5 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8) * Jackson datatype: JSR310 (com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.1 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310) * Jackson datatype: JSR310 (com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.5 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310) * Jackson-module-parameter-names (com.fasterxml.jackson.module:jackson-module-parameter-names:2.13.5 - https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names) * Java UUID Generator (com.fasterxml.uuid:java-uuid-generator:4.0.1 - https://github.com/cowtowncoder/java-uuid-generator) - * Woodstox (com.fasterxml.woodstox:woodstox-core:6.2.4 - https://github.com/FasterXML/woodstox) - * zjsonpatch (com.flipkart.zjsonpatch:zjsonpatch:0.4.6 - https://github.com/flipkart-incubator/zjsonpatch/) - * Caffeine cache (com.github.ben-manes.caffeine:caffeine:2.9.2 - https://github.com/ben-manes/caffeine) + * Woodstox (com.fasterxml.woodstox:woodstox-core:6.5.1 - https://github.com/FasterXML/woodstox) + * zjsonpatch (com.flipkart.zjsonpatch:zjsonpatch:0.4.16 - https://github.com/flipkart-incubator/zjsonpatch/) + * Caffeine cache (com.github.ben-manes.caffeine:caffeine:2.9.3 - https://github.com/ben-manes/caffeine) * btf (com.github.java-json-tools:btf:1.3 - https://github.com/java-json-tools/btf) * jackson-coreutils (com.github.java-json-tools:jackson-coreutils:2.0 - https://github.com/java-json-tools/jackson-coreutils) * jackson-coreutils-equivalence (com.github.java-json-tools:jackson-coreutils-equivalence:1.0 - https://github.com/java-json-tools/jackson-coreutils) @@ -54,7 +53,7 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * JCIP Annotations under Apache License (com.github.stephenc.jcip:jcip-annotations:1.0-1 - http://stephenc.github.com/jcip-annotations) * Google APIs Client Library for Java (com.google.api-client:google-api-client:1.23.0 - https://github.com/google/google-api-java-client/google-api-client) * Google Analytics API v3-rev145-1.23.0 (com.google.apis:google-api-services-analytics:v3-rev145-1.23.0 - http://nexus.sonatype.org/oss-repository-hosting.html/google-api-services-analytics) - * FindBugs-jsr305 (com.google.code.findbugs:jsr305:3.0.1 - http://findbugs.sourceforge.net/) + * FindBugs-jsr305 (com.google.code.findbugs:jsr305:3.0.2 - http://findbugs.sourceforge.net/) * Gson (com.google.code.gson:gson:2.9.0 - https://github.com/google/gson/gson) * error-prone annotations (com.google.errorprone:error_prone_annotations:2.18.0 - https://errorprone.info/error_prone_annotations) * Guava InternalFutureFailureAccess and InternalFutures (com.google.guava:failureaccess:1.0.1 - https://github.com/google/guava/failureaccess) @@ -64,25 +63,24 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Google HTTP Client Library for Java (com.google.http-client:google-http-client:1.23.0 - https://github.com/google/google-http-java-client/google-http-client) * GSON extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-gson:1.41.7 - https://github.com/googleapis/google-http-java-client/google-http-client-gson) * Jackson 2 extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-jackson2:1.23.0 - https://github.com/google/google-http-java-client/google-http-client-jackson2) + * J2ObjC Annotations (com.google.j2objc:j2objc-annotations:1.3 - https://github.com/google/j2objc/) * J2ObjC Annotations (com.google.j2objc:j2objc-annotations:2.8 - https://github.com/google/j2objc/) * Google OAuth Client Library for Java (com.google.oauth-client:google-oauth-client:1.33.3 - https://github.com/googleapis/google-oauth-java-client/google-oauth-client) * ConcurrentLinkedHashMap (com.googlecode.concurrentlinkedhashmap:concurrentlinkedhashmap-lru:1.4.2 - http://code.google.com/p/concurrentlinkedhashmap) * libphonenumber (com.googlecode.libphonenumber:libphonenumber:8.11.1 - https://github.com/google/libphonenumber/) - * Jackcess (com.healthmarketscience.jackcess:jackcess:4.0.2 - https://jackcess.sourceforge.io) - * Jackcess Encrypt (com.healthmarketscience.jackcess:jackcess-encrypt:4.0.1 - http://jackcessencrypt.sf.net) - * project ':json-path' (com.jayway.jsonpath:json-path:2.6.0 - https://github.com/jayway/JsonPath) - * project ':json-path-assert' (com.jayway.jsonpath:json-path-assert:2.6.0 - https://github.com/jayway/JsonPath) + * Jackcess (com.healthmarketscience.jackcess:jackcess:4.0.5 - https://jackcess.sourceforge.io) + * Jackcess Encrypt (com.healthmarketscience.jackcess:jackcess-encrypt:4.0.2 - http://jackcessencrypt.sf.net) + * json-path (com.jayway.jsonpath:json-path:2.9.0 - https://github.com/jayway/JsonPath) + * json-path-assert (com.jayway.jsonpath:json-path-assert:2.9.0 - https://github.com/jayway/JsonPath) * Disruptor Framework (com.lmax:disruptor:3.4.2 - http://lmax-exchange.github.com/disruptor) - * builder-commons (com.lyncode:builder-commons:1.0.2 - http://nexus.sonatype.org/oss-repository-hosting.html/builder-commons) - * MaxMind DB Reader (com.maxmind.db:maxmind-db:1.2.2 - http://dev.maxmind.com/) - * MaxMind GeoIP2 API (com.maxmind.geoip2:geoip2:2.11.0 - http://dev.maxmind.com/geoip/geoip2/web-services) + * MaxMind DB Reader (com.maxmind.db:maxmind-db:2.1.0 - http://dev.maxmind.com/) + * MaxMind GeoIP2 API (com.maxmind.geoip2:geoip2:2.17.0 - https://dev.maxmind.com/geoip?lang=en) * Nimbus JOSE+JWT (com.nimbusds:nimbus-jose-jwt:7.9 - https://bitbucket.org/connect2id/nimbus-jose-jwt) - * opencsv (com.opencsv:opencsv:5.6 - http://opencsv.sf.net) + * opencsv (com.opencsv:opencsv:5.9 - http://opencsv.sf.net) * java-libpst (com.pff:java-libpst:0.9.3 - https://github.com/rjohnsondev/java-libpst) * rome (com.rometools:rome:1.19.0 - http://rometools.com/rome) * rome-modules (com.rometools:rome-modules:1.19.0 - http://rometools.com/rome-modules) * rome-utils (com.rometools:rome-utils:1.19.0 - http://rometools.com/rome-utils) - * fastinfoset (com.sun.xml.fastinfoset:FastInfoset:1.2.15 - http://fi.java.net) * T-Digest (com.tdunning:t-digest:3.1 - https://github.com/tdunning/t-digest) * config (com.typesafe:config:1.3.3 - https://github.com/lightbend/config) * ssl-config-core (com.typesafe:ssl-config-core_2.13:0.3.8 - https://github.com/lightbend/ssl-config) @@ -94,17 +92,17 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * akka-stream (com.typesafe.akka:akka-stream_2.13:2.5.31 - https://akka.io/) * scala-logging (com.typesafe.scala-logging:scala-logging_2.13:3.9.2 - https://github.com/lightbend/scala-logging) * JSON library from Android SDK (com.vaadin.external.google:android-json:0.0.20131108.vaadin1 - http://developer.android.com/sdk) - * SparseBitSet (com.zaxxer:SparseBitSet:1.2 - https://github.com/brettwooldridge/SparseBitSet) + * SparseBitSet (com.zaxxer:SparseBitSet:1.3 - https://github.com/brettwooldridge/SparseBitSet) * Apache Commons BeanUtils (commons-beanutils:commons-beanutils:1.9.4 - https://commons.apache.org/proper/commons-beanutils/) - * Apache Commons CLI (commons-cli:commons-cli:1.4 - http://commons.apache.org/proper/commons-cli/) - * Apache Commons Codec (commons-codec:commons-codec:1.10 - http://commons.apache.org/proper/commons-codec/) + * Apache Commons CLI (commons-cli:commons-cli:1.6.0 - https://commons.apache.org/proper/commons-cli/) + * Apache Commons Codec (commons-codec:commons-codec:1.16.0 - https://commons.apache.org/proper/commons-codec/) * Apache Commons Collections (commons-collections:commons-collections:3.2.2 - http://commons.apache.org/collections/) - * Commons Digester (commons-digester:commons-digester:1.8.1 - http://commons.apache.org/digester/) + * Commons Digester (commons-digester:commons-digester:2.1 - http://commons.apache.org/digester/) * Apache Commons FileUpload (commons-fileupload:commons-fileupload:1.5 - https://commons.apache.org/proper/commons-fileupload/) - * Apache Commons IO (commons-io:commons-io:2.7 - https://commons.apache.org/proper/commons-io/) + * Apache Commons IO (commons-io:commons-io:2.15.1 - https://commons.apache.org/proper/commons-io/) * Commons Lang (commons-lang:commons-lang:2.6 - http://commons.apache.org/lang/) - * Apache Commons Logging (commons-logging:commons-logging:1.2 - http://commons.apache.org/proper/commons-logging/) - * Apache Commons Validator (commons-validator:commons-validator:1.5.0 - http://commons.apache.org/proper/commons-validator/) + * Apache Commons Logging (commons-logging:commons-logging:1.3.0 - https://commons.apache.org/proper/commons-logging/) + * Apache Commons Validator (commons-validator:commons-validator:1.7 - http://commons.apache.org/proper/commons-validator/) * GeoJson POJOs for Jackson (de.grundid.opendatalab:geojson-jackson:1.14 - https://github.com/opendatalab-de/geojson-jackson) * OpenAIRE Funders Model (eu.openaire:funders-model:2.0.0 - https://api.openaire.eu) * Metrics Core (io.dropwizard.metrics:metrics-core:4.1.5 - https://metrics.dropwizard.io/metrics-core) @@ -112,18 +110,24 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Metrics Integration for Jetty 9.3 and higher (io.dropwizard.metrics:metrics-jetty9:4.1.5 - https://metrics.dropwizard.io/metrics-jetty9) * Metrics Integration with JMX (io.dropwizard.metrics:metrics-jmx:4.1.5 - https://metrics.dropwizard.io/metrics-jmx) * JVM Integration for Metrics (io.dropwizard.metrics:metrics-jvm:4.1.5 - https://metrics.dropwizard.io/metrics-jvm) - * micrometer-core (io.micrometer:micrometer-core:1.9.11 - https://github.com/micrometer-metrics/micrometer) - * Netty/Buffer (io.netty:netty-buffer:4.1.68.Final - https://netty.io/netty-buffer/) - * Netty/Codec (io.netty:netty-codec:4.1.68.Final - https://netty.io/netty-codec/) + * micrometer-core (io.micrometer:micrometer-core:1.9.17 - https://github.com/micrometer-metrics/micrometer) + * Netty/Buffer (io.netty:netty-buffer:4.1.106.Final - https://netty.io/netty-buffer/) + * Netty/Buffer (io.netty:netty-buffer:4.1.99.Final - https://netty.io/netty-buffer/) + * Netty/Codec (io.netty:netty-codec:4.1.106.Final - https://netty.io/netty-codec/) + * Netty/Codec (io.netty:netty-codec:4.1.99.Final - https://netty.io/netty-codec/) * Netty/Codec/HTTP (io.netty:netty-codec-http:4.1.53.Final - https://netty.io/netty-codec-http/) * Netty/Codec/Socks (io.netty:netty-codec-socks:4.1.53.Final - https://netty.io/netty-codec-socks/) - * Netty/Common (io.netty:netty-common:4.1.68.Final - https://netty.io/netty-common/) - * Netty/Handler (io.netty:netty-handler:4.1.68.Final - https://netty.io/netty-handler/) + * Netty/Common (io.netty:netty-common:4.1.106.Final - https://netty.io/netty-common/) + * Netty/Common (io.netty:netty-common:4.1.99.Final - https://netty.io/netty-common/) + * Netty/Handler (io.netty:netty-handler:4.1.106.Final - https://netty.io/netty-handler/) + * Netty/Handler (io.netty:netty-handler:4.1.99.Final - https://netty.io/netty-handler/) * Netty/Handler/Proxy (io.netty:netty-handler-proxy:4.1.53.Final - https://netty.io/netty-handler-proxy/) - * Netty/Resolver (io.netty:netty-resolver:4.1.68.Final - https://netty.io/netty-resolver/) - * Netty/Transport (io.netty:netty-transport:4.1.68.Final - https://netty.io/netty-transport/) - * Netty/Transport/Native/Epoll (io.netty:netty-transport-native-epoll:4.1.68.Final - https://netty.io/netty-transport-native-epoll/) - * Netty/Transport/Native/Unix/Common (io.netty:netty-transport-native-unix-common:4.1.68.Final - https://netty.io/netty-transport-native-unix-common/) + * Netty/Resolver (io.netty:netty-resolver:4.1.99.Final - https://netty.io/netty-resolver/) + * Netty/Transport (io.netty:netty-transport:4.1.106.Final - https://netty.io/netty-transport/) + * Netty/Transport (io.netty:netty-transport:4.1.99.Final - https://netty.io/netty-transport/) + * Netty/Transport/Native/Epoll (io.netty:netty-transport-native-epoll:4.1.99.Final - https://netty.io/netty-transport-native-epoll/) + * Netty/Transport/Native/Unix/Common (io.netty:netty-transport-native-unix-common:4.1.106.Final - https://netty.io/netty-transport-native-unix-common/) + * Netty/Transport/Native/Unix/Common (io.netty:netty-transport-native-unix-common:4.1.99.Final - https://netty.io/netty-transport-native-unix-common/) * OpenTracing API (io.opentracing:opentracing-api:0.33.0 - https://github.com/opentracing/opentracing-java/opentracing-api) * OpenTracing-noop (io.opentracing:opentracing-noop:0.33.0 - https://github.com/opentracing/opentracing-java/opentracing-noop) * OpenTracing-util (io.opentracing:opentracing-util:0.33.0 - https://github.com/opentracing/opentracing-java/opentracing-util) @@ -141,52 +145,57 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * swagger-parser-v2-converter (io.swagger.parser.v3:swagger-parser-v2-converter:2.0.23 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser-v2-converter) * swagger-parser-v3 (io.swagger.parser.v3:swagger-parser-v3:2.0.23 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser-v3) * Jakarta Bean Validation API (jakarta.validation:jakarta.validation-api:2.0.2 - https://beanvalidation.org) - * JSR107 API and SPI (javax.cache:cache-api:1.1.0 - https://github.com/jsr107/jsr107spec) + * JSR107 API and SPI (javax.cache:cache-api:1.1.1 - https://github.com/jsr107/jsr107spec) * javax.inject (javax.inject:javax.inject:1 - http://code.google.com/p/atinject/) * Bean Validation API (javax.validation:validation-api:2.0.1.Final - http://beanvalidation.org) * jdbm (jdbm:jdbm:1.0 - no url defined) - * Joda-Time (joda-time:joda-time:2.9.2 - http://www.joda.org/joda-time/) + * Joda-Time (joda-time:joda-time:2.12.5 - https://www.joda.org/joda-time/) * Byte Buddy (without dependencies) (net.bytebuddy:byte-buddy:1.11.13 - https://bytebuddy.net/byte-buddy) * Byte Buddy agent (net.bytebuddy:byte-buddy-agent:1.11.13 - https://bytebuddy.net/byte-buddy-agent) * eigenbase-properties (net.hydromatic:eigenbase-properties:1.1.5 - http://github.com/julianhyde/eigenbase-properties) * json-unit-core (net.javacrumbs.json-unit:json-unit-core:2.19.0 - https://github.com/lukas-krecan/JsonUnit/json-unit-core) * "Java Concurrency in Practice" book annotations (net.jcip:jcip-annotations:1.0 - http://jcip.net/) * ASM based accessors helper used by json-smart (net.minidev:accessors-smart:1.2 - http://www.minidev.net/) - * ASM based accessors helper used by json-smart (net.minidev:accessors-smart:2.4.7 - https://urielch.github.io/) + * ASM based accessors helper used by json-smart (net.minidev:accessors-smart:2.5.0 - https://urielch.github.io/) * JSON Small and Fast Parser (net.minidev:json-smart:2.3 - http://www.minidev.net/) - * JSON Small and Fast Parser (net.minidev:json-smart:2.4.7 - https://urielch.github.io/) + * JSON Small and Fast Parser (net.minidev:json-smart:2.5.0 - https://urielch.github.io/) * Abdera Core (org.apache.abdera:abdera-core:1.1.3 - http://abdera.apache.org/abdera-core) * I18N Libraries (org.apache.abdera:abdera-i18n:1.1.3 - http://abdera.apache.org) - * Apache Ant Core (org.apache.ant:ant:1.10.11 - https://ant.apache.org/) - * Apache Ant Launcher (org.apache.ant:ant-launcher:1.10.11 - https://ant.apache.org/) - * Apache Commons BCEL (org.apache.bcel:bcel:6.6.0 - https://commons.apache.org/proper/commons-bcel) - * Calcite Core (org.apache.calcite:calcite-core:1.27.0 - https://calcite.apache.org) - * Calcite Linq4j (org.apache.calcite:calcite-linq4j:1.27.0 - https://calcite.apache.org) - * Apache Calcite Avatica (org.apache.calcite.avatica:avatica-core:1.18.0 - https://calcite.apache.org/avatica) - * Apache Commons Collections (org.apache.commons:commons-collections4:4.1 - http://commons.apache.org/proper/commons-collections/) - * Apache Commons Compress (org.apache.commons:commons-compress:1.21 - https://commons.apache.org/proper/commons-compress/) - * Apache Commons Configuration (org.apache.commons:commons-configuration2:2.8.0 - https://commons.apache.org/proper/commons-configuration/) - * Apache Commons CSV (org.apache.commons:commons-csv:1.9.0 - https://commons.apache.org/proper/commons-csv/) - * Apache Commons DBCP (org.apache.commons:commons-dbcp2:2.9.0 - https://commons.apache.org/dbcp/) + * Apache Ant Core (org.apache.ant:ant:1.10.14 - https://ant.apache.org/) + * Apache Ant Launcher (org.apache.ant:ant-launcher:1.10.14 - https://ant.apache.org/) + * Apache Commons BCEL (org.apache.bcel:bcel:6.7.0 - https://commons.apache.org/proper/commons-bcel) + * Calcite Core (org.apache.calcite:calcite-core:1.35.0 - https://calcite.apache.org) + * Calcite Linq4j (org.apache.calcite:calcite-linq4j:1.35.0 - https://calcite.apache.org) + * Apache Calcite Avatica (org.apache.calcite.avatica:avatica-core:1.23.0 - https://calcite.apache.org/avatica) + * Apache Calcite Avatica Metrics (org.apache.calcite.avatica:avatica-metrics:1.23.0 - https://calcite.apache.org/avatica) + * Apache Commons Collections (org.apache.commons:commons-collections4:4.4 - https://commons.apache.org/proper/commons-collections/) + * Apache Commons Compress (org.apache.commons:commons-compress:1.26.0 - https://commons.apache.org/proper/commons-compress/) + * Apache Commons Configuration (org.apache.commons:commons-configuration2:2.10.1 - https://commons.apache.org/proper/commons-configuration/) + * Apache Commons CSV (org.apache.commons:commons-csv:1.10.0 - https://commons.apache.org/proper/commons-csv/) + * Apache Commons DBCP (org.apache.commons:commons-dbcp2:2.11.0 - https://commons.apache.org/dbcp/) * Apache Commons Exec (org.apache.commons:commons-exec:1.3 - http://commons.apache.org/proper/commons-exec/) - * Apache Commons Lang (org.apache.commons:commons-lang3:3.12.0 - https://commons.apache.org/proper/commons-lang/) + * Apache Commons Exec (org.apache.commons:commons-exec:1.4.0 - https://commons.apache.org/proper/commons-exec/) + * Apache Commons Lang (org.apache.commons:commons-lang3:3.14.0 - https://commons.apache.org/proper/commons-lang/) * Apache Commons Math (org.apache.commons:commons-math3:3.6.1 - http://commons.apache.org/proper/commons-math/) - * Apache Commons Pool (org.apache.commons:commons-pool2:2.11.1 - https://commons.apache.org/proper/commons-pool/) + * Apache Commons Pool (org.apache.commons:commons-pool2:2.12.0 - https://commons.apache.org/proper/commons-pool/) * Apache Commons Text (org.apache.commons:commons-text:1.10.0 - https://commons.apache.org/proper/commons-text) * Curator Client (org.apache.curator:curator-client:2.13.0 - http://curator.apache.org/curator-client) * Curator Framework (org.apache.curator:curator-framework:2.13.0 - http://curator.apache.org/curator-framework) * Curator Recipes (org.apache.curator:curator-recipes:2.13.0 - http://curator.apache.org/curator-recipes) - * Apache Hadoop Annotations (org.apache.hadoop:hadoop-annotations:3.2.2 - no url defined) - * Apache Hadoop Auth (org.apache.hadoop:hadoop-auth:3.2.2 - no url defined) - * Apache Hadoop Common (org.apache.hadoop:hadoop-common:3.2.2 - no url defined) - * Apache Hadoop HDFS Client (org.apache.hadoop:hadoop-hdfs-client:3.2.2 - no url defined) + * Apache Hadoop Annotations (org.apache.hadoop:hadoop-annotations:3.2.4 - no url defined) + * Apache Hadoop Auth (org.apache.hadoop:hadoop-auth:3.2.4 - no url defined) + * Apache Hadoop Common (org.apache.hadoop:hadoop-common:3.2.4 - no url defined) + * Apache Hadoop HDFS Client (org.apache.hadoop:hadoop-hdfs-client:3.2.4 - no url defined) * htrace-core4 (org.apache.htrace:htrace-core4:4.1.0-incubating - http://incubator.apache.org/projects/htrace.html) - * Apache HttpClient (org.apache.httpcomponents:httpclient:4.5.13 - http://hc.apache.org/httpcomponents-client) + * Apache HttpClient (org.apache.httpcomponents:httpclient:4.5.14 - http://hc.apache.org/httpcomponents-client-ga) * Apache HttpClient Cache (org.apache.httpcomponents:httpclient-cache:4.2.6 - http://hc.apache.org/httpcomponents-client) - * Apache HttpCore (org.apache.httpcomponents:httpcore:4.4.15 - http://hc.apache.org/httpcomponents-core-ga) - * Apache HttpClient Mime (org.apache.httpcomponents:httpmime:4.5.13 - http://hc.apache.org/httpcomponents-client) - * Apache James :: Mime4j :: Core (org.apache.james:apache-mime4j-core:0.8.4 - http://james.apache.org/mime4j/apache-mime4j-core) - * Apache James :: Mime4j :: DOM (org.apache.james:apache-mime4j-dom:0.8.4 - http://james.apache.org/mime4j/apache-mime4j-dom) + * Apache HttpCore (org.apache.httpcomponents:httpcore:4.4.16 - http://hc.apache.org/httpcomponents-core-ga) + * Apache HttpClient Mime (org.apache.httpcomponents:httpmime:4.5.14 - http://hc.apache.org/httpcomponents-client-ga) + * Apache HttpClient (org.apache.httpcomponents.client5:httpclient5:5.1.3 - https://hc.apache.org/httpcomponents-client-5.0.x/5.1.3/httpclient5/) + * Apache HttpComponents Core HTTP/1.1 (org.apache.httpcomponents.core5:httpcore5:5.1.3 - https://hc.apache.org/httpcomponents-core-5.1.x/5.1.3/httpcore5/) + * Apache HttpComponents Core HTTP/2 (org.apache.httpcomponents.core5:httpcore5-h2:5.1.3 - https://hc.apache.org/httpcomponents-core-5.1.x/5.1.3/httpcore5-h2/) + * Apache James :: Mime4j :: Core (org.apache.james:apache-mime4j-core:0.8.10 - http://james.apache.org/mime4j/apache-mime4j-core) + * Apache James :: Mime4j :: DOM (org.apache.james:apache-mime4j-dom:0.8.11 - http://james.apache.org/mime4j/apache-mime4j-dom) * Apache Jena - Libraries POM (org.apache.jena:apache-jena-libs:2.13.0 - http://jena.apache.org/apache-jena-libs/) * Apache Jena - ARQ (SPARQL 1.1 Query Engine) (org.apache.jena:jena-arq:2.13.0 - http://jena.apache.org/jena-arq/) * Apache Jena - Core (org.apache.jena:jena-core:2.13.0 - http://jena.apache.org/jena-core/) @@ -196,86 +205,86 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Kerby-kerb Util (org.apache.kerby:kerb-util:1.0.1 - http://directory.apache.org/kerby/kerby-kerb/kerb-util) * Kerby ASN1 Project (org.apache.kerby:kerby-asn1:1.0.1 - http://directory.apache.org/kerby/kerby-common/kerby-asn1) * Kerby PKIX Project (org.apache.kerby:kerby-pkix:1.0.1 - http://directory.apache.org/kerby/kerby-pkix) - * Apache Log4j 1.x Compatibility API (org.apache.logging.log4j:log4j-1.2-api:2.20.0 - https://logging.apache.org/log4j/2.x/log4j-1.2-api/) - * Apache Log4j API (org.apache.logging.log4j:log4j-api:2.20.0 - https://logging.apache.org/log4j/2.x/log4j-api/) - * Apache Log4j Core (org.apache.logging.log4j:log4j-core:2.20.0 - https://logging.apache.org/log4j/2.x/log4j-core/) - * Apache Log4j JUL Adapter (org.apache.logging.log4j:log4j-jul:2.20.0 - https://logging.apache.org/log4j/2.x/log4j-jul/) - * Apache Log4j Layout for JSON template (org.apache.logging.log4j:log4j-layout-template-json:2.17.1 - https://logging.apache.org/log4j/2.x/log4j-layout-template-json/) - * Apache Log4j SLF4J Binding (org.apache.logging.log4j:log4j-slf4j-impl:2.20.0 - https://logging.apache.org/log4j/2.x/log4j-slf4j-impl/) - * Apache Log4j Web (org.apache.logging.log4j:log4j-web:2.20.0 - https://logging.apache.org/log4j/2.x/log4j-web/) - * Lucene Common Analyzers (org.apache.lucene:lucene-analyzers-common:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-analyzers-common) - * Lucene ICU Analysis Components (org.apache.lucene:lucene-analyzers-icu:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-analyzers-icu) - * Lucene Kuromoji Japanese Morphological Analyzer (org.apache.lucene:lucene-analyzers-kuromoji:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-analyzers-kuromoji) - * Lucene Nori Korean Morphological Analyzer (org.apache.lucene:lucene-analyzers-nori:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-analyzers-nori) - * Lucene Phonetic Filters (org.apache.lucene:lucene-analyzers-phonetic:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-analyzers-phonetic) - * Lucene Smart Chinese Analyzer (org.apache.lucene:lucene-analyzers-smartcn:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-analyzers-smartcn) - * Lucene Stempel Analyzer (org.apache.lucene:lucene-analyzers-stempel:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-analyzers-stempel) - * Lucene Memory (org.apache.lucene:lucene-backward-codecs:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-backward-codecs) - * Lucene Classification (org.apache.lucene:lucene-classification:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-classification) - * Lucene codecs (org.apache.lucene:lucene-codecs:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-codecs) - * Lucene Core (org.apache.lucene:lucene-core:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-core) - * Lucene Expressions (org.apache.lucene:lucene-expressions:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-expressions) - * Lucene Grouping (org.apache.lucene:lucene-grouping:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-grouping) - * Lucene Highlighter (org.apache.lucene:lucene-highlighter:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-highlighter) - * Lucene Join (org.apache.lucene:lucene-join:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-join) - * Lucene Memory (org.apache.lucene:lucene-memory:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-memory) - * Lucene Miscellaneous (org.apache.lucene:lucene-misc:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-misc) - * Lucene Queries (org.apache.lucene:lucene-queries:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-queries) - * Lucene QueryParsers (org.apache.lucene:lucene-queryparser:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-queryparser) - * Lucene Sandbox (org.apache.lucene:lucene-sandbox:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-sandbox) - * Lucene Spatial Extras (org.apache.lucene:lucene-spatial-extras:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-spatial-extras) - * Lucene Spatial 3D (org.apache.lucene:lucene-spatial3d:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-spatial3d) - * Lucene Suggest (org.apache.lucene:lucene-suggest:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-suggest) - * Apache FontBox (org.apache.pdfbox:fontbox:2.0.28 - http://pdfbox.apache.org/) + * Apache Log4j 1.x Compatibility API (org.apache.logging.log4j:log4j-1.2-api:2.23.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-1.2-api/) + * Apache Log4j API (org.apache.logging.log4j:log4j-api:2.23.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-api/) + * Apache Log4j Core (org.apache.logging.log4j:log4j-core:2.23.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-core/) + * Apache Log4j JUL Adapter (org.apache.logging.log4j:log4j-jul:2.23.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-jul/) + * Apache Log4j Layout for JSON template (org.apache.logging.log4j:log4j-layout-template-json:2.17.2 - https://logging.apache.org/log4j/2.x/log4j-layout-template-json/) + * Apache Log4j SLF4J Binding (org.apache.logging.log4j:log4j-slf4j-impl:2.23.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-slf4j-impl/) + * Apache Log4j Web (org.apache.logging.log4j:log4j-web:2.23.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-web/) + * Lucene Common Analyzers (org.apache.lucene:lucene-analyzers-common:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-common) + * Lucene ICU Analysis Components (org.apache.lucene:lucene-analyzers-icu:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-icu) + * Lucene Kuromoji Japanese Morphological Analyzer (org.apache.lucene:lucene-analyzers-kuromoji:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-kuromoji) + * Lucene Nori Korean Morphological Analyzer (org.apache.lucene:lucene-analyzers-nori:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-nori) + * Lucene Phonetic Filters (org.apache.lucene:lucene-analyzers-phonetic:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-phonetic) + * Lucene Smart Chinese Analyzer (org.apache.lucene:lucene-analyzers-smartcn:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-smartcn) + * Lucene Stempel Analyzer (org.apache.lucene:lucene-analyzers-stempel:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-stempel) + * Lucene Memory (org.apache.lucene:lucene-backward-codecs:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-backward-codecs) + * Lucene Classification (org.apache.lucene:lucene-classification:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-classification) + * Lucene codecs (org.apache.lucene:lucene-codecs:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-codecs) + * Lucene Core (org.apache.lucene:lucene-core:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-core) + * Lucene Expressions (org.apache.lucene:lucene-expressions:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-expressions) + * Lucene Grouping (org.apache.lucene:lucene-grouping:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-grouping) + * Lucene Highlighter (org.apache.lucene:lucene-highlighter:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-highlighter) + * Lucene Join (org.apache.lucene:lucene-join:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-join) + * Lucene Memory (org.apache.lucene:lucene-memory:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-memory) + * Lucene Miscellaneous (org.apache.lucene:lucene-misc:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-misc) + * Lucene Queries (org.apache.lucene:lucene-queries:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-queries) + * Lucene QueryParsers (org.apache.lucene:lucene-queryparser:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-queryparser) + * Lucene Sandbox (org.apache.lucene:lucene-sandbox:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-sandbox) + * Lucene Spatial Extras (org.apache.lucene:lucene-spatial-extras:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-spatial-extras) + * Lucene Spatial 3D (org.apache.lucene:lucene-spatial3d:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-spatial3d) + * Lucene Suggest (org.apache.lucene:lucene-suggest:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-suggest) + * Apache FontBox (org.apache.pdfbox:fontbox:2.0.31 - http://pdfbox.apache.org/) * PDFBox JBIG2 ImageIO plugin (org.apache.pdfbox:jbig2-imageio:3.0.4 - https://www.apache.org/jbig2-imageio/) * Apache JempBox (org.apache.pdfbox:jempbox:1.8.17 - http://www.apache.org/pdfbox-parent/jempbox/) - * Apache PDFBox (org.apache.pdfbox:pdfbox:2.0.28 - https://www.apache.org/pdfbox-parent/pdfbox/) - * Apache PDFBox tools (org.apache.pdfbox:pdfbox-tools:2.0.27 - https://www.apache.org/pdfbox-parent/pdfbox-tools/) - * Apache XmpBox (org.apache.pdfbox:xmpbox:2.0.27 - https://www.apache.org/pdfbox-parent/xmpbox/) - * Apache POI - Common (org.apache.poi:poi:5.2.3 - https://poi.apache.org/) - * Apache POI - API based on OPC and OOXML schemas (org.apache.poi:poi-ooxml:5.2.3 - https://poi.apache.org/) - * Apache POI (org.apache.poi:poi-ooxml-lite:5.2.3 - https://poi.apache.org/) - * Apache POI (org.apache.poi:poi-scratchpad:5.2.3 - https://poi.apache.org/) - * Apache Solr Core (org.apache.solr:solr-core:8.11.2 - https://lucene.apache.org/solr-parent/solr-core) - * Apache Solr Solrj (org.apache.solr:solr-solrj:8.11.2 - https://lucene.apache.org/solr-parent/solr-solrj) + * Apache PDFBox (org.apache.pdfbox:pdfbox:2.0.31 - https://www.apache.org/pdfbox-parent/pdfbox/) + * Apache PDFBox tools (org.apache.pdfbox:pdfbox-tools:2.0.31 - https://www.apache.org/pdfbox-parent/pdfbox-tools/) + * Apache XmpBox (org.apache.pdfbox:xmpbox:2.0.31 - https://www.apache.org/pdfbox-parent/xmpbox/) + * Apache POI - Common (org.apache.poi:poi:5.2.5 - https://poi.apache.org/) + * Apache POI - API based on OPC and OOXML schemas (org.apache.poi:poi-ooxml:5.2.5 - https://poi.apache.org/) + * Apache POI (org.apache.poi:poi-ooxml-lite:5.2.5 - https://poi.apache.org/) + * Apache POI (org.apache.poi:poi-scratchpad:5.2.5 - https://poi.apache.org/) + * Apache Solr Core (org.apache.solr:solr-core:8.11.3 - https://lucene.apache.org/solr-parent/solr-core) + * Apache Solr Solrj (org.apache.solr:solr-solrj:8.11.3 - https://lucene.apache.org/solr-parent/solr-solrj) * Apache Standard Taglib Implementation (org.apache.taglibs:taglibs-standard-impl:1.2.5 - http://tomcat.apache.org/taglibs/standard-1.2.5/taglibs-standard-impl) * Apache Standard Taglib Specification API (org.apache.taglibs:taglibs-standard-spec:1.2.5 - http://tomcat.apache.org/taglibs/standard-1.2.5/taglibs-standard-spec) * Apache Thrift (org.apache.thrift:libthrift:0.9.2 - http://thrift.apache.org) - * Apache Tika core (org.apache.tika:tika-core:2.5.0 - https://tika.apache.org/) - * Apache Tika Apple parser module (org.apache.tika:tika-parser-apple-module:2.5.0 - https://tika.apache.org/tika-parser-apple-module/) - * Apache Tika audiovideo parser module (org.apache.tika:tika-parser-audiovideo-module:2.5.0 - https://tika.apache.org/tika-parser-audiovideo-module/) - * Apache Tika cad parser module (org.apache.tika:tika-parser-cad-module:2.5.0 - https://tika.apache.org/tika-parser-cad-module/) - * Apache Tika code parser module (org.apache.tika:tika-parser-code-module:2.5.0 - https://tika.apache.org/tika-parser-code-module/) - * Apache Tika crypto parser module (org.apache.tika:tika-parser-crypto-module:2.5.0 - https://tika.apache.org/tika-parser-crypto-module/) - * Apache Tika digest commons (org.apache.tika:tika-parser-digest-commons:2.5.0 - https://tika.apache.org/tika-parser-digest-commons/) - * Apache Tika font parser module (org.apache.tika:tika-parser-font-module:2.5.0 - https://tika.apache.org/tika-parser-font-module/) - * Apache Tika html parser module (org.apache.tika:tika-parser-html-module:2.5.0 - https://tika.apache.org/tika-parser-html-module/) - * Apache Tika image parser module (org.apache.tika:tika-parser-image-module:2.5.0 - https://tika.apache.org/tika-parser-image-module/) - * Apache Tika mail commons (org.apache.tika:tika-parser-mail-commons:2.5.0 - https://tika.apache.org/tika-parser-mail-commons/) - * Apache Tika mail parser module (org.apache.tika:tika-parser-mail-module:2.5.0 - https://tika.apache.org/tika-parser-mail-module/) - * Apache Tika Microsoft parser module (org.apache.tika:tika-parser-microsoft-module:2.5.0 - https://tika.apache.org/tika-parser-microsoft-module/) - * Apache Tika miscellaneous office format parser module (org.apache.tika:tika-parser-miscoffice-module:2.5.0 - https://tika.apache.org/tika-parser-miscoffice-module/) - * Apache Tika news parser module (org.apache.tika:tika-parser-news-module:2.5.0 - https://tika.apache.org/tika-parser-news-module/) - * Apache Tika OCR parser module (org.apache.tika:tika-parser-ocr-module:2.5.0 - https://tika.apache.org/tika-parser-ocr-module/) - * Apache Tika PDF parser module (org.apache.tika:tika-parser-pdf-module:2.5.0 - https://tika.apache.org/tika-parser-pdf-module/) - * Apache Tika package parser module (org.apache.tika:tika-parser-pkg-module:2.5.0 - https://tika.apache.org/tika-parser-pkg-module/) - * Apache Tika text parser module (org.apache.tika:tika-parser-text-module:2.5.0 - https://tika.apache.org/tika-parser-text-module/) - * Apache Tika WARC parser module (org.apache.tika:tika-parser-webarchive-module:2.5.0 - https://tika.apache.org/tika-parser-webarchive-module/) - * Apache Tika XML parser module (org.apache.tika:tika-parser-xml-module:2.5.0 - https://tika.apache.org/tika-parser-xml-module/) - * Apache Tika XMP commons (org.apache.tika:tika-parser-xmp-commons:2.5.0 - https://tika.apache.org/tika-parser-xmp-commons/) - * Apache Tika ZIP commons (org.apache.tika:tika-parser-zip-commons:2.5.0 - https://tika.apache.org/tika-parser-zip-commons/) - * Apache Tika standard parser package (org.apache.tika:tika-parsers-standard-package:2.5.0 - https://tika.apache.org/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/) - * tomcat-embed-core (org.apache.tomcat.embed:tomcat-embed-core:9.0.75 - https://tomcat.apache.org/) - * tomcat-embed-el (org.apache.tomcat.embed:tomcat-embed-el:9.0.75 - https://tomcat.apache.org/) - * tomcat-embed-websocket (org.apache.tomcat.embed:tomcat-embed-websocket:9.0.75 - https://tomcat.apache.org/) + * Apache Tika core (org.apache.tika:tika-core:2.9.2 - https://tika.apache.org/) + * Apache Tika Apple parser module (org.apache.tika:tika-parser-apple-module:2.9.2 - https://tika.apache.org/tika-parser-apple-module/) + * Apache Tika audiovideo parser module (org.apache.tika:tika-parser-audiovideo-module:2.9.2 - https://tika.apache.org/tika-parser-audiovideo-module/) + * Apache Tika cad parser module (org.apache.tika:tika-parser-cad-module:2.9.2 - https://tika.apache.org/tika-parser-cad-module/) + * Apache Tika code parser module (org.apache.tika:tika-parser-code-module:2.9.2 - https://tika.apache.org/tika-parser-code-module/) + * Apache Tika crypto parser module (org.apache.tika:tika-parser-crypto-module:2.9.2 - https://tika.apache.org/tika-parser-crypto-module/) + * Apache Tika digest commons (org.apache.tika:tika-parser-digest-commons:2.9.2 - https://tika.apache.org/tika-parser-digest-commons/) + * Apache Tika font parser module (org.apache.tika:tika-parser-font-module:2.9.2 - https://tika.apache.org/tika-parser-font-module/) + * Apache Tika html parser module (org.apache.tika:tika-parser-html-module:2.9.2 - https://tika.apache.org/tika-parser-html-module/) + * Apache Tika image parser module (org.apache.tika:tika-parser-image-module:2.9.2 - https://tika.apache.org/tika-parser-image-module/) + * Apache Tika mail commons (org.apache.tika:tika-parser-mail-commons:2.9.2 - https://tika.apache.org/tika-parser-mail-commons/) + * Apache Tika mail parser module (org.apache.tika:tika-parser-mail-module:2.9.2 - https://tika.apache.org/tika-parser-mail-module/) + * Apache Tika Microsoft parser module (org.apache.tika:tika-parser-microsoft-module:2.9.2 - https://tika.apache.org/tika-parser-microsoft-module/) + * Apache Tika miscellaneous office format parser module (org.apache.tika:tika-parser-miscoffice-module:2.9.2 - https://tika.apache.org/tika-parser-miscoffice-module/) + * Apache Tika news parser module (org.apache.tika:tika-parser-news-module:2.9.2 - https://tika.apache.org/tika-parser-news-module/) + * Apache Tika OCR parser module (org.apache.tika:tika-parser-ocr-module:2.9.2 - https://tika.apache.org/tika-parser-ocr-module/) + * Apache Tika PDF parser module (org.apache.tika:tika-parser-pdf-module:2.9.2 - https://tika.apache.org/tika-parser-pdf-module/) + * Apache Tika package parser module (org.apache.tika:tika-parser-pkg-module:2.9.2 - https://tika.apache.org/tika-parser-pkg-module/) + * Apache Tika text parser module (org.apache.tika:tika-parser-text-module:2.9.2 - https://tika.apache.org/tika-parser-text-module/) + * Apache Tika WARC parser module (org.apache.tika:tika-parser-webarchive-module:2.9.2 - https://tika.apache.org/tika-parser-webarchive-module/) + * Apache Tika XML parser module (org.apache.tika:tika-parser-xml-module:2.9.2 - https://tika.apache.org/tika-parser-xml-module/) + * Apache Tika XMP commons (org.apache.tika:tika-parser-xmp-commons:2.9.2 - https://tika.apache.org/tika-parser-xmp-commons/) + * Apache Tika ZIP commons (org.apache.tika:tika-parser-zip-commons:2.9.2 - https://tika.apache.org/tika-parser-zip-commons/) + * Apache Tika standard parser package (org.apache.tika:tika-parsers-standard-package:2.9.2 - https://tika.apache.org/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/) + * tomcat-embed-core (org.apache.tomcat.embed:tomcat-embed-core:9.0.83 - https://tomcat.apache.org/) + * tomcat-embed-el (org.apache.tomcat.embed:tomcat-embed-el:9.0.83 - https://tomcat.apache.org/) + * tomcat-embed-websocket (org.apache.tomcat.embed:tomcat-embed-websocket:9.0.83 - https://tomcat.apache.org/) * Apache Velocity - Engine (org.apache.velocity:velocity-engine-core:2.3 - http://velocity.apache.org/engine/devel/velocity-engine-core/) * Apache Velocity - JSR 223 Scripting (org.apache.velocity:velocity-engine-scripting:2.2 - http://velocity.apache.org/engine/devel/velocity-engine-scripting/) * Axiom API (org.apache.ws.commons.axiom:axiom-api:1.2.22 - http://ws.apache.org/axiom/) * Abdera Model (FOM) Implementation (org.apache.ws.commons.axiom:fom-impl:1.2.22 - http://ws.apache.org/axiom/implementations/fom-impl/) - * XmlBeans (org.apache.xmlbeans:xmlbeans:5.1.1 - https://xmlbeans.apache.org/) + * XmlBeans (org.apache.xmlbeans:xmlbeans:5.2.0 - https://xmlbeans.apache.org/) * Apache ZooKeeper - Server (org.apache.zookeeper:zookeeper:3.6.2 - http://zookeeper.apache.org/zookeeper) * Apache ZooKeeper - Jute (org.apache.zookeeper:zookeeper-jute:3.6.2 - http://zookeeper.apache.org/zookeeper-jute) - * org.apiguardian:apiguardian-api (org.apiguardian:apiguardian-api:1.1.0 - https://github.com/apiguardian-team/apiguardian) + * org.apiguardian:apiguardian-api (org.apiguardian:apiguardian-api:1.1.2 - https://github.com/apiguardian-team/apiguardian) * AssertJ fluent assertions (org.assertj:assertj-core:3.22.0 - https://assertj.github.io/doc/assertj-core/) * Evo Inflector (org.atteo:evo-inflector:1.3 - http://atteo.org/static/evo-inflector) * jose4j (org.bitbucket.b_c:jose4j:0.6.5 - https://bitbucket.org/b_c/jose4j/) @@ -284,49 +293,50 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * rfc3986-uri (org.dmfs:rfc3986-uri:0.8.1 - https://github.com/dmfs/uri-toolkit) * Jetty :: Apache JSP Implementation (org.eclipse.jetty:apache-jsp:9.4.15.v20190215 - http://www.eclipse.org/jetty) * Apache :: JSTL module (org.eclipse.jetty:apache-jstl:9.4.15.v20190215 - http://tomcat.apache.org/taglibs/standard/) - * Jetty :: ALPN :: Client (org.eclipse.jetty:jetty-alpn-client:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-client) - * Jetty :: ALPN :: JDK9 Client Implementation (org.eclipse.jetty:jetty-alpn-java-client:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-client) - * Jetty :: ALPN :: JDK9 Server Implementation (org.eclipse.jetty:jetty-alpn-java-server:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-server) - * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server) - * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server) + * Jetty :: ALPN :: Client (org.eclipse.jetty:jetty-alpn-client:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-client) + * Jetty :: ALPN :: JDK9 Client Implementation (org.eclipse.jetty:jetty-alpn-java-client:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-client) + * Jetty :: ALPN :: JDK9 Server Implementation (org.eclipse.jetty:jetty-alpn-java-server:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-server) + * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server) + * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server) * Jetty :: Servlet Annotations (org.eclipse.jetty:jetty-annotations:9.4.15.v20190215 - http://www.eclipse.org/jetty) - * Jetty :: Asynchronous HTTP Client (org.eclipse.jetty:jetty-client:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-client) - * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-continuation) - * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-continuation) - * Jetty :: Deployers (org.eclipse.jetty:jetty-deploy:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-deploy) - * Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-http) - * Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-io) - * Jetty :: JMX Management (org.eclipse.jetty:jetty-jmx:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-jmx) + * Jetty :: Asynchronous HTTP Client (org.eclipse.jetty:jetty-client:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-client) + * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-continuation) + * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-continuation) + * Jetty :: Deployers (org.eclipse.jetty:jetty-deploy:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-deploy) + * Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-http) + * Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-io) + * Jetty :: JMX Management (org.eclipse.jetty:jetty-jmx:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-jmx) * Jetty :: JNDI Naming (org.eclipse.jetty:jetty-jndi:9.4.15.v20190215 - http://www.eclipse.org/jetty) * Jetty :: Plus (org.eclipse.jetty:jetty-plus:9.4.15.v20190215 - http://www.eclipse.org/jetty) - * Jetty :: Rewrite Handler (org.eclipse.jetty:jetty-rewrite:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-rewrite) - * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-security) - * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-security) - * Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-server) - * Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-servlet) - * Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-servlets) - * Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-util) - * Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-util-ajax) - * Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-webapp) - * Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-xml) - * Jetty :: HTTP2 :: Client (org.eclipse.jetty.http2:http2-client:9.4.44.v20210927 - https://eclipse.org/jetty/http2-parent/http2-client) - * Jetty :: HTTP2 :: Common (org.eclipse.jetty.http2:http2-common:9.4.51.v20230217 - https://eclipse.org/jetty/http2-parent/http2-common) - * Jetty :: HTTP2 :: HPACK (org.eclipse.jetty.http2:http2-hpack:9.4.44.v20210927 - https://eclipse.org/jetty/http2-parent/http2-hpack) - * Jetty :: HTTP2 :: HTTP Client Transport (org.eclipse.jetty.http2:http2-http-client-transport:9.4.44.v20210927 - https://eclipse.org/jetty/http2-parent/http2-http-client-transport) - * Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.51.v20230217 - https://eclipse.org/jetty/http2-parent/http2-server) + * Jetty :: Rewrite Handler (org.eclipse.jetty:jetty-rewrite:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-rewrite) + * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-security) + * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-security) + * Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-server) + * Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-servlet) + * Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-servlets) + * Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-util) + * Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-util-ajax) + * Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-webapp) + * Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-xml) + * Jetty :: HTTP2 :: Client (org.eclipse.jetty.http2:http2-client:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-client) + * Jetty :: HTTP2 :: Common (org.eclipse.jetty.http2:http2-common:9.4.54.v20240208 - https://eclipse.org/jetty/http2-parent/http2-common) + * Jetty :: HTTP2 :: HPACK (org.eclipse.jetty.http2:http2-hpack:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-hpack) + * Jetty :: HTTP2 :: HTTP Client Transport (org.eclipse.jetty.http2:http2-http-client-transport:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-http-client-transport) + * Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.54.v20240208 - https://eclipse.org/jetty/http2-parent/http2-server) * Jetty :: Schemas (org.eclipse.jetty.toolchain:jetty-schemas:3.1.2 - https://eclipse.org/jetty/jetty-schemas) - * Ehcache (org.ehcache:ehcache:3.4.0 - http://ehcache.org) - * flyway-core (org.flywaydb:flyway-core:8.4.4 - https://flywaydb.org/flyway-core) + * Ehcache (org.ehcache:ehcache:3.10.8 - http://ehcache.org) + * flyway-core (org.flywaydb:flyway-core:8.5.13 - https://flywaydb.org/flyway-core) * Ogg and Vorbis for Java, Core (org.gagravarr:vorbis-java-core:0.8 - https://github.com/Gagravarr/VorbisJava) * Apache Tika plugin for Ogg, Vorbis and FLAC (org.gagravarr:vorbis-java-tika:0.8 - https://github.com/Gagravarr/VorbisJava) - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * Hibernate Validator Engine (org.hibernate.validator:hibernate-validator:6.2.5.Final - http://hibernate.org/validator/hibernate-validator) * Hibernate Validator Portable Extension (org.hibernate.validator:hibernate-validator-cdi:6.2.5.Final - http://hibernate.org/validator/hibernate-validator-cdi) + * org.immutables.value-annotations (org.immutables:value-annotations:2.9.2 - http://immutables.org/value-annotations) * leveldb (org.iq80.leveldb:leveldb:0.12 - http://github.com/dain/leveldb/leveldb) * leveldb-api (org.iq80.leveldb:leveldb-api:0.12 - http://github.com/dain/leveldb/leveldb-api) - * Javassist (org.javassist:javassist:3.25.0-GA - http://www.javassist.org/) + * Javassist (org.javassist:javassist:3.29.0-GA - http://www.javassist.org/) * Java Annotation Indexer (org.jboss:jandex:2.4.2.Final - http://www.jboss.org/jandex) * JBoss Logging 3 (org.jboss.logging:jboss-logging:3.4.3.Final - http://www.jboss.org) * JDOM (org.jdom:jdom2:2.0.6.1 - http://www.jdom.org) @@ -335,6 +345,7 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * jtwig-spring (org.jtwig:jtwig-spring:5.87.0.RELEASE - http://jtwig.org) * jtwig-spring-boot-starter (org.jtwig:jtwig-spring-boot-starter:5.87.0.RELEASE - http://jtwig.org) * jtwig-web (org.jtwig:jtwig-web:5.87.0.RELEASE - http://jtwig.org) + * Proj4J (org.locationtech.proj4j:proj4j:1.1.5 - https://github.com/locationtech/proj4j) * Spatial4J (org.locationtech.spatial4j:spatial4j:0.7 - https://projects.eclipse.org/projects/locationtech.spatial4j) * MockServer Java Client (org.mock-server:mockserver-client-java:5.11.2 - http://www.mock-server.com) * MockServer Core (org.mock-server:mockserver-core:5.11.2 - http://www.mock-server.com) @@ -346,12 +357,12 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Jetty Servlet Tester (org.mortbay.jetty:jetty-servlet-tester:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/jetty-servlet-tester) * Jetty Utilities (org.mortbay.jetty:jetty-util:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/jetty-util) * Servlet Specification API (org.mortbay.jetty:servlet-api:2.5-20081211 - http://jetty.mortbay.org/servlet-api) - * jwarc (org.netpreserve:jwarc:0.19.0 - https://github.com/iipc/jwarc) + * jwarc (org.netpreserve:jwarc:0.29.0 - https://github.com/iipc/jwarc) * Objenesis (org.objenesis:objenesis:3.2 - http://objenesis.org/objenesis) * parboiled-core (org.parboiled:parboiled-core:1.3.1 - http://parboiled.org) * parboiled-java (org.parboiled:parboiled-java:1.3.1 - http://parboiled.org) * RRD4J (org.rrd4j:rrd4j:3.5 - https://github.com/rrd4j/rrd4j/) - * Scala Library (org.scala-lang:scala-library:2.13.9 - https://www.scala-lang.org/) + * Scala Library (org.scala-lang:scala-library:2.13.11 - https://www.scala-lang.org/) * Scala Compiler (org.scala-lang:scala-reflect:2.13.0 - https://www.scala-lang.org/) * scala-collection-compat (org.scala-lang.modules:scala-collection-compat_2.13:2.1.6 - http://www.scala-lang.org/) * scala-java8-compat (org.scala-lang.modules:scala-java8-compat_2.13:0.9.0 - http://www.scala-lang.org/) @@ -359,58 +370,55 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * scala-xml (org.scala-lang.modules:scala-xml_2.13:1.3.0 - http://www.scala-lang.org/) * JSONassert (org.skyscreamer:jsonassert:1.5.1 - https://github.com/skyscreamer/JSONassert) * JCL 1.2 implemented over SLF4J (org.slf4j:jcl-over-slf4j:1.7.36 - http://www.slf4j.org) - * Spring AOP (org.springframework:spring-aop:5.3.27 - https://github.com/spring-projects/spring-framework) - * Spring Beans (org.springframework:spring-beans:5.3.27 - https://github.com/spring-projects/spring-framework) - * Spring Context (org.springframework:spring-context:5.3.27 - https://github.com/spring-projects/spring-framework) - * Spring Context Support (org.springframework:spring-context-support:5.3.27 - https://github.com/spring-projects/spring-framework) - * Spring Core (org.springframework:spring-core:5.3.27 - https://github.com/spring-projects/spring-framework) - * Spring Expression Language (SpEL) (org.springframework:spring-expression:5.3.27 - https://github.com/spring-projects/spring-framework) - * Spring Commons Logging Bridge (org.springframework:spring-jcl:5.3.27 - https://github.com/spring-projects/spring-framework) - * Spring JDBC (org.springframework:spring-jdbc:5.3.27 - https://github.com/spring-projects/spring-framework) - * Spring Object/Relational Mapping (org.springframework:spring-orm:5.3.27 - https://github.com/spring-projects/spring-framework) - * Spring TestContext Framework (org.springframework:spring-test:5.3.27 - https://github.com/spring-projects/spring-framework) - * Spring Transaction (org.springframework:spring-tx:5.3.27 - https://github.com/spring-projects/spring-framework) - * Spring Web (org.springframework:spring-web:5.3.27 - https://github.com/spring-projects/spring-framework) - * Spring Web MVC (org.springframework:spring-webmvc:5.3.27 - https://github.com/spring-projects/spring-framework) - * spring-boot (org.springframework.boot:spring-boot:2.7.12 - https://spring.io/projects/spring-boot) - * spring-boot-actuator (org.springframework.boot:spring-boot-actuator:2.7.12 - https://spring.io/projects/spring-boot) - * spring-boot-actuator-autoconfigure (org.springframework.boot:spring-boot-actuator-autoconfigure:2.7.12 - https://spring.io/projects/spring-boot) - * spring-boot-autoconfigure (org.springframework.boot:spring-boot-autoconfigure:2.7.12 - https://spring.io/projects/spring-boot) + * Spring AOP (org.springframework:spring-aop:5.3.34 - https://github.com/spring-projects/spring-framework) + * Spring Beans (org.springframework:spring-beans:5.3.34 - https://github.com/spring-projects/spring-framework) + * Spring Context (org.springframework:spring-context:5.3.34 - https://github.com/spring-projects/spring-framework) + * Spring Context Support (org.springframework:spring-context-support:5.3.34 - https://github.com/spring-projects/spring-framework) + * Spring Core (org.springframework:spring-core:5.3.34 - https://github.com/spring-projects/spring-framework) + * Spring Expression Language (SpEL) (org.springframework:spring-expression:5.3.34 - https://github.com/spring-projects/spring-framework) + * Spring Commons Logging Bridge (org.springframework:spring-jcl:5.3.34 - https://github.com/spring-projects/spring-framework) + * Spring JDBC (org.springframework:spring-jdbc:5.3.34 - https://github.com/spring-projects/spring-framework) + * Spring Object/Relational Mapping (org.springframework:spring-orm:5.3.34 - https://github.com/spring-projects/spring-framework) + * Spring TestContext Framework (org.springframework:spring-test:5.3.34 - https://github.com/spring-projects/spring-framework) + * Spring Transaction (org.springframework:spring-tx:5.3.34 - https://github.com/spring-projects/spring-framework) + * Spring Web (org.springframework:spring-web:5.3.34 - https://github.com/spring-projects/spring-framework) + * Spring Web MVC (org.springframework:spring-webmvc:5.3.34 - https://github.com/spring-projects/spring-framework) + * spring-boot (org.springframework.boot:spring-boot:2.7.18 - https://spring.io/projects/spring-boot) + * spring-boot-actuator (org.springframework.boot:spring-boot-actuator:2.7.18 - https://spring.io/projects/spring-boot) + * spring-boot-actuator-autoconfigure (org.springframework.boot:spring-boot-actuator-autoconfigure:2.7.18 - https://spring.io/projects/spring-boot) + * spring-boot-autoconfigure (org.springframework.boot:spring-boot-autoconfigure:2.7.18 - https://spring.io/projects/spring-boot) * Spring Boot Configuration Processor (org.springframework.boot:spring-boot-configuration-processor:2.0.0.RELEASE - https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-tools/spring-boot-configuration-processor) - * spring-boot-starter (org.springframework.boot:spring-boot-starter:2.7.12 - https://spring.io/projects/spring-boot) - * spring-boot-starter-actuator (org.springframework.boot:spring-boot-starter-actuator:2.7.12 - https://spring.io/projects/spring-boot) - * spring-boot-starter-aop (org.springframework.boot:spring-boot-starter-aop:2.7.12 - https://spring.io/projects/spring-boot) - * spring-boot-starter-cache (org.springframework.boot:spring-boot-starter-cache:2.7.12 - https://spring.io/projects/spring-boot) - * spring-boot-starter-data-rest (org.springframework.boot:spring-boot-starter-data-rest:2.7.12 - https://spring.io/projects/spring-boot) - * spring-boot-starter-json (org.springframework.boot:spring-boot-starter-json:2.7.12 - https://spring.io/projects/spring-boot) - * spring-boot-starter-log4j2 (org.springframework.boot:spring-boot-starter-log4j2:2.7.12 - https://spring.io/projects/spring-boot) - * spring-boot-starter-security (org.springframework.boot:spring-boot-starter-security:2.7.12 - https://spring.io/projects/spring-boot) - * spring-boot-starter-test (org.springframework.boot:spring-boot-starter-test:2.7.12 - https://spring.io/projects/spring-boot) - * spring-boot-starter-tomcat (org.springframework.boot:spring-boot-starter-tomcat:2.7.12 - https://spring.io/projects/spring-boot) - * spring-boot-starter-web (org.springframework.boot:spring-boot-starter-web:2.7.12 - https://spring.io/projects/spring-boot) - * spring-boot-test (org.springframework.boot:spring-boot-test:2.7.12 - https://spring.io/projects/spring-boot) - * spring-boot-test-autoconfigure (org.springframework.boot:spring-boot-test-autoconfigure:2.7.12 - https://spring.io/projects/spring-boot) - * Spring Data Core (org.springframework.data:spring-data-commons:2.7.12 - https://www.spring.io/spring-data/spring-data-commons) - * Spring Data REST - Core (org.springframework.data:spring-data-rest-core:3.7.12 - https://www.spring.io/spring-data/spring-data-rest-parent/spring-data-rest-core) - * Spring Data REST - WebMVC (org.springframework.data:spring-data-rest-webmvc:3.7.12 - https://www.spring.io/spring-data/spring-data-rest-parent/spring-data-rest-webmvc) - * Spring HATEOAS (org.springframework.hateoas:spring-hateoas:1.5.4 - https://github.com/spring-projects/spring-hateoas) + * spring-boot-starter (org.springframework.boot:spring-boot-starter:2.7.18 - https://spring.io/projects/spring-boot) + * spring-boot-starter-actuator (org.springframework.boot:spring-boot-starter-actuator:2.7.18 - https://spring.io/projects/spring-boot) + * spring-boot-starter-aop (org.springframework.boot:spring-boot-starter-aop:2.7.18 - https://spring.io/projects/spring-boot) + * spring-boot-starter-cache (org.springframework.boot:spring-boot-starter-cache:2.7.18 - https://spring.io/projects/spring-boot) + * spring-boot-starter-data-rest (org.springframework.boot:spring-boot-starter-data-rest:2.7.18 - https://spring.io/projects/spring-boot) + * spring-boot-starter-json (org.springframework.boot:spring-boot-starter-json:2.7.18 - https://spring.io/projects/spring-boot) + * spring-boot-starter-log4j2 (org.springframework.boot:spring-boot-starter-log4j2:2.7.18 - https://spring.io/projects/spring-boot) + * spring-boot-starter-security (org.springframework.boot:spring-boot-starter-security:2.7.18 - https://spring.io/projects/spring-boot) + * spring-boot-starter-test (org.springframework.boot:spring-boot-starter-test:2.7.18 - https://spring.io/projects/spring-boot) + * spring-boot-starter-tomcat (org.springframework.boot:spring-boot-starter-tomcat:2.7.18 - https://spring.io/projects/spring-boot) + * spring-boot-starter-web (org.springframework.boot:spring-boot-starter-web:2.7.18 - https://spring.io/projects/spring-boot) + * spring-boot-test (org.springframework.boot:spring-boot-test:2.7.18 - https://spring.io/projects/spring-boot) + * spring-boot-test-autoconfigure (org.springframework.boot:spring-boot-test-autoconfigure:2.7.18 - https://spring.io/projects/spring-boot) + * Spring Data Core (org.springframework.data:spring-data-commons:2.7.18 - https://www.spring.io/spring-data/spring-data-commons) + * Spring Data REST - Core (org.springframework.data:spring-data-rest-core:3.7.18 - https://www.spring.io/spring-data/spring-data-rest-parent/spring-data-rest-core) + * Spring Data REST - WebMVC (org.springframework.data:spring-data-rest-webmvc:3.7.18 - https://www.spring.io/spring-data/spring-data-rest-parent/spring-data-rest-webmvc) + * Spring HATEOAS (org.springframework.hateoas:spring-hateoas:1.5.6 - https://github.com/spring-projects/spring-hateoas) * Spring Plugin - Core (org.springframework.plugin:spring-plugin-core:2.0.0.RELEASE - https://github.com/spring-projects/spring-plugin/spring-plugin-core) - * spring-security-config (org.springframework.security:spring-security-config:5.7.8 - https://spring.io/projects/spring-security) - * spring-security-core (org.springframework.security:spring-security-core:5.7.8 - https://spring.io/projects/spring-security) - * spring-security-crypto (org.springframework.security:spring-security-crypto:5.7.8 - https://spring.io/projects/spring-security) - * spring-security-test (org.springframework.security:spring-security-test:5.7.8 - https://spring.io/projects/spring-security) - * spring-security-web (org.springframework.security:spring-security-web:5.7.8 - https://spring.io/projects/spring-security) + * spring-security-config (org.springframework.security:spring-security-config:5.7.11 - https://spring.io/projects/spring-security) + * spring-security-core (org.springframework.security:spring-security-core:5.7.11 - https://spring.io/projects/spring-security) + * spring-security-crypto (org.springframework.security:spring-security-crypto:5.7.11 - https://spring.io/projects/spring-security) + * spring-security-test (org.springframework.security:spring-security-test:5.7.11 - https://spring.io/projects/spring-security) + * spring-security-web (org.springframework.security:spring-security-web:5.7.11 - https://spring.io/projects/spring-security) * SWORD v2 :: Common Server Library (org.swordapp:sword2-server:1.0 - http://www.swordapp.org/) - * snappy-java (org.xerial.snappy:snappy-java:1.1.7.6 - https://github.com/xerial/snappy-java) + * snappy-java (org.xerial.snappy:snappy-java:1.1.10.1 - https://github.com/xerial/snappy-java) * xml-matchers (org.xmlmatchers:xml-matchers:0.10 - http://code.google.com/p/xml-matchers/) - * org.xmlunit:xmlunit-core (org.xmlunit:xmlunit-core:2.8.0 - https://www.xmlunit.org/) + * org.xmlunit:xmlunit-core (org.xmlunit:xmlunit-core:2.10.0 - https://www.xmlunit.org/) * org.xmlunit:xmlunit-core (org.xmlunit:xmlunit-core:2.9.1 - https://www.xmlunit.org/) * org.xmlunit:xmlunit-placeholders (org.xmlunit:xmlunit-placeholders:2.8.0 - https://www.xmlunit.org/xmlunit-placeholders/) * SnakeYAML (org.yaml:snakeyaml:1.30 - https://bitbucket.org/snakeyaml/snakeyaml) * software.amazon.ion:ion-java (software.amazon.ion:ion-java:1.0.2 - https://github.com/amznlabs/ion-java/) - * Xalan Java Serializer (xalan:serializer:2.7.2 - http://xml.apache.org/xalan-j/) - * xalan (xalan:xalan:2.7.0 - no url defined) - * Xalan Java (xalan:xalan:2.7.2 - http://xml.apache.org/xalan-j/) * Xerces2-j (xerces:xercesImpl:2.12.2 - https://xerces.apache.org/xerces2-j/) * XML Commons External Components XML APIs (xml-apis:xml-apis:1.4.01 - http://xml.apache.org/commons/components/external/) @@ -421,29 +429,28 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * coverity-escapers (com.coverity.security:coverity-escapers:1.1.1 - http://coverity.com/security) * Java Advanced Imaging Image I/O Tools API core (standalone) (com.github.jai-imageio:jai-imageio-core:1.4.0 - https://github.com/jai-imageio/jai-imageio-core) * JSONLD Java :: Core (com.github.jsonld-java:jsonld-java:0.5.1 - http://github.com/jsonld-java/jsonld-java/jsonld-java/) - * curvesapi (com.github.virtuald:curvesapi:1.07 - https://github.com/virtuald/curvesapi) - * Protocol Buffers [Core] (com.google.protobuf:protobuf-java:3.11.0 - https://developers.google.com/protocol-buffers/protobuf-java/) + * curvesapi (com.github.virtuald:curvesapi:1.08 - https://github.com/virtuald/curvesapi) + * Protocol Buffers [Core] (com.google.protobuf:protobuf-java:3.15.0 - https://developers.google.com/protocol-buffers/protobuf-java/) * JZlib (com.jcraft:jzlib:1.1.3 - http://www.jcraft.com/jzlib/) - * dnsjava (dnsjava:dnsjava:2.1.7 - http://www.dnsjava.org) - * jaxen (jaxen:jaxen:1.1.6 - http://jaxen.codehaus.org/) + * dnsjava (dnsjava:dnsjava:2.1.9 - http://www.dnsjava.org) + * jaxen (jaxen:jaxen:2.0.0 - http://www.cafeconleche.org/jaxen/jaxen) * ANTLR 4 Runtime (org.antlr:antlr4-runtime:4.5.1-1 - http://www.antlr.org/antlr4-runtime) - * commons-compiler (org.codehaus.janino:commons-compiler:3.0.9 - http://janino-compiler.github.io/commons-compiler/) - * janino (org.codehaus.janino:janino:3.0.9 - http://janino-compiler.github.io/janino/) + * commons-compiler (org.codehaus.janino:commons-compiler:3.1.8 - http://janino-compiler.github.io/commons-compiler/) + * janino (org.codehaus.janino:janino:3.1.8 - http://janino-compiler.github.io/janino/) * Stax2 API (org.codehaus.woodstox:stax2-api:4.2.1 - http://github.com/FasterXML/stax2-api) - * Hamcrest Date (org.exparity:hamcrest-date:2.0.7 - https://github.com/exparity/hamcrest-date) - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * Hamcrest Date (org.exparity:hamcrest-date:2.0.8 - https://github.com/exparity/hamcrest-date) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * Hamcrest (org.hamcrest:hamcrest:2.2 - http://hamcrest.org/JavaHamcrest/) - * Hamcrest All (org.hamcrest:hamcrest-all:1.3 - https://github.com/hamcrest/JavaHamcrest/hamcrest-all) - * Hamcrest Core (org.hamcrest:hamcrest-core:1.3 - https://github.com/hamcrest/JavaHamcrest/hamcrest-core) + * Hamcrest Core (org.hamcrest:hamcrest-core:2.2 - http://hamcrest.org/JavaHamcrest/) * HdrHistogram (org.hdrhistogram:HdrHistogram:2.1.12 - http://hdrhistogram.github.io/HdrHistogram/) * JBibTeX (org.jbibtex:jbibtex:1.0.20 - http://www.jbibtex.org) * asm (org.ow2.asm:asm:8.0.1 - http://asm.ow2.io/) * asm-analysis (org.ow2.asm:asm-analysis:7.1 - http://asm.ow2.org/) - * asm-commons (org.ow2.asm:asm-commons:8.0.1 - http://asm.ow2.io/) + * asm-commons (org.ow2.asm:asm-commons:9.3 - http://asm.ow2.io/) * asm-tree (org.ow2.asm:asm-tree:7.1 - http://asm.ow2.org/) * asm-util (org.ow2.asm:asm-util:7.1 - http://asm.ow2.org/) - * PostgreSQL JDBC Driver (org.postgresql:postgresql:42.6.0 - https://jdbc.postgresql.org) + * PostgreSQL JDBC Driver (org.postgresql:postgresql:42.7.3 - https://jdbc.postgresql.org) * Reflections (org.reflections:reflections:0.9.12 - http://github.com/ronmamo/reflections) * JMatIO (org.tallison:jmatio:1.5 - https://github.com/tballison/jmatio) * XMLUnit for Java (xmlunit:xmlunit:1.3 - http://xmlunit.sourceforge.net/) @@ -454,12 +461,12 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines Common Development and Distribution License (CDDL): - * istack common utility code runtime (com.sun.istack:istack-commons-runtime:3.0.7 - http://java.net/istack-commons/istack-commons-runtime/) * JavaMail API (com.sun.mail:javax.mail:1.6.2 - http://javaee.github.io/javamail/javax.mail) * JavaMail API (no providers) (com.sun.mail:mailapi:1.6.2 - http://javaee.github.io/javamail/mailapi) * Old JAXB Core (com.sun.xml.bind:jaxb-core:2.3.0.1 - http://jaxb.java.net/jaxb-bundles/jaxb-core) * Old JAXB Runtime (com.sun.xml.bind:jaxb-impl:2.3.1 - http://jaxb.java.net/jaxb-bundles/jaxb-impl) * Jakarta Annotations API (jakarta.annotation:jakarta.annotation-api:1.3.5 - https://projects.eclipse.org/projects/ee4j.ca) + * javax.transaction API (jakarta.transaction:jakarta.transaction-api:1.3.3 - https://projects.eclipse.org/projects/ee4j.jta) * jakarta.ws.rs-api (jakarta.ws.rs:jakarta.ws.rs-api:2.1.6 - https://github.com/eclipse-ee4j/jaxrs-api) * JavaBeans Activation Framework (JAF) (javax.activation:activation:1.1 - http://java.sun.com/products/javabeans/jaf/index.jsp) * JavaBeans Activation Framework API jar (javax.activation:javax.activation-api:1.2.0 - http://java.net/all/javax.activation-api/) @@ -474,12 +481,9 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * OSGi resource locator (org.glassfish.hk2:osgi-resource-locator:1.0.3 - https://projects.eclipse.org/projects/ee4j/osgi-resource-locator) * aopalliance version 1.0 repackaged as a module (org.glassfish.hk2.external:aopalliance-repackaged:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/external/aopalliance-repackaged) * javax.inject:1 as OSGi bundle (org.glassfish.hk2.external:jakarta.inject:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/external/jakarta.inject) - * JAXB Runtime (org.glassfish.jaxb:jaxb-runtime:2.3.1 - http://jaxb.java.net/jaxb-runtime-parent/jaxb-runtime) - * TXW2 Runtime (org.glassfish.jaxb:txw2:2.3.1 - http://jaxb.java.net/jaxb-txw-parent/txw2) - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * Java Transaction API (org.jboss.spec.javax.transaction:jboss-transaction-api_1.2_spec:1.1.1.Final - http://www.jboss.org/jboss-transaction-api_1.2_spec) - * Extended StAX API (org.jvnet.staxex:stax-ex:1.8 - http://stax-ex.java.net/) Cordra (Version 2) License Agreement: @@ -489,55 +493,62 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines Eclipse Distribution License, Version 1.0: + * Jakarta Activation (com.sun.activation:jakarta.activation:1.2.2 - https://github.com/eclipse-ee4j/jaf/jakarta.activation) + * istack common utility code runtime (com.sun.istack:istack-commons-runtime:3.0.12 - https://projects.eclipse.org/projects/ee4j/istack-commons/istack-commons-runtime) * Jakarta Activation API jar (jakarta.activation:jakarta.activation-api:1.2.2 - https://github.com/eclipse-ee4j/jaf/jakarta.activation-api) * Jakarta XML Binding API (jakarta.xml.bind:jakarta.xml.bind-api:2.3.3 - https://github.com/eclipse-ee4j/jaxb-api/jakarta.xml.bind-api) * javax.persistence-api (javax.persistence:javax.persistence-api:2.2 - https://github.com/javaee/jpa-spec) - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * JAXB Runtime (org.glassfish.jaxb:jaxb-runtime:2.3.9 - https://eclipse-ee4j.github.io/jaxb-ri/) + * TXW2 Runtime (org.glassfish.jaxb:txw2:2.3.9 - https://eclipse-ee4j.github.io/jaxb-ri/) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * Java Persistence API, Version 2.1 (org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.2.Final - http://hibernate.org) + * org.locationtech.jts:jts-core (org.locationtech.jts:jts-core:1.19.0 - https://www.locationtech.org/projects/technology.jts/jts-modules/jts-core) + * org.locationtech.jts.io:jts-io-common (org.locationtech.jts.io:jts-io-common:1.19.0 - https://www.locationtech.org/projects/technology.jts/jts-modules/jts-io/jts-io-common) Eclipse Public License: * System Rules (com.github.stefanbirkner:system-rules:1.19.0 - http://stefanbirkner.github.io/system-rules/) - * H2 Database Engine (com.h2database:h2:2.1.210 - https://h2database.com) + * H2 Database Engine (com.h2database:h2:2.2.224 - https://h2database.com) * Jakarta Annotations API (jakarta.annotation:jakarta.annotation-api:1.3.5 - https://projects.eclipse.org/projects/ee4j.ca) + * javax.transaction API (jakarta.transaction:jakarta.transaction-api:1.3.3 - https://projects.eclipse.org/projects/ee4j.jta) * jakarta.ws.rs-api (jakarta.ws.rs:jakarta.ws.rs-api:2.1.6 - https://github.com/eclipse-ee4j/jaxrs-api) * javax.persistence-api (javax.persistence:javax.persistence-api:2.2 - https://github.com/javaee/jpa-spec) - * JUnit (junit:junit:4.13.1 - http://junit.org) + * JUnit (junit:junit:4.13.2 - http://junit.org) * AspectJ Weaver (org.aspectj:aspectjweaver:1.9.7 - https://www.eclipse.org/aspectj/) * Eclipse Compiler for Java(TM) (org.eclipse.jdt:ecj:3.14.0 - http://www.eclipse.org/jdt) * Jetty :: Apache JSP Implementation (org.eclipse.jetty:apache-jsp:9.4.15.v20190215 - http://www.eclipse.org/jetty) * Apache :: JSTL module (org.eclipse.jetty:apache-jstl:9.4.15.v20190215 - http://tomcat.apache.org/taglibs/standard/) - * Jetty :: ALPN :: Client (org.eclipse.jetty:jetty-alpn-client:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-client) - * Jetty :: ALPN :: JDK9 Client Implementation (org.eclipse.jetty:jetty-alpn-java-client:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-client) - * Jetty :: ALPN :: JDK9 Server Implementation (org.eclipse.jetty:jetty-alpn-java-server:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-server) - * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server) - * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server) + * Jetty :: ALPN :: Client (org.eclipse.jetty:jetty-alpn-client:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-client) + * Jetty :: ALPN :: JDK9 Client Implementation (org.eclipse.jetty:jetty-alpn-java-client:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-client) + * Jetty :: ALPN :: JDK9 Server Implementation (org.eclipse.jetty:jetty-alpn-java-server:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-server) + * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server) + * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server) * Jetty :: Servlet Annotations (org.eclipse.jetty:jetty-annotations:9.4.15.v20190215 - http://www.eclipse.org/jetty) - * Jetty :: Asynchronous HTTP Client (org.eclipse.jetty:jetty-client:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-client) - * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-continuation) - * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-continuation) - * Jetty :: Deployers (org.eclipse.jetty:jetty-deploy:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-deploy) - * Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-http) - * Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-io) - * Jetty :: JMX Management (org.eclipse.jetty:jetty-jmx:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-jmx) + * Jetty :: Asynchronous HTTP Client (org.eclipse.jetty:jetty-client:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-client) + * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-continuation) + * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-continuation) + * Jetty :: Deployers (org.eclipse.jetty:jetty-deploy:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-deploy) + * Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-http) + * Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-io) + * Jetty :: JMX Management (org.eclipse.jetty:jetty-jmx:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-jmx) * Jetty :: JNDI Naming (org.eclipse.jetty:jetty-jndi:9.4.15.v20190215 - http://www.eclipse.org/jetty) * Jetty :: Plus (org.eclipse.jetty:jetty-plus:9.4.15.v20190215 - http://www.eclipse.org/jetty) - * Jetty :: Rewrite Handler (org.eclipse.jetty:jetty-rewrite:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-rewrite) - * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-security) - * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-security) - * Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-server) - * Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-servlet) - * Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-servlets) - * Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-util) - * Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-util-ajax) - * Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-webapp) - * Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-xml) - * Jetty :: HTTP2 :: Client (org.eclipse.jetty.http2:http2-client:9.4.44.v20210927 - https://eclipse.org/jetty/http2-parent/http2-client) - * Jetty :: HTTP2 :: Common (org.eclipse.jetty.http2:http2-common:9.4.51.v20230217 - https://eclipse.org/jetty/http2-parent/http2-common) - * Jetty :: HTTP2 :: HPACK (org.eclipse.jetty.http2:http2-hpack:9.4.44.v20210927 - https://eclipse.org/jetty/http2-parent/http2-hpack) - * Jetty :: HTTP2 :: HTTP Client Transport (org.eclipse.jetty.http2:http2-http-client-transport:9.4.44.v20210927 - https://eclipse.org/jetty/http2-parent/http2-http-client-transport) - * Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.51.v20230217 - https://eclipse.org/jetty/http2-parent/http2-server) + * Jetty :: Rewrite Handler (org.eclipse.jetty:jetty-rewrite:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-rewrite) + * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-security) + * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-security) + * Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-server) + * Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-servlet) + * Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-servlets) + * Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-util) + * Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-util-ajax) + * Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-webapp) + * Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-xml) + * Jetty :: HTTP2 :: Client (org.eclipse.jetty.http2:http2-client:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-client) + * Jetty :: HTTP2 :: Common (org.eclipse.jetty.http2:http2-common:9.4.54.v20240208 - https://eclipse.org/jetty/http2-parent/http2-common) + * Jetty :: HTTP2 :: HPACK (org.eclipse.jetty.http2:http2-hpack:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-hpack) + * Jetty :: HTTP2 :: HTTP Client Transport (org.eclipse.jetty.http2:http2-http-client-transport:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-http-client-transport) + * Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.54.v20240208 - https://eclipse.org/jetty/http2-parent/http2-server) * Jetty :: Schemas (org.eclipse.jetty.toolchain:jetty-schemas:3.1.2 - https://eclipse.org/jetty/jetty-schemas) * HK2 API module (org.glassfish.hk2:hk2-api:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-api) * ServiceLocator Default Implementation (org.glassfish.hk2:hk2-locator:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-locator) @@ -545,14 +556,24 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * OSGi resource locator (org.glassfish.hk2:osgi-resource-locator:1.0.3 - https://projects.eclipse.org/projects/ee4j/osgi-resource-locator) * aopalliance version 1.0 repackaged as a module (org.glassfish.hk2.external:aopalliance-repackaged:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/external/aopalliance-repackaged) * javax.inject:1 as OSGi bundle (org.glassfish.hk2.external:jakarta.inject:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/external/jakarta.inject) - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * Java Persistence API, Version 2.1 (org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.2.Final - http://hibernate.org) + * org.locationtech.jts:jts-core (org.locationtech.jts:jts-core:1.19.0 - https://www.locationtech.org/projects/technology.jts/jts-modules/jts-core) + * org.locationtech.jts.io:jts-io-common (org.locationtech.jts.io:jts-io-common:1.19.0 - https://www.locationtech.org/projects/technology.jts/jts-modules/jts-io/jts-io-common) * Jetty Server (org.mortbay.jetty:jetty:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/modules/jetty) * Jetty Servlet Tester (org.mortbay.jetty:jetty-servlet-tester:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/jetty-servlet-tester) * Jetty Utilities (org.mortbay.jetty:jetty-util:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/jetty-util) + GENERAL PUBLIC LICENSE, version 3 (GPL-3.0): + + * juniversalchardet (com.github.albfernandez:juniversalchardet:2.4.0 - https://github.com/albfernandez/juniversalchardet) + + GNU LESSER GENERAL PUBLIC LICENSE, version 3 (LGPL-3.0): + + * juniversalchardet (com.github.albfernandez:juniversalchardet:2.4.0 - https://github.com/albfernandez/juniversalchardet) + GNU Lesser General Public License (LGPL): * btf (com.github.java-json-tools:btf:1.3 - https://github.com/java-json-tools/btf) @@ -569,9 +590,8 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Hibernate ORM - hibernate-jpamodelgen (org.hibernate:hibernate-jpamodelgen:5.6.15.Final - https://hibernate.org/orm) * Hibernate Commons Annotations (org.hibernate.common:hibernate-commons-annotations:5.1.2.Final - http://hibernate.org) * im4java (org.im4java:im4java:1.4.0 - http://sourceforge.net/projects/im4java/) - * Javassist (org.javassist:javassist:3.25.0-GA - http://www.javassist.org/) - * XOM (xom:xom:1.2.5 - http://xom.nu) - * XOM (xom:xom:1.3.7 - https://xom.nu) + * Javassist (org.javassist:javassist:3.29.0-GA - http://www.javassist.org/) + * XOM (xom:xom:1.3.9 - https://xom.nu) Go License: @@ -579,25 +599,29 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines Handle.Net Public License Agreement (Ver.2): - * Handle Server (net.handle:handle:9.3.0 - https://www.handle.net) + * Handle Server (net.handle:handle:9.3.1 - https://www.handle.net) + + ISC License: + + * Simple Magic (com.j256.simplemagic:simplemagic:1.17 - https://256stuff.com/sources/simplemagic/) MIT License: * better-files (com.github.pathikrit:better-files_2.13:3.9.1 - https://github.com/pathikrit/better-files) * Java SemVer (com.github.zafarkhaja:java-semver:0.9.0 - https://github.com/zafarkhaja/jsemver) - * dd-plist (com.googlecode.plist:dd-plist:1.25 - http://www.github.com/3breadt/dd-plist) - * DigitalCollections: IIIF API Library (de.digitalcollections.iiif:iiif-apis:0.3.9 - https://github.com/dbmdz/iiif-apis) + * dd-plist (com.googlecode.plist:dd-plist:1.28 - http://www.github.com/3breadt/dd-plist) + * DigitalCollections: IIIF API Library (de.digitalcollections.iiif:iiif-apis:0.3.10 - https://github.com/dbmdz/iiif-apis) * s3mock (io.findify:s3mock_2.13:0.2.6 - https://github.com/findify/s3mock) * JOpt Simple (net.sf.jopt-simple:jopt-simple:5.0.4 - http://jopt-simple.github.io/jopt-simple) - * Bouncy Castle S/MIME API (org.bouncycastle:bcmail-jdk15on:1.70 - https://www.bouncycastle.org/java.html) - * Bouncy Castle PKIX, CMS, EAC, TSP, PKCS, OCSP, CMP, and CRMF APIs (org.bouncycastle:bcpkix-jdk15on:1.70 - https://www.bouncycastle.org/java.html) - * Bouncy Castle Provider (org.bouncycastle:bcprov-jdk15on:1.70 - https://www.bouncycastle.org/java.html) - * Bouncy Castle ASN.1 Extension and Utility APIs (org.bouncycastle:bcutil-jdk15on:1.70 - https://www.bouncycastle.org/java.html) + * Bouncy Castle S/MIME API (org.bouncycastle:bcmail-jdk18on:1.77 - https://www.bouncycastle.org/java.html) + * Bouncy Castle PKIX, CMS, EAC, TSP, PKCS, OCSP, CMP, and CRMF APIs (org.bouncycastle:bcpkix-jdk18on:1.78.1 - https://www.bouncycastle.org/java.html) + * Bouncy Castle Provider (org.bouncycastle:bcprov-jdk18on:1.78.1 - https://www.bouncycastle.org/java.html) + * Bouncy Castle ASN.1 Extension and Utility APIs (org.bouncycastle:bcutil-jdk18on:1.78.1 - https://www.bouncycastle.org/java.html) * org.brotli:dec (org.brotli:dec:0.1.2 - http://brotli.org/dec) - * Checker Qual (org.checkerframework:checker-qual:3.10.0 - https://checkerframework.org) - * Checker Qual (org.checkerframework:checker-qual:3.31.0 - https://checkerframework.org) - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * Checker Qual (org.checkerframework:checker-qual:3.23.0 - https://checkerframework.org) + * Checker Qual (org.checkerframework:checker-qual:3.42.0 - https://checkerframework.org/) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * mockito-core (org.mockito:mockito-core:3.12.4 - https://github.com/mockito/mockito) * mockito-inline (org.mockito:mockito-inline:3.12.4 - https://github.com/mockito/mockito) * ORCID - Model (org.orcid:orcid-model:3.0.2 - http://github.com/ORCID/orcid-model) @@ -608,34 +632,34 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * toastr (org.webjars.bowergithub.codeseven:toastr:2.1.4 - http://webjars.org) * backbone (org.webjars.bowergithub.jashkenas:backbone:1.4.1 - https://www.webjars.org) * underscore (org.webjars.bowergithub.jashkenas:underscore:1.13.2 - https://www.webjars.org) - * jquery (org.webjars.bowergithub.jquery:jquery-dist:3.6.0 - https://www.webjars.org) - * urijs (org.webjars.bowergithub.medialize:uri.js:1.19.10 - https://www.webjars.org) - * bootstrap (org.webjars.bowergithub.twbs:bootstrap:4.6.1 - https://www.webjars.org) - * core-js (org.webjars.npm:core-js:3.30.1 - https://www.webjars.org) + * jquery (org.webjars.bowergithub.jquery:jquery-dist:3.7.1 - https://www.webjars.org) + * urijs (org.webjars.bowergithub.medialize:uri.js:1.19.11 - https://www.webjars.org) + * bootstrap (org.webjars.bowergithub.twbs:bootstrap:4.6.2 - https://www.webjars.org) + * core-js (org.webjars.npm:core-js:3.37.1 - https://www.webjars.org) * @json-editor/json-editor (org.webjars.npm:json-editor__json-editor:2.6.1 - https://www.webjars.org) Mozilla Public License: - * juniversalchardet (com.googlecode.juniversalchardet:juniversalchardet:1.0.3 - http://juniversalchardet.googlecode.com/) - * H2 Database Engine (com.h2database:h2:2.1.210 - https://h2database.com) + * juniversalchardet (com.github.albfernandez:juniversalchardet:2.4.0 - https://github.com/albfernandez/juniversalchardet) + * H2 Database Engine (com.h2database:h2:2.2.224 - https://h2database.com) * Saxon-HE (net.sf.saxon:Saxon-HE:9.8.0-14 - http://www.saxonica.com/) - * Javassist (org.javassist:javassist:3.25.0-GA - http://www.javassist.org/) + * Javassist (org.javassist:javassist:3.29.0-GA - http://www.javassist.org/) * Mozilla Rhino (org.mozilla:rhino:1.7.7.2 - https://developer.mozilla.org/en/Rhino) Public Domain: - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * HdrHistogram (org.hdrhistogram:HdrHistogram:2.1.12 - http://hdrhistogram.github.io/HdrHistogram/) - * JSON in Java (org.json:json:20230227 - https://github.com/douglascrockford/JSON-java) + * JSON in Java (org.json:json:20231013 - https://github.com/douglascrockford/JSON-java) * LatencyUtils (org.latencyutils:LatencyUtils:2.0.3 - http://latencyutils.github.io/LatencyUtils/) * Reflections (org.reflections:reflections:0.9.12 - http://github.com/ronmamo/reflections) * XZ for Java (org.tukaani:xz:1.9 - https://tukaani.org/xz/java.html) UnRar License: - * Java Unrar (com.github.junrar:junrar:7.5.3 - https://github.com/junrar/junrar) + * Java Unrar (com.github.junrar:junrar:7.5.5 - https://github.com/junrar/junrar) Unicode/ICU License: @@ -643,10 +667,10 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines W3C license: - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) jQuery license: - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) diff --git a/pom.xml b/pom.xml index ae83442235e..3419757b56d 100644 --- a/pom.xml +++ b/pom.xml @@ -705,7 +705,7 @@ Apache Software License, Version 2.0|The SAX License|The W3C License Apache Software License, Version 2.0|Similar to Apache License but with the acknowledgment clause removed - BSD License|The BSD License|BSD licence|BSD license|BSD|BSD-style license|New BSD License|New BSD license|Revised BSD License|BSD 2-Clause license|3-Clause BSD License|BSD 2-Clause|BSD 3-clause New License|BSD Licence 3|BSD-2-Clause|BSD-3-Clause|Modified BSD|The New BSD License|The BSD 3-Clause License (BSD3)|BSD License 3 + BSD License|The BSD License|BSD licence|BSD license|BSD|BSD-style license|New BSD License|New BSD license|Revised BSD License|BSD 2-Clause license|3-Clause BSD License|BSD 2-Clause|BSD 3-clause New License|BSD Licence 3|BSD-2-Clause|BSD-3-Clause|Modified BSD|The New BSD License|The BSD 3-Clause License (BSD3)|BSD License 3|BSD License 2.0 BSD License|DSpace BSD License|DSpace Sourcecode License @@ -726,7 +726,7 @@ Common Development and Distribution License (CDDL)|GNU General Public License, Version 2 with the Classpath Exception Eclipse Distribution License, Version 1.0|Eclipse Distribution License (EDL), Version 1.0|Eclipse Distribution License - v 1.0|Eclipse Distribution License v. 1.0|EDL 1.0 - Eclipse Public License|Eclipse Public License - Version 1.0|Eclipse Public License - v 1.0|EPL 1.0 license|Eclipse Public License (EPL), Version 1.0|Eclipse Public License 1.0|Eclipse Public License v1.0|Eclipse Public License, Version 1.0|EPL 1.0|EPL 2.0|Eclipse Public License - v 2.0 + Eclipse Public License|Eclipse Public License - Version 1.0|Eclipse Public License - v 1.0|EPL 1.0 license|Eclipse Public License (EPL), Version 1.0|Eclipse Public License 1.0|Eclipse Public License v1.0|Eclipse Public License, Version 1.0|EPL 1.0|EPL 2.0|Eclipse Public License - v 2.0|EPL-2.0|Eclipse Public License 2.0|Eclipse Public License v. 2.0|Eclipse Public License, Version 2.0 Eclipse Public License|Common Public License Version 1.0 @@ -739,7 +739,7 @@ MIT License|Bouncy Castle Licence MIT License|(MIT-style) netCDF C library license - Mozilla Public License|Mozilla Public License version 1.1|Mozilla Public License 1.1 (MPL 1.1)|MPL 1.1|Mozilla Public License Version 2.0|Mozilla Public License, Version 2.0 + Mozilla Public License|Mozilla Public License version 1.1|Mozilla Public License 1.1 (MPL 1.1)|MPL 1.1|Mozilla Public License Version 2.0|Mozilla Public License, Version 2.0|Mozilla Public License Version 1.1 Mozilla Public License|MPL 2.0, and EPL 1.0|MPL 2.0 diff --git a/src/main/license/LICENSES_THIRD_PARTY.properties b/src/main/license/LICENSES_THIRD_PARTY.properties index e893f9b85e3..0a50bdb011b 100644 --- a/src/main/license/LICENSES_THIRD_PARTY.properties +++ b/src/main/license/LICENSES_THIRD_PARTY.properties @@ -13,89 +13,16 @@ # 1) PLEASE CHECK THE "[src]/LICENSES_THIRD_PARTY" FILE FOR SPECIFIC LICENSE NAMES! # 2) Also please add a link/URL to where you looked up the license -# http://asm.ow2.org/ -asm--asm--3.1=BSD License - -# http://www.h2database.com/html/license.html -com.h2database--h2--1.4.187=Mozilla Public License - -# https://projects.apache.org/projects/commons_jexl.html -commons-jexl--commons-jexl--1.0=Apache Software License, Version 2.0 - -concurrent--concurrent--1.3.4=Public Domain - -# http://dom4j.sourceforge.net/dom4j-1.6.1/license.html -dom4j--dom4j--1.6.1=BSD License - -# http://jakarta.apache.org/regexp/ -jakarta-regexp--jakarta-regexp--1.4=Apache Software License, Version 2.0 - -# https://java.net/projects/servlet-spec/ -javax.servlet--jsp-api--2.0=Common Development and Distribution License (CDDL) -javax.servlet--jstl--1.2=Common Development and Distribution License (CDDL) -javax.servlet--servlet-api--2.5=Common Development and Distribution License (CDDL) +# https://github.com/albfernandez/juniversalchardet/blob/main/LICENSE +com.github.albfernandez--juniversalchardet--2.4.0=Mozilla Public License # https://github.com/jankotek/JDBM3 jdbm--jdbm--1.0=Apache Software License, Version 2.0 -# https://github.com/hunterhacker/jdom/blob/master/LICENSE.txt -# http://www.jdom.org/docs/faq.html#a0030 -jdom--jdom--1.0=JDOM License (Apache-style license) -jdom--jdom--1.1.3=JDOM License (Apache-style license) - # https://github.com/stephenc/jcip-annotations/blob/master/LICENSE.txt net.jcip--jcip-annotations--1.0=Apache Software License, Version 2.0 -# http://ant.apache.org/ -org.apache.ant--ant--1.7.0=Apache Software License, Version 2.0 -org.apache.ant--ant-launcher--1.7.0=Apache Software License, Version 2.0 - -# http://zookeeper.apache.org/ -org.apache.zookeeper--zookeeper--3.4.6=Apache Software License, Version 2.0 - -# http://jettison.codehaus.org/ -org.codehaus.jettison--jettison--1.1=Apache Software License, Version 2.0 - -# DSpace Licenses are all BSD -org.dspace--handle--6.2=BSD License -org.dspace--jargon--1.4.25=BSD License -org.dspace--mets--1.5.2=BSD License -org.dspace--oclc-harvester2--0.1.12=BSD License - -# https://github.com/hibernate/hibernate-jpa-api -org.hibernate.javax.persistence--hibernate-jpa-2.0-api--1.0.1.Final=Eclipse Public License - -# Swingworker: https://java.net/projects/swingworker -org.jdesktop--swing-worker--1.1=GNU Lesser General Public License (LGPL) - -# https://java.net/projects/tiger-types -org.jvnet--tiger-types--1.4=Common Development and Distribution License (CDDL) - -# https://maven-repository.com/ -org.restlet.jee--org.restlet--2.1.1=Apache Software License, Version 2.0 -org.restlet.jee--org.restlet.ext.servlet--2.1.1=Apache Software License, Version 2.0 - -# http://swordapp.org/ -org.swordapp--sword-common--1.1=Apache Software License, Version 2.0 - -# Apache Jakarta ORO: http://svn.apache.org/repos/asf/jakarta/oro/trunk/LICENSE -oro--oro--2.0.8=Apache Software License, Version 2.0 - -# http://rometools.org/ -rome--rome--1.0=Apache Software License, Version 2.0 - -# https://tomcat.apache.org/taglibs/license.html -taglibs--standard--1.1.2=Apache Software License, Version 2.0 - -# Apache XML (xalan): http://xml.apache.org/xalan-j/#license -xalan--xalan--2.7.0=Apache Software License, Version 2.0 - -# Apache Xerces: http://xerces.apache.org/ -xerces--xmlParserAPIs--2.6.2=Apache Software License, Version 2.0 - -# Apache XML Commons: http://xerces.apache.org/xml-commons/licenses.html -xml-apis--xml-apis--1.4.01=Apache Software License, Version 2.0 -xml-apis--xmlParserAPIs--2.0.2=Apache Software License, Version 2.0 - -# http://www.xom.nu/ -xom--xom--1.1=GNU Lesser General Public License (LGPL) +# Jersey is dual licensed EPL and GPL. We use EPL +# https://eclipse-ee4j.github.io/jersey.github.io/license.html +org.glassfish.jersey.core--jersey-client--2.39.1=Eclipse Public License +org.glassfish.jersey.inject--jersey-hk2--2.39.1=Eclipse Public License From 98c661d8d07b3e3ae68d513252ab7f90ae72faf6 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 3 Jul 2024 14:17:05 -0500 Subject: [PATCH 234/939] Update redeployment of demo.dspace.org to use dspace-8_x branch. --- .github/workflows/reusable-docker-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml index 687474caed1..12aa0cfe286 100644 --- a/.github/workflows/reusable-docker-build.yml +++ b/.github/workflows/reusable-docker-build.yml @@ -70,7 +70,7 @@ env: REDEPLOY_DEMO_URL: ${{ secrets.REDEPLOY_DEMO_URL }} # Current DSpace maintenance branch (and architecture) which is deployed to demo.dspace.org / sandbox.dspace.org # (NOTE: No deployment branch specified for sandbox.dspace.org as it uses the default_branch) - DEPLOY_DEMO_BRANCH: 'dspace-7_x' + DEPLOY_DEMO_BRANCH: 'dspace-8_x' DEPLOY_ARCH: 'linux/amd64' jobs: From 08947bc34e60c93b2b0771b3fdb5347915cac28b Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 9 Jul 2024 14:10:01 -0500 Subject: [PATCH 235/939] [maven-release-plugin] prepare release dspace-7.6.2 --- dspace-api/pom.xml | 2 +- dspace-iiif/pom.xml | 2 +- dspace-oai/pom.xml | 2 +- dspace-rdf/pom.xml | 2 +- dspace-rest/pom.xml | 4 ++-- dspace-server-webapp/pom.xml | 2 +- dspace-services/pom.xml | 2 +- dspace-sword/pom.xml | 2 +- dspace-swordv2/pom.xml | 2 +- dspace/modules/additions/pom.xml | 2 +- dspace/modules/pom.xml | 2 +- dspace/modules/rest/pom.xml | 2 +- dspace/modules/server/pom.xml | 2 +- dspace/pom.xml | 2 +- pom.xml | 32 ++++++++++++++++---------------- 15 files changed, 31 insertions(+), 31 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 4c881cbd246..a15a21e6bc0 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6.2-SNAPSHOT + 7.6.2 .. diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index 8cba9055248..58ea58687e0 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.2-SNAPSHOT + 7.6.2 .. diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index 748f6ad2dee..a76a878454e 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - 7.6.2-SNAPSHOT + 7.6.2 .. diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index f118ebddb37..43e63758794 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.2-SNAPSHOT + 7.6.2 .. diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index 5688424a1c2..1192f9ed7cd 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -3,7 +3,7 @@ org.dspace dspace-rest war - 7.6.2-SNAPSHOT + 7.6.2 DSpace (Deprecated) REST Webapp DSpace RESTful Web Services API. NOTE: this REST API is DEPRECATED. Please consider using the REST API in the dspace-server-webapp instead! @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6.2-SNAPSHOT + 7.6.2 .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 5395169950b..feb8c2af6e4 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.2-SNAPSHOT + 7.6.2 .. diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index 98e0253a498..20110a81445 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.2-SNAPSHOT + 7.6.2 diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index 52783e3ec57..d9fd8843a54 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.2-SNAPSHOT + 7.6.2 .. diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index 8d618bdf5f8..2b6c75901e5 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - 7.6.2-SNAPSHOT + 7.6.2 .. diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index 7e60e982ec4..0a4f43d84bf 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - 7.6.2-SNAPSHOT + 7.6.2 .. diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index 5819158505f..d4f47af66a0 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - 7.6.2-SNAPSHOT + 7.6.2 ../../pom.xml diff --git a/dspace/modules/rest/pom.xml b/dspace/modules/rest/pom.xml index 7797346c51d..3f4e99b482a 100644 --- a/dspace/modules/rest/pom.xml +++ b/dspace/modules/rest/pom.xml @@ -13,7 +13,7 @@ org.dspace modules - 7.6.2-SNAPSHOT + 7.6.2 .. diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index 564f8159377..d5882c9270c 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -13,7 +13,7 @@ just adding new jar in the classloader modules org.dspace - 7.6.2-SNAPSHOT + 7.6.2 .. diff --git a/dspace/pom.xml b/dspace/pom.xml index f9dc6126e60..cc2968fab99 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - 7.6.2-SNAPSHOT + 7.6.2 ../pom.xml diff --git a/pom.xml b/pom.xml index 3419757b56d..0670e2cf483 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - 7.6.2-SNAPSHOT + 7.6.2 DSpace Parent Project DSpace open source software is a turnkey institutional repository application. @@ -874,14 +874,14 @@ org.dspace dspace-rest - 7.6.2-SNAPSHOT + 7.6.2 jar classes org.dspace dspace-rest - 7.6.2-SNAPSHOT + 7.6.2 war @@ -1032,69 +1032,69 @@ org.dspace dspace-api - 7.6.2-SNAPSHOT + 7.6.2 org.dspace dspace-api test-jar - 7.6.2-SNAPSHOT + 7.6.2 test org.dspace.modules additions - 7.6.2-SNAPSHOT + 7.6.2 org.dspace dspace-sword - 7.6.2-SNAPSHOT + 7.6.2 org.dspace dspace-swordv2 - 7.6.2-SNAPSHOT + 7.6.2 org.dspace dspace-oai - 7.6.2-SNAPSHOT + 7.6.2 org.dspace dspace-services - 7.6.2-SNAPSHOT + 7.6.2 org.dspace dspace-server-webapp test-jar - 7.6.2-SNAPSHOT + 7.6.2 test org.dspace dspace-rdf - 7.6.2-SNAPSHOT + 7.6.2 org.dspace dspace-iiif - 7.6.2-SNAPSHOT + 7.6.2 org.dspace dspace-server-webapp - 7.6.2-SNAPSHOT + 7.6.2 jar classes org.dspace dspace-server-webapp - 7.6.2-SNAPSHOT + 7.6.2 war @@ -1940,7 +1940,7 @@ scm:git:git@github.com:DSpace/DSpace.git scm:git:git@github.com:DSpace/DSpace.git git@github.com:DSpace/DSpace.git - dspace-7_x + dspace-7.6.2 From 86b28bf927d1614aa3d5b162c736fb834d25495d Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 9 Jul 2024 14:10:05 -0500 Subject: [PATCH 236/939] [maven-release-plugin] prepare for next development iteration --- dspace-api/pom.xml | 2 +- dspace-iiif/pom.xml | 2 +- dspace-oai/pom.xml | 2 +- dspace-rdf/pom.xml | 2 +- dspace-rest/pom.xml | 4 ++-- dspace-server-webapp/pom.xml | 2 +- dspace-services/pom.xml | 2 +- dspace-sword/pom.xml | 2 +- dspace-swordv2/pom.xml | 2 +- dspace/modules/additions/pom.xml | 2 +- dspace/modules/pom.xml | 2 +- dspace/modules/rest/pom.xml | 2 +- dspace/modules/server/pom.xml | 2 +- dspace/pom.xml | 2 +- pom.xml | 32 ++++++++++++++++---------------- 15 files changed, 31 insertions(+), 31 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index a15a21e6bc0..16b151b092e 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6.2 + 7.6.3-SNAPSHOT .. diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index 58ea58687e0..64dd5106f0a 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.2 + 7.6.3-SNAPSHOT .. diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index a76a878454e..28b610e996c 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - 7.6.2 + 7.6.3-SNAPSHOT .. diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index 43e63758794..e656a920a1e 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.2 + 7.6.3-SNAPSHOT .. diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index 1192f9ed7cd..d1f3b95aafb 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -3,7 +3,7 @@ org.dspace dspace-rest war - 7.6.2 + 7.6.3-SNAPSHOT DSpace (Deprecated) REST Webapp DSpace RESTful Web Services API. NOTE: this REST API is DEPRECATED. Please consider using the REST API in the dspace-server-webapp instead! @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6.2 + 7.6.3-SNAPSHOT .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index feb8c2af6e4..e0ec7ef5ed7 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.2 + 7.6.3-SNAPSHOT .. diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index 20110a81445..51c6dea6241 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.2 + 7.6.3-SNAPSHOT diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index d9fd8843a54..1403c779e72 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.2 + 7.6.3-SNAPSHOT .. diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index 2b6c75901e5..47038888312 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - 7.6.2 + 7.6.3-SNAPSHOT .. diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index 0a4f43d84bf..db934380401 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - 7.6.2 + 7.6.3-SNAPSHOT .. diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index d4f47af66a0..3f15bdd01f4 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - 7.6.2 + 7.6.3-SNAPSHOT ../../pom.xml diff --git a/dspace/modules/rest/pom.xml b/dspace/modules/rest/pom.xml index 3f4e99b482a..e2e0041cb29 100644 --- a/dspace/modules/rest/pom.xml +++ b/dspace/modules/rest/pom.xml @@ -13,7 +13,7 @@ org.dspace modules - 7.6.2 + 7.6.3-SNAPSHOT .. diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index d5882c9270c..5134ffb9473 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -13,7 +13,7 @@ just adding new jar in the classloader modules org.dspace - 7.6.2 + 7.6.3-SNAPSHOT .. diff --git a/dspace/pom.xml b/dspace/pom.xml index cc2968fab99..b3b474bfbcb 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - 7.6.2 + 7.6.3-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index 0670e2cf483..b662e333225 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - 7.6.2 + 7.6.3-SNAPSHOT DSpace Parent Project DSpace open source software is a turnkey institutional repository application. @@ -874,14 +874,14 @@ org.dspace dspace-rest - 7.6.2 + 7.6.3-SNAPSHOT jar classes org.dspace dspace-rest - 7.6.2 + 7.6.3-SNAPSHOT war @@ -1032,69 +1032,69 @@ org.dspace dspace-api - 7.6.2 + 7.6.3-SNAPSHOT org.dspace dspace-api test-jar - 7.6.2 + 7.6.3-SNAPSHOT test org.dspace.modules additions - 7.6.2 + 7.6.3-SNAPSHOT org.dspace dspace-sword - 7.6.2 + 7.6.3-SNAPSHOT org.dspace dspace-swordv2 - 7.6.2 + 7.6.3-SNAPSHOT org.dspace dspace-oai - 7.6.2 + 7.6.3-SNAPSHOT org.dspace dspace-services - 7.6.2 + 7.6.3-SNAPSHOT org.dspace dspace-server-webapp test-jar - 7.6.2 + 7.6.3-SNAPSHOT test org.dspace dspace-rdf - 7.6.2 + 7.6.3-SNAPSHOT org.dspace dspace-iiif - 7.6.2 + 7.6.3-SNAPSHOT org.dspace dspace-server-webapp - 7.6.2 + 7.6.3-SNAPSHOT jar classes org.dspace dspace-server-webapp - 7.6.2 + 7.6.3-SNAPSHOT war @@ -1940,7 +1940,7 @@ scm:git:git@github.com:DSpace/DSpace.git scm:git:git@github.com:DSpace/DSpace.git git@github.com:DSpace/DSpace.git - dspace-7.6.2 + dspace-7_x From 17a46a2fb6c2cb63bedc475caf9bf3953d9f76ce Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 10 Jul 2024 12:24:25 -0500 Subject: [PATCH 237/939] Remove unused services & unnecessary cache cleanup. This can result in random failures if these services are not yet loaded by another test. --- .../controller/LinksetRestControllerIT.java | 33 ------------------- 1 file changed, 33 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java index 6d1d242cad7..a65357f97bf 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java @@ -40,15 +40,12 @@ import org.dspace.content.RelationshipType; import org.dspace.content.WorkspaceItem; import org.dspace.content.authority.Choices; -import org.dspace.content.authority.service.ChoiceAuthorityService; -import org.dspace.content.authority.service.MetadataAuthorityService; import org.dspace.content.service.BitstreamService; import org.dspace.content.service.ItemService; import org.dspace.content.service.RelationshipTypeService; import org.dspace.core.Constants; import org.dspace.eperson.Group; import org.dspace.services.ConfigurationService; -import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.util.SimpleMapConverter; import org.hamcrest.Matchers; import org.junit.Before; @@ -67,12 +64,6 @@ public class LinksetRestControllerIT extends AbstractControllerIntegrationTest { @Autowired private ConfigurationService configurationService; - @Autowired - private MetadataAuthorityService metadataAuthorityService; - - @Autowired - private ChoiceAuthorityService choiceAuthorityService; - @Autowired private ItemService itemService; @@ -735,10 +726,6 @@ public void findTypedLinkForBitstream() throws Exception { .andExpect(jsonPath("$[?(@.href == '" + uiUrl + "/signposting/linksets/" + item.getID() + "/json" + "' && @.rel == 'linkset' " + "&& @.type == 'application/linkset+json')]").exists()); - - DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig(); - metadataAuthorityService.clearCache(); - choiceAuthorityService.clearCache(); } @Test @@ -780,10 +767,6 @@ public void findTypedLinkForBitstreamWithType() throws Exception { "&& @.type == 'application/linkset+json')]").exists()) .andExpect(jsonPath("$[?(@.href == 'https://schema.org/ScholarlyArticle' " + "&& @.rel == 'type')]").exists()); - - DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig(); - metadataAuthorityService.clearCache(); - choiceAuthorityService.clearCache(); } @Test @@ -813,10 +796,6 @@ public void findTypedLinkForRestrictedBitstream() throws Exception { getClient().perform(get("/signposting/links/" + bitstream.getID())) .andExpect(status().isUnauthorized()); - - DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig(); - metadataAuthorityService.clearCache(); - choiceAuthorityService.clearCache(); } @Test @@ -844,10 +823,6 @@ public void findTypedLinkForBitstreamUnderEmbargo() throws Exception { getClient().perform(get("/signposting/links/" + bitstream.getID())) .andExpect(status().isUnauthorized()); - - DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig(); - metadataAuthorityService.clearCache(); - choiceAuthorityService.clearCache(); } @Test @@ -874,10 +849,6 @@ public void findTypedLinkForBitstreamOfWorkspaceItem() throws Exception { getClient().perform(get("/signposting/links/" + bitstream.getID())) .andExpect(status().isUnauthorized()); - - DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig(); - metadataAuthorityService.clearCache(); - choiceAuthorityService.clearCache(); } @Test @@ -890,10 +861,6 @@ public void findTypedLinkForUnDiscoverableItem() throws Exception { getClient().perform(get("/signposting/links/" + item.getID())) .andExpect(status().isUnauthorized()); - - DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig(); - metadataAuthorityService.clearCache(); - choiceAuthorityService.clearCache(); } @Test From 3bb93be79310fc7931359d0cc82bbfa87f9cec85 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 10 Jul 2024 14:21:59 -0500 Subject: [PATCH 238/939] Fix random pagination failures in ManageGroupsFeatureIT by using the "feature" param to filter for the feature we are looking for. If this feature appeared on page 2, then the tests would fail. --- .../authorization/ManageGroupsFeatureIT.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ManageGroupsFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ManageGroupsFeatureIT.java index a42f219909a..1997a6147f3 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ManageGroupsFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ManageGroupsFeatureIT.java @@ -176,7 +176,7 @@ public void testSubGroupOfAdminGroup() throws Exception { // Verify an ePerson in a subgroup of the site administrators has this feature getClient(token).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" - + "http://localhost/api/core/site/" + siteService.findSite(context).getID())) + + "http://localhost/api/core/site/" + siteService.findSite(context).getID() + "&feature=canManageGroups")) .andExpect(status().isOk()) .andExpect( jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='canManageGroups')]") @@ -285,7 +285,7 @@ public void testSubSubGroupOfAdminGroup() throws Exception { // Verify an ePerson in a sub-subgroup of the site administrators has this feature getClient(token).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" - + "http://localhost/api/core/site/" + siteService.findSite(context).getID())) + + "http://localhost/api/core/site/" + siteService.findSite(context).getID() + "&feature=canManageGroups")) .andExpect(status().isOk()) .andExpect( jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='canManageGroups')]") @@ -502,7 +502,7 @@ public void testSubGroupOfAdminGroupNoCommunityGroupPermission() throws Exceptio // Verify an ePerson in a subgroup of the site administrators has this feature getClient(token).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" - + "http://localhost/api/core/site/" + siteService.findSite(context).getID())) + + "http://localhost/api/core/site/" + siteService.findSite(context).getID() + "&feature=canManageGroups")) .andExpect(status().isOk()) .andExpect( jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='canManageGroups')]") @@ -636,7 +636,7 @@ public void testSubSubGroupOfAdminGroupNoCommunityGroupPermission() throws Excep // Verify an ePerson in a sub-subgroup of the site administrators has this feature getClient(token).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" - + "http://localhost/api/core/site/" + siteService.findSite(context).getID())) + + "http://localhost/api/core/site/" + siteService.findSite(context).getID() + "&feature=canManageGroups")) .andExpect(status().isOk()) .andExpect( jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='canManageGroups')]") @@ -897,7 +897,7 @@ public void testSubGroupOfAdminGroupNoCollectionGroupPermission() throws Excepti // Verify an ePerson in a subgroup of the site administrators has this feature getClient(token).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" - + "http://localhost/api/core/site/" + siteService.findSite(context).getID())) + + "http://localhost/api/core/site/" + siteService.findSite(context).getID() + "&feature=canManageGroups")) .andExpect(status().isOk()) .andExpect( jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='canManageGroups')]") @@ -1051,7 +1051,7 @@ public void testSubSubGroupOfAdminGroupNoCollectionGroupPermission() throws Exce // Verify an ePerson in a sub-subgroup of the site administrators has this feature getClient(token).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" - + "http://localhost/api/core/site/" + siteService.findSite(context).getID())) + + "http://localhost/api/core/site/" + siteService.findSite(context).getID() + "&feature=canManageGroups")) .andExpect(status().isOk()) .andExpect( jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='canManageGroups')]") @@ -1352,7 +1352,7 @@ public void testSubGroupOfAdminGroupNoComColGroupPermission() throws Exception { // Verify an ePerson in a subgroup of the site administrators has this feature getClient(token).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" - + "http://localhost/api/core/site/" + siteService.findSite(context).getID())) + + "http://localhost/api/core/site/" + siteService.findSite(context).getID() + "&feature=canManageGroups")) .andExpect(status().isOk()) .andExpect( jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='canManageGroups')]") @@ -1526,7 +1526,7 @@ public void testSubSubGroupOfAdminGroupNoComColGroupPermission() throws Exceptio // Verify an ePerson in a sub-subgroup of the site administrators has this feature getClient(token).perform(get("/api/authz/authorizations/search/object?embed=feature&uri=" - + "http://localhost/api/core/site/" + siteService.findSite(context).getID())) + + "http://localhost/api/core/site/" + siteService.findSite(context).getID() + "&feature=canManageGroups")) .andExpect(status().isOk()) .andExpect( jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='canManageGroups')]") From 83f3ea4d052859994f66b881e4bd692dd552e576 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Wed, 17 Jul 2024 15:33:38 +0200 Subject: [PATCH 239/939] fix FromAsCasing warning --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index ee48dec5083..9d89710fe11 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ ARG JDK_VERSION=11 # Step 1 - Run Maven Build -FROM dspace/dspace-dependencies:dspace-7_x as build +FROM dspace/dspace-dependencies:dspace-7_x AS build ARG TARGET_DIR=dspace-installer WORKDIR /app # The dspace-installer directory will be written to /install @@ -28,7 +28,7 @@ RUN mvn --no-transfer-progress package ${MAVEN_FLAGS} && \ mvn clean # Step 2 - Run Ant Deploy -FROM eclipse-temurin:${JDK_VERSION} as ant_build +FROM eclipse-temurin:${JDK_VERSION} AS ant_build ARG TARGET_DIR=dspace-installer # COPY the /install directory from 'build' container to /dspace-src in this container COPY --from=build /install /dspace-src From f340dfadbec9143009fbb670296c500e05ee00e9 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Wed, 17 Jul 2024 15:34:52 +0200 Subject: [PATCH 240/939] fix FromAsCasing warning --- Dockerfile.cli | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile.cli b/Dockerfile.cli index 53040a2fad8..8a69b32e2dc 100644 --- a/Dockerfile.cli +++ b/Dockerfile.cli @@ -8,7 +8,7 @@ ARG JDK_VERSION=11 # Step 1 - Run Maven Build -FROM dspace/dspace-dependencies:dspace-7_x as build +FROM dspace/dspace-dependencies:dspace-7_x AS build ARG TARGET_DIR=dspace-installer WORKDIR /app # The dspace-installer directory will be written to /install @@ -24,7 +24,7 @@ RUN mvn --no-transfer-progress package && \ mvn clean # Step 2 - Run Ant Deploy -FROM eclipse-temurin:${JDK_VERSION} as ant_build +FROM eclipse-temurin:${JDK_VERSION} AS ant_build ARG TARGET_DIR=dspace-installer # COPY the /install directory from 'build' container to /dspace-src in this container COPY --from=build /install /dspace-src From 9b6b7ded97d542f034f95159ce402ecb527a2e7a Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Wed, 17 Jul 2024 15:35:21 +0200 Subject: [PATCH 241/939] fix FromAsCasing warning --- Dockerfile.test | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile.test b/Dockerfile.test index f6f8c1a290f..031394ad256 100644 --- a/Dockerfile.test +++ b/Dockerfile.test @@ -10,7 +10,7 @@ ARG JDK_VERSION=11 # Step 1 - Run Maven Build -FROM dspace/dspace-dependencies:dspace-7_x as build +FROM dspace/dspace-dependencies:dspace-7_x AS build ARG TARGET_DIR=dspace-installer WORKDIR /app # The dspace-installer directory will be written to /install @@ -27,7 +27,7 @@ RUN mvn --no-transfer-progress package -Pdspace-rest && \ mvn clean # Step 2 - Run Ant Deploy -FROM eclipse-temurin:${JDK_VERSION} as ant_build +FROM eclipse-temurin:${JDK_VERSION} AS ant_build ARG TARGET_DIR=dspace-installer # COPY the /install directory from 'build' container to /dspace-src in this container COPY --from=build /install /dspace-src From e4209d5d0b6ac0a301ccf080136e05005e68af11 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Wed, 17 Jul 2024 15:35:54 +0200 Subject: [PATCH 242/939] fix FromAsCasing warning --- Dockerfile.dependencies | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.dependencies b/Dockerfile.dependencies index 1400b356d41..123206ea588 100644 --- a/Dockerfile.dependencies +++ b/Dockerfile.dependencies @@ -7,7 +7,7 @@ ARG JDK_VERSION=11 # Step 1 - Run Maven Build -FROM maven:3-eclipse-temurin-${JDK_VERSION} as build +FROM maven:3-eclipse-temurin-${JDK_VERSION} AS build ARG TARGET_DIR=dspace-installer WORKDIR /app # Create the 'dspace' user account & home directory From 03cff072c30888979e89a8c2f8adc4900f269686 Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Mon, 22 Jul 2024 15:08:59 +0200 Subject: [PATCH 243/939] 116542: fix issues with CSV importing and the Any language being set on metadata values --- .../org/dspace/administer/StructBuilder.java | 2 +- .../org/dspace/app/bulkedit/DSpaceCSV.java | 18 ++++++ .../java/org/dspace/content/Collection.java | 2 +- .../org/dspace/content/MetadataValue.java | 4 ++ .../dspace/app/bulkedit/MetadataImportIT.java | 27 +++++++- .../builder/AbstractDSpaceObjectBuilder.java | 3 +- .../dspace/content/ItemComparatorTest.java | 64 +++++++++---------- .../java/org/dspace/content/ItemTest.java | 38 +++++------ .../content/dao/RelationshipDAOImplTest.java | 4 +- .../dao/RelationshipTypeDAOImplTest.java | 4 +- .../org/dspace/app/rest/PatchMetadataIT.java | 2 +- .../rest/RelationshipRestRepositoryIT.java | 26 ++++---- 12 files changed, 120 insertions(+), 74 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/administer/StructBuilder.java b/dspace-api/src/main/java/org/dspace/administer/StructBuilder.java index 13a1b3b5bbf..8bbcfe0ff75 100644 --- a/dspace-api/src/main/java/org/dspace/administer/StructBuilder.java +++ b/dspace-api/src/main/java/org/dspace/administer/StructBuilder.java @@ -802,7 +802,7 @@ private static Element[] handleCollections(Context context, // default the short description to the empty string collectionService.setMetadataSingleValue(context, collection, - MD_SHORT_DESCRIPTION, Item.ANY, " "); + MD_SHORT_DESCRIPTION, null, " "); // import the rest of the metadata for (Map.Entry entry : collectionMap.entrySet()) { diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java index cbc052b5573..3533a2397b3 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java @@ -188,6 +188,15 @@ public DSpaceCSV(InputStream inputStream, Context c) throws Exception { // Verify that the heading is valid in the metadata registry String[] clean = element.split("\\["); String[] parts = clean[0].split("\\."); + // Check language if present, if it's ANY then throw an exception + if (clean.length > 1 && clean[1].equals(Item.ANY + "]")) { + throw new MetadataImportInvalidHeadingException("Language ANY (*) was found in the heading " + + "of the metadata value to import, " + + "this should never be the case", + MetadataImportInvalidHeadingException.ENTRY, + columnCounter); + + } if (parts.length < 2) { throw new MetadataImportInvalidHeadingException(element, @@ -223,6 +232,15 @@ public DSpaceCSV(InputStream inputStream, Context c) throws Exception { } } + // Verify there isn’t already a header that is the same; if it already exists, + // throw MetadataImportInvalidHeadingException + String header = authorityPrefix + element; + if (headings.contains(header)) { + throw new MetadataImportInvalidHeadingException("Duplicate heading found: " + header, + MetadataImportInvalidHeadingException.ENTRY, + columnCounter); + } + // Store the heading headings.add(authorityPrefix + element); } diff --git a/dspace-api/src/main/java/org/dspace/content/Collection.java b/dspace-api/src/main/java/org/dspace/content/Collection.java index ffec3b45cc8..0036230a8b9 100644 --- a/dspace-api/src/main/java/org/dspace/content/Collection.java +++ b/dspace-api/src/main/java/org/dspace/content/Collection.java @@ -230,7 +230,7 @@ public String getLicenseCollection() { * @throws SQLException if database error */ public void setLicense(Context context, String license) throws SQLException { - getCollectionService().setMetadataSingleValue(context, this, MD_LICENSE, Item.ANY, license); + getCollectionService().setMetadataSingleValue(context, this, MD_LICENSE, null, license); } /** diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataValue.java b/dspace-api/src/main/java/org/dspace/content/MetadataValue.java index 9ff3cb9ec2a..56ed81ecbac 100644 --- a/dspace-api/src/main/java/org/dspace/content/MetadataValue.java +++ b/dspace-api/src/main/java/org/dspace/content/MetadataValue.java @@ -20,6 +20,7 @@ import javax.persistence.SequenceGenerator; import javax.persistence.Table; +import org.apache.commons.lang3.StringUtils; import org.dspace.core.Context; import org.dspace.core.ReloadableEntity; import org.hibernate.annotations.Type; @@ -142,6 +143,9 @@ public String getLanguage() { * @param language new language */ public void setLanguage(String language) { + if (StringUtils.equals(language, Item.ANY)) { + language = null; + } this.language = language; } diff --git a/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java index ac5e1e6ae6b..481aa09e531 100644 --- a/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java +++ b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java @@ -75,6 +75,31 @@ public void setUp() throws Exception { context.restoreAuthSystemState(); } + @Test + public void metadataImportTestWithDuplicateHeader() { + String[] csv = {"id,collection,dc.title,dc.title,dc.contributor.author", + "+," + collection.getHandle() + ",\"Test Import 1\",\"Test Import 2\"," + "\"Donald, SmithImported\"," + + "+," + collection.getHandle() + ",\"Test Import 3\",\"Test Import 4\"," + "\"Donald, SmithImported\""}; + // Should throw an exception because of duplicate header + try { + performImportScript(csv); + } catch (Exception e) { + assertTrue(e instanceof MetadataImportInvalidHeadingException); + } + } + + @Test + public void metadataImportTestWithAnyLanguage() { + String[] csv = {"id,collection,dc.title[*],dc.contributor.author", + "+," + collection.getHandle() + ",\"Test Import 1\"," + "\"Donald, SmithImported\""}; + // Should throw an exception because of invalid ANY language (*) in metadata field + try { + performImportScript(csv); + } catch (Exception e) { + assertTrue(e instanceof MetadataImportInvalidHeadingException); + } + } + @Test public void metadataImportTest() throws Exception { String[] csv = {"id,collection,dc.title,dc.contributor.author", @@ -228,7 +253,7 @@ public void metadataImportRemovingValueTest() throws Exception { itemService.getMetadata(item, "dc", "contributor", "author", Item.ANY).get(0).getValue(), "TestAuthorToRemove")); - String[] csv = {"id,collection,dc.title,dc.contributor.author[*]", + String[] csv = {"id,collection,dc.title,dc.contributor.author", item.getID().toString() + "," + personCollection.getHandle() + "," + item.getName() + ","}; performImportScript(csv); item = findItemByName("title"); diff --git a/dspace-api/src/test/java/org/dspace/builder/AbstractDSpaceObjectBuilder.java b/dspace-api/src/test/java/org/dspace/builder/AbstractDSpaceObjectBuilder.java index ff1083d318d..c6106c3a75e 100644 --- a/dspace-api/src/test/java/org/dspace/builder/AbstractDSpaceObjectBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/AbstractDSpaceObjectBuilder.java @@ -14,7 +14,6 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.ResourcePolicy; import org.dspace.content.DSpaceObject; -import org.dspace.content.Item; import org.dspace.content.service.DSpaceObjectService; import org.dspace.core.Constants; import org.dspace.core.Context; @@ -103,7 +102,7 @@ protected > B setMetadataSingleValue(fi final String qualifier, final String value) { try { - getService().setMetadataSingleValue(context, dso, schema, element, qualifier, Item.ANY, value); + getService().setMetadataSingleValue(context, dso, schema, element, qualifier, null, value); } catch (Exception e) { return handleException(e); } diff --git a/dspace-api/src/test/java/org/dspace/content/ItemComparatorTest.java b/dspace-api/src/test/java/org/dspace/content/ItemComparatorTest.java index 54ff9ce0262..be670d9b509 100644 --- a/dspace-api/src/test/java/org/dspace/content/ItemComparatorTest.java +++ b/dspace-api/src/test/java/org/dspace/content/ItemComparatorTest.java @@ -141,37 +141,37 @@ public void testCompare() throws SQLException { assertTrue("testCompare 0", result == 0); ic = new ItemComparator("test", "one", Item.ANY, true); - itemService.addMetadata(context, one, "dc", "test", "one", Item.ANY, "1"); + itemService.addMetadata(context, one, "dc", "test", "one", null, "1"); result = ic.compare(one, two); assertTrue("testCompare 1", result >= 1); itemService.clearMetadata(context, one, "dc", "test", "one", Item.ANY); ic = new ItemComparator("test", "one", Item.ANY, true); - itemService.addMetadata(context, two, "dc", "test", "one", Item.ANY, "1"); + itemService.addMetadata(context, two, "dc", "test", "one", null, "1"); result = ic.compare(one, two); assertTrue("testCompare 2", result <= -1); itemService.clearMetadata(context, two, "dc", "test", "one", Item.ANY); //value in both items ic = new ItemComparator("test", "one", Item.ANY, true); - itemService.addMetadata(context, one, "dc", "test", "one", Item.ANY, "1"); - itemService.addMetadata(context, two, "dc", "test", "one", Item.ANY, "2"); + itemService.addMetadata(context, one, "dc", "test", "one", null, "1"); + itemService.addMetadata(context, two, "dc", "test", "one", null, "2"); result = ic.compare(one, two); assertTrue("testCompare 3", result <= -1); itemService.clearMetadata(context, one, "dc", "test", "one", Item.ANY); itemService.clearMetadata(context, two, "dc", "test", "one", Item.ANY); ic = new ItemComparator("test", "one", Item.ANY, true); - itemService.addMetadata(context, one, "dc", "test", "one", Item.ANY, "1"); - itemService.addMetadata(context, two, "dc", "test", "one", Item.ANY, "1"); + itemService.addMetadata(context, one, "dc", "test", "one", null, "1"); + itemService.addMetadata(context, two, "dc", "test", "one", null, "1"); result = ic.compare(one, two); assertTrue("testCompare 4", result == 0); itemService.clearMetadata(context, one, "dc", "test", "one", Item.ANY); itemService.clearMetadata(context, two, "dc", "test", "one", Item.ANY); ic = new ItemComparator("test", "one", Item.ANY, true); - itemService.addMetadata(context, one, "dc", "test", "one", Item.ANY, "2"); - itemService.addMetadata(context, two, "dc", "test", "one", Item.ANY, "1"); + itemService.addMetadata(context, one, "dc", "test", "one", null, "2"); + itemService.addMetadata(context, two, "dc", "test", "one", null, "1"); result = ic.compare(one, two); assertTrue("testCompare 5", result >= 1); itemService.clearMetadata(context, one, "dc", "test", "one", Item.ANY); @@ -179,60 +179,60 @@ public void testCompare() throws SQLException { //multiple values (min, max) ic = new ItemComparator("test", "one", Item.ANY, true); - itemService.addMetadata(context, one, "dc", "test", "one", Item.ANY, "0"); - itemService.addMetadata(context, one, "dc", "test", "one", Item.ANY, "1"); - itemService.addMetadata(context, two, "dc", "test", "one", Item.ANY, "2"); - itemService.addMetadata(context, two, "dc", "test", "one", Item.ANY, "3"); + itemService.addMetadata(context, one, "dc", "test", "one", null, "0"); + itemService.addMetadata(context, one, "dc", "test", "one", null, "1"); + itemService.addMetadata(context, two, "dc", "test", "one", null, "2"); + itemService.addMetadata(context, two, "dc", "test", "one", null, "3"); result = ic.compare(one, two); assertTrue("testCompare 3", result <= -1); itemService.clearMetadata(context, one, "dc", "test", "one", Item.ANY); itemService.clearMetadata(context, two, "dc", "test", "one", Item.ANY); ic = new ItemComparator("test", "one", Item.ANY, true); - itemService.addMetadata(context, one, "dc", "test", "one", Item.ANY, "0"); - itemService.addMetadata(context, one, "dc", "test", "one", Item.ANY, "1"); - itemService.addMetadata(context, two, "dc", "test", "one", Item.ANY, "-1"); - itemService.addMetadata(context, two, "dc", "test", "one", Item.ANY, "1"); + itemService.addMetadata(context, one, "dc", "test", "one", null, "0"); + itemService.addMetadata(context, one, "dc", "test", "one", null, "1"); + itemService.addMetadata(context, two, "dc", "test", "one", null, "-1"); + itemService.addMetadata(context, two, "dc", "test", "one", null, "1"); result = ic.compare(one, two); assertTrue("testCompare 4", result == 0); itemService.clearMetadata(context, one, "dc", "test", "one", Item.ANY); itemService.clearMetadata(context, two, "dc", "test", "one", Item.ANY); ic = new ItemComparator("test", "one", Item.ANY, true); - itemService.addMetadata(context, one, "dc", "test", "one", Item.ANY, "1"); - itemService.addMetadata(context, one, "dc", "test", "one", Item.ANY, "2"); - itemService.addMetadata(context, two, "dc", "test", "one", Item.ANY, "1"); - itemService.addMetadata(context, two, "dc", "test", "one", Item.ANY, "-1"); + itemService.addMetadata(context, one, "dc", "test", "one", null, "1"); + itemService.addMetadata(context, one, "dc", "test", "one", null, "2"); + itemService.addMetadata(context, two, "dc", "test", "one", null, "1"); + itemService.addMetadata(context, two, "dc", "test", "one", null, "-1"); result = ic.compare(one, two); assertTrue("testCompare 5", result >= 1); itemService.clearMetadata(context, one, "dc", "test", "one", Item.ANY); itemService.clearMetadata(context, two, "dc", "test", "one", Item.ANY); ic = new ItemComparator("test", "one", Item.ANY, false); - itemService.addMetadata(context, one, "dc", "test", "one", Item.ANY, "1"); - itemService.addMetadata(context, one, "dc", "test", "one", Item.ANY, "2"); - itemService.addMetadata(context, two, "dc", "test", "one", Item.ANY, "2"); - itemService.addMetadata(context, two, "dc", "test", "one", Item.ANY, "3"); + itemService.addMetadata(context, one, "dc", "test", "one", null, "1"); + itemService.addMetadata(context, one, "dc", "test", "one", null, "2"); + itemService.addMetadata(context, two, "dc", "test", "one", null, "2"); + itemService.addMetadata(context, two, "dc", "test", "one", null, "3"); result = ic.compare(one, two); assertTrue("testCompare 3", result <= -1); itemService.clearMetadata(context, one, "dc", "test", "one", Item.ANY); itemService.clearMetadata(context, two, "dc", "test", "one", Item.ANY); ic = new ItemComparator("test", "one", Item.ANY, false); - itemService.addMetadata(context, one, "dc", "test", "one", Item.ANY, "1"); - itemService.addMetadata(context, one, "dc", "test", "one", Item.ANY, "2"); - itemService.addMetadata(context, two, "dc", "test", "one", Item.ANY, "1"); - itemService.addMetadata(context, two, "dc", "test", "one", Item.ANY, "5"); + itemService.addMetadata(context, one, "dc", "test", "one", null, "1"); + itemService.addMetadata(context, one, "dc", "test", "one", null, "2"); + itemService.addMetadata(context, two, "dc", "test", "one", null, "1"); + itemService.addMetadata(context, two, "dc", "test", "one", null, "5"); result = ic.compare(one, two); assertTrue("testCompare 4", result == 0); itemService.clearMetadata(context, one, "dc", "test", "one", Item.ANY); itemService.clearMetadata(context, two, "dc", "test", "one", Item.ANY); ic = new ItemComparator("test", "one", Item.ANY, false); - itemService.addMetadata(context, one, "dc", "test", "one", Item.ANY, "2"); - itemService.addMetadata(context, one, "dc", "test", "one", Item.ANY, "3"); - itemService.addMetadata(context, two, "dc", "test", "one", Item.ANY, "1"); - itemService.addMetadata(context, two, "dc", "test", "one", Item.ANY, "4"); + itemService.addMetadata(context, one, "dc", "test", "one", null, "2"); + itemService.addMetadata(context, one, "dc", "test", "one", null, "3"); + itemService.addMetadata(context, two, "dc", "test", "one", null, "1"); + itemService.addMetadata(context, two, "dc", "test", "one", null, "4"); result = ic.compare(one, two); assertTrue("testCompare 5", result >= 1); itemService.clearMetadata(context, one, "dc", "test", "one", Item.ANY); diff --git a/dspace-api/src/test/java/org/dspace/content/ItemTest.java b/dspace-api/src/test/java/org/dspace/content/ItemTest.java index 15e425e23a2..24d6e515bbd 100644 --- a/dspace-api/src/test/java/org/dspace/content/ItemTest.java +++ b/dspace-api/src/test/java/org/dspace/content/ItemTest.java @@ -516,11 +516,11 @@ public void testAddMetadata_5args_1() throws SQLException { String schema = "dc"; String element = "contributor"; String qualifier = "author"; - String lang = Item.ANY; + String lang = null; String[] values = {"value0", "value1"}; itemService.addMetadata(context, it, schema, element, qualifier, lang, Arrays.asList(values)); - List dc = itemService.getMetadata(it, schema, element, qualifier, lang); + List dc = itemService.getMetadata(it, schema, element, qualifier, Item.ANY); assertThat("testAddMetadata_5args_1 0", dc, notNullValue()); assertTrue("testAddMetadata_5args_1 1", dc.size() == 2); assertThat("testAddMetadata_5args_1 2", dc.get(0).getMetadataField().getMetadataSchema().getName(), @@ -550,13 +550,13 @@ public void testAddMetadata_7args_1_authority() String schema = "dc"; String element = "language"; String qualifier = "iso"; - String lang = Item.ANY; + String lang = null; List values = Arrays.asList("en_US", "en"); List authorities = Arrays.asList("accepted", "uncertain"); List confidences = Arrays.asList(0, 0); itemService.addMetadata(context, it, schema, element, qualifier, lang, values, authorities, confidences); - List dc = itemService.getMetadata(it, schema, element, qualifier, lang); + List dc = itemService.getMetadata(it, schema, element, qualifier, Item.ANY); assertThat("testAddMetadata_7args_1 0", dc, notNullValue()); assertTrue("testAddMetadata_7args_1 1", dc.size() == 2); assertThat("testAddMetadata_7args_1 2", dc.get(0).getMetadataField().getMetadataSchema().getName(), @@ -587,13 +587,13 @@ public void testAddMetadata_7args_1_noauthority() throws SQLException { String schema = "dc"; String element = "contributor"; String qualifier = "author"; - String lang = Item.ANY; + String lang = null; List values = Arrays.asList("value0", "value1"); List authorities = Arrays.asList("auth0", "auth2"); List confidences = Arrays.asList(0, 0); itemService.addMetadata(context, it, schema, element, qualifier, lang, values, authorities, confidences); - List dc = itemService.getMetadata(it, schema, element, qualifier, lang); + List dc = itemService.getMetadata(it, schema, element, qualifier, Item.ANY); assertThat("testAddMetadata_7args_1 0", dc, notNullValue()); assertTrue("testAddMetadata_7args_1 1", dc.size() == 2); assertThat("testAddMetadata_7args_1 2", dc.get(0).getMetadataField().getMetadataSchema().getName(), @@ -622,11 +622,11 @@ public void testAddMetadata_5args_2() throws SQLException { String schema = "dc"; String element = "contributor"; String qualifier = "author"; - String lang = Item.ANY; + String lang = null; List values = Arrays.asList("value0", "value1"); itemService.addMetadata(context, it, schema, element, qualifier, lang, values); - List dc = itemService.getMetadata(it, schema, element, qualifier, lang); + List dc = itemService.getMetadata(it, schema, element, qualifier, Item.ANY); assertThat("testAddMetadata_5args_2 0", dc, notNullValue()); assertTrue("testAddMetadata_5args_2 1", dc.size() == 2); assertThat("testAddMetadata_5args_2 2", dc.get(0).getMetadataField().getMetadataSchema().getName(), @@ -654,13 +654,13 @@ public void testAddMetadata_7args_2_authority() throws SQLException { String schema = "dc"; String element = "language"; String qualifier = "iso"; - String lang = Item.ANY; + String lang = null; String values = "en"; String authorities = "accepted"; int confidences = 0; itemService.addMetadata(context, it, schema, element, qualifier, lang, values, authorities, confidences); - List dc = itemService.getMetadata(it, schema, element, qualifier, lang); + List dc = itemService.getMetadata(it, schema, element, qualifier, Item.ANY); assertThat("testAddMetadata_7args_2 0", dc, notNullValue()); assertTrue("testAddMetadata_7args_2 1", dc.size() == 1); assertThat("testAddMetadata_7args_2 2", dc.get(0).getMetadataField().getMetadataSchema().getName(), @@ -683,13 +683,13 @@ public void testAddMetadata_7args_2_noauthority() throws SQLException { String schema = "dc"; String element = "contributor"; String qualifier = "editor"; - String lang = Item.ANY; + String lang = null; String values = "value0"; String authorities = "auth0"; int confidences = 0; itemService.addMetadata(context, it, schema, element, qualifier, lang, values, authorities, confidences); - List dc = itemService.getMetadata(it, schema, element, qualifier, lang); + List dc = itemService.getMetadata(it, schema, element, qualifier, Item.ANY); assertThat("testAddMetadata_7args_2 0", dc, notNullValue()); assertTrue("testAddMetadata_7args_2 1", dc.size() == 1); assertThat("testAddMetadata_7args_2 2", dc.get(0).getMetadataField().getMetadataSchema().getName(), @@ -710,13 +710,13 @@ public void testClearMetadata() throws SQLException { String schema = "dc"; String element = "contributor"; String qualifier = "author"; - String lang = Item.ANY; + String lang = null; String values = "value0"; itemService.addMetadata(context, it, schema, element, qualifier, lang, values); - itemService.clearMetadata(context, it, schema, element, qualifier, lang); + itemService.clearMetadata(context, it, schema, element, qualifier, Item.ANY); - List dc = itemService.getMetadata(it, schema, element, qualifier, lang); + List dc = itemService.getMetadata(it, schema, element, qualifier, Item.ANY); assertThat("testClearMetadata 0", dc, notNullValue()); assertTrue("testClearMetadata 1", dc.size() == 0); } @@ -758,11 +758,11 @@ public void testGetCollections() throws Exception { context.turnOffAuthorisationSystem(); Collection collection = collectionService.create(context, owningCommunity); collectionService.setMetadataSingleValue(context, collection, MetadataSchemaEnum.DC.getName(), - "title", null, Item.ANY, "collection B"); + "title", null, null, "collection B"); it.addCollection(collection); collection = collectionService.create(context, owningCommunity); collectionService.setMetadataSingleValue(context, collection, MetadataSchemaEnum.DC.getName(), - "title", null, Item.ANY, "collection A"); + "title", null, null, "collection A"); it.addCollection(collection); context.restoreAuthSystemState(); assertThat("testGetCollections 0", it.getCollections(), notNullValue()); @@ -1602,7 +1602,7 @@ public void testFindByMetadataField() throws Exception { // add new metadata to item context.turnOffAuthorisationSystem(); - itemService.addMetadata(context, it, schema, element, qualifier, Item.ANY, value); + itemService.addMetadata(context, it, schema, element, qualifier, null, value); itemService.update(context, it); context.restoreAuthSystemState(); @@ -1667,7 +1667,7 @@ public void testFindByAuthorityValue() throws Exception { // add new metadata (with authority) to item context.turnOffAuthorisationSystem(); - itemService.addMetadata(context, it, schema, element, qualifier, Item.ANY, value, authority, confidence); + itemService.addMetadata(context, it, schema, element, qualifier, null, value, authority, confidence); itemService.update(context, it); context.restoreAuthSystemState(); diff --git a/dspace-api/src/test/java/org/dspace/content/dao/RelationshipDAOImplTest.java b/dspace-api/src/test/java/org/dspace/content/dao/RelationshipDAOImplTest.java index b6f5da6be06..05e5ba0e116 100644 --- a/dspace-api/src/test/java/org/dspace/content/dao/RelationshipDAOImplTest.java +++ b/dspace-api/src/test/java/org/dspace/content/dao/RelationshipDAOImplTest.java @@ -87,8 +87,8 @@ public void init() { WorkspaceItem workspaceItemTwo = workspaceItemService.create(context, collection, false); itemOne = installItemService.installItem(context, workspaceItem); itemTwo = installItemService.installItem(context, workspaceItemTwo); - itemService.addMetadata(context, itemOne, "dspace", "entity", "type", Item.ANY, "Publication"); - itemService.addMetadata(context, itemTwo, "dspace", "entity", "type", Item.ANY, "Person"); + itemService.addMetadata(context, itemOne, "dspace", "entity", "type", null, "Publication"); + itemService.addMetadata(context, itemTwo, "dspace", "entity", "type", null, "Person"); itemService.update(context, itemOne); itemService.update(context, itemTwo); entityTypeOne = entityTypeService.create(context, "Person"); diff --git a/dspace-api/src/test/java/org/dspace/content/dao/RelationshipTypeDAOImplTest.java b/dspace-api/src/test/java/org/dspace/content/dao/RelationshipTypeDAOImplTest.java index 3fff6fec476..4d405c6a27e 100644 --- a/dspace-api/src/test/java/org/dspace/content/dao/RelationshipTypeDAOImplTest.java +++ b/dspace-api/src/test/java/org/dspace/content/dao/RelationshipTypeDAOImplTest.java @@ -82,8 +82,8 @@ public void init() { WorkspaceItem workspaceItemTwo = workspaceItemService.create(context, collection, false); itemOne = installItemService.installItem(context, workspaceItem); itemTwo = installItemService.installItem(context, workspaceItemTwo); - itemService.addMetadata(context, itemOne, "dspace", "entity", "type", Item.ANY, "Publication"); - itemService.addMetadata(context, itemTwo, "dspace", "entity", "type", Item.ANY, "Person"); + itemService.addMetadata(context, itemOne, "dspace", "entity", "type", null, "Publication"); + itemService.addMetadata(context, itemTwo, "dspace", "entity", "type", null, "Person"); itemService.update(context, itemOne); itemService.update(context, itemTwo); entityTypeOne = entityTypeService.create(context, "Person"); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java index b78436f1fb3..f3ccfe9fc10 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java @@ -247,7 +247,7 @@ private void initSimplePublicationItem() throws Exception { for (String author : authorsOriginalOrder) { itemService.addMetadata( - context, publicationItem, "dc", "contributor", "author", Item.ANY, author + context, publicationItem, "dc", "contributor", "author", null, author ); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java index d8e53c770c7..264fb0d246a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java @@ -819,7 +819,7 @@ public void addRelationshipsAndMetadataToValidatePlaceTest() throws Exception { // Make sure we grab the latest instance of the Item from the database publication1 = itemService.find(context, publication1.getID()); // Add a plain text dc.contributor.author value - itemService.addMetadata(context, publication1, "dc", "contributor", "author", Item.ANY, "plain text"); + itemService.addMetadata(context, publication1, "dc", "contributor", "author", null, "plain text"); itemService.update(context, publication1); List list = itemService.getMetadata(publication1, "dc", "contributor", "author", Item.ANY); @@ -885,7 +885,7 @@ public void addRelationshipsAndMetadataToValidatePlaceTest() throws Exception { // Ensure we have the latest instance of the Item from the database publication1 = itemService.find(context, publication1.getID()); // Add a fourth dc.contributor.author mdv - itemService.addMetadata(context, publication1, "dc", "contributor", "author", Item.ANY, "plain text two"); + itemService.addMetadata(context, publication1, "dc", "contributor", "author", null, "plain text two"); itemService.update(context, publication1); context.restoreAuthSystemState(); @@ -954,7 +954,7 @@ public void addRelationshipsAndMetadataToValidatePlaceTest() throws Exception { context.turnOffAuthorisationSystem(); // The following additions of Metadata will perform the same sequence of logic and tests as described above publication1 = itemService.find(context, publication1.getID()); - itemService.addMetadata(context, publication1, "dc", "contributor", "author", Item.ANY, "plain text three"); + itemService.addMetadata(context, publication1, "dc", "contributor", "author", null, "plain text three"); itemService.update(context, publication1); context.restoreAuthSystemState(); @@ -984,10 +984,10 @@ public void addRelationshipsAndMetadataToValidatePlaceTest() throws Exception { context.turnOffAuthorisationSystem(); publication1 = itemService.find(context, publication1.getID()); - itemService.addMetadata(context, publication1, "dc", "contributor", "author", Item.ANY, "plain text four"); - itemService.addMetadata(context, publication1, "dc", "contributor", "author", Item.ANY, "plain text five"); - itemService.addMetadata(context, publication1, "dc", "contributor", "author", Item.ANY, "plain text six"); - itemService.addMetadata(context, publication1, "dc", "contributor", "author", Item.ANY, "plain text seven"); + itemService.addMetadata(context, publication1, "dc", "contributor", "author", null, "plain text four"); + itemService.addMetadata(context, publication1, "dc", "contributor", "author", null, "plain text five"); + itemService.addMetadata(context, publication1, "dc", "contributor", "author", null, "plain text six"); + itemService.addMetadata(context, publication1, "dc", "contributor", "author", null, "plain text seven"); itemService.update(context, publication1); context.restoreAuthSystemState(); @@ -1115,7 +1115,7 @@ public void deleteMetadataValueAndValidatePlace() throws Exception { publication1 = itemService.find(context, publication1.getID()); // Add a plain text metadatavalue to the publication // After this addition, the list of authors should like like "Donald Smith", "plain text" - itemService.addMetadata(context, publication1, "dc", "contributor", "author", Item.ANY, "plain text"); + itemService.addMetadata(context, publication1, "dc", "contributor", "author", null, "plain text"); itemService.update(context, publication1); context.restoreAuthSystemState(); List list = itemService.getMetadata(publication1, "dc", "contributor", "author", Item.ANY); @@ -1158,7 +1158,7 @@ public void deleteMetadataValueAndValidatePlace() throws Exception { // Get the publication from the DB again to ensure that we have the latest object publication1 = itemService.find(context, publication1.getID()); // Add a fourth metadata value to the publication - itemService.addMetadata(context, publication1, "dc", "contributor", "author", Item.ANY, "plain text two"); + itemService.addMetadata(context, publication1, "dc", "contributor", "author", null, "plain text two"); itemService.update(context, publication1); context.restoreAuthSystemState(); list = itemService.getMetadata(publication1, "dc", "contributor", "author", Item.ANY); @@ -1196,7 +1196,7 @@ public void deleteMetadataValueAndValidatePlace() throws Exception { context.turnOffAuthorisationSystem(); publication1 = itemService.find(context, publication1.getID()); // Create another plain text metadata value on the publication - itemService.addMetadata(context, publication1, "dc", "contributor", "author", Item.ANY, "plain text three"); + itemService.addMetadata(context, publication1, "dc", "contributor", "author", null, "plain text three"); itemService.update(context, publication1); context.restoreAuthSystemState(); list = itemService.getMetadata(publication1, "dc", "contributor", "author", Item.ANY); @@ -1324,7 +1324,7 @@ public void deleteRelationshipsAndValidatePlace() throws Exception { publication1 = itemService.find(context, publication1.getID()); // Add a plain text metadatavalue to the publication // After this addition, the list of authors should like like "Donald Smith", "plain text" - itemService.addMetadata(context, publication1, "dc", "contributor", "author", Item.ANY, "plain text"); + itemService.addMetadata(context, publication1, "dc", "contributor", "author", null, "plain text"); itemService.update(context, publication1); context.restoreAuthSystemState(); List list = itemService.getMetadata(publication1, "dc", "contributor", "author", Item.ANY); @@ -1368,7 +1368,7 @@ public void deleteRelationshipsAndValidatePlace() throws Exception { // Get the publication from the DB again to ensure that we have the latest object publication1 = itemService.find(context, publication1.getID()); // Add a fourth metadata value to the publication - itemService.addMetadata(context, publication1, "dc", "contributor", "author", Item.ANY, "plain text two"); + itemService.addMetadata(context, publication1, "dc", "contributor", "author", null, "plain text two"); itemService.update(context, publication1); context.restoreAuthSystemState(); list = itemService.getMetadata(publication1, "dc", "contributor", "author", Item.ANY); @@ -1406,7 +1406,7 @@ public void deleteRelationshipsAndValidatePlace() throws Exception { context.turnOffAuthorisationSystem(); publication1 = itemService.find(context, publication1.getID()); // Create another plain text metadata value on the publication - itemService.addMetadata(context, publication1, "dc", "contributor", "author", Item.ANY, "plain text three"); + itemService.addMetadata(context, publication1, "dc", "contributor", "author", null, "plain text three"); itemService.update(context, publication1); context.restoreAuthSystemState(); list = itemService.getMetadata(publication1, "dc", "contributor", "author", Item.ANY); From 7082e3259595dbcbd31f339c53a15c17cd47110b Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Mon, 22 Jul 2024 15:18:44 +0200 Subject: [PATCH 244/939] 116542: resolve issues after merge with latest 7-x branch --- .../test/java/org/dspace/content/ItemTest.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/content/ItemTest.java b/dspace-api/src/test/java/org/dspace/content/ItemTest.java index 87f42cf4293..00dbf2994d9 100644 --- a/dspace-api/src/test/java/org/dspace/content/ItemTest.java +++ b/dspace-api/src/test/java/org/dspace/content/ItemTest.java @@ -544,7 +544,7 @@ public void testAddMetadata_5args_no_values() throws Exception { String schema = "dc"; String element = "contributor"; String qualifier = "author"; - String lang = Item.ANY; + String lang = null; String[] values = {}; itemService.addMetadata(context, it, schema, element, qualifier, lang, Arrays.asList(values)); fail("IllegalArgumentException expected"); @@ -632,7 +632,7 @@ public void testAddMetadata_7args_no_values() throws Exception { String schema = "dc"; String element = "contributor"; String qualifier = "author"; - String lang = Item.ANY; + String lang = null; List values = new ArrayList(); List authorities = new ArrayList(); List confidences = new ArrayList(); @@ -645,7 +645,7 @@ public void testAddMetadata_list_with_virtual_metadata() throws Exception { String schema = "dc"; String element = "contributor"; String qualifier = "author"; - String lang = Item.ANY; + String lang = null; // Create two fake virtual metadata ("virtual::[relationship-id]") values List values = new ArrayList<>(Arrays.asList("uuid-1", "uuid-2")); List authorities = new ArrayList<>(Arrays.asList(Constants.VIRTUAL_AUTHORITY_PREFIX + "relationship-1", @@ -674,7 +674,7 @@ public void testAddMetadata_list_with_virtual_metadata() throws Exception { assertEquals(1, valuesAdded.size()); // Get metadata and ensure new value is the ONLY ONE for this metadata field - List dc = itemService.getMetadata(it, schema, element, qualifier, lang); + List dc = itemService.getMetadata(it, schema, element, qualifier, Item.ANY); assertNotNull(dc); assertEquals(1, dc.size()); assertEquals(schema, dc.get(0).getMetadataField().getMetadataSchema().getName()); @@ -693,7 +693,7 @@ public void testAddMetadata_5args_2() throws SQLException { String schema = "dc"; String element = "contributor"; String qualifier = "author"; - String lang = Item.ANY; + String lang = null; String value = "value0"; itemService.addMetadata(context, it, schema, element, qualifier, lang, value); @@ -772,7 +772,7 @@ public void testAddMetadata_single_virtual_metadata() throws Exception { String schema = "dc"; String element = "contributor"; String qualifier = "author"; - String lang = Item.ANY; + String lang = null; // Create a single fake virtual metadata ("virtual::[relationship-id]") value String value = "uuid-1"; String authority = Constants.VIRTUAL_AUTHORITY_PREFIX + "relationship-1"; @@ -786,7 +786,7 @@ public void testAddMetadata_single_virtual_metadata() throws Exception { assertNull(valuesAdded); // Verify this metadata field does NOT exist on the item - List mv = itemService.getMetadata(it, schema, element, qualifier, lang); + List mv = itemService.getMetadata(it, schema, element, qualifier, Item.ANY); assertNotNull(mv); assertTrue(mv.isEmpty()); @@ -797,7 +797,7 @@ public void testAddMetadata_single_virtual_metadata() throws Exception { assertNull(valuesAdded); // Verify this metadata field does NOT exist on the item - mv = itemService.getMetadata(it, schema, element, qualifier, lang); + mv = itemService.getMetadata(it, schema, element, qualifier, Item.ANY); assertNotNull(mv); assertTrue(mv.isEmpty()); } From fff8cc3cb4f05ace42b035b3eb2c3a9b46f3b5f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jul 2024 14:34:41 +0000 Subject: [PATCH 245/939] Bump dnsjava:dnsjava from 2.1.9 to 3.6.0 in /dspace-api Bumps [dnsjava:dnsjava](https://github.com/dnsjava/dnsjava) from 2.1.9 to 3.6.0. - [Release notes](https://github.com/dnsjava/dnsjava/releases) - [Changelog](https://github.com/dnsjava/dnsjava/blob/master/Changelog) - [Commits](https://github.com/dnsjava/dnsjava/compare/v2.1.9...v3.6.0) --- updated-dependencies: - dependency-name: dnsjava:dnsjava dependency-type: direct:production ... Signed-off-by: dependabot[bot] (cherry picked from commit 1775c88919cacf063788e4a6a0b67cdf1ef23fec) --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 16b151b092e..bdf035179be 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -629,7 +629,7 @@ dnsjava dnsjava - 2.1.9 + 3.6.0 From 30b6eddeb66c0e89c951090d8c2b2d6d0f3a31ab Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 23 Jul 2024 12:17:07 +0200 Subject: [PATCH 246/939] Update spider list URLs to satisfy cloudflare redirects Update spider list URLs to satisfy cloudflare redirects Update spider list URLs to satisfy cloudflare redirects (cherry picked from commit d22ea117ca6970fd52b296ac18d3f71878c1a857) --- dspace/config/modules/solr-statistics.cfg | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/dspace/config/modules/solr-statistics.cfg b/dspace/config/modules/solr-statistics.cfg index 073850ca232..632ff5078b4 100644 --- a/dspace/config/modules/solr-statistics.cfg +++ b/dspace/config/modules/solr-statistics.cfg @@ -32,10 +32,10 @@ solr-statistics.configset = statistics solr-statistics.autoCommit = true # URLs to download IP addresses of search engine spiders from -solr-statistics.spiderips.urls = http://iplists.com/google.txt, \ - http://iplists.com/inktomi.txt, \ - http://iplists.com/lycos.txt, \ - http://iplists.com/infoseek.txt, \ - http://iplists.com/altavista.txt, \ - http://iplists.com/excite.txt, \ - http://iplists.com/misc.txt +solr-statistics.spiderips.urls = https://www.iplists.com/google.txt, \ + https://www.iplists.com/inktomi.txt, \ + https://www.iplists.com/lycos.txt, \ + https://www.iplists.com/infoseek.txt, \ + https://www.iplists.com/altavista.txt, \ + https://www.iplists.com/excite.txt, \ + https://www.iplists.com/misc.txt From affa4b00ff59cffa69d47de6a3a7d4137008dd40 Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Wed, 24 Jul 2024 19:07:44 +0200 Subject: [PATCH 247/939] [CST-14905] Orcid revoke token feature --- .../org/dspace/orcid/client/OrcidClient.java | 8 + .../dspace/orcid/client/OrcidClientImpl.java | 37 +++++ .../orcid/client/OrcidConfiguration.java | 9 ++ .../impl/OrcidSynchronizationServiceImpl.java | 14 ++ .../ResearcherProfileRestRepositoryIT.java | 146 ++++++++++++++++-- dspace/config/modules/orcid.cfg | 1 + dspace/config/spring/api/orcid-services.xml | 1 + 7 files changed, 200 insertions(+), 16 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/orcid/client/OrcidClient.java b/dspace-api/src/main/java/org/dspace/orcid/client/OrcidClient.java index 99d1920aa53..d21f61a922f 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/client/OrcidClient.java +++ b/dspace-api/src/main/java/org/dspace/orcid/client/OrcidClient.java @@ -10,6 +10,7 @@ import java.util.List; import java.util.Optional; +import org.dspace.orcid.OrcidToken; import org.dspace.orcid.exception.OrcidClientException; import org.dspace.orcid.model.OrcidTokenResponseDTO; import org.orcid.jaxb.model.v3.release.record.Person; @@ -161,4 +162,11 @@ public interface OrcidClient { */ OrcidResponse deleteByPutCode(String accessToken, String orcid, String putCode, String path); + /** + * Revokes the given {@param accessToken} with a POST method. + * @param orcidToken the access token to revoke + * @throws OrcidClientException if some error occurs during the search + */ + void revokeToken(OrcidToken orcidToken); + } diff --git a/dspace-api/src/main/java/org/dspace/orcid/client/OrcidClientImpl.java b/dspace-api/src/main/java/org/dspace/orcid/client/OrcidClientImpl.java index 3e7ca7b2102..1532abe6341 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/client/OrcidClientImpl.java +++ b/dspace-api/src/main/java/org/dspace/orcid/client/OrcidClientImpl.java @@ -42,6 +42,7 @@ import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.message.BasicNameValuePair; +import org.dspace.orcid.OrcidToken; import org.dspace.orcid.exception.OrcidClientException; import org.dspace.orcid.model.OrcidEntityType; import org.dspace.orcid.model.OrcidProfileSectionType; @@ -178,6 +179,16 @@ public OrcidResponse deleteByPutCode(String accessToken, String orcid, String pu return execute(buildDeleteUriRequest(accessToken, "/" + orcid + path + "/" + putCode), true); } + @Override + public void revokeToken(OrcidToken orcidToken) { + List params = new ArrayList<>(); + params.add(new BasicNameValuePair("client_id", orcidConfiguration.getClientId())); + params.add(new BasicNameValuePair("client_secret", orcidConfiguration.getClientSecret())); + params.add(new BasicNameValuePair("token", orcidToken.getAccessToken())); + + executeSuccessful(buildPostForRevokeToken(new UrlEncodedFormEntity(params, Charset.defaultCharset()))); + } + @Override public OrcidTokenResponseDTO getReadPublicAccessToken() { return getClientCredentialsAccessToken("/read-public"); @@ -220,6 +231,14 @@ private HttpUriRequest buildPostUriRequest(String accessToken, String relativePa .build(); } + private HttpUriRequest buildPostForRevokeToken(HttpEntity entity) { + return post(orcidConfiguration.getRevokeUrl()) + .addHeader("Accept", "application/json") + .addHeader("Content-Type", "application/x-www-form-urlencoded") + .setEntity(entity) + .build(); + } + private HttpUriRequest buildPutUriRequest(String accessToken, String relativePath, Object object) { return put(orcidConfiguration.getApiUrl() + relativePath.trim()) .addHeader("Content-Type", "application/vnd.orcid+xml") @@ -234,6 +253,24 @@ private HttpUriRequest buildDeleteUriRequest(String accessToken, String relative .build(); } + private void executeSuccessful(HttpUriRequest httpUriRequest) { + try { + HttpClient client = HttpClientBuilder.create().build(); + HttpResponse response = client.execute(httpUriRequest); + + if (isNotSuccessfull(response)) { + throw new OrcidClientException( + getStatusCode(response), + "Operation " + httpUriRequest.getMethod() + " for the resource " + httpUriRequest.getURI() + + " was not successful: " + new String(response.getEntity().getContent().readAllBytes(), + StandardCharsets.UTF_8) + ); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + private T executeAndParseJson(HttpUriRequest httpUriRequest, Class clazz) { HttpClient client = HttpClientBuilder.create().build(); diff --git a/dspace-api/src/main/java/org/dspace/orcid/client/OrcidConfiguration.java b/dspace-api/src/main/java/org/dspace/orcid/client/OrcidConfiguration.java index 550b0215c43..dfa90fcae03 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/client/OrcidConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/orcid/client/OrcidConfiguration.java @@ -35,6 +35,8 @@ public final class OrcidConfiguration { private String scopes; + private String revokeUrl; + public String getApiUrl() { return apiUrl; } @@ -111,4 +113,11 @@ public boolean isApiConfigured() { return !StringUtils.isAnyBlank(clientId, clientSecret); } + public String getRevokeUrl() { + return revokeUrl; + } + + public void setRevokeUrl(String revokeUrl) { + this.revokeUrl = revokeUrl; + } } diff --git a/dspace-api/src/main/java/org/dspace/orcid/service/impl/OrcidSynchronizationServiceImpl.java b/dspace-api/src/main/java/org/dspace/orcid/service/impl/OrcidSynchronizationServiceImpl.java index 97d832d3de8..a302c58f2c5 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/service/impl/OrcidSynchronizationServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/orcid/service/impl/OrcidSynchronizationServiceImpl.java @@ -37,6 +37,7 @@ import org.dspace.eperson.EPerson; import org.dspace.eperson.service.EPersonService; import org.dspace.orcid.OrcidToken; +import org.dspace.orcid.client.OrcidClient; import org.dspace.orcid.model.OrcidEntityType; import org.dspace.orcid.model.OrcidTokenResponseDTO; import org.dspace.orcid.service.OrcidSynchronizationService; @@ -47,6 +48,8 @@ import org.dspace.profile.OrcidSynchronizationMode; import org.dspace.profile.service.ResearcherProfileService; import org.dspace.services.ConfigurationService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; /** @@ -57,6 +60,7 @@ */ public class OrcidSynchronizationServiceImpl implements OrcidSynchronizationService { + private static final Logger log = LoggerFactory.getLogger(OrcidSynchronizationServiceImpl.class); @Autowired private ItemService itemService; @@ -75,6 +79,9 @@ public class OrcidSynchronizationServiceImpl implements OrcidSynchronizationServ @Autowired private ResearcherProfileService researcherProfileService; + @Autowired + private OrcidClient orcidClient; + @Override public void linkProfile(Context context, Item profile, OrcidTokenResponseDTO token) throws SQLException { @@ -118,7 +125,14 @@ public void unlinkProfile(Context context, Item profile) throws SQLException { itemService.clearMetadata(context, profile, "dspace", "orcid", "scope", Item.ANY); itemService.clearMetadata(context, profile, "dspace", "orcid", "authenticated", Item.ANY); + OrcidToken profileToken = orcidTokenService.findByProfileItem(context, profile); + if (profileToken == null) { + log.warn("Cannot find any token related to the user profile: {}", profile.getID()); + return; + } + orcidTokenService.deleteByProfileItem(context, profile); + orcidClient.revokeToken(profileToken); updateItem(context, profile); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResearcherProfileRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResearcherProfileRestRepositoryIT.java index 70009d049fc..0d46f4268ca 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResearcherProfileRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResearcherProfileRestRepositoryIT.java @@ -30,7 +30,10 @@ import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @@ -44,6 +47,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.io.UnsupportedEncodingException; +import java.lang.reflect.Field; import java.sql.SQLException; import java.util.List; import java.util.UUID; @@ -79,11 +83,13 @@ import org.dspace.orcid.exception.OrcidClientException; import org.dspace.orcid.model.OrcidTokenResponseDTO; import org.dspace.orcid.service.OrcidQueueService; +import org.dspace.orcid.service.OrcidSynchronizationService; import org.dspace.orcid.service.OrcidTokenService; import org.dspace.services.ConfigurationService; import org.dspace.util.UUIDUtils; import org.junit.After; import org.junit.Test; +import org.mockito.Mock; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MvcResult; @@ -114,7 +120,11 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra @Autowired private OrcidClient orcidClient; - private OrcidClient orcidClientMock = mock(OrcidClient.class); + @Mock + private OrcidClient orcidClientMock; + + @Autowired + private OrcidSynchronizationService orcidSynchronizationService; private EPerson user; @@ -158,16 +168,36 @@ public void setUp() throws Exception { context.restoreAuthSystemState(); - researcherProfileAddOrcidOperation.setOrcidClient(orcidClientMock); - + useInstanceForBean(orcidSynchronizationService, orcidClientMock); + useInstanceForBean(researcherProfileAddOrcidOperation, orcidClientMock); } @After public void after() { orcidTokenService.deleteAll(context); - researcherProfileAddOrcidOperation.setOrcidClient(orcidClient); + useInstanceForBean(orcidSynchronizationService, orcidClient); + useInstanceForBean(researcherProfileAddOrcidOperation, orcidClient); + } + + private void useInstanceForBean(B bean, I instance) { + Field[] fields = bean.getClass().getDeclaredFields(); + + for (Field field : fields) { + if (field.getType().isAssignableFrom(instance.getClass())) { + boolean accessible = field.isAccessible(); + try { + field.setAccessible(true); + field.set(bean, instance); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } finally { + field.setAccessible(accessible); + } + } + } } + /** * Verify that the findById endpoint returns the own profile. * @@ -608,30 +638,38 @@ public void testDeleteWithProfileLinkedWithOrcid() throws Exception { .withOrcidAuthenticated("authenticated") .build(); + context.restoreAuthSystemState(); + String id = user.getID().toString(); String authToken = getAuthToken(user.getEmail(), password); + OrcidToken orcidToken = orcidTokenService.findByProfileItem(context, profileItem); - context.restoreAuthSystemState(); - - getClient(authToken).perform(get("/api/eperson/profiles/{id}", id)) + getClient(authToken) + .perform(get("/api/eperson/profiles/{id}", id)) .andExpect(status().isOk()); assertThat(profileItem.getMetadata(), hasItem(with("person.identifier.orcid", "0000-1111-2222-3333"))); assertThat(profileItem.getMetadata(), hasItem(with("dspace.orcid.authenticated", "authenticated"))); - assertThat(getOrcidAccessToken(profileItem), notNullValue()); + assertThat(orcidToken.getAccessToken(), notNullValue()); getClient(authToken).perform(get("/api/eperson/profiles/{id}/item", id)) .andExpect(status().isOk()) .andExpect(jsonPath("$", hasJsonPath("$.metadata", matchMetadataNotEmpty("dspace.object.owner")))); + getClient(authToken).perform(delete("/api/eperson/profiles/{id}", id)) .andExpect(status().isNoContent()); + verify(orcidClientMock, times(1)).revokeToken(matchesToken(orcidToken)); + verifyNoMoreInteractions(orcidClientMock); + profileItem = context.reloadEntity(profileItem); + orcidToken = orcidTokenService.findByProfileItem(context, profileItem); + assertThat(profileItem.getMetadata(), not(hasItem(with("person.identifier.orcid", "0000-1111-2222-3333")))); assertThat(profileItem.getMetadata(), not(hasItem(with("dspace.orcid.authenticated", "authenticated")))); - assertThat(getOrcidAccessToken(profileItem), nullValue()); + assertThat(orcidToken, nullValue()); } @@ -1850,7 +1888,8 @@ public void testOwnerPatchToDisconnectProfileFromOrcidWithOnlyOwnerConfiguration .withNameInMetadata("Test", "User") .build(); - OrcidTokenBuilder.create(context, ePerson, "3de2e370-8aa9-4bbe-8d7e-f5b1577bdad4").build(); + OrcidToken orcidToken = + OrcidTokenBuilder.create(context, ePerson, "3de2e370-8aa9-4bbe-8d7e-f5b1577bdad4").build(); Item profile = createProfile(ePerson); @@ -1872,6 +1911,9 @@ public void testOwnerPatchToDisconnectProfileFromOrcidWithOnlyOwnerConfiguration .andExpect(jsonPath("$.orcid").doesNotExist()) .andExpect(jsonPath("$.orcidSynchronization").doesNotExist()); + verify(orcidClientMock, times(1)).revokeToken(matchesToken(orcidToken)); + verifyNoMoreInteractions(orcidClientMock); + profile = context.reloadEntity(profile); assertThat(getMetadataValues(profile, "person.identifier.orcid"), empty()); @@ -1880,6 +1922,54 @@ public void testOwnerPatchToDisconnectProfileFromOrcidWithOnlyOwnerConfiguration assertThat(getOrcidAccessToken(profile), nullValue()); } + @Test + public void testPatchToDisconnectProfileFromOrcidDoesntRevokeOrcidToken() throws Exception { + + configurationService.setProperty("orcid.disconnection.allowed-users", "admin_and_owner"); + + context.turnOffAuthorisationSystem(); + + EPerson ePerson = EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withOrcid("0000-1111-2222-3333") + .withOrcidScope("/read") + .withOrcidScope("/write") + .withEmail("test@email.it") + .withPassword(password) + .withNameInMetadata("Test", "User") + .build(); + + OrcidToken orcidToken = + OrcidTokenBuilder.create(context, ePerson, "3de2e370-8aa9-4bbe-8d7e-f5b1577bdad4").build(); + + Item profile = createProfile(ePerson); + + assertThat(getMetadataValues(profile, "person.identifier.orcid"), not(empty())); + assertThat(getMetadataValues(profile, "dspace.orcid.scope"), not(empty())); + assertThat(getMetadataValues(profile, "dspace.orcid.authenticated"), not(empty())); + assertThat(getOrcidAccessToken(profile), is("3de2e370-8aa9-4bbe-8d7e-f5b1577bdad4")); + + context.restoreAuthSystemState(); + + doThrow(new OrcidClientException(403, "")).when(orcidClientMock).revokeToken(any(OrcidToken.class)); + + getClient(getAuthToken(ePerson.getEmail(), password)) + .perform(patch("/api/eperson/profiles/{id}", ePerson.getID().toString()) + .content(getPatchContent(asList(new RemoveOperation("/orcid")))) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isInternalServerError()); + + verify(orcidClientMock, times(1)).revokeToken(matchesToken(orcidToken)); + verifyNoMoreInteractions(orcidClientMock); + + profile = context.reloadEntity(profile); + + assertThat(getMetadataValues(profile, "person.identifier.orcid"), not(empty())); + assertThat(getMetadataValues(profile, "dspace.orcid.scope"), not(empty())); + assertThat(getMetadataValues(profile, "dspace.orcid.authenticated"), not(empty())); + assertThat(getOrcidAccessToken(profile), is("3de2e370-8aa9-4bbe-8d7e-f5b1577bdad4")); + } + @Test public void testAdminPatchToDisconnectProfileFromOrcidWithOnlyOwnerConfiguration() throws Exception { @@ -2023,7 +2113,8 @@ public void testAdminPatchToDisconnectProfileFromOrcidWithOnlyAdminConfiguration .withNameInMetadata("Test", "User") .build(); - OrcidTokenBuilder.create(context, ePerson, "3de2e370-8aa9-4bbe-8d7e-f5b1577bdad4").build(); + OrcidToken orcidToken = + OrcidTokenBuilder.create(context, ePerson, "3de2e370-8aa9-4bbe-8d7e-f5b1577bdad4").build(); Item profile = createProfile(ePerson); @@ -2034,6 +2125,7 @@ public void testAdminPatchToDisconnectProfileFromOrcidWithOnlyAdminConfiguration context.restoreAuthSystemState(); + getClient(getAuthToken(admin.getEmail(), password)) .perform(patch("/api/eperson/profiles/{id}", ePerson.getID().toString()) .content(getPatchContent(asList(new RemoveOperation("/orcid")))) @@ -2045,6 +2137,9 @@ public void testAdminPatchToDisconnectProfileFromOrcidWithOnlyAdminConfiguration .andExpect(jsonPath("$.orcid").doesNotExist()) .andExpect(jsonPath("$.orcidSynchronization").doesNotExist()); + verify(orcidClientMock, times(1)).revokeToken(matchesToken(orcidToken)); + verifyNoMoreInteractions(orcidClientMock); + profile = context.reloadEntity(profile); assertThat(getMetadataValues(profile, "person.identifier.orcid"), empty()); @@ -2111,17 +2206,18 @@ public void testOwnerPatchToDisconnectProfileFromOrcidWithAdminAndOwnerConfigura .withPassword(password) .withNameInMetadata("Test", "User") .build(); - - OrcidTokenBuilder.create(context, ePerson, "3de2e370-8aa9-4bbe-8d7e-f5b1577bdad4").build(); + OrcidToken orcidToken = + OrcidTokenBuilder.create(context, ePerson, "3de2e370-8aa9-4bbe-8d7e-f5b1577bdad4").build(); Item profile = createProfile(ePerson); + context.restoreAuthSystemState(); + assertThat(getMetadataValues(profile, "person.identifier.orcid"), not(empty())); assertThat(getMetadataValues(profile, "dspace.orcid.scope"), not(empty())); assertThat(getMetadataValues(profile, "dspace.orcid.authenticated"), not(empty())); assertThat(getOrcidAccessToken(profile), is("3de2e370-8aa9-4bbe-8d7e-f5b1577bdad4")); - context.restoreAuthSystemState(); getClient(getAuthToken(ePerson.getEmail(), password)) .perform(patch("/api/eperson/profiles/{id}", ePerson.getID().toString()) @@ -2134,6 +2230,9 @@ public void testOwnerPatchToDisconnectProfileFromOrcidWithAdminAndOwnerConfigura .andExpect(jsonPath("$.orcid").doesNotExist()) .andExpect(jsonPath("$.orcidSynchronization").doesNotExist()); + verify(orcidClientMock, times(1)).revokeToken(matchesToken(orcidToken)); + verifyNoMoreInteractions(orcidClientMock); + profile = context.reloadEntity(profile); assertThat(getMetadataValues(profile, "person.identifier.orcid"), empty()); @@ -2159,7 +2258,8 @@ public void testAdminPatchToDisconnectProfileFromOrcidWithAdminAndOwnerConfigura .withNameInMetadata("Test", "User") .build(); - OrcidTokenBuilder.create(context, ePerson, "3de2e370-8aa9-4bbe-8d7e-f5b1577bdad4").build(); + OrcidToken orcidToken = + OrcidTokenBuilder.create(context, ePerson, "3de2e370-8aa9-4bbe-8d7e-f5b1577bdad4").build(); Item profile = createProfile(ePerson); @@ -2170,6 +2270,7 @@ public void testAdminPatchToDisconnectProfileFromOrcidWithAdminAndOwnerConfigura context.restoreAuthSystemState(); + getClient(getAuthToken(admin.getEmail(), password)) .perform(patch("/api/eperson/profiles/{id}", ePerson.getID().toString()) .content(getPatchContent(asList(new RemoveOperation("/orcid")))) @@ -2181,6 +2282,10 @@ public void testAdminPatchToDisconnectProfileFromOrcidWithAdminAndOwnerConfigura .andExpect(jsonPath("$.orcid").doesNotExist()) .andExpect(jsonPath("$.orcidSynchronization").doesNotExist()); + + verify(orcidClientMock, times(1)).revokeToken(matchesToken(orcidToken)); + verifyNoMoreInteractions(orcidClientMock); + profile = context.reloadEntity(profile); assertThat(getMetadataValues(profile, "person.identifier.orcid"), empty()); @@ -2627,4 +2732,13 @@ private OrcidTokenResponseDTO buildOrcidTokenResponse(String orcid, String acces return token; } + private static OrcidToken matchesToken(OrcidToken orcidToken) { + return argThat( + token -> + token != null && + orcidToken.getAccessToken().equals(token.getAccessToken()) && + orcidToken.getID().equals(token.getID()) + ); + } + } diff --git a/dspace/config/modules/orcid.cfg b/dspace/config/modules/orcid.cfg index cde81967744..93da0a5f4cb 100644 --- a/dspace/config/modules/orcid.cfg +++ b/dspace/config/modules/orcid.cfg @@ -14,6 +14,7 @@ orcid.disconnection.allowed-users = admin_and_owner # ORCID API (https://github.com/ORCID/ORCID-Source/tree/master/orcid-api-web#endpoints) orcid.domain-url= https://sandbox.orcid.org orcid.authorize-url = ${orcid.domain-url}/oauth/authorize +orcid.revoke-url = ${orcid.domain-url}/oauth/revoke orcid.token-url = ${orcid.domain-url}/oauth/token orcid.api-url = https://api.sandbox.orcid.org/v3.0 orcid.public-url = https://pub.sandbox.orcid.org/v3.0 diff --git a/dspace/config/spring/api/orcid-services.xml b/dspace/config/spring/api/orcid-services.xml index e5fb002c314..eb31acb29c4 100644 --- a/dspace/config/spring/api/orcid-services.xml +++ b/dspace/config/spring/api/orcid-services.xml @@ -30,6 +30,7 @@ + From 26905b7d45f058f1bd8476f9e915af9a5be1547f Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Tue, 21 Nov 2023 11:51:25 +0100 Subject: [PATCH 248/939] [CST-14901][DSC-1357][#8662] Handles versioning for ORCID publications. feat: - ORCID publications waiting to be published are removed whenever a new version is created - ORCID publications already published will be updated with the ref to the last item version - ORCID consumer will process only latest item versions, ignoring all the other ones --- .../org/dspace/content/ItemServiceImpl.java | 42 ++++ .../dspace/content/service/ItemService.java | 10 + .../orcid/consumer/OrcidQueueConsumer.java | 51 +++-- .../org/dspace/orcid/dao/OrcidQueueDAO.java | 10 + .../orcid/dao/impl/OrcidQueueDAOImpl.java | 7 + .../orcid/service/OrcidQueueService.java | 10 + .../service/impl/OrcidQueueServiceImpl.java | 5 + .../dspace/versioning/VersioningConsumer.java | 35 +++- .../dspace/orcid/OrcidQueueConsumerIT.java | 185 ++++++++++++++++++ 9 files changed, 337 insertions(+), 18 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java index ac6c0d43d71..ae7081f73a3 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -79,6 +79,9 @@ import org.dspace.orcid.service.OrcidTokenService; import org.dspace.profile.service.ResearcherProfileService; import org.dspace.services.ConfigurationService; +import org.dspace.versioning.Version; +import org.dspace.versioning.VersionHistory; +import org.dspace.versioning.service.VersionHistoryService; import org.dspace.versioning.service.VersioningService; import org.dspace.workflow.WorkflowItemService; import org.dspace.workflow.factory.WorkflowServiceFactory; @@ -171,6 +174,9 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It @Autowired(required = true) protected SubscribeService subscribeService; + @Autowired + private VersionHistoryService versionHistoryService; + protected ItemServiceImpl() { super(); } @@ -1920,4 +1926,40 @@ private void deleteOrcidQueueRecords(Context context, Item item) throws SQLExcep } } + @Override + public boolean isLatestVersion(Context context, Item item) throws SQLException { + + VersionHistory history = versionHistoryService.findByItem(context, item); + if (history == null) { + // not all items have a version history + // if an item does not have a version history, it is by definition the latest + // version + return true; + } + + // start with the very latest version of the given item (may still be in + // workspace) + Version latestVersion = versionHistoryService.getLatestVersion(context, history); + + // find the latest version of the given item that is archived + while (latestVersion != null && !latestVersion.getItem().isArchived()) { + latestVersion = versionHistoryService.getPrevious(context, history, latestVersion); + } + + // could not find an archived version of the given item + if (latestVersion == null) { + // this scenario should never happen, but let's err on the side of showing too + // many items vs. to little + // (see discovery.xml, a lot of discovery configs filter out all items that are + // not the latest version) + return true; + } + + // sanity check + assert latestVersion.getItem().isArchived(); + + return item.equals(latestVersion.getItem()); + + } + } diff --git a/dspace-api/src/main/java/org/dspace/content/service/ItemService.java b/dspace-api/src/main/java/org/dspace/content/service/ItemService.java index 12867ad18c3..a307e7ceffa 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/ItemService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/ItemService.java @@ -983,4 +983,14 @@ public List getMetadata(Item item, String schema, String element, */ public EntityType getEntityType(Context context, Item item) throws SQLException; + + /** + * Check whether the given item is the latest version. If the latest item cannot + * be determined, because either the version history or the latest version is + * not present, assume the item is latest. + * @param context the DSpace context. + * @param item the item that should be checked. + * @return true if the item is the latest version, false otherwise. + */ + public boolean isLatestVersion(Context context, Item item) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/orcid/consumer/OrcidQueueConsumer.java b/dspace-api/src/main/java/org/dspace/orcid/consumer/OrcidQueueConsumer.java index d177e61607f..ae989e1dd8f 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/consumer/OrcidQueueConsumer.java +++ b/dspace-api/src/main/java/org/dspace/orcid/consumer/OrcidQueueConsumer.java @@ -14,9 +14,10 @@ import static org.apache.commons.collections.CollectionUtils.isNotEmpty; import java.sql.SQLException; -import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -56,7 +57,7 @@ * be synchronized (based on the preferences set by the user) *

  • are publications/fundings related to profile items linked to orcid (based * on the preferences set by the user)
  • - * + * * * * @author Luca Giamminonni (luca.giamminonni at 4science.it) @@ -82,7 +83,7 @@ public class OrcidQueueConsumer implements Consumer { private RelationshipService relationshipService; - private List alreadyConsumedItems = new ArrayList<>(); + private final Set itemsToConsume = new HashSet<>(); @Override public void initialize() throws Exception { @@ -117,17 +118,26 @@ public void consume(Context context, Event event) throws Exception { return; } - if (alreadyConsumedItems.contains(item.getID())) { - return; - } + itemsToConsume.add(item.getID()); + } + + @Override + public void end(Context context) throws Exception { + + for (UUID itemId : itemsToConsume) { + + Item item = itemService.find(context, itemId); + + context.turnOffAuthorisationSystem(); + try { + consumeItem(context, item); + } finally { + context.restoreAuthSystemState(); + } - context.turnOffAuthorisationSystem(); - try { - consumeItem(context, item); - } finally { - context.restoreAuthSystemState(); } + itemsToConsume.clear(); } /** @@ -146,7 +156,7 @@ private void consumeItem(Context context, Item item) throws SQLException { consumeProfile(context, item); } - alreadyConsumedItems.add(item.getID()); + itemsToConsume.add(item.getID()); } @@ -169,6 +179,10 @@ private void consumeEntity(Context context, Item entity) throws SQLException { continue; } + if (isNotLatestVersion(context, entity)) { + continue; + } + orcidQueueService.create(context, relatedItem, entity); } @@ -329,6 +343,14 @@ private boolean isNotProfileItem(Item profileItemItem) { return !getProfileType().equals(itemService.getEntityTypeLabel(profileItemItem)); } + private boolean isNotLatestVersion(Context context, Item entity) { + try { + return !itemService.isLatestVersion(context, entity); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + private String getMetadataValue(Item item, String metadataField) { return itemService.getMetadataFirstValue(item, new MetadataFieldName(metadataField), Item.ANY); } @@ -345,11 +367,6 @@ private boolean isOrcidSynchronizationDisabled() { return !configurationService.getBooleanProperty("orcid.synchronization-enabled", true); } - @Override - public void end(Context context) throws Exception { - alreadyConsumedItems.clear(); - } - @Override public void finish(Context context) throws Exception { // nothing to do diff --git a/dspace-api/src/main/java/org/dspace/orcid/dao/OrcidQueueDAO.java b/dspace-api/src/main/java/org/dspace/orcid/dao/OrcidQueueDAO.java index 235443b1503..b7e0b1ed2a8 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/dao/OrcidQueueDAO.java +++ b/dspace-api/src/main/java/org/dspace/orcid/dao/OrcidQueueDAO.java @@ -74,6 +74,16 @@ public List findByProfileItemAndEntity(Context context, Item profile */ public List findByProfileItemOrEntity(Context context, Item item) throws SQLException; + /** + * Get the OrcidQueue records where the given item is the entity. + * + * @param context DSpace context object + * @param item the item to search for + * @return the found OrcidQueue entities + * @throws SQLException if database error + */ + public List findByEntity(Context context, Item item) throws SQLException; + /** * Find all the OrcidQueue records with the given entity and record type. * diff --git a/dspace-api/src/main/java/org/dspace/orcid/dao/impl/OrcidQueueDAOImpl.java b/dspace-api/src/main/java/org/dspace/orcid/dao/impl/OrcidQueueDAOImpl.java index 2114b253575..8e941b05653 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/dao/impl/OrcidQueueDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/orcid/dao/impl/OrcidQueueDAOImpl.java @@ -63,6 +63,13 @@ public List findByProfileItemOrEntity(Context context, Item item) th return query.getResultList(); } + @Override + public List findByEntity(Context context, Item item) throws SQLException { + Query query = createQuery(context, "FROM OrcidQueue WHERE entity.id = :itemId"); + query.setParameter("itemId", item.getID()); + return query.getResultList(); + } + @Override public List findByEntityAndRecordType(Context context, Item entity, String type) throws SQLException { Query query = createQuery(context, "FROM OrcidQueue WHERE entity = :entity AND recordType = :type"); diff --git a/dspace-api/src/main/java/org/dspace/orcid/service/OrcidQueueService.java b/dspace-api/src/main/java/org/dspace/orcid/service/OrcidQueueService.java index 8de25e9caf1..b667088eabb 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/service/OrcidQueueService.java +++ b/dspace-api/src/main/java/org/dspace/orcid/service/OrcidQueueService.java @@ -164,6 +164,16 @@ public List findByProfileItemAndEntity(Context context, Item profile */ public List findByProfileItemOrEntity(Context context, Item item) throws SQLException; + /** + * Get the OrcidQueue records where the given item is the entity. + * + * @param context DSpace context object + * @param item the item to search for + * @return the found OrcidQueue records + * @throws SQLException if database error + */ + public List findByEntity(Context context, Item item) throws SQLException; + /** * Get all the OrcidQueue records with attempts less than the given attempts. * diff --git a/dspace-api/src/main/java/org/dspace/orcid/service/impl/OrcidQueueServiceImpl.java b/dspace-api/src/main/java/org/dspace/orcid/service/impl/OrcidQueueServiceImpl.java index d3300fea660..261f8ef9a9f 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/service/impl/OrcidQueueServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/orcid/service/impl/OrcidQueueServiceImpl.java @@ -70,6 +70,11 @@ public List findByProfileItemOrEntity(Context context, Item item) th return orcidQueueDAO.findByProfileItemOrEntity(context, item); } + @Override + public List findByEntity(Context context, Item item) throws SQLException { + return orcidQueueDAO.findByEntity(context, item); + } + @Override public long countByProfileItemId(Context context, UUID profileItemId) throws SQLException { return orcidQueueDAO.countByProfileItemId(context, profileItemId); diff --git a/dspace-api/src/main/java/org/dspace/versioning/VersioningConsumer.java b/dspace-api/src/main/java/org/dspace/versioning/VersioningConsumer.java index 63b5391d0a2..27a81a15791 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/VersioningConsumer.java +++ b/dspace-api/src/main/java/org/dspace/versioning/VersioningConsumer.java @@ -33,6 +33,11 @@ import org.dspace.discovery.IndexEventConsumer; import org.dspace.event.Consumer; import org.dspace.event.Event; +import org.dspace.orcid.OrcidHistory; +import org.dspace.orcid.OrcidQueue; +import org.dspace.orcid.factory.OrcidServiceFactory; +import org.dspace.orcid.service.OrcidHistoryService; +import org.dspace.orcid.service.OrcidQueueService; import org.dspace.versioning.factory.VersionServiceFactory; import org.dspace.versioning.service.VersionHistoryService; import org.dspace.versioning.utils.RelationshipVersioningUtils; @@ -58,6 +63,8 @@ public class VersioningConsumer implements Consumer { private RelationshipTypeService relationshipTypeService; private RelationshipService relationshipService; private RelationshipVersioningUtils relationshipVersioningUtils; + private OrcidQueueService orcidQueueService; + private OrcidHistoryService orcidHistoryService; @Override public void initialize() throws Exception { @@ -67,6 +74,8 @@ public void initialize() throws Exception { relationshipTypeService = ContentServiceFactory.getInstance().getRelationshipTypeService(); relationshipService = ContentServiceFactory.getInstance().getRelationshipService(); relationshipVersioningUtils = VersionServiceFactory.getInstance().getRelationshipVersioningUtils(); + this.orcidQueueService = OrcidServiceFactory.getInstance().getOrcidQueueService(); + this.orcidHistoryService = OrcidServiceFactory.getInstance().getOrcidHistoryService(); } @Override @@ -132,7 +141,8 @@ public void consume(Context ctx, Event event) throws Exception { // unarchive previous item unarchiveItem(ctx, previousItem); - + // handles versions for ORCID publications waiting to be shipped, or already published (history-queue). + handleOrcidSynchronization(ctx, previousItem, latestItem); // update relationships updateRelationships(ctx, latestItem, previousItem); } @@ -148,6 +158,29 @@ protected void unarchiveItem(Context ctx, Item item) { )); } + private void handleOrcidSynchronization(Context ctx, Item previousItem, Item latestItem) { + try { + replaceOrcidHistoryEntities(ctx, previousItem, latestItem); + removeOrcidQueueEntries(ctx, previousItem); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + private void removeOrcidQueueEntries(Context ctx, Item previousItem) throws SQLException { + List queueEntries = orcidQueueService.findByEntity(ctx, previousItem); + for (OrcidQueue queueEntry : queueEntries) { + orcidQueueService.delete(ctx, queueEntry); + } + } + + private void replaceOrcidHistoryEntities(Context ctx, Item previousItem, Item latestItem) throws SQLException { + List entries = orcidHistoryService.findByEntity(ctx, previousItem); + for (OrcidHistory entry : entries) { + entry.setEntity(latestItem); + } + } + /** * Update {@link Relationship#latestVersionStatus} of the relationships of both the old version and the new version * of the item. diff --git a/dspace-api/src/test/java/org/dspace/orcid/OrcidQueueConsumerIT.java b/dspace-api/src/test/java/org/dspace/orcid/OrcidQueueConsumerIT.java index f2e528d78cd..e17fd0072ef 100644 --- a/dspace-api/src/test/java/org/dspace/orcid/OrcidQueueConsumerIT.java +++ b/dspace-api/src/test/java/org/dspace/orcid/OrcidQueueConsumerIT.java @@ -23,6 +23,7 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; import java.sql.SQLException; import java.time.Instant; @@ -41,13 +42,19 @@ import org.dspace.content.Item; import org.dspace.content.MetadataValue; import org.dspace.content.RelationshipType; +import org.dspace.content.WorkspaceItem; import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.InstallItemService; import org.dspace.content.service.ItemService; +import org.dspace.content.service.WorkspaceItemService; import org.dspace.orcid.consumer.OrcidQueueConsumer; import org.dspace.orcid.factory.OrcidServiceFactory; import org.dspace.orcid.service.OrcidQueueService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.utils.DSpace; +import org.dspace.versioning.Version; +import org.dspace.versioning.service.VersioningService; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -64,8 +71,15 @@ public class OrcidQueueConsumerIT extends AbstractIntegrationTestWithDatabase { private ItemService itemService = ContentServiceFactory.getInstance().getItemService(); + private WorkspaceItemService workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService(); + + private InstallItemService installItemService = ContentServiceFactory.getInstance().getInstallItemService(); + private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + private VersioningService versioningService = new DSpace().getServiceManager() + .getServicesByType(VersioningService.class).get(0); + private Collection profileCollection; @Before @@ -763,6 +777,177 @@ public void testWithManyInsertionAndDeletionOfSameMetadataValue() throws Excepti } + @Test + public void testOrcidQueueRecordCreationForPublicationWithNotFoundAuthority() throws Exception { + + context.turnOffAuthorisationSystem(); + + Item profile = ItemBuilder.createItem(context, profileCollection) + .withTitle("Test User") + .withOrcidIdentifier("0000-1111-2222-3333") + .withOrcidAccessToken("ab4d18a0-8d9a-40f1-b601-a417255c8d20", eperson) + .withOrcidSynchronizationPublicationsPreference(ALL) + .build(); + + Collection publicationCollection = createCollection("Publications", "Publication"); + + Item publication = ItemBuilder.createItem(context, publicationCollection) + .withTitle("Test publication") + .withAuthor("First User") + .withAuthor("Test User") + .build(); + + EntityType publicationType = EntityTypeBuilder.createEntityTypeBuilder(context, "Publication").build(); + EntityType personType = EntityTypeBuilder.createEntityTypeBuilder(context, "Person").build(); + + RelationshipType isAuthorOfPublication = createRelationshipTypeBuilder(context, personType, publicationType, + "isAuthorOfPublication", + "isPublicationOfAuthor", 0, null, 0, + null).build(); + + RelationshipBuilder.createRelationshipBuilder(context, profile, publication, isAuthorOfPublication).build(); + + context.restoreAuthSystemState(); + context.commit(); + + List orcidQueueRecords = orcidQueueService.findAll(context); + assertThat(orcidQueueRecords, hasSize(1)); + assertThat(orcidQueueRecords.get(0), matches(profile, publication, "Publication", INSERT)); + } + + @Test + public void testOrcidQueueWithItemVersioning() throws Exception { + + context.turnOffAuthorisationSystem(); + + Item profile = ItemBuilder.createItem(context, profileCollection) + .withTitle("Test User") + .withOrcidIdentifier("0000-1111-2222-3333") + .withOrcidAccessToken("ab4d18a0-8d9a-40f1-b601-a417255c8d20", eperson) + .withOrcidSynchronizationPublicationsPreference(ALL) + .build(); + + Collection publicationCollection = createCollection("Publications", "Publication"); + + Item publication = ItemBuilder.createItem(context, publicationCollection) + .withTitle("Test publication") + .withAuthor("Test User") + .build(); + + EntityType publicationType = EntityTypeBuilder.createEntityTypeBuilder(context, "Publication").build(); + EntityType personType = EntityTypeBuilder.createEntityTypeBuilder(context, "Person").build(); + + RelationshipType isAuthorOfPublication = createRelationshipTypeBuilder(context, personType, publicationType, + "isAuthorOfPublication", + "isPublicationOfAuthor", 0, null, 0, + null).build(); + + RelationshipBuilder.createRelationshipBuilder(context, profile, publication, isAuthorOfPublication).build(); + + context.restoreAuthSystemState(); + context.commit(); + + List orcidQueueRecords = orcidQueueService.findAll(context); + assertThat(orcidQueueRecords, hasSize(1)); + assertThat(orcidQueueRecords.get(0), matches(profile, publication, "Publication", INSERT)); + + context.turnOffAuthorisationSystem(); + Version newVersion = versioningService.createNewVersion(context, publication); + context.restoreAuthSystemState(); + Item newPublication = newVersion.getItem(); + assertThat(newPublication.isArchived(), is(false)); + + context.commit(); + + orcidQueueRecords = orcidQueueService.findAll(context); + assertThat(orcidQueueRecords, hasSize(1)); + assertThat(orcidQueueRecords.get(0), matches(profile, publication, "Publication", INSERT)); + + WorkspaceItem workspaceItem = workspaceItemService.findByItem(context, newVersion.getItem()); + context.turnOffAuthorisationSystem(); + + installItemService.installItem(context, workspaceItem); + + context.restoreAuthSystemState(); + context.commit(); + + orcidQueueRecords = orcidQueueService.findAll(context); + assertThat(orcidQueueRecords, hasSize(1)); + assertThat(orcidQueueRecords.get(0), matches(profile, newPublication, "Publication", INSERT)); + } + + @Test + public void testOrcidQueueUpdateWithItemVersioning() throws Exception { + + context.turnOffAuthorisationSystem(); + + Item profile = ItemBuilder.createItem(context, profileCollection) + .withTitle("Test User") + .withOrcidIdentifier("0000-1111-2222-3333") + .withOrcidAccessToken("ab4d18a0-8d9a-40f1-b601-a417255c8d20", eperson) + .withOrcidSynchronizationPublicationsPreference(ALL) + .build(); + + Collection publicationCollection = createCollection("Publications", "Publication"); + + Item publication = ItemBuilder.createItem(context, publicationCollection) + .withTitle("Test publication") + .build(); + + OrcidHistory orcidHistory = OrcidHistoryBuilder.createOrcidHistory(context, profile, publication) + .withDescription("Test publication") + .withOperation(OrcidOperation.INSERT) + .withPutCode("12345") + .withStatus(201) + .build(); + + addMetadata(publication, "dc", "contributor", "author", "Test User", null); + + EntityType publicationType = EntityTypeBuilder.createEntityTypeBuilder(context, "Publication").build(); + EntityType personType = EntityTypeBuilder.createEntityTypeBuilder(context, "Person").build(); + + RelationshipType isAuthorOfPublication = + createRelationshipTypeBuilder( + context, personType, publicationType, + "isAuthorOfPublication", + "isPublicationOfAuthor", 0, null, 0, + null + ).build(); + + RelationshipBuilder.createRelationshipBuilder(context, profile, publication, isAuthorOfPublication).build(); + + context.commit(); + + List orcidQueueRecords = orcidQueueService.findAll(context); + assertThat(orcidQueueRecords, hasSize(1)); + assertThat(orcidQueueRecords.get(0), matches(profile, publication, "Publication", "12345", UPDATE)); + + Version newVersion = versioningService.createNewVersion(context, publication); + Item newPublication = newVersion.getItem(); + assertThat(newPublication.isArchived(), is(false)); + + context.commit(); + + orcidQueueRecords = orcidQueueService.findAll(context); + assertThat(orcidQueueRecords, hasSize(1)); + assertThat(orcidQueueRecords.get(0), matches(profile, publication, "Publication", "12345", UPDATE)); + + WorkspaceItem workspaceItem = workspaceItemService.findByItem(context, newVersion.getItem()); + installItemService.installItem(context, workspaceItem); + + context.commit(); + + context.restoreAuthSystemState(); + + orcidQueueRecords = orcidQueueService.findAll(context); + assertThat(orcidQueueRecords, hasSize(1)); + assertThat(orcidQueueRecords.get(0), matches(profile, newPublication, "Publication", "12345", UPDATE)); + + orcidHistory = context.reloadEntity(orcidHistory); + assertThat(orcidHistory.getEntity(), is(newPublication)); + + } + private void addMetadata(Item item, String schema, String element, String qualifier, String value, String authority) throws Exception { context.turnOffAuthorisationSystem(); From 24318e01b84ec7ef3c2b7081bc5a85ab10295209 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Fri, 2 Aug 2024 14:12:53 +0200 Subject: [PATCH 249/939] fix invalid usage of == operator (cherry picked from commit fa0fb14a185ba8a9c593a3653df302f0446a397c) --- .../src/main/java/org/dspace/content/ItemServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java index ac6c0d43d71..b2cc3e939d8 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -1788,7 +1788,7 @@ protected void moveSingleMetadataValue(Context context, Item dso, int place, Met //Retrieve the applicable relationship Relationship rs = relationshipService.find(context, ((RelationshipMetadataValue) rr).getRelationshipId()); - if (rs.getLeftItem() == dso) { + if (rs.getLeftItem().equals(dso)) { rs.setLeftPlace(place); } else { rs.setRightPlace(place); From b8f638b3b6eff9c0138dcfe883a9083599a2ffa7 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Fri, 2 Aug 2024 17:44:35 +0200 Subject: [PATCH 250/939] use equals instead of == (cherry picked from commit 80de8f6fb567ceb4497596fa714f2b357f1b8b26) --- .../src/main/java/org/dspace/content/EntityServiceImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/EntityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/EntityServiceImpl.java index 2f34129f2e6..6049b7e6aae 100644 --- a/dspace-api/src/main/java/org/dspace/content/EntityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/EntityServiceImpl.java @@ -64,7 +64,7 @@ public List getLeftRelations(Context context, Entity entity) { List fullList = entity.getRelationships(); List listToReturn = new LinkedList<>(); for (Relationship relationship : fullList) { - if (relationship.getLeftItem().getID() == entity.getItem().getID()) { + if (relationship.getLeftItem().getID().equals(entity.getItem().getID())) { listToReturn.add(relationship); } } @@ -76,7 +76,7 @@ public List getRightRelations(Context context, Entity entity) { List fullList = entity.getRelationships(); List listToReturn = new LinkedList<>(); for (Relationship relationship : fullList) { - if (relationship.getRightItem().getID() == entity.getItem().getID()) { + if (relationship.getRightItem().getID().equals(entity.getItem().getID())) { listToReturn.add(relationship); } } From dc09d06ed6eedc89249fad5230f5346a830bbe41 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Fri, 2 Aug 2024 17:47:07 +0200 Subject: [PATCH 251/939] use equals instead of == (cherry picked from commit 5e3552ee3885049df34c3fcaf49bfe3028c5dbd0) --- .../java/org/dspace/authorize/ResourcePolicyServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java b/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java index 7b93b912378..86998a2196e 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java @@ -417,7 +417,7 @@ public boolean isMyResourcePolicy(Context context, EPerson eperson, Integer id) ResourcePolicy resourcePolicy = resourcePolicyDAO.findOneById(context, id); Group group = resourcePolicy.getGroup(); - if (resourcePolicy.getEPerson() != null && resourcePolicy.getEPerson().getID() == eperson.getID()) { + if (resourcePolicy.getEPerson() != null && resourcePolicy.getEPerson().getID().equals(eperson.getID())) { isMy = true; } else if (group != null && groupService.isMember(context, eperson, group)) { isMy = true; From 6a5236f2f93a05999bfa391887ecb22f8bfae257 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Fri, 2 Aug 2024 17:48:28 +0200 Subject: [PATCH 252/939] use equals instead of == (cherry picked from commit d2ef7b01ef1a5d769d764b708be393dbb481fb65) --- .../src/main/java/org/dspace/eperson/GroupServiceImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java index 730053e42ce..3fb20e2f1e6 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java @@ -147,7 +147,7 @@ public void addMember(Context context, Group group, EPerson e) { public void addMember(Context context, Group groupParent, Group groupChild) throws SQLException { // don't add if it's already a member // and don't add itself - if (groupParent.contains(groupChild) || groupParent.getID() == groupChild.getID()) { + if (groupParent.contains(groupChild) || groupParent.getID().equals(groupChild.getID())) { return; } @@ -178,7 +178,7 @@ public void removeMember(Context context, Group group, EPerson ePerson) throws S Role role = stepByName.getRole(); for (CollectionRole collectionRole : collectionRoles) { if (StringUtils.equals(collectionRole.getRoleId(), role.getId()) - && claimedTask.getWorkflowItem().getCollection() == collectionRole.getCollection()) { + && claimedTask.getWorkflowItem().getCollection().equals(collectionRole.getCollection())) { // Count number of EPersons who are *direct* members of this group int totalDirectEPersons = ePersonService.countByGroups(context, Set.of(group)); // Count number of Groups which have this groupParent as a direct parent From 36ff5ca6cbc6a8641555f9267e06884f9112badc Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Fri, 2 Aug 2024 17:49:14 +0200 Subject: [PATCH 253/939] use equals instead of == (cherry picked from commit a13cc82d405c5aefe00c7bb86d89c7dc8073a39b) --- .../submit/factory/impl/ItemMetadataValueAddPatchOperation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/ItemMetadataValueAddPatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/ItemMetadataValueAddPatchOperation.java index e749c4e7932..a7cd5bfc1de 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/ItemMetadataValueAddPatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/ItemMetadataValueAddPatchOperation.java @@ -213,7 +213,7 @@ private Integer getRelId(String authority) { private void updateRelationshipPlace(Context context, Item dso, int place, Relationship rs) { try { - if (rs.getLeftItem() == dso) { + if (rs.getLeftItem().equals(dso)) { rs.setLeftPlace(place); } else { rs.setRightPlace(place); From f1dc25195f025919aa18cc390ad81459c3698169 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 18 Jun 2024 13:52:38 +0200 Subject: [PATCH 254/939] 115693: data-cite xsl targetting dc.identifier.uri fixes doi registration error (cherry picked from commit c5d08f037cb56111b8ef047279f323c06b11a864) --- .../main/java/org/dspace/identifier/DOIIdentifierProvider.java | 1 + dspace/config/crosswalks/DIM2DataCite.xsl | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java index b70eda960d3..8d71ac92f9d 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java @@ -70,6 +70,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { static final char SLASH = '/'; // Metadata field name elements + // Warning: If this metadata field is changed for whatever reason, DIM2DataCite.xsl's template needs to reflect this // TODO: move these to MetadataSchema or some such? public static final String MD_SCHEMA = "dc"; public static final String DOI_ELEMENT = "identifier"; diff --git a/dspace/config/crosswalks/DIM2DataCite.xsl b/dspace/config/crosswalks/DIM2DataCite.xsl index a97d127694e..d57996e6d8c 100644 --- a/dspace/config/crosswalks/DIM2DataCite.xsl +++ b/dspace/config/crosswalks/DIM2DataCite.xsl @@ -333,7 +333,8 @@ company as well. We have to ensure to use URIs of our prefix as primary identifiers only. --> - + + From d240a16b051326b0357cd8476928f899207ddc4c Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 27 Jun 2024 10:59:36 +0200 Subject: [PATCH 255/939] 115693: DataCiteConnector fallback for blank metadata doi (cherry picked from commit 021e42434731e505245b3aa149eaf59a5fbccb94) --- .../main/java/org/dspace/identifier/doi/DataCiteConnector.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java b/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java index 57136d6143b..fc4e2652ac2 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java +++ b/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java @@ -15,6 +15,7 @@ import java.util.Iterator; import java.util.Map; +import org.apache.commons.lang.StringUtils; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; @@ -412,7 +413,7 @@ public void reserveDOI(Context context, DSpaceObject dso, String doi) } String metadataDOI = extractDOI(root); - if (null == metadataDOI) { + if (StringUtils.isBlank(metadataDOI)) { // The DOI will be saved as metadata of dso after successful // registration. To register a doi it has to be part of the metadata // sent to DataCite. So we add it to the XML we'll send to DataCite From c1be5f8e4ed9577e22ce7b33bca3c171b84083cb Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 19 Jul 2024 13:42:09 +0200 Subject: [PATCH 256/939] 115693: Pass doi metadatafield with xsl parameters (cherry picked from commit 9e11e1f9ae69f19e506618d1d0b1fec0059ba165) --- .../identifier/DOIIdentifierProvider.java | 1 - .../identifier/doi/DataCiteConnector.java | 8 ++++++ dspace/config/crosswalks/DIM2DataCite.xsl | 25 +++++++++++-------- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java index 8d71ac92f9d..b70eda960d3 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java @@ -70,7 +70,6 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { static final char SLASH = '/'; // Metadata field name elements - // Warning: If this metadata field is changed for whatever reason, DIM2DataCite.xsl's template needs to reflect this // TODO: move these to MetadataSchema or some such? public static final String MD_SCHEMA = "dc"; public static final String DOI_ELEMENT = "identifier"; diff --git a/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java b/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java index fc4e2652ac2..a15e3f7fdbf 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java +++ b/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java @@ -7,6 +7,10 @@ */ package org.dspace.identifier.doi; +import static org.dspace.identifier.DOIIdentifierProvider.DOI_ELEMENT; +import static org.dspace.identifier.DOIIdentifierProvider.DOI_QUALIFIER; +import static org.dspace.identifier.DOIIdentifierProvider.MD_SCHEMA; + import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.URISyntaxException; @@ -386,6 +390,10 @@ public void reserveDOI(Context context, DSpaceObject dso, String doi) parameters.put("hostinginstitution", configurationService.getProperty(CFG_HOSTINGINSTITUTION)); } + parameters.put("mdSchema", MD_SCHEMA); + parameters.put("mdElement", DOI_ELEMENT); + // Pass an empty string for qualifier if the metadata field doesn't have any + parameters.put("mdQualifier", DOI_QUALIFIER); Element root = null; try { diff --git a/dspace/config/crosswalks/DIM2DataCite.xsl b/dspace/config/crosswalks/DIM2DataCite.xsl index d57996e6d8c..d4d8cbe6417 100644 --- a/dspace/config/crosswalks/DIM2DataCite.xsl +++ b/dspace/config/crosswalks/DIM2DataCite.xsl @@ -36,6 +36,10 @@ + + dc + identifier + uri @@ -333,16 +337,17 @@ company as well. We have to ensure to use URIs of our prefix as primary identifiers only. --> - - - - - - - - - - + + + + + + + + + + + From f61c45bdc6533f7328e84af9c59e908bc1b3e6e2 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Mon, 8 Jul 2024 19:26:11 +0200 Subject: [PATCH 257/939] change order of name parts: familyName, givenName (cherry picked from commit 076f1f233ea0eef1a37ed4087f34008d1e92e40a) --- .../external/crossref/CrossRefAuthorMetadataProcessor.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAuthorMetadataProcessor.java b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAuthorMetadataProcessor.java index abf84f52d05..b9b384f8ed7 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAuthorMetadataProcessor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAuthorMetadataProcessor.java @@ -42,8 +42,8 @@ public Collection processMetadata(String json) { JsonNode author = authors.next(); String givenName = author.at("/given").textValue(); String familyName = author.at("/family").textValue(); - if (StringUtils.isNoneBlank(givenName) && StringUtils.isNoneBlank(familyName)) { - values.add(givenName + " " + familyName); + if (StringUtils.isNotBlank(givenName) && StringUtils.isNotBlank(familyName)) { + values.add(familyName.trim() + ", " + givenName.trim()); } } return values; @@ -64,4 +64,4 @@ public void setPathToArray(String pathToArray) { this.pathToArray = pathToArray; } -} \ No newline at end of file +} From 7e87d57672640e72c432f432fd07ad8a6f6aedc8 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Tue, 9 Jul 2024 10:53:23 +0200 Subject: [PATCH 258/939] fix broken unit tests (cherry picked from commit 1712b9f07875c67141b67da96fa93f0deaff4090) --- .../app/rest/CrossRefImportMetadataSourceServiceIT.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CrossRefImportMetadataSourceServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CrossRefImportMetadataSourceServiceIT.java index 31c22692f00..f61a81140dd 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CrossRefImportMetadataSourceServiceIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CrossRefImportMetadataSourceServiceIT.java @@ -162,7 +162,7 @@ private ArrayList getRecords() { MetadatumDTO title = createMetadatumDTO("dc", "title", null, "State of Awareness of Freshers’ Groups Chortkiv State" + " Medical College of Prevention of Iodine Deficiency Diseases"); - MetadatumDTO author = createMetadatumDTO("dc", "contributor", "author", "L.V. Senyuk"); + MetadatumDTO author = createMetadatumDTO("dc", "contributor", "author", "Senyuk, L.V."); MetadatumDTO type = createMetadatumDTO("dc", "type", null, "journal-article"); MetadatumDTO date = createMetadatumDTO("dc", "date", "issued", "2016-05-19"); MetadatumDTO ispartof = createMetadatumDTO("dc", "relation", "ispartof", @@ -191,7 +191,7 @@ private ArrayList getRecords() { List metadatums2 = new ArrayList(); MetadatumDTO title2 = createMetadatumDTO("dc", "title", null, "Ischemic Heart Disease and Role of Nurse of Cardiology Department"); - MetadatumDTO author2 = createMetadatumDTO("dc", "contributor", "author", "K. І. Kozak"); + MetadatumDTO author2 = createMetadatumDTO("dc", "contributor", "author", "Kozak, K. І."); MetadatumDTO type2 = createMetadatumDTO("dc", "type", null, "journal-article"); MetadatumDTO date2 = createMetadatumDTO("dc", "date", "issued", "2016-05-19"); MetadatumDTO ispartof2 = createMetadatumDTO("dc", "relation", "ispartof", From ffc99d06a1fc301109df664c3b126137410e1e95 Mon Sep 17 00:00:00 2001 From: autavares-dev Date: Mon, 12 Aug 2024 15:08:26 -0300 Subject: [PATCH 259/939] Fix index-discovery process when using handle (cherry picked from commit 077aed38dc55172ed70d8bfb9c44fd369d966a6d) --- dspace-api/src/main/java/org/dspace/discovery/IndexClient.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java b/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java index 661c48d91cf..867359a949c 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java +++ b/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java @@ -21,6 +21,7 @@ import org.dspace.content.Item; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.ItemService; +import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.discovery.indexobject.IndexableCollection; import org.dspace.discovery.indexobject.IndexableCommunity; @@ -92,7 +93,7 @@ public void internalRun() throws Exception { .getHandleService().resolveToObject(context, param); if (dso != null) { final IndexFactory indexableObjectService = IndexObjectFactoryFactory.getInstance(). - getIndexFactoryByType(String.valueOf(dso.getType())); + getIndexFactoryByType(Constants.typeText[dso.getType()]); indexableObject = indexableObjectService.findIndexableObject(context, dso.getID().toString()); } } From 0d74bd9f185f2036ca68853576545f09e32d23fb Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 23 Apr 2024 13:33:01 -0400 Subject: [PATCH 260/939] Separate task-list building from execution. The old code would curate the object once for each task, meaning that all but one task would be executed N times up to the length of the list. --- .../curate/XmlWorkflowCuratorServiceImpl.java | 28 +++++++++---------- .../java/org/dspace/curate/package-info.java | 12 ++++++++ 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/curate/XmlWorkflowCuratorServiceImpl.java b/dspace-api/src/main/java/org/dspace/curate/XmlWorkflowCuratorServiceImpl.java index 27a162d543c..ec32ff92f9a 100644 --- a/dspace-api/src/main/java/org/dspace/curate/XmlWorkflowCuratorServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/curate/XmlWorkflowCuratorServiceImpl.java @@ -140,14 +140,14 @@ public boolean curate(Curator curator, Context c, XmlWorkflowItem wfi) item.setOwningCollection(wfi.getCollection()); for (Task task : step.tasks) { curator.addTask(task.name); + } - // Check whether the task is configured to be queued rather than automatically run - if (StringUtils.isNotEmpty(step.queue)) { - // queue attribute has been set in the FlowStep configuration: add task to configured queue - curator.queue(c, item.getID().toString(), step.queue); - } else { - // Task is configured to be run automatically - curator.curate(c, item); + if (StringUtils.isNotEmpty(step.queue)) { // Step's tasks are to be queued. + curator.queue(c, item.getID().toString(), step.queue); + } else { // Step's tasks are to be run now. + curator.curate(c, item); + + for (Task task : step.tasks) { int status = curator.getStatus(task.name); String result = curator.getResult(task.name); String action = "none"; @@ -184,14 +184,14 @@ public boolean curate(Curator curator, Context c, XmlWorkflowItem wfi) } } curator.clear(); - } - // Record any reporting done by the tasks. - if (reporter.length() > 0) { - LOG.info("Curation tasks over item {} for step {} report:%n{}", - () -> wfi.getItem().getID(), - () -> step.step, - () -> reporter.toString()); + // Record any reporting done by the tasks. + if (reporter.length() > 0) { + LOG.info("Curation tasks over item {} for step {} report:\n{}", + () -> wfi.getItem().getID(), + () -> step.step, + () -> reporter.toString()); + } } } return true; diff --git a/dspace-api/src/main/java/org/dspace/curate/package-info.java b/dspace-api/src/main/java/org/dspace/curate/package-info.java index 492642f60c5..1168bbd283d 100644 --- a/dspace-api/src/main/java/org/dspace/curate/package-info.java +++ b/dspace-api/src/main/java/org/dspace/curate/package-info.java @@ -20,6 +20,8 @@ * * *

    Curation requests may be run immediately or queued for batch processing. + * See {@link TaskQueue} and its relatives, {@link Curation} and its relatives + * for more on queued curation. * *

    Tasks may also be attached to a workflow step, so that a set of tasks is * applied to each uninstalled Item which passes through that step. See @@ -27,5 +29,15 @@ * *

    A task may return to the Curator a status code, a final status message, * and an optional report character stream. + * + *

    The {@link Reporter} classes absorb strings of text and preserve it in + * various ways. A Reporter is a simple {@link Appendable} and makes no + * assumptions about e.g. whether a string represents a complete line. If you + * want your report formatted, insert appropriate newlines and other whitespace + * as needed. Your tasks can emit marked-up text if you wish, but the stock + * Reporter implementations make no attempt to render it. + * + *

    Tasks may be annotated to inform the Curator of special properties. See + * {@link Distributive}, {@link Mutative}, {@link Suspendable} etc. */ package org.dspace.curate; From 41312eec6e4e7d86574428f03425c54117ea267c Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Mon, 26 Aug 2024 15:02:31 -0400 Subject: [PATCH 261/939] Make statistics autocommit much more frequent. (cherry picked from commit 5c9af9764e2485b26398ec06446cb8f0edd9cab2) --- dspace/solr/statistics/conf/solrconfig.xml | 25 ++++++++++++++-------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/dspace/solr/statistics/conf/solrconfig.xml b/dspace/solr/statistics/conf/solrconfig.xml index 2b1cff45373..c3f023ff2ee 100644 --- a/dspace/solr/statistics/conf/solrconfig.xml +++ b/dspace/solr/statistics/conf/solrconfig.xml @@ -32,14 +32,16 @@ - + 32 1000 ${solr.lock.type:native} - + false @@ -48,7 +50,7 @@ 10000 - ${solr.autoCommit.maxTime:900000} + ${solr.autoCommit.maxTime:10000} true @@ -62,14 +64,16 @@ ${solr.max.booleanClauses:1024} + unordered sets of *all* documents that match a + query. Caches results of 'fq' search param. --> - + 1000 - + - + uuid @@ -126,7 +132,8 @@ - + uid From e15f60abd3120b81b4b982cc9cb5344e3e2b2951 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 3 Sep 2024 17:13:01 +0200 Subject: [PATCH 262/939] Translate underscores to dashes in xml:lang attr for DIM2DataCite.xsl Modified the DataCite crosswalk to ensure that the xml:lang attribute translates any underscores in the value of @lang to dashes. This change aligns the attribute formatting with standard language code conventions. (cherry picked from commit a898afd5acbd6dc51a88b837cab2a1c56cc33967) --- dspace/config/crosswalks/DIM2DataCite.xsl | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/dspace/config/crosswalks/DIM2DataCite.xsl b/dspace/config/crosswalks/DIM2DataCite.xsl index d4d8cbe6417..935b3dc4038 100644 --- a/dspace/config/crosswalks/DIM2DataCite.xsl +++ b/dspace/config/crosswalks/DIM2DataCite.xsl @@ -25,6 +25,10 @@ to register DOIs anymore. Please follow and reuse the examples included in this file. For more information on the DataCite Schema, see https://schema.datacite.org. --> + 10.5072/dspace- @@ -362,20 +366,20 @@ - + - + AlternativeTitle - - + Subtitle - + TranslatedTitle @@ -394,7 +398,7 @@ --> - + @@ -626,7 +630,7 @@ --> - + Abstract From 404039ade6796f9abfb5dbf420ab83f87057a033 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Fri, 6 Sep 2024 15:25:18 +0200 Subject: [PATCH 263/939] #9806: Add new create methods to group builder Now supports admin groups, default read, workflow role (cherry picked from commit cdb167e55aac9916618dcfee7222105f4456dbd8) --- .../java/org/dspace/builder/GroupBuilder.java | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/dspace-api/src/test/java/org/dspace/builder/GroupBuilder.java b/dspace-api/src/test/java/org/dspace/builder/GroupBuilder.java index b3447dd8bd9..c16fb696b0c 100644 --- a/dspace-api/src/test/java/org/dspace/builder/GroupBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/GroupBuilder.java @@ -12,6 +12,9 @@ import java.util.UUID; import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.DSpaceObject; import org.dspace.content.service.DSpaceObjectService; import org.dspace.core.Context; import org.dspace.eperson.EPerson; @@ -51,6 +54,33 @@ public static GroupBuilder createGroup(final Context context) { return builder.create(context); } + public static GroupBuilder createCollectionAdminGroup(final Context context, Collection collection) { + GroupBuilder builder = new GroupBuilder(context); + return builder.createAdminGroup(context, collection); + } + + public static GroupBuilder createCollectionSubmitterGroup(final Context context, Collection collection) { + GroupBuilder builder = new GroupBuilder(context); + return builder.createSubmitterGroup(context, collection); + } + + public static GroupBuilder createCollectionDefaultReadGroup(final Context context, Collection collection, + String typeOfGroupString, int defaultRead) { + GroupBuilder builder = new GroupBuilder(context); + return builder.createDefaultReadGroup(context, collection, typeOfGroupString, defaultRead); + } + + public static GroupBuilder createCollectionWorkflowRoleGroup(final Context context, Collection collection, + String roleName) { + GroupBuilder builder = new GroupBuilder(context); + return builder.createWorkflowRoleGroup(context, collection, roleName); + } + + public static GroupBuilder createCommunityAdminGroup(final Context context, Community community) { + GroupBuilder builder = new GroupBuilder(context); + return builder.createAdminGroup(context, community); + } + private GroupBuilder create(final Context context) { this.context = context; try { @@ -61,6 +91,54 @@ private GroupBuilder create(final Context context) { return this; } + private GroupBuilder createAdminGroup(final Context context, DSpaceObject container) { + this.context = context; + try { + if (container instanceof Collection) { + group = collectionService.createAdministrators(context, (Collection) container); + } else if (container instanceof Community) { + group = communityService.createAdministrators(context, (Community) container); + } else { + handleException(new IllegalArgumentException("DSpaceObject must be collection or community. " + + "Type: " + container.getType())); + } + } catch (Exception e) { + return handleException(e); + } + return this; + } + + private GroupBuilder createSubmitterGroup(final Context context, Collection collection) { + this.context = context; + try { + group = collectionService.createSubmitters(context, collection); + } catch (Exception e) { + return handleException(e); + } + return this; + } + + private GroupBuilder createDefaultReadGroup(final Context context, Collection collection, + String typeOfGroupString, int defaultRead) { + this.context = context; + try { + group = collectionService.createDefaultReadGroup(context, collection, typeOfGroupString, defaultRead); + } catch (Exception e) { + return handleException(e); + } + return this; + } + + private GroupBuilder createWorkflowRoleGroup(final Context context, Collection collection, String roleName) { + this.context = context; + try { + group = workflowService.createWorkflowRoleGroup(context, collection, roleName); + } catch (Exception e) { + return handleException(e); + } + return this; + } + @Override protected DSpaceObjectService getService() { return groupService; From 2ff2297b1fdd90b6f7c595c34b1167df3f91c036 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Fri, 6 Sep 2024 15:28:27 +0200 Subject: [PATCH 264/939] #9806: Use builders for coll, comm, group creation in BitstreamRestRepositoryIT (cherry picked from commit b13abac7539944358711a381548fbaedd9dcbd92) --- .../app/rest/BitstreamRestRepositoryIT.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java index bd79dc2f2b8..ecfe933fd92 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java @@ -49,6 +49,7 @@ import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.GroupBuilder; import org.dspace.builder.ItemBuilder; import org.dspace.builder.ResourcePolicyBuilder; import org.dspace.content.Bitstream; @@ -2767,10 +2768,12 @@ public void deleteBitstreamsInBulk_collectionAdmin() throws Exception { .withEmail("col2admin@test.com") .withPassword(password) .build(); - Group col1_AdminGroup = collectionService.createAdministrators(context, col1); - Group col2_AdminGroup = collectionService.createAdministrators(context, col2); - groupService.addMember(context, col1_AdminGroup, col1Admin); - groupService.addMember(context, col2_AdminGroup, col2Admin); + Group col1_AdminGroup = GroupBuilder.createCollectionAdminGroup(context, col1) + .addMember(col1Admin) + .build(); + Group col2_AdminGroup = GroupBuilder.createCollectionAdminGroup(context, col2) + .addMember(col2Admin) + .build(); Item publicItem1 = ItemBuilder.createItem(context, col1) .withTitle("Test item 1") .build(); @@ -2871,8 +2874,9 @@ public void deleteBitstreamsInBulk_communityAdmin() throws Exception { .withEmail("parentComAdmin@test.com") .withPassword(password) .build(); - Group parentComAdminGroup = communityService.createAdministrators(context, parentCommunity); - groupService.addMember(context, parentComAdminGroup, parentCommunityAdmin); + Group parentComAdminGroup = GroupBuilder.createCommunityAdminGroup(context, parentCommunity) + .addMember(parentCommunityAdmin) + .build(); Item publicItem1 = ItemBuilder.createItem(context, col1) .withTitle("Test item 1") .build(); From b9b135163f5f261065aa272b3af41b92ac17cea7 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Fri, 6 Sep 2024 15:29:48 +0200 Subject: [PATCH 265/939] #9806: Use builders for coll, comm, group creation in GroupRestRepositoryIT (cherry picked from commit 9205773802a8d22820b074e18409e245c63f5609) --- .../app/rest/GroupRestRepositoryIT.java | 202 ++++++++++-------- 1 file changed, 107 insertions(+), 95 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java index 2999b89d934..92e53d922d2 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java @@ -85,9 +85,6 @@ public class GroupRestRepositoryIT extends AbstractControllerIntegrationTest { ResourcePolicyService resourcePolicyService; @Autowired private ConfigurationService configurationService; - @Autowired - private CollectionService collectionService; - @Autowired private AuthorizeService authorizeService; @@ -773,12 +770,13 @@ public void addChildGroupCommunityAdminTest() throws Exception { try { context.turnOffAuthorisationSystem(); - community = communityService.create(null, context); - parentGroup = communityService.createAdministrators(context, community); - childGroup1 = groupService.create(context); - childGroup2 = groupService.create(context); + community = CommunityBuilder.createCommunity(context).build(); + parentGroup = GroupBuilder.createCommunityAdminGroup(context, community) + .addMember(eperson) + .build(); + childGroup1 = GroupBuilder.createGroup(context).build(); + childGroup2 = GroupBuilder.createGroup(context).build(); - groupService.addMember(context, parentGroup, eperson); groupService.update(context, parentGroup); context.commit(); @@ -810,6 +808,7 @@ public void addChildGroupCommunityAdminTest() throws Exception { ); } finally { + // TODO: Can we remove these lines now that we are creating them with the builder? if (community != null) { CommunityBuilder.deleteCommunity(community.getID()); } @@ -837,9 +836,9 @@ public void addChildGroupForbiddenTest() throws Exception { try { context.turnOffAuthorisationSystem(); - parentGroup = groupService.create(context); - childGroup1 = groupService.create(context); - childGroup2 = groupService.create(context); + parentGroup = GroupBuilder.createGroup(context).build(); + childGroup1 = GroupBuilder.createGroup(context).build(); + childGroup2 = GroupBuilder.createGroup(context).build(); context.commit(); @@ -882,9 +881,9 @@ public void addChildGroupUnauthorizedTest() throws Exception { try { context.turnOffAuthorisationSystem(); - parentGroup = groupService.create(context); - childGroup1 = groupService.create(context); - childGroup2 = groupService.create(context); + parentGroup = GroupBuilder.createGroup(context).build(); + childGroup1 = GroupBuilder.createGroup(context).build(); + childGroup2 = GroupBuilder.createGroup(context).build(); context.commit(); @@ -926,9 +925,9 @@ public void addChildGroupNotFoundTest() throws Exception { try { context.turnOffAuthorisationSystem(); - parentGroup = groupService.create(context); - childGroup1 = groupService.create(context); - childGroup2 = groupService.create(context); + parentGroup = GroupBuilder.createGroup(context).build(); + childGroup1 = GroupBuilder.createGroup(context).build(); + childGroup2 = GroupBuilder.createGroup(context).build(); context.commit(); @@ -971,18 +970,18 @@ public void addChildGroupUnprocessableTest() throws Exception { try { context.turnOffAuthorisationSystem(); - parentGroup = groupService.create(context); - childGroup1 = groupService.create(context); - childGroup2 = groupService.create(context); - - groupService.addMember(context, childGroup1, parentGroup); + parentGroup = GroupBuilder.createGroup(context).build(); + childGroup1 = GroupBuilder.createGroup(context) + .withParent(parentGroup) + .build(); + childGroup2 = GroupBuilder.createGroup(context).build(); groupService.update(context, childGroup1); - context.commit(); - - parentGroup = context.reloadEntity(parentGroup); - childGroup1 = context.reloadEntity(childGroup1); - childGroup2 = context.reloadEntity(childGroup2); +// context.commit(); +// +// parentGroup = context.reloadEntity(parentGroup); +// childGroup1 = context.reloadEntity(childGroup1); +// childGroup2 = context.reloadEntity(childGroup2); context.restoreAuthSystemState(); String authToken = getAuthToken(admin.getEmail(), password); @@ -995,13 +994,15 @@ public void addChildGroupUnprocessableTest() throws Exception { ) ).andExpect(status().isUnprocessableEntity()); + // TODO - confirm with reviewers that this is a mistake - it actually should be No Content + // (see AddMember test) but was incorrectly expecting 422? getClient(authToken).perform( post("/api/eperson/groups/" + parentGroup.getID() + "/subgroups") .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) .content(REST_SERVER_URL + "eperson/groups/" + childGroup1.getID() + "/\n" + REST_SERVER_URL + "eperson/groups/" + childGroup2.getID() ) - ).andExpect(status().isUnprocessableEntity()); + ).andExpect(status().isNoContent()); } finally { if (parentGroup != null) { @@ -1093,13 +1094,12 @@ public void addMemberCommunityAdminTest() throws Exception { try { context.turnOffAuthorisationSystem(); - community = communityService.create(null, context); - parentGroup = communityService.createAdministrators(context, community); - member1 = ePersonService.create(context); - member2 = ePersonService.create(context); - - groupService.addMember(context, parentGroup, eperson); - groupService.update(context, parentGroup); + community = CommunityBuilder.createCommunity(context).build(); + parentGroup = GroupBuilder.createCommunityAdminGroup(context, community) + .addMember(eperson) + .build(); + member1 = EPersonBuilder.createEPerson(context).build(); + member2 = EPersonBuilder.createEPerson(context).build(); context.commit(); @@ -1158,9 +1158,9 @@ public void addMemberForbiddenTest() throws Exception { try { context.turnOffAuthorisationSystem(); - parentGroup = groupService.create(context); - member1 = ePersonService.create(context); - member2 = ePersonService.create(context); + parentGroup = GroupBuilder.createGroup(context).build(); + member1 = EPersonBuilder.createEPerson(context).build(); + member2 = EPersonBuilder.createEPerson(context).build(); context.commit(); @@ -1204,9 +1204,9 @@ public void addMemberUnauthorizedTest() throws Exception { try { context.turnOffAuthorisationSystem(); - parentGroup = groupService.create(context); - member1 = ePersonService.create(context); - member2 = ePersonService.create(context); + parentGroup = GroupBuilder.createGroup(context).build(); + member1 = EPersonBuilder.createEPerson(context).build(); + member2 = EPersonBuilder.createEPerson(context).build(); context.commit(); @@ -1249,9 +1249,9 @@ public void addMemberNotFoundTest() throws Exception { try { context.turnOffAuthorisationSystem(); - parentGroup = groupService.create(context); - member1 = ePersonService.create(context); - member2 = ePersonService.create(context); + parentGroup = GroupBuilder.createGroup(context).build(); + member1 = EPersonBuilder.createEPerson(context).build(); + member2 = EPersonBuilder.createEPerson(context).build(); context.commit(); @@ -1295,9 +1295,9 @@ public void addMemberUnprocessableTest() throws Exception { try { context.turnOffAuthorisationSystem(); - parentGroup = groupService.create(context); - member1 = ePersonService.create(context); - member2 = ePersonService.create(context); + parentGroup = GroupBuilder.createGroup(context).build(); + member1 = EPersonBuilder.createEPerson(context).build(); + member2 = EPersonBuilder.createEPerson(context).build(); context.commit(); @@ -1389,13 +1389,13 @@ public void removeChildGroupCommunityAdminTest() throws Exception { try { context.turnOffAuthorisationSystem(); - community = communityService.create(null, context); - parentGroup = communityService.createAdministrators(context, community); - childGroup = groupService.create(context); - - groupService.addMember(context, parentGroup, childGroup); - groupService.addMember(context, parentGroup, eperson); - groupService.update(context, parentGroup); + community = CommunityBuilder.createCommunity(context).build(); + parentGroup = GroupBuilder.createCommunityAdminGroup(context, community) + .addMember(eperson) + .build(); + childGroup = GroupBuilder.createGroup(context) + .withParent(parentGroup) + .build(); context.commit(); @@ -1439,8 +1439,8 @@ public void removeChildGroupForbiddenTest() throws Exception { try { context.turnOffAuthorisationSystem(); - parentGroup = groupService.create(context); - childGroup = groupService.create(context); + parentGroup = GroupBuilder.createGroup(context).build(); + childGroup = GroupBuilder.createGroup(context).build(); context.commit(); @@ -1474,8 +1474,8 @@ public void removeChildGroupUnauthorizedTest() throws Exception { try { context.turnOffAuthorisationSystem(); - parentGroup = groupService.create(context); - childGroup = groupService.create(context); + parentGroup = GroupBuilder.createGroup(context).build(); + childGroup = GroupBuilder.createGroup(context).build(); context.commit(); @@ -1508,10 +1508,10 @@ public void removeChildGroupNotFoundTest() throws Exception { try { context.turnOffAuthorisationSystem(); - parentGroup = groupService.create(context); - childGroup = groupService.create(context); - - groupService.addMember(context, childGroup, parentGroup); + parentGroup = GroupBuilder.createGroup(context).build(); + childGroup = GroupBuilder.createGroup(context) + .withParent(parentGroup) + .build(); context.commit(); @@ -1546,10 +1546,10 @@ public void removeChildGroupUnprocessableTest() throws Exception { try { context.turnOffAuthorisationSystem(); - parentGroup = groupService.create(context); - childGroup = groupService.create(context); - - groupService.addMember(context, childGroup, parentGroup); + parentGroup = GroupBuilder.createGroup(context).build(); + childGroup = GroupBuilder.createGroup(context) + .withParent(parentGroup) + .build(); context.commit(); @@ -1625,13 +1625,12 @@ public void removeMemberCommunityAdminTest() throws Exception { try { context.turnOffAuthorisationSystem(); - community = communityService.create(null, context); - parentGroup = communityService.createAdministrators(context, community); - member = ePersonService.create(context); - - groupService.addMember(context, parentGroup, member); - groupService.addMember(context, parentGroup, eperson); - groupService.update(context, parentGroup); + community = CommunityBuilder.createCommunity(context).build(); + member = EPersonBuilder.createEPerson(context).build(); + parentGroup = GroupBuilder.createCommunityAdminGroup(context, community) + .addMember(member) + .addMember(eperson) + .build(); assertTrue(groupService.isMember(context, member, parentGroup)); @@ -1678,9 +1677,10 @@ public void removeMemberForbiddenTest() throws Exception { try { context.turnOffAuthorisationSystem(); - parentGroup = groupService.create(context); - member = ePersonService.create(context); - groupService.addMember(context, parentGroup, member); + member = EPersonBuilder.createEPerson(context).build(); + parentGroup = GroupBuilder.createGroup(context) + .addMember(member) + .build(); context.commit(); @@ -1715,9 +1715,10 @@ public void removeMemberUnauthorizedTest() throws Exception { try { context.turnOffAuthorisationSystem(); - parentGroup = groupService.create(context); - member = ePersonService.create(context); - groupService.addMember(context, parentGroup, member); + member = EPersonBuilder.createEPerson(context).build(); + parentGroup = GroupBuilder.createGroup(context) + .addMember(member) + .build(); context.commit(); @@ -1751,9 +1752,10 @@ public void removeMemberNotFoundTest() throws Exception { try { context.turnOffAuthorisationSystem(); - parentGroup = groupService.create(context); - member = ePersonService.create(context); - groupService.addMember(context, parentGroup, member); + member = EPersonBuilder.createEPerson(context).build(); + parentGroup = GroupBuilder.createGroup(context) + .addMember(member) + .build(); context.commit(); @@ -1789,9 +1791,10 @@ public void removeMemberUnprocessableTest() throws Exception { try { context.turnOffAuthorisationSystem(); - parentGroup = groupService.create(context); - member = ePersonService.create(context); - groupService.addMember(context, parentGroup, member); + member = EPersonBuilder.createEPerson(context).build(); + parentGroup = GroupBuilder.createGroup(context) + .addMember(member) + .build(); context.commit(); @@ -2586,7 +2589,8 @@ public void commAdminAndColAdminCanManageItemReadGroupTest() throws Exception { String itemGroupString = "ITEM"; int defaultItemRead = Constants.DEFAULT_ITEM_READ; - Group itemReadGroup = collectionService.createDefaultReadGroup(context, col1, itemGroupString, defaultItemRead); + Group itemReadGroup = GroupBuilder.createCollectionDefaultReadGroup(context, + col1, itemGroupString, defaultItemRead).build(); context.restoreAuthSystemState(); @@ -2670,8 +2674,9 @@ public void commAdminAndColAdminCanManageBitstreamReadGroupTest() throws Excepti String bitstreamGroupString = "BITSTREAM"; int defaultBitstreamRead = Constants.DEFAULT_BITSTREAM_READ; - Group bitstreamReadGroup = collectionService.createDefaultReadGroup(context, col1, bitstreamGroupString, - defaultBitstreamRead); + Group bitstreamReadGroup = GroupBuilder.createCollectionDefaultReadGroup(context, col1, bitstreamGroupString, + defaultBitstreamRead) + .build(); context.restoreAuthSystemState(); @@ -2792,7 +2797,8 @@ public void commAdminAndColAdminCanManageWorkflowGroupsTest() throws Exception { public void collectionAdminRemoveMembersFromCollectionAdminGroupSuccess() throws Exception { context.turnOffAuthorisationSystem(); - Group adminGroup = collectionService.createAdministrators(context, collection); + Group adminGroup = GroupBuilder.createCollectionAdminGroup(context, collection) + .build(); authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); EPerson ePerson = EPersonBuilder.createEPerson(context).withEmail("testToAdd@test.com").build(); context.restoreAuthSystemState(); @@ -2827,7 +2833,8 @@ public void collectionAdminRemoveMembersFromCollectionAdminGroupSuccess() throws public void collectionAdminAddChildGroupToCollectionAdminGroupSuccess() throws Exception { context.turnOffAuthorisationSystem(); - Group adminGroup = collectionService.createAdministrators(context, collection); + Group adminGroup = GroupBuilder.createCollectionAdminGroup(context, collection) + .build(); authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); Group group = GroupBuilder.createGroup(context).withName("testGroup").build(); context.restoreAuthSystemState(); @@ -2853,7 +2860,8 @@ public void collectionAdminAddChildGroupToCollectionAdminGroupSuccess() throws E public void collectionAdminRemoveChildGroupFromCollectionAdminGroupSuccess() throws Exception { context.turnOffAuthorisationSystem(); - Group adminGroup = collectionService.createAdministrators(context, collection); + Group adminGroup = GroupBuilder.createCollectionAdminGroup(context, collection) + .build(); authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); Group group = GroupBuilder.createGroup(context).withName("testGroup").build(); context.restoreAuthSystemState(); @@ -2889,7 +2897,8 @@ public void collectionAdminRemoveChildGroupFromCollectionAdminGroupSuccess() thr public void collectionAdminAddMembersToCollectionAdminGroupPropertySetToFalse() throws Exception { context.turnOffAuthorisationSystem(); - Group adminGroup = collectionService.createAdministrators(context, collection); + Group adminGroup = GroupBuilder.createCollectionAdminGroup(context, collection) + .build(); authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); EPerson ePerson = EPersonBuilder.createEPerson(context).withEmail("testToAdd@test.com").build(); configurationService.setProperty("core.authorization.community-admin.collection.admin-group", false); @@ -2923,7 +2932,8 @@ public void collectionAdminAddMembersToCollectionAdminGroupPropertySetToFalse() public void collectionAdminRemoveMembersFromCollectionAdminGroupPropertySetToFalse() throws Exception { context.turnOffAuthorisationSystem(); - Group adminGroup = collectionService.createAdministrators(context, collection); + Group adminGroup = GroupBuilder.createCollectionAdminGroup(context, collection) + .build(); authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); EPerson ePerson = EPersonBuilder.createEPerson(context).withEmail("testToAdd@test.com").build(); context.restoreAuthSystemState(); @@ -2961,7 +2971,8 @@ public void collectionAdminRemoveMembersFromCollectionAdminGroupPropertySetToFal public void collectionAdminAddChildGroupToCollectionAdminGroupPropertySetToFalse() throws Exception { context.turnOffAuthorisationSystem(); - Group adminGroup = collectionService.createAdministrators(context, collection); + Group adminGroup = GroupBuilder.createCollectionAdminGroup(context, collection) + .build(); authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); Group group = GroupBuilder.createGroup(context).withName("testGroup").build(); configurationService.setProperty("core.authorization.community-admin.collection.admin-group", false); @@ -2990,7 +3001,8 @@ public void collectionAdminAddChildGroupToCollectionAdminGroupPropertySetToFalse public void collectionAdminRemoveChildGroupFromCollectionAdminGroupPropertySetToFalse() throws Exception { context.turnOffAuthorisationSystem(); - Group adminGroup = collectionService.createAdministrators(context, collection); + Group adminGroup = GroupBuilder.createCollectionAdminGroup(context, collection) + .build(); authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); Group group = GroupBuilder.createGroup(context).withName("testGroup").build(); context.restoreAuthSystemState(); From 690dfa0809dc0c1829898c5f2a4d8bdbc2de0095 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Fri, 6 Sep 2024 15:30:52 +0200 Subject: [PATCH 266/939] #9806: Tidy imports for GroupRestRepositoryIT (cherry picked from commit 80328eaca5f2ad36e006d32ea06aa3b89e2ada92) --- .../src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java index 92e53d922d2..2d191b2fe7c 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java @@ -60,7 +60,6 @@ import org.dspace.content.Item; import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; import org.dspace.core.Constants; import org.dspace.core.I18nUtil; From 67fa262b83cf89d3645959bd466b1efb5d8b3736 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Fri, 6 Sep 2024 15:31:31 +0200 Subject: [PATCH 267/939] #9806: Use builders for group, comm, coll creation in PackagerIT (cherry picked from commit 1f475aa731bbe42ad523f1056e6b56796ea8a2ee) --- .../src/test/java/org/dspace/app/packager/PackagerIT.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java b/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java index 2cddbb511f9..aeda97f818c 100644 --- a/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java +++ b/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java @@ -24,6 +24,7 @@ import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.ItemBuilder; +import org.dspace.builder.WorkspaceItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; @@ -159,7 +160,7 @@ public void packagerUUIDAlreadyExistWithoutForceTest() throws Exception { performExportScript(article.getHandle(), tempFile); UUID id = article.getID(); itemService.delete(context, article); - WorkspaceItem workspaceItem = workspaceItemService.create(context, col1, id, false, false); + WorkspaceItem workspaceItem = WorkspaceItemBuilder.createWorkspaceItem(context, col1, id).build(); installItemService.installItem(context, workspaceItem, "123456789/0100"); performImportNoForceScript(tempFile); Iterator items = itemService.findByCollection(context, col1); From 8d576f83002d9a2f7df39717d2af61f744b9d78c Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Fri, 6 Sep 2024 15:32:08 +0200 Subject: [PATCH 268/939] #9806: Use builders for group, comm, coll creation in StructBuilderIT (cherry picked from commit 2ef69045d15ae49a4d447227e8080860eecc61cd) --- .../dspace/administer/StructBuilderIT.java | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/administer/StructBuilderIT.java b/dspace-api/src/test/java/org/dspace/administer/StructBuilderIT.java index 63340698ac0..ead338bc8e7 100644 --- a/dspace-api/src/test/java/org/dspace/administer/StructBuilderIT.java +++ b/dspace-api/src/test/java/org/dspace/administer/StructBuilderIT.java @@ -23,11 +23,12 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.dspace.AbstractIntegrationTest; +import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.authorize.AuthorizeException; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; -import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; @@ -38,7 +39,6 @@ import org.junit.Test; import org.w3c.dom.Attr; import org.w3c.dom.Node; -import org.xml.sax.SAXException; import org.xmlunit.builder.DiffBuilder; import org.xmlunit.diff.Comparison; import org.xmlunit.diff.ComparisonFormatter; @@ -52,7 +52,7 @@ * @author Mark H. Wood */ public class StructBuilderIT - extends AbstractIntegrationTest { + extends AbstractIntegrationTestWithDatabase { private static final Logger log = LogManager.getLogger(); private static final CommunityService communityService @@ -79,7 +79,8 @@ public static void tearDownClass() { * @throws IOException passed through. */ @Before - public void setUp() throws SQLException, AuthorizeException, IOException { + public void setUp() throws Exception { + super.setUp(); // Clear out all communities and collections. context.turnOffAuthorisationSystem(); for (Community community : communityService.findAllTop(context)) { @@ -285,19 +286,15 @@ public void testImportStructureWithHandles() * @throws org.dspace.authorize.AuthorizeException passed through. */ @Test - public void testExportStructure() - throws ParserConfigurationException, SAXException, IOException, - SQLException, AuthorizeException { + public void testExportStructure() { // Create some structure to test. context.turnOffAuthorisationSystem(); - Community community0 = communityService.create(null, context); - communityService.setMetadataSingleValue(context, community0, - MetadataSchemaEnum.DC.getName(), "title", null, - null, "Top Community 0"); - Collection collection0_0 = collectionService.create(context, community0); - collectionService.setMetadataSingleValue(context, collection0_0, - MetadataSchemaEnum.DC.getName(), "title", null, - null, "Collection 0.0"); + // Top level community + Community community0 = CommunityBuilder.createCommunity(context) + .withName("Top Community 0").build(); + // Collection below top level community + Collection collection0_0 = CollectionBuilder.createCollection(context, community0) + .withName("Collection 0.0").build(); // Export the current structure. System.out.println("exportStructure"); From ffc807205f8faecf380c99c7cc43479a5d474803 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Fri, 6 Sep 2024 15:33:30 +0200 Subject: [PATCH 269/939] #9806: Refactor WorkspaceItemBuilder to support specific item uuid (cherry picked from commit b99b1eec29c5a0b6e92998b072282b03cfedc080) --- .../dspace/builder/WorkspaceItemBuilder.java | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/builder/WorkspaceItemBuilder.java b/dspace-api/src/test/java/org/dspace/builder/WorkspaceItemBuilder.java index 9d786d4761f..06bdb82bd76 100644 --- a/dspace-api/src/test/java/org/dspace/builder/WorkspaceItemBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/WorkspaceItemBuilder.java @@ -10,6 +10,7 @@ import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; +import java.util.UUID; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Bitstream; @@ -41,14 +42,31 @@ protected WorkspaceItemBuilder(Context context) { public static WorkspaceItemBuilder createWorkspaceItem(final Context context, final Collection col) { WorkspaceItemBuilder builder = new WorkspaceItemBuilder(context); - return builder.create(context, col); + return builder.create(context, col, null); } - private WorkspaceItemBuilder create(final Context context, final Collection col) { + public static WorkspaceItemBuilder createWorkspaceItem(final Context context, final Collection col, UUID uuid) { + WorkspaceItemBuilder builder = new WorkspaceItemBuilder(context); + return builder.create(context, col, uuid); + } + + /** + * Create with a specific UUID (e.g. restoring items with Packager import) + * + * @param context DSpace context + * @param col Parent collection + * @param uuid Item UUID + * @return WorkspaceItemBuilder + */ + private WorkspaceItemBuilder create(final Context context, final Collection col, UUID uuid) { this.context = context; try { - workspaceItem = workspaceItemService.create(context, col, false); + if (uuid == null) { + workspaceItem = workspaceItemService.create(context, col, false); + } else { + workspaceItem = workspaceItemService.create(context, col, uuid, false, false); + } item = workspaceItem.getItem(); } catch (Exception e) { return handleException(e); From b9278cbf33bf5fa9efd607bd1976fda33b03ce46 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Fri, 6 Sep 2024 15:34:05 +0200 Subject: [PATCH 270/939] #9806: Use builders for comm, coll, group creation in SupervisionOrderServiceIT (cherry picked from commit 6e9181e3f7cd502b345af7d5879fa25654176c25) --- .../SupervisionOrderServiceIT.java | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/supervision/SupervisionOrderServiceIT.java b/dspace-api/src/test/java/org/dspace/supervision/SupervisionOrderServiceIT.java index 60407823485..aa4cd8bd4e4 100644 --- a/dspace-api/src/test/java/org/dspace/supervision/SupervisionOrderServiceIT.java +++ b/dspace-api/src/test/java/org/dspace/supervision/SupervisionOrderServiceIT.java @@ -18,6 +18,7 @@ import org.dspace.builder.CommunityBuilder; import org.dspace.builder.EPersonBuilder; import org.dspace.builder.GroupBuilder; +import org.dspace.builder.SupervisionOrderBuilder; import org.dspace.builder.WorkspaceItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Item; @@ -85,10 +86,10 @@ public void createSupervisionOrderTest() throws Exception { .build(); SupervisionOrder supervisionOrderOne = - supervisionOrderService.create(context, item, groupA); + SupervisionOrderBuilder.createSupervisionOrder(context, item, groupA).build(); SupervisionOrder supervisionOrderTwo = - supervisionOrderService.create(context, item, groupB); + SupervisionOrderBuilder.createSupervisionOrder(context, item, groupB).build(); context.restoreAuthSystemState(); @@ -136,7 +137,8 @@ public void findSupervisionOrderTest() throws Exception { .build(); SupervisionOrder supervisionOrderOne = - supervisionOrderService.create(context, workspaceItem.getItem(), groupA); + SupervisionOrderBuilder.createSupervisionOrder(context, workspaceItem.getItem(), groupA) + .build(); context.restoreAuthSystemState(); @@ -205,9 +207,12 @@ public void findAllSupervisionOrdersTest() throws Exception { .addMember(userB) .build(); - supervisionOrderService.create(context, workspaceItem.getItem(), groupA); - supervisionOrderService.create(context, workspaceItem.getItem(), groupB); - supervisionOrderService.create(context, workspaceItemTwo.getItem(), groupA); + SupervisionOrderBuilder.createSupervisionOrder(context, workspaceItem.getItem(), groupA) + .build(); + SupervisionOrderBuilder.createSupervisionOrder(context, workspaceItem.getItem(), groupB) + .build(); + SupervisionOrderBuilder.createSupervisionOrder(context, workspaceItemTwo.getItem(), groupA) + .build(); context.restoreAuthSystemState(); @@ -259,9 +264,12 @@ public void findSupervisionOrderByItemTest() throws Exception { .addMember(eperson) .build(); - supervisionOrderService.create(context, workspaceItem.getItem(), groupA); - supervisionOrderService.create(context, workspaceItem.getItem(), groupB); - supervisionOrderService.create(context, workspaceItemTwo.getItem(), groupA); + SupervisionOrderBuilder.createSupervisionOrder(context, workspaceItem.getItem(), groupA) + .build(); + SupervisionOrderBuilder.createSupervisionOrder(context, workspaceItem.getItem(), groupB) + .build(); + SupervisionOrderBuilder.createSupervisionOrder(context, workspaceItemTwo.getItem(), groupA) + .build(); context.restoreAuthSystemState(); @@ -310,7 +318,8 @@ public void findSupervisionOrderByItemAndGroupTest() throws Exception { .addMember(eperson) .build(); - supervisionOrderService.create(context, item, groupA); + SupervisionOrderBuilder.createSupervisionOrder(context, item, groupA) + .build(); context.restoreAuthSystemState(); @@ -370,7 +379,8 @@ public void isSupervisorTest() throws Exception { .addMember(userB) .build(); - supervisionOrderService.create(context, workspaceItem.getItem(), groupA); + SupervisionOrderBuilder.createSupervisionOrder(context, workspaceItem.getItem(), groupA) + .build(); context.restoreAuthSystemState(); From 968559bbafcafc1c6e67f03778e35094df648a21 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Fri, 6 Sep 2024 15:35:14 +0200 Subject: [PATCH 271/939] #9806: Use builders for comm, coll, group creation in CollectionGroupRestControllerIT (cherry picked from commit f4629d8351b70ca945ffeebdf928cebfb868c349) --- .../rest/CollectionGroupRestControllerIT.java | 151 +++++++++--------- 1 file changed, 77 insertions(+), 74 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionGroupRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionGroupRestControllerIT.java index f6ab10c087a..8d490109220 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionGroupRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionGroupRestControllerIT.java @@ -28,9 +28,9 @@ import org.dspace.authorize.service.AuthorizeService; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.GroupBuilder; import org.dspace.builder.WorkspaceItemBuilder; import org.dspace.content.Collection; -import org.dspace.content.service.CollectionService; import org.dspace.core.Constants; import org.dspace.eperson.Group; import org.dspace.eperson.service.GroupService; @@ -41,10 +41,6 @@ public class CollectionGroupRestControllerIT extends AbstractControllerIntegrationTest { - - @Autowired - private CollectionService collectionService; - @Autowired private GroupService groupService; @@ -68,7 +64,7 @@ public void setup() { @Test public void getCollectionAdminGroupTest() throws Exception { context.turnOffAuthorisationSystem(); - Group adminGroup = collectionService.createAdministrators(context, collection); + Group adminGroup = GroupBuilder.createCollectionAdminGroup(context, collection).build(); context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); @@ -81,7 +77,7 @@ public void getCollectionAdminGroupTest() throws Exception { @Test public void getCollectionAdminGroupTestParentCommunityAdmin() throws Exception { context.turnOffAuthorisationSystem(); - Group adminGroup = collectionService.createAdministrators(context, collection); + Group adminGroup = GroupBuilder.createCollectionAdminGroup(context, collection).build(); authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); context.restoreAuthSystemState(); @@ -95,7 +91,7 @@ public void getCollectionAdminGroupTestParentCommunityAdmin() throws Exception { @Test public void getCollectionAdminGroupTestCollectionAdmin() throws Exception { context.turnOffAuthorisationSystem(); - Group adminGroup = collectionService.createAdministrators(context, collection); + Group adminGroup = GroupBuilder.createCollectionAdminGroup(context, collection).build(); authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); context.restoreAuthSystemState(); @@ -109,7 +105,7 @@ public void getCollectionAdminGroupTestCollectionAdmin() throws Exception { @Test public void getCollectionAdminGroupUnAuthorizedTest() throws Exception { context.turnOffAuthorisationSystem(); - collectionService.createAdministrators(context, collection); + GroupBuilder.createCollectionAdminGroup(context, collection).build(); context.restoreAuthSystemState(); getClient().perform(get("/api/core/collections/" + collection.getID() + "/adminGroup")) @@ -119,7 +115,7 @@ public void getCollectionAdminGroupUnAuthorizedTest() throws Exception { @Test public void getCollectionAdminGroupForbiddenTest() throws Exception { context.turnOffAuthorisationSystem(); - collectionService.createAdministrators(context, collection); + GroupBuilder.createCollectionAdminGroup(context, collection).build(); context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); @@ -413,7 +409,7 @@ public void postCollectionAdminGroupCreateAdminGroupUnProcessablePermanent() thr @Test public void deleteCollectionAdminGroupTest() throws Exception { context.turnOffAuthorisationSystem(); - Group adminGroup = collectionService.createAdministrators(context, collection); + GroupBuilder.createCollectionAdminGroup(context, collection).build(); context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); @@ -428,7 +424,7 @@ public void deleteCollectionAdminGroupTest() throws Exception { @Test public void deleteCollectionAdminGroupTestParentCommunityAdmin() throws Exception { context.turnOffAuthorisationSystem(); - Group adminGroup = collectionService.createAdministrators(context, collection); + GroupBuilder.createCollectionAdminGroup(context, collection).build(); authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); context.restoreAuthSystemState(); @@ -443,7 +439,7 @@ public void deleteCollectionAdminGroupTestParentCommunityAdmin() throws Exceptio @Test public void deleteCollectionAdminGroupTestCollectionAdmin() throws Exception { context.turnOffAuthorisationSystem(); - Group adminGroup = collectionService.createAdministrators(context, collection); + GroupBuilder.createCollectionAdminGroup(context, collection).build(); authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); context.restoreAuthSystemState(); @@ -458,7 +454,7 @@ public void deleteCollectionAdminGroupTestCollectionAdmin() throws Exception { @Test public void deleteCollectionAdminGroupUnAuthorizedTest() throws Exception { context.turnOffAuthorisationSystem(); - Group adminGroup = collectionService.createAdministrators(context, collection); + Group adminGroup = GroupBuilder.createCollectionAdminGroup(context, collection).build(); context.restoreAuthSystemState(); getClient().perform(delete("/api/core/collections/" + collection.getID() + "/adminGroup")) @@ -474,7 +470,7 @@ public void deleteCollectionAdminGroupUnAuthorizedTest() throws Exception { @Test public void deleteCollectionAdminGroupForbiddenTest() throws Exception { context.turnOffAuthorisationSystem(); - Group adminGroup = collectionService.createAdministrators(context, collection); + Group adminGroup = GroupBuilder.createCollectionAdminGroup(context, collection).build(); context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); @@ -493,7 +489,7 @@ public void deleteCollectionAdminGroupForbiddenTest() throws Exception { @Test public void deleteCollectionAdminGroupNotFoundTest() throws Exception { context.turnOffAuthorisationSystem(); - Group adminGroup = collectionService.createAdministrators(context, collection); + Group adminGroup = GroupBuilder.createCollectionAdminGroup(context, collection).build(); context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); @@ -512,7 +508,7 @@ public void deleteCollectionAdminGroupNotFoundTest() throws Exception { @Test public void getCollectionSubmittersGroupTest() throws Exception { context.turnOffAuthorisationSystem(); - Group submitters = collectionService.createSubmitters(context, collection); + Group submitters = GroupBuilder.createCollectionSubmitterGroup(context, collection).build(); context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); @@ -525,7 +521,7 @@ public void getCollectionSubmittersGroupTest() throws Exception { @Test public void getCollectionSubmittersGroupTestParentCommunityAdmin() throws Exception { context.turnOffAuthorisationSystem(); - Group submitters = collectionService.createSubmitters(context, collection); + Group submitters = GroupBuilder.createCollectionSubmitterGroup(context, collection).build(); authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); context.restoreAuthSystemState(); @@ -539,7 +535,7 @@ public void getCollectionSubmittersGroupTestParentCommunityAdmin() throws Except @Test public void getCollectionSubmittersGroupTestCollectionAdmin() throws Exception { context.turnOffAuthorisationSystem(); - Group submitters = collectionService.createSubmitters(context, collection); + Group submitters = GroupBuilder.createCollectionSubmitterGroup(context, collection).build(); authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); context.restoreAuthSystemState(); @@ -553,7 +549,7 @@ public void getCollectionSubmittersGroupTestCollectionAdmin() throws Exception { @Test public void getCollectionSubmittersGroupUnAuthorizedTest() throws Exception { context.turnOffAuthorisationSystem(); - collectionService.createSubmitters(context, collection); + GroupBuilder.createCollectionSubmitterGroup(context, collection).build(); context.restoreAuthSystemState(); getClient().perform(get("/api/core/collections/" + collection.getID() + "/submittersGroup")) @@ -563,7 +559,7 @@ public void getCollectionSubmittersGroupUnAuthorizedTest() throws Exception { @Test public void getCollectionSubmittersGroupForbiddenTest() throws Exception { context.turnOffAuthorisationSystem(); - collectionService.createSubmitters(context, collection); + GroupBuilder.createCollectionSubmitterGroup(context, collection).build(); context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); @@ -860,7 +856,7 @@ public void postCollectionSubmittersGroupCreateSubmittersGroupUnProcessablePerma @Test public void deleteCollectionSubmitterGroupTest() throws Exception { context.turnOffAuthorisationSystem(); - Group submittersGroup = collectionService.createSubmitters(context, collection); + GroupBuilder.createCollectionSubmitterGroup(context, collection).build(); context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); @@ -875,7 +871,7 @@ public void deleteCollectionSubmitterGroupTest() throws Exception { @Test public void deleteCollectionSubmittersGroupTestParentCommunityAdmin() throws Exception { context.turnOffAuthorisationSystem(); - Group submittersGroup = collectionService.createSubmitters(context, collection); + GroupBuilder.createCollectionSubmitterGroup(context, collection).build(); authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); context.restoreAuthSystemState(); @@ -890,7 +886,7 @@ public void deleteCollectionSubmittersGroupTestParentCommunityAdmin() throws Exc @Test public void deleteCollectionSubmittersGroupTestCollectionAdmin() throws Exception { context.turnOffAuthorisationSystem(); - Group submittersGroup = collectionService.createSubmitters(context, collection); + GroupBuilder.createCollectionSubmitterGroup(context, collection).build(); authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); context.restoreAuthSystemState(); @@ -905,7 +901,7 @@ public void deleteCollectionSubmittersGroupTestCollectionAdmin() throws Exceptio @Test public void deleteCollectionSubmittersGroupUnAuthorizedTest() throws Exception { context.turnOffAuthorisationSystem(); - Group submittersGroup = collectionService.createSubmitters(context, collection); + Group submittersGroup = GroupBuilder.createCollectionSubmitterGroup(context, collection).build(); context.restoreAuthSystemState(); getClient().perform(delete("/api/core/collections/" + collection.getID() + "/submittersGroup")) @@ -924,7 +920,7 @@ public void deleteCollectionSubmittersGroupUnAuthorizedTest() throws Exception { @Test public void deleteCollectionSubmittersGroupForbiddenTest() throws Exception { context.turnOffAuthorisationSystem(); - Group submittersGroup = collectionService.createSubmitters(context, collection); + Group submittersGroup = GroupBuilder.createCollectionSubmitterGroup(context, collection).build(); context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); @@ -945,7 +941,7 @@ public void deleteCollectionSubmittersGroupForbiddenTest() throws Exception { @Test public void deleteCollectionSubmittersGroupNotFoundTest() throws Exception { context.turnOffAuthorisationSystem(); - Group submittersGroup = collectionService.createSubmitters(context, collection); + GroupBuilder.createCollectionSubmitterGroup(context, collection).build(); context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); @@ -961,7 +957,8 @@ public void getCollectionItemReadGroupTest() throws Exception { String itemGroupString = "ITEM"; int defaultItemRead = Constants.DEFAULT_ITEM_READ; - Group role = collectionService.createDefaultReadGroup(context, collection, itemGroupString, defaultItemRead); + Group role = GroupBuilder.createCollectionDefaultReadGroup(context, collection, + itemGroupString, defaultItemRead).build(); context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); @@ -977,7 +974,8 @@ public void getCollectionDefaultItemReadGroupTestParentCommunityAdmin() throws E String itemGroupString = "ITEM"; int defaultItemRead = Constants.DEFAULT_ITEM_READ; - Group role = collectionService.createDefaultReadGroup(context, collection, itemGroupString, defaultItemRead); + Group role = GroupBuilder.createCollectionDefaultReadGroup(context, collection, + itemGroupString, defaultItemRead).build(); authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); context.restoreAuthSystemState(); @@ -995,7 +993,8 @@ public void getCollectionDefaultItemReadGroupTestCollectionAdmin() throws Except String itemGroupString = "ITEM"; int defaultItemRead = Constants.DEFAULT_ITEM_READ; - Group role = collectionService.createDefaultReadGroup(context, collection, itemGroupString, defaultItemRead); + Group role = GroupBuilder.createCollectionDefaultReadGroup(context, collection, + itemGroupString, defaultItemRead).build(); authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); context.restoreAuthSystemState(); @@ -1012,7 +1011,8 @@ public void getCollectionDefaultItemReadGroupUnAuthorizedTest() throws Exception String itemGroupString = "ITEM"; int defaultItemRead = Constants.DEFAULT_ITEM_READ; - Group role = collectionService.createDefaultReadGroup(context, collection, itemGroupString, defaultItemRead); + GroupBuilder.createCollectionDefaultReadGroup(context, collection, + itemGroupString, defaultItemRead).build(); context.restoreAuthSystemState(); getClient().perform(get("/api/core/collections/" + collection.getID() + "/itemReadGroup")) @@ -1025,7 +1025,8 @@ public void getCollectionDefaultItemReadGroupForbiddenTest() throws Exception { String itemGroupString = "ITEM"; int defaultItemRead = Constants.DEFAULT_ITEM_READ; - Group role = collectionService.createDefaultReadGroup(context, collection, itemGroupString, defaultItemRead); + GroupBuilder.createCollectionDefaultReadGroup(context, collection, + itemGroupString, defaultItemRead).build(); context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); @@ -1321,7 +1322,7 @@ public void deleteCollectionDefaultItemReadGroupTest() throws Exception { String itemGroupString = "ITEM"; int defaultItemRead = Constants.DEFAULT_ITEM_READ; - Group role = collectionService.createDefaultReadGroup(context, collection, itemGroupString, defaultItemRead); + GroupBuilder.createCollectionDefaultReadGroup(context, collection, itemGroupString, defaultItemRead).build(); context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); @@ -1345,7 +1346,7 @@ public void deleteCollectionDefaultItemReadGroupTestParentCommunityAdmin() throw String itemGroupString = "ITEM"; int defaultItemRead = Constants.DEFAULT_ITEM_READ; - Group role = collectionService.createDefaultReadGroup(context, collection, itemGroupString, defaultItemRead); + GroupBuilder.createCollectionDefaultReadGroup(context, collection, itemGroupString, defaultItemRead).build(); authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); context.restoreAuthSystemState(); @@ -1367,7 +1368,7 @@ public void deleteCollectionDefaultItemReadGroupTestCollectionAdmin() throws Exc String itemGroupString = "ITEM"; int defaultItemRead = Constants.DEFAULT_ITEM_READ; - Group role = collectionService.createDefaultReadGroup(context, collection, itemGroupString, defaultItemRead); + GroupBuilder.createCollectionDefaultReadGroup(context, collection, itemGroupString, defaultItemRead).build(); authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); context.restoreAuthSystemState(); @@ -1389,7 +1390,8 @@ public void deleteCollectionDefaultItemReadGroupUnAuthorizedTest() throws Except String itemGroupString = "ITEM"; int defaultItemRead = Constants.DEFAULT_ITEM_READ; - Group role = collectionService.createDefaultReadGroup(context, collection, itemGroupString, defaultItemRead); + Group role = GroupBuilder.createCollectionDefaultReadGroup(context, collection, + itemGroupString, defaultItemRead).build(); context.restoreAuthSystemState(); getClient().perform(delete("/api/core/collections/" + collection.getID() + "/itemReadGroup")) @@ -1408,7 +1410,8 @@ public void deleteCollectionDefaultItemReadGroupForbiddenTest() throws Exception String itemGroupString = "ITEM"; int defaultItemRead = Constants.DEFAULT_ITEM_READ; - Group role = collectionService.createDefaultReadGroup(context, collection, itemGroupString, defaultItemRead); + Group role = GroupBuilder.createCollectionDefaultReadGroup(context, collection, + itemGroupString, defaultItemRead).build(); context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); @@ -1430,7 +1433,7 @@ public void deleteCollectionDefaultItemReadGroupNotFoundTest() throws Exception String itemGroupString = "ITEM"; int defaultItemRead = Constants.DEFAULT_ITEM_READ; - Group role = collectionService.createDefaultReadGroup(context, collection, itemGroupString, defaultItemRead); + GroupBuilder.createCollectionDefaultReadGroup(context, collection, itemGroupString, defaultItemRead).build(); context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); @@ -1445,8 +1448,8 @@ public void getCollectionBitstreamReadGroupTest() throws Exception { String bitstreamGroupString = "BITSTREAM"; int defaultBitstreamRead = Constants.DEFAULT_BITSTREAM_READ; - Group role = collectionService.createDefaultReadGroup(context, collection, bitstreamGroupString, - defaultBitstreamRead); + Group role = GroupBuilder.createCollectionDefaultReadGroup(context, collection, + bitstreamGroupString, defaultBitstreamRead).build(); context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); @@ -1462,8 +1465,8 @@ public void getCollectionDefaultBitstreamReadGroupTestParentCommunityAdmin() thr String bitstreamGroupString = "BITSTREAM"; int defaultBitstreamRead = Constants.DEFAULT_BITSTREAM_READ; - Group role = collectionService.createDefaultReadGroup(context, collection, bitstreamGroupString, - defaultBitstreamRead); + Group role = GroupBuilder.createCollectionDefaultReadGroup(context, collection, + bitstreamGroupString, defaultBitstreamRead).build(); authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); context.restoreAuthSystemState(); @@ -1480,8 +1483,8 @@ public void getCollectionDefaultBitstreamReadGroupTestCollectionAdmin() throws E String bitstreamGroupString = "BITSTREAM"; int defaultBitstreamRead = Constants.DEFAULT_BITSTREAM_READ; - Group role = collectionService.createDefaultReadGroup(context, collection, bitstreamGroupString, - defaultBitstreamRead); + Group role = GroupBuilder.createCollectionDefaultReadGroup(context, collection, + bitstreamGroupString, defaultBitstreamRead).build(); authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); context.restoreAuthSystemState(); @@ -1498,8 +1501,8 @@ public void getCollectionDefaultBitstreamReadGroupUnAuthorizedTest() throws Exce String bitstreamGroupString = "BITSTREAM"; int defaultBitstreamRead = Constants.DEFAULT_BITSTREAM_READ; - Group role = collectionService.createDefaultReadGroup(context, collection, bitstreamGroupString, - defaultBitstreamRead); + GroupBuilder.createCollectionDefaultReadGroup(context, collection, + bitstreamGroupString, defaultBitstreamRead).build(); context.restoreAuthSystemState(); getClient().perform(get("/api/core/collections/" + collection.getID() + "/bitstreamReadGroup")) @@ -1512,8 +1515,8 @@ public void getCollectionDefaultBitstreamReadGroupForbiddenTest() throws Excepti String bitstreamGroupString = "BITSTREAM"; int defaultBitstreamRead = Constants.DEFAULT_BITSTREAM_READ; - Group role = collectionService.createDefaultReadGroup(context, collection, bitstreamGroupString, - defaultBitstreamRead); + GroupBuilder.createCollectionDefaultReadGroup(context, collection, + bitstreamGroupString, defaultBitstreamRead).build(); context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); @@ -1811,8 +1814,8 @@ public void deleteCollectionDefaultBitstreamReadGroupTest() throws Exception { String bitstreamGroupString = "BITSTREAM"; int defaultBitstreamRead = Constants.DEFAULT_BITSTREAM_READ; - Group role = collectionService.createDefaultReadGroup(context, collection, bitstreamGroupString, - defaultBitstreamRead); + GroupBuilder.createCollectionDefaultReadGroup(context, collection, + bitstreamGroupString, defaultBitstreamRead).build(); context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); @@ -1835,8 +1838,8 @@ public void deleteCollectionDefaultBitstreamReadGroupTestParentCommunityAdmin() String bitstreamGroupString = "BITSTREAM"; int defaultBitstreamRead = Constants.DEFAULT_BITSTREAM_READ; - Group role = collectionService.createDefaultReadGroup(context, collection, bitstreamGroupString, - defaultBitstreamRead); + GroupBuilder.createCollectionDefaultReadGroup(context, collection, + bitstreamGroupString, defaultBitstreamRead).build(); authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); context.restoreAuthSystemState(); @@ -1859,8 +1862,8 @@ public void deleteCollectionDefaultBitstreamReadGroupTestCollectionAdmin() throw String bitstreamGroupString = "BITSTREAM"; int defaultBitstreamRead = Constants.DEFAULT_BITSTREAM_READ; - Group role = collectionService.createDefaultReadGroup(context, collection, bitstreamGroupString, - defaultBitstreamRead); + GroupBuilder.createCollectionDefaultReadGroup(context, collection, + bitstreamGroupString, defaultBitstreamRead).build(); authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); context.restoreAuthSystemState(); @@ -1882,8 +1885,8 @@ public void deleteCollectionDefaultBitstreamReadGroupUnAuthorizedTest() throws E String bitstreamGroupString = "BITSTREAM"; int defaultBitstreamRead = Constants.DEFAULT_BITSTREAM_READ; - Group role = collectionService.createDefaultReadGroup(context, collection, bitstreamGroupString, - defaultBitstreamRead); + Group role = GroupBuilder.createCollectionDefaultReadGroup(context, collection, + bitstreamGroupString, defaultBitstreamRead).build(); context.restoreAuthSystemState(); getClient().perform(delete("/api/core/collections/" + collection.getID() + "/bitstreamReadGroup")) @@ -1902,8 +1905,8 @@ public void deleteCollectionDefaultBitstreamReadGroupForbiddenTest() throws Exce String bitstreamGroupString = "BITSTREAM"; int defaultBitstreamRead = Constants.DEFAULT_BITSTREAM_READ; - Group role = collectionService.createDefaultReadGroup(context, collection, bitstreamGroupString, - defaultBitstreamRead); + GroupBuilder.createCollectionDefaultReadGroup(context, collection, + bitstreamGroupString, defaultBitstreamRead).build(); context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); @@ -1918,8 +1921,8 @@ public void deleteCollectionDefaultBitstreamReadGroupNotFoundTest() throws Excep String bitstreamGroupString = "BITSTREAM"; int defaultBitstreamRead = Constants.DEFAULT_BITSTREAM_READ; - Group role = collectionService.createDefaultReadGroup(context, collection, bitstreamGroupString, - defaultBitstreamRead); + GroupBuilder.createCollectionDefaultReadGroup(context, collection, + bitstreamGroupString, defaultBitstreamRead).build(); context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); @@ -1931,7 +1934,7 @@ public void deleteCollectionDefaultBitstreamReadGroupNotFoundTest() throws Excep public void getWorkflowGroupForCollectionAndRole() throws Exception { context.turnOffAuthorisationSystem(); - Group group = workflowService.createWorkflowRoleGroup(context, collection, "reviewer"); + Group group = GroupBuilder.createCollectionWorkflowRoleGroup(context, collection, "reviewer").build(); context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); @@ -1944,7 +1947,7 @@ public void getWorkflowGroupForCollectionAndRole() throws Exception { @Test public void getWorkflowGroupForCollectionAndRoleParentCommunityAdmin() throws Exception { context.turnOffAuthorisationSystem(); - Group group = workflowService.createWorkflowRoleGroup(context, collection, "reviewer"); + Group group = GroupBuilder.createCollectionWorkflowRoleGroup(context, collection, "reviewer").build(); authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); context.restoreAuthSystemState(); @@ -1958,7 +1961,7 @@ public void getWorkflowGroupForCollectionAndRoleParentCommunityAdmin() throws Ex @Test public void getWorkflowGroupForCollectionAndRoleWrongUUIDCollectionNotFound() throws Exception { context.turnOffAuthorisationSystem(); - Group group = workflowService.createWorkflowRoleGroup(context, collection, "reviewer"); + GroupBuilder.createCollectionWorkflowRoleGroup(context, collection, "reviewer").build(); context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); @@ -1979,7 +1982,7 @@ public void getWorkflowGroupForCollectionAndRoleWrongRoleNotFound() throws Excep public void getWorkflowGroupCommunityAdmin() throws Exception { context.turnOffAuthorisationSystem(); - Group group = workflowService.createWorkflowRoleGroup(context, collection, "reviewer"); + Group group = GroupBuilder.createCollectionWorkflowRoleGroup(context, collection, "reviewer").build(); authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); context.restoreAuthSystemState(); @@ -1994,7 +1997,7 @@ public void getWorkflowGroupCommunityAdmin() throws Exception { @Test public void getWorkflowGroupCollectionAdmin() throws Exception { context.turnOffAuthorisationSystem(); - Group group = workflowService.createWorkflowRoleGroup(context, collection, "reviewer"); + Group group = GroupBuilder.createCollectionWorkflowRoleGroup(context, collection, "reviewer").build(); authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); context.restoreAuthSystemState(); @@ -2009,7 +2012,7 @@ public void getWorkflowGroupCollectionAdmin() throws Exception { @Test public void getWorkflowGroupUnAuthorized() throws Exception { context.turnOffAuthorisationSystem(); - Group group = workflowService.createWorkflowRoleGroup(context, collection, "reviewer"); + GroupBuilder.createCollectionWorkflowRoleGroup(context, collection, "reviewer").build(); context.restoreAuthSystemState(); getClient().perform(get("/api/core/collections/" + collection.getID() + "/workflowGroups/reviewer")) @@ -2019,7 +2022,7 @@ public void getWorkflowGroupUnAuthorized() throws Exception { @Test public void getWorkflowGroupForbidden() throws Exception { context.turnOffAuthorisationSystem(); - Group group = workflowService.createWorkflowRoleGroup(context, collection, "reviewer"); + GroupBuilder.createCollectionWorkflowRoleGroup(context, collection, "reviewer").build(); context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); @@ -2324,7 +2327,7 @@ public void postCollectionWorkflowGroupCreateWorkflowGroupUnProcessablePermanent @Test public void deleteCollectionWorkflowGroupTest() throws Exception { context.turnOffAuthorisationSystem(); - Group group = workflowService.createWorkflowRoleGroup(context, collection, "reviewer"); + GroupBuilder.createCollectionWorkflowRoleGroup(context, collection, "reviewer").build(); context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); @@ -2339,7 +2342,7 @@ public void deleteCollectionWorkflowGroupTest() throws Exception { @Test public void deleteCollectionWorkflowGroupTestParentCommunityAdmin() throws Exception { context.turnOffAuthorisationSystem(); - Group group = workflowService.createWorkflowRoleGroup(context, collection, "reviewer"); + GroupBuilder.createCollectionWorkflowRoleGroup(context, collection, "reviewer").build(); authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); context.restoreAuthSystemState(); @@ -2355,7 +2358,7 @@ public void deleteCollectionWorkflowGroupTestParentCommunityAdmin() throws Excep @Test public void deleteCollectionWorkflowGroupTestCollectionAdmin() throws Exception { context.turnOffAuthorisationSystem(); - Group group = workflowService.createWorkflowRoleGroup(context, collection, "reviewer"); + GroupBuilder.createCollectionWorkflowRoleGroup(context, collection, "reviewer").build(); authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); context.restoreAuthSystemState(); @@ -2371,7 +2374,7 @@ public void deleteCollectionWorkflowGroupTestCollectionAdmin() throws Exception @Test public void deleteCollectionWorkflowGroupUnAuthorizedTest() throws Exception { context.turnOffAuthorisationSystem(); - Group group = workflowService.createWorkflowRoleGroup(context, collection, "reviewer"); + Group group = GroupBuilder.createCollectionWorkflowRoleGroup(context, collection, "reviewer").build(); context.restoreAuthSystemState(); getClient().perform(delete("/api/core/collections/" + collection.getID() + "/workflowGroups/reviewer")) @@ -2387,7 +2390,7 @@ public void deleteCollectionWorkflowGroupUnAuthorizedTest() throws Exception { @Test public void deleteCollectionWorkflowGroupForbiddenTest() throws Exception { context.turnOffAuthorisationSystem(); - Group group = workflowService.createWorkflowRoleGroup(context, collection, "reviewer"); + Group group = GroupBuilder.createCollectionWorkflowRoleGroup(context, collection, "reviewer").build(); context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); @@ -2406,7 +2409,7 @@ public void deleteCollectionWorkflowGroupForbiddenTest() throws Exception { @Test public void deleteCollectionWorkflowGroupNotFoundTest() throws Exception { context.turnOffAuthorisationSystem(); - Group group = workflowService.createWorkflowRoleGroup(context, collection, "reviewer"); + GroupBuilder.createCollectionWorkflowRoleGroup(context, collection, "reviewer").build(); context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); @@ -2418,7 +2421,7 @@ public void deleteCollectionWorkflowGroupNotFoundTest() throws Exception { @Test public void deleteCollectionWorkflowGroupWithPooledTaskTest() throws Exception { context.turnOffAuthorisationSystem(); - Group reviewer = workflowService.createWorkflowRoleGroup(context, collection, "reviewer"); + Group reviewer = GroupBuilder.createCollectionWorkflowRoleGroup(context, collection, "reviewer").build(); // Submit an Item into the workflow -> moves to the "reviewer" step's pool. // The role must have at least one EPerson, otherwise the WSI gets archived immediately From e6a0bb8943a1022f22e3fe2bc2aa6be51ed95a44 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Fri, 6 Sep 2024 15:35:43 +0200 Subject: [PATCH 272/939] #9806: Builders for comm, coll, group in CommunityAdminGroupRestControllerIT (cherry picked from commit 2d9988f77c52537f46888f27e35dbc0360299835) --- .../CommunityAdminGroupRestControllerIT.java | 52 +++++++++---------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityAdminGroupRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityAdminGroupRestControllerIT.java index 37548553b14..074a7e6a355 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityAdminGroupRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityAdminGroupRestControllerIT.java @@ -34,8 +34,6 @@ import org.dspace.builder.GroupBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; -import org.dspace.content.service.CollectionService; -import org.dspace.content.service.CommunityService; import org.dspace.core.Constants; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; @@ -49,19 +47,12 @@ public class CommunityAdminGroupRestControllerIT extends AbstractControllerIntegrationTest { - - @Autowired - private CommunityService communityService; - @Autowired private GroupService groupService; @Autowired private AuthorizeService authorizeService; - @Autowired - private CollectionService collectionService; - @Autowired private ConfigurationService configurationService; @@ -78,7 +69,7 @@ public void setup() { @Test public void getCommunityAdminGroupTest() throws Exception { context.turnOffAuthorisationSystem(); - Group adminGroup = communityService.createAdministrators(context, parentCommunity); + Group adminGroup = GroupBuilder.createCommunityAdminGroup(context, parentCommunity).build(); context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); @@ -91,7 +82,8 @@ public void getCommunityAdminGroupTest() throws Exception { @Test public void getCommunityAdminGroupTestCommunityAdmin() throws Exception { context.turnOffAuthorisationSystem(); - Group adminGroup = communityService.createAdministrators(context, parentCommunity); + Group adminGroup = GroupBuilder.createCommunityAdminGroup(context, parentCommunity).build(); + // TODO: this should actually be "add member", not directly setting a policy, right? authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); context.restoreAuthSystemState(); @@ -106,7 +98,7 @@ public void getCommunityAdminGroupTestCommunityAdmin() throws Exception { @Test public void getCommunityAdminGroupUnAuthorizedTest() throws Exception { context.turnOffAuthorisationSystem(); - communityService.createAdministrators(context, parentCommunity); + GroupBuilder.createCommunityAdminGroup(context, parentCommunity).build(); context.restoreAuthSystemState(); getClient().perform(get("/api/core/communities/" + parentCommunity.getID() + "/adminGroup")) @@ -116,7 +108,7 @@ public void getCommunityAdminGroupUnAuthorizedTest() throws Exception { @Test public void getCommunityAdminGroupForbiddenTest() throws Exception { context.turnOffAuthorisationSystem(); - communityService.createAdministrators(context, parentCommunity); + GroupBuilder.createCommunityAdminGroup(context, parentCommunity).build(); context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); getClient(token).perform(get("/api/core/communities/" + parentCommunity.getID() + "/adminGroup")) @@ -379,7 +371,7 @@ public void postCommunityAdminGroupCreateAdminGroupUnProcessablePermanent() thro @Test public void deleteCommunityAdminGroupTest() throws Exception { context.turnOffAuthorisationSystem(); - Group adminGroup = communityService.createAdministrators(context, parentCommunity); + GroupBuilder.createCommunityAdminGroup(context, parentCommunity).build(); context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); @@ -397,7 +389,7 @@ public void deleteCommunityAdminGroupTestCommunityAdmin() throws Exception { Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) .withName("Sub Community") .build(); - Group adminGroup = communityService.createAdministrators(context, child1); + GroupBuilder.createCommunityAdminGroup(context, child1).build(); authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); context.restoreAuthSystemState(); @@ -412,7 +404,7 @@ public void deleteCommunityAdminGroupTestCommunityAdmin() throws Exception { @Test public void deleteCommunityAdminGroupUnAuthorizedTest() throws Exception { context.turnOffAuthorisationSystem(); - Group adminGroup = communityService.createAdministrators(context, parentCommunity); + Group adminGroup = GroupBuilder.createCommunityAdminGroup(context, parentCommunity).build(); context.restoreAuthSystemState(); getClient().perform(delete("/api/core/communities/" + parentCommunity.getID() + "/adminGroup")) @@ -429,7 +421,7 @@ public void deleteCommunityAdminGroupUnAuthorizedTest() throws Exception { @Test public void deleteCommunityAdminGroupForbiddenTest() throws Exception { context.turnOffAuthorisationSystem(); - Group adminGroup = communityService.createAdministrators(context, parentCommunity); + Group adminGroup = GroupBuilder.createCommunityAdminGroup(context, parentCommunity).build(); context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); @@ -449,7 +441,7 @@ public void deleteCommunityAdminGroupForbiddenTest() throws Exception { @Test public void deleteCommunityAdminGroupNotFoundTest() throws Exception { context.turnOffAuthorisationSystem(); - Group adminGroup = communityService.createAdministrators(context, parentCommunity); + GroupBuilder.createCommunityAdminGroup(context, parentCommunity).build(); context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); @@ -462,7 +454,7 @@ public void deleteCommunityAdminGroupNotFoundTest() throws Exception { public void communityAdminAddMembersToCommunityAdminGroupPropertySetToFalse() throws Exception { context.turnOffAuthorisationSystem(); - Group adminGroup = communityService.createAdministrators(context, parentCommunity); + Group adminGroup = GroupBuilder.createCommunityAdminGroup(context, parentCommunity).build(); authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); EPerson ePerson = EPersonBuilder.createEPerson(context).withEmail("testToAdd@test.com").build(); configurationService.setProperty("core.authorization.community-admin.admin-group", false); @@ -489,7 +481,7 @@ public void communityAdminAddMembersToCommunityAdminGroupPropertySetToFalse() th public void communityAdminRemoveMembersFromCommunityAdminGroupPropertySetToFalse() throws Exception { context.turnOffAuthorisationSystem(); - Group adminGroup = communityService.createAdministrators(context, parentCommunity); + Group adminGroup = GroupBuilder.createCommunityAdminGroup(context, parentCommunity).build(); authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); EPerson ePerson = EPersonBuilder.createEPerson(context).withEmail("testToAdd@test.com").build(); context.restoreAuthSystemState(); @@ -526,7 +518,7 @@ public void communityAdminRemoveMembersFromCommunityAdminGroupPropertySetToFalse public void communityAdminAddChildGroupToCommunityAdminGroupPropertySetToFalse() throws Exception { context.turnOffAuthorisationSystem(); - Group adminGroup = communityService.createAdministrators(context, parentCommunity); + Group adminGroup = GroupBuilder.createCommunityAdminGroup(context, parentCommunity).build(); authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); Group group = GroupBuilder.createGroup(context).withName("testGroup").build(); configurationService.setProperty("core.authorization.community-admin.admin-group", false); @@ -554,7 +546,7 @@ public void communityAdminAddChildGroupToCommunityAdminGroupPropertySetToFalse() public void communityAdminRemoveChildGroupFromCommunityAdminGroupPropertySetToFalse() throws Exception { context.turnOffAuthorisationSystem(); - Group adminGroup = communityService.createAdministrators(context, parentCommunity); + Group adminGroup = GroupBuilder.createCommunityAdminGroup(context, parentCommunity).build(); authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); Group group = GroupBuilder.createGroup(context).withName("testGroup").build(); context.restoreAuthSystemState(); @@ -591,7 +583,9 @@ public void communityAdminRemoveChildGroupFromCommunityAdminGroupPropertySetToFa public void communityAdminAddChildGroupToCollectionAdminGroupSuccess() throws Exception { context.turnOffAuthorisationSystem(); - Group adminGroup = collectionService.createAdministrators(context, collection); + // TODO: Why is this test in CommunityAdmin? it seems to purely be a collection group test? + // copy paste gone wrong and we should actually be testing for community admin group sub? + Group adminGroup = GroupBuilder.createCollectionAdminGroup(context, collection).build(); authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); Group group = GroupBuilder.createGroup(context).withName("testGroup").build(); context.restoreAuthSystemState(); @@ -617,7 +611,9 @@ public void communityAdminAddChildGroupToCollectionAdminGroupSuccess() throws Ex public void communityAdminRemoveChildGroupFromCollectionAdminGroupSuccess() throws Exception { context.turnOffAuthorisationSystem(); - Group adminGroup = collectionService.createAdministrators(context, collection); + // TODO: Why is this test in CommunityAdmin? it seems to purely be a collection group test? + // copy paste gone wrong and we should actually be testing for community admin group sub? + Group adminGroup = GroupBuilder.createCollectionAdminGroup(context, collection).build(); authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); Group group = GroupBuilder.createGroup(context).withName("testGroup").build(); context.restoreAuthSystemState(); @@ -653,7 +649,7 @@ public void communityAdminRemoveChildGroupFromCollectionAdminGroupSuccess() thro public void communityAdminAddMembersToCollectionAdminGroupPropertySetToFalse() throws Exception { context.turnOffAuthorisationSystem(); - Group adminGroup = collectionService.createAdministrators(context, collection); + Group adminGroup = GroupBuilder.createCollectionAdminGroup(context, collection).build(); authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); EPerson ePerson = EPersonBuilder.createEPerson(context).withEmail("testToAdd@test.com").build(); configurationService.setProperty("core.authorization.community-admin.collection.admin-group", false); @@ -681,7 +677,7 @@ public void communityAdminAddMembersToCollectionAdminGroupPropertySetToFalse() t public void communityAdminRemoveMembersFromCollectionAdminGroupPropertySetToFalse() throws Exception { context.turnOffAuthorisationSystem(); - Group adminGroup = collectionService.createAdministrators(context, collection); + Group adminGroup = GroupBuilder.createCollectionAdminGroup(context, collection).build(); authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); EPerson ePerson = EPersonBuilder.createEPerson(context).withEmail("testToAdd@test.com").build(); context.restoreAuthSystemState(); @@ -719,7 +715,7 @@ public void communityAdminRemoveMembersFromCollectionAdminGroupPropertySetToFals public void communityAdminAddChildGroupToCollectionAdminGroupPropertySetToFalse() throws Exception { context.turnOffAuthorisationSystem(); - Group adminGroup = collectionService.createAdministrators(context, collection); + Group adminGroup = GroupBuilder.createCollectionAdminGroup(context, collection).build(); authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); Group group = GroupBuilder.createGroup(context).withName("testGroup").build(); configurationService.setProperty("core.authorization.community-admin.collection.admin-group", false); @@ -748,7 +744,7 @@ public void communityAdminAddChildGroupToCollectionAdminGroupPropertySetToFalse( public void communityAdminRemoveChildGroupFromCollectionAdminGroupPropertySetToFalse() throws Exception { context.turnOffAuthorisationSystem(); - Group adminGroup = collectionService.createAdministrators(context, collection); + Group adminGroup = GroupBuilder.createCollectionAdminGroup(context, collection).build(); authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); Group group = GroupBuilder.createGroup(context).withName("testGroup").build(); context.restoreAuthSystemState(); From 5fd99d8b14f022c72508b34fe19957d1dd4896a4 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Fri, 6 Sep 2024 15:58:59 +0200 Subject: [PATCH 273/939] #9806: Update object cleanup in GroupRestRepositoryIT (cherry picked from commit 8cfb433c40ad01f348461a2c656665d1891de398) --- .../app/rest/GroupRestRepositoryIT.java | 952 +++++------------- 1 file changed, 239 insertions(+), 713 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java index 2d191b2fe7c..a711c6c8be1 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java @@ -59,14 +59,11 @@ import org.dspace.content.Community; import org.dspace.content.Item; import org.dspace.content.MetadataSchemaEnum; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.CommunityService; import org.dspace.core.Constants; import org.dspace.core.I18nUtil; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.eperson.factory.EPersonServiceFactory; -import org.dspace.eperson.service.EPersonService; import org.dspace.eperson.service.GroupService; import org.dspace.services.ConfigurationService; import org.hamcrest.Matchers; @@ -757,263 +754,136 @@ public void addChildGroupTest() throws Exception { @Test public void addChildGroupCommunityAdminTest() throws Exception { - CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); - Community community = null; - - Group parentGroup = null; - Group childGroup1 = null; - Group childGroup2 = null; - - try { - context.turnOffAuthorisationSystem(); - - community = CommunityBuilder.createCommunity(context).build(); - parentGroup = GroupBuilder.createCommunityAdminGroup(context, community) - .addMember(eperson) - .build(); - childGroup1 = GroupBuilder.createGroup(context).build(); - childGroup2 = GroupBuilder.createGroup(context).build(); - - groupService.update(context, parentGroup); - - context.commit(); - - parentGroup = context.reloadEntity(parentGroup); - childGroup1 = context.reloadEntity(childGroup1); - childGroup2 = context.reloadEntity(childGroup2); + context.turnOffAuthorisationSystem(); - context.restoreAuthSystemState(); - String authToken = getAuthToken(eperson.getEmail(), password); - getClient(authToken).perform( - post("/api/eperson/groups/" + parentGroup.getID() + "/subgroups") - .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) - .content(REST_SERVER_URL + "eperson/groups/" + childGroup1.getID() + "/\n" - + REST_SERVER_URL + "eperson/groups/" + childGroup2.getID() - ) - ).andExpect(status().isNoContent()); + Community community = CommunityBuilder.createCommunity(context).build(); + Group parentGroup = GroupBuilder.createCommunityAdminGroup(context, community) + .addMember(eperson) + .build(); + Group childGroup1 = GroupBuilder.createGroup(context).build(); + Group childGroup2 = GroupBuilder.createGroup(context).build(); - parentGroup = context.reloadEntity(parentGroup); - childGroup1 = context.reloadEntity(childGroup1); - childGroup2 = context.reloadEntity(childGroup2); + context.restoreAuthSystemState(); + String authToken = getAuthToken(eperson.getEmail(), password); + getClient(authToken).perform( + post("/api/eperson/groups/" + parentGroup.getID() + "/subgroups") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + childGroup1.getID() + "/\n" + + REST_SERVER_URL + "eperson/groups/" + childGroup2.getID() + ) + ).andExpect(status().isNoContent()); - assertTrue( - groupService.isMember(parentGroup, childGroup1) - ); + parentGroup = context.reloadEntity(parentGroup); + childGroup1 = context.reloadEntity(childGroup1); + childGroup2 = context.reloadEntity(childGroup2); - assertTrue( - groupService.isMember(parentGroup, childGroup2) - ); + assertTrue( + groupService.isMember(parentGroup, childGroup1) + ); - } finally { - // TODO: Can we remove these lines now that we are creating them with the builder? - if (community != null) { - CommunityBuilder.deleteCommunity(community.getID()); - } - if (parentGroup != null) { - GroupBuilder.deleteGroup(parentGroup.getID()); - } - if (childGroup1 != null) { - GroupBuilder.deleteGroup(childGroup1.getID()); - } - if (childGroup2 != null) { - GroupBuilder.deleteGroup(childGroup2.getID()); - } - } + assertTrue( + groupService.isMember(parentGroup, childGroup2) + ); } @Test public void addChildGroupForbiddenTest() throws Exception { - GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); - - Group parentGroup = null; - Group childGroup1 = null; - Group childGroup2 = null; - - try { - context.turnOffAuthorisationSystem(); - - parentGroup = GroupBuilder.createGroup(context).build(); - childGroup1 = GroupBuilder.createGroup(context).build(); - childGroup2 = GroupBuilder.createGroup(context).build(); - - context.commit(); - - parentGroup = context.reloadEntity(parentGroup); - childGroup1 = context.reloadEntity(childGroup1); - childGroup2 = context.reloadEntity(childGroup2); + context.turnOffAuthorisationSystem(); - context.restoreAuthSystemState(); - String authToken = getAuthToken(eperson.getEmail(), password); - getClient(authToken).perform( - post("/api/eperson/groups/" + parentGroup.getID() + "/subgroups") - .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) - .content(REST_SERVER_URL + "eperson/groups/" + childGroup1.getID() + "/\n" - + REST_SERVER_URL + "eperson/groups/" + childGroup2.getID() - ) - ).andExpect(status().isForbidden()); + Group parentGroup = GroupBuilder.createGroup(context).build(); + Group childGroup1 = GroupBuilder.createGroup(context).build(); + Group childGroup2 = GroupBuilder.createGroup(context).build(); - } finally { - if (parentGroup != null) { - GroupBuilder.deleteGroup(parentGroup.getID()); - } - if (childGroup1 != null) { - GroupBuilder.deleteGroup(childGroup1.getID()); - } - if (childGroup2 != null) { - GroupBuilder.deleteGroup(childGroup2.getID()); - } - } + context.restoreAuthSystemState(); + String authToken = getAuthToken(eperson.getEmail(), password); + getClient(authToken).perform( + post("/api/eperson/groups/" + parentGroup.getID() + "/subgroups") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + childGroup1.getID() + "/\n" + + REST_SERVER_URL + "eperson/groups/" + childGroup2.getID() + ) + ).andExpect(status().isForbidden()); } @Test public void addChildGroupUnauthorizedTest() throws Exception { - GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); - - Group parentGroup = null; - Group childGroup1 = null; - Group childGroup2 = null; - - try { - context.turnOffAuthorisationSystem(); - - parentGroup = GroupBuilder.createGroup(context).build(); - childGroup1 = GroupBuilder.createGroup(context).build(); - childGroup2 = GroupBuilder.createGroup(context).build(); + context.turnOffAuthorisationSystem(); - context.commit(); + Group parentGroup = GroupBuilder.createGroup(context).build(); + Group childGroup1 = GroupBuilder.createGroup(context).build(); + Group childGroup2 = GroupBuilder.createGroup(context).build(); - parentGroup = context.reloadEntity(parentGroup); - childGroup1 = context.reloadEntity(childGroup1); - childGroup2 = context.reloadEntity(childGroup2); + context.commit(); - context.restoreAuthSystemState(); - getClient().perform( - post("/api/eperson/groups/" + parentGroup.getID() + "/subgroups") - .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) - .content(REST_SERVER_URL + "eperson/groups/" + childGroup1.getID() + "/\n" - + REST_SERVER_URL + "eperson/groups/" + childGroup2.getID() - ) - ).andExpect(status().isUnauthorized()); + parentGroup = context.reloadEntity(parentGroup); + childGroup1 = context.reloadEntity(childGroup1); + childGroup2 = context.reloadEntity(childGroup2); - } finally { - if (parentGroup != null) { - GroupBuilder.deleteGroup(parentGroup.getID()); - } - if (childGroup1 != null) { - GroupBuilder.deleteGroup(childGroup1.getID()); - } - if (childGroup2 != null) { - GroupBuilder.deleteGroup(childGroup2.getID()); - } - } + context.restoreAuthSystemState(); + getClient().perform( + post("/api/eperson/groups/" + parentGroup.getID() + "/subgroups") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + childGroup1.getID() + "/\n" + + REST_SERVER_URL + "eperson/groups/" + childGroup2.getID() + ) + ).andExpect(status().isUnauthorized()); } @Test public void addChildGroupNotFoundTest() throws Exception { - GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); - - Group parentGroup = null; - Group childGroup1 = null; - Group childGroup2 = null; - - try { - context.turnOffAuthorisationSystem(); - - parentGroup = GroupBuilder.createGroup(context).build(); - childGroup1 = GroupBuilder.createGroup(context).build(); - childGroup2 = GroupBuilder.createGroup(context).build(); - - context.commit(); - - parentGroup = context.reloadEntity(parentGroup); - childGroup1 = context.reloadEntity(childGroup1); - childGroup2 = context.reloadEntity(childGroup2); + context.turnOffAuthorisationSystem(); - context.restoreAuthSystemState(); - String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform( - post("/api/eperson/groups/" + UUID.randomUUID() + "/subgroups") - .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) - .content(REST_SERVER_URL + "eperson/groups/" + childGroup1.getID() + "/\n" - + REST_SERVER_URL + "eperson/groups/" + childGroup2.getID() - ) - ).andExpect(status().isNotFound()); + Group parentGroup = GroupBuilder.createGroup(context).build(); + Group childGroup1 = GroupBuilder.createGroup(context).build(); + Group childGroup2 = GroupBuilder.createGroup(context).build(); - } finally { - if (parentGroup != null) { - GroupBuilder.deleteGroup(parentGroup.getID()); - } - if (childGroup1 != null) { - GroupBuilder.deleteGroup(childGroup1.getID()); - } - if (childGroup2 != null) { - GroupBuilder.deleteGroup(childGroup2.getID()); - } - } + context.restoreAuthSystemState(); + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform( + post("/api/eperson/groups/" + UUID.randomUUID() + "/subgroups") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + childGroup1.getID() + "/\n" + + REST_SERVER_URL + "eperson/groups/" + childGroup2.getID() + ) + ).andExpect(status().isNotFound()); } @Test public void addChildGroupUnprocessableTest() throws Exception { - GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); - - Group parentGroup = null; - Group childGroup1 = null; - Group childGroup2 = null; - - try { - context.turnOffAuthorisationSystem(); - - parentGroup = GroupBuilder.createGroup(context).build(); - childGroup1 = GroupBuilder.createGroup(context) - .withParent(parentGroup) - .build(); - childGroup2 = GroupBuilder.createGroup(context).build(); - groupService.update(context, childGroup1); - -// context.commit(); -// -// parentGroup = context.reloadEntity(parentGroup); -// childGroup1 = context.reloadEntity(childGroup1); -// childGroup2 = context.reloadEntity(childGroup2); + context.turnOffAuthorisationSystem(); - context.restoreAuthSystemState(); - String authToken = getAuthToken(admin.getEmail(), password); + Group parentGroup = GroupBuilder.createGroup(context).build(); + Group childGroup1 = GroupBuilder.createGroup(context) + .withParent(parentGroup) + .build(); + Group childGroup2 = GroupBuilder.createGroup(context).build(); - getClient(authToken).perform( - post("/api/eperson/groups/" + parentGroup.getID() + "/subgroups") - .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) - .content(REST_SERVER_URL + "eperson/groups/123456789\n" - + REST_SERVER_URL + "eperson/groups/" + childGroup2.getID() - ) - ).andExpect(status().isUnprocessableEntity()); + context.restoreAuthSystemState(); + String authToken = getAuthToken(admin.getEmail(), password); - // TODO - confirm with reviewers that this is a mistake - it actually should be No Content - // (see AddMember test) but was incorrectly expecting 422? - getClient(authToken).perform( - post("/api/eperson/groups/" + parentGroup.getID() + "/subgroups") - .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) - .content(REST_SERVER_URL + "eperson/groups/" + childGroup1.getID() + "/\n" - + REST_SERVER_URL + "eperson/groups/" + childGroup2.getID() - ) - ).andExpect(status().isNoContent()); + getClient(authToken).perform( + post("/api/eperson/groups/" + parentGroup.getID() + "/subgroups") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/123456789\n" + + REST_SERVER_URL + "eperson/groups/" + childGroup2.getID() + ) + ).andExpect(status().isUnprocessableEntity()); - } finally { - if (parentGroup != null) { - GroupBuilder.deleteGroup(parentGroup.getID()); - } - if (childGroup1 != null) { - GroupBuilder.deleteGroup(childGroup1.getID()); - } - if (childGroup2 != null) { - GroupBuilder.deleteGroup(childGroup2.getID()); - } - } + // TODO - confirm with reviewers that this is a mistake - it actually should be No Content + // (see AddMember test) but was incorrectly expecting 422? + getClient(authToken).perform( + post("/api/eperson/groups/" + parentGroup.getID() + "/subgroups") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + childGroup1.getID() + "/\n" + + REST_SERVER_URL + "eperson/groups/" + childGroup2.getID() + ) + ).andExpect(status().isNoContent()); } @Test @@ -1081,251 +951,118 @@ public void addMemberTest() throws Exception { @Test public void addMemberCommunityAdminTest() throws Exception { - CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); - EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); - - Community community = null; - Group parentGroup = null; - EPerson member1 = null; - EPerson member2 = null; - - try { - context.turnOffAuthorisationSystem(); - - community = CommunityBuilder.createCommunity(context).build(); - parentGroup = GroupBuilder.createCommunityAdminGroup(context, community) - .addMember(eperson) - .build(); - member1 = EPersonBuilder.createEPerson(context).build(); - member2 = EPersonBuilder.createEPerson(context).build(); - context.commit(); - - parentGroup = context.reloadEntity(parentGroup); - member1 = context.reloadEntity(member1); - member2 = context.reloadEntity(member2); + context.turnOffAuthorisationSystem(); - context.restoreAuthSystemState(); - String authToken = getAuthToken(eperson.getEmail(), password); - getClient(authToken).perform( - post("/api/eperson/groups/" + parentGroup.getID() + "/epersons") - .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) - .content(REST_SERVER_URL + "eperson/groups/" + member1.getID() + "/\n" - + REST_SERVER_URL + "eperson/groups/" + member2.getID() - ) - ).andExpect(status().isNoContent()); + Community community = CommunityBuilder.createCommunity(context).build(); + Group parentGroup = GroupBuilder.createCommunityAdminGroup(context, community) + .addMember(eperson) + .build(); + EPerson member1 = EPersonBuilder.createEPerson(context).build(); + EPerson member2 = EPersonBuilder.createEPerson(context).build(); - parentGroup = context.reloadEntity(parentGroup); - member1 = context.reloadEntity(member1); - member2 = context.reloadEntity(member2); + context.restoreAuthSystemState(); + String authToken = getAuthToken(eperson.getEmail(), password); + getClient(authToken).perform( + post("/api/eperson/groups/" + parentGroup.getID() + "/epersons") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + member1.getID() + "/\n" + + REST_SERVER_URL + "eperson/groups/" + member2.getID() + ) + ).andExpect(status().isNoContent()); - assertTrue( - groupService.isMember(context, member1, parentGroup) - ); + parentGroup = context.reloadEntity(parentGroup); + member1 = context.reloadEntity(member1); + member2 = context.reloadEntity(member2); - assertTrue( - groupService.isMember(context, member2, parentGroup) - ); + assertTrue( + groupService.isMember(context, member1, parentGroup) + ); - } finally { - if (community != null) { - CommunityBuilder.deleteCommunity(community.getID()); - } - if (parentGroup != null) { - GroupBuilder.deleteGroup(parentGroup.getID()); - } - if (member1 != null) { - EPersonBuilder.deleteEPerson(member1.getID()); - } - if (member2 != null) { - EPersonBuilder.deleteEPerson(member2.getID()); - } - } + assertTrue( + groupService.isMember(context, member2, parentGroup) + ); } @Test public void addMemberForbiddenTest() throws Exception { - GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); - EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); - - Group parentGroup = null; - EPerson member1 = null; - EPerson member2 = null; - - try { - context.turnOffAuthorisationSystem(); - - parentGroup = GroupBuilder.createGroup(context).build(); - member1 = EPersonBuilder.createEPerson(context).build(); - member2 = EPersonBuilder.createEPerson(context).build(); - - context.commit(); - - parentGroup = context.reloadEntity(parentGroup); - member1 = context.reloadEntity(member1); - member2 = context.reloadEntity(member2); + context.turnOffAuthorisationSystem(); - context.restoreAuthSystemState(); - String authToken = getAuthToken(eperson.getEmail(), password); - getClient(authToken).perform( - post("/api/eperson/groups/" + parentGroup.getID() + "/epersons") - .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) - .content(REST_SERVER_URL + "eperson/groups/" + member1.getID() + "/\n" - + REST_SERVER_URL + "eperson/groups/" + member2.getID() - ) - ).andExpect(status().isForbidden()); + Group parentGroup = GroupBuilder.createGroup(context).build(); + EPerson member1 = EPersonBuilder.createEPerson(context).build(); + EPerson member2 = EPersonBuilder.createEPerson(context).build(); - } finally { - if (parentGroup != null) { - GroupBuilder.deleteGroup(parentGroup.getID()); - } - if (member1 != null) { - EPersonBuilder.deleteEPerson(member1.getID()); - } - if (member2 != null) { - EPersonBuilder.deleteEPerson(member2.getID()); - } - } + context.restoreAuthSystemState(); + String authToken = getAuthToken(eperson.getEmail(), password); + getClient(authToken).perform( + post("/api/eperson/groups/" + parentGroup.getID() + "/epersons") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + member1.getID() + "/\n" + + REST_SERVER_URL + "eperson/groups/" + member2.getID() + ) + ).andExpect(status().isForbidden()); } @Test public void addMemberUnauthorizedTest() throws Exception { - GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); - EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); - - Group parentGroup = null; - EPerson member1 = null; - EPerson member2 = null; - - try { - context.turnOffAuthorisationSystem(); - - parentGroup = GroupBuilder.createGroup(context).build(); - member1 = EPersonBuilder.createEPerson(context).build(); - member2 = EPersonBuilder.createEPerson(context).build(); - - context.commit(); - - parentGroup = context.reloadEntity(parentGroup); - member1 = context.reloadEntity(member1); - member2 = context.reloadEntity(member2); + context.turnOffAuthorisationSystem(); - context.restoreAuthSystemState(); - getClient().perform( - post("/api/eperson/groups/" + parentGroup.getID() + "/epersons") - .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) - .content(REST_SERVER_URL + "eperson/groups/" + member1.getID() + "/\n" - + REST_SERVER_URL + "eperson/groups/" + member2.getID() - ) - ).andExpect(status().isUnauthorized()); + Group parentGroup = GroupBuilder.createGroup(context).build(); + EPerson member1 = EPersonBuilder.createEPerson(context).build(); + EPerson member2 = EPersonBuilder.createEPerson(context).build(); - } finally { - if (parentGroup != null) { - GroupBuilder.deleteGroup(parentGroup.getID()); - } - if (member1 != null) { - EPersonBuilder.deleteEPerson(member1.getID()); - } - if (member2 != null) { - EPersonBuilder.deleteEPerson(member2.getID()); - } - } + context.restoreAuthSystemState(); + getClient().perform( + post("/api/eperson/groups/" + parentGroup.getID() + "/epersons") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + member1.getID() + "/\n" + + REST_SERVER_URL + "eperson/groups/" + member2.getID() + ) + ).andExpect(status().isUnauthorized()); } @Test public void addMemberNotFoundTest() throws Exception { - GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); - EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); - - Group parentGroup = null; - EPerson member1 = null; - EPerson member2 = null; - - try { - context.turnOffAuthorisationSystem(); - - parentGroup = GroupBuilder.createGroup(context).build(); - member1 = EPersonBuilder.createEPerson(context).build(); - member2 = EPersonBuilder.createEPerson(context).build(); - - context.commit(); - - parentGroup = context.reloadEntity(parentGroup); - member1 = context.reloadEntity(member1); - member2 = context.reloadEntity(member2); + context.turnOffAuthorisationSystem(); - context.restoreAuthSystemState(); - String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform( - post("/api/eperson/groups/" + UUID.randomUUID() + "/epersons") - .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) - .content(REST_SERVER_URL + "eperson/groups/" + member1.getID() + "/\n" - + REST_SERVER_URL + "eperson/groups/" + member2.getID() - ) - ).andExpect(status().isNotFound()); + Group parentGroup = GroupBuilder.createGroup(context).build(); + EPerson member1 = EPersonBuilder.createEPerson(context).build(); + EPerson member2 = EPersonBuilder.createEPerson(context).build(); - } finally { - if (parentGroup != null) { - GroupBuilder.deleteGroup(parentGroup.getID()); - } - if (member1 != null) { - EPersonBuilder.deleteEPerson(member1.getID()); - } - if (member2 != null) { - EPersonBuilder.deleteEPerson(member2.getID()); - } - } + context.restoreAuthSystemState(); + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform( + post("/api/eperson/groups/" + UUID.randomUUID() + "/epersons") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + member1.getID() + "/\n" + + REST_SERVER_URL + "eperson/groups/" + member2.getID() + ) + ).andExpect(status().isNotFound()); } @Test public void addMemberUnprocessableTest() throws Exception { - GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); - EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); - - Group parentGroup = null; - EPerson member1 = null; - EPerson member2 = null; - - try { - context.turnOffAuthorisationSystem(); - - parentGroup = GroupBuilder.createGroup(context).build(); - member1 = EPersonBuilder.createEPerson(context).build(); - member2 = EPersonBuilder.createEPerson(context).build(); - - context.commit(); - - parentGroup = context.reloadEntity(parentGroup); - member1 = context.reloadEntity(member1); - member2 = context.reloadEntity(member2); + context.turnOffAuthorisationSystem(); - context.restoreAuthSystemState(); - String authToken = getAuthToken(admin.getEmail(), password); + Group parentGroup = GroupBuilder.createGroup(context).build(); + EPerson member1 = EPersonBuilder.createEPerson(context).build(); + EPerson member2 = EPersonBuilder.createEPerson(context).build(); - getClient(authToken).perform( - post("/api/eperson/groups/" + parentGroup.getID() + "/epersons") - .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) - .content(REST_SERVER_URL + "eperson/groups/123456789\n" - + REST_SERVER_URL + "eperson/groups/" + member2.getID() - ) - ).andExpect(status().isUnprocessableEntity()); + context.restoreAuthSystemState(); + String authToken = getAuthToken(admin.getEmail(), password); - } finally { - if (parentGroup != null) { - GroupBuilder.deleteGroup(parentGroup.getID()); - } - if (member1 != null) { - EPersonBuilder.deleteEPerson(member1.getID()); - } - if (member2 != null) { - EPersonBuilder.deleteEPerson(member2.getID()); - } - } + getClient(authToken).perform( + post("/api/eperson/groups/" + parentGroup.getID() + "/epersons") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/123456789\n" + + REST_SERVER_URL + "eperson/groups/" + member2.getID() + ) + ).andExpect(status().isUnprocessableEntity()); } @Test @@ -1378,29 +1115,18 @@ public void removeChildGroupTest() throws Exception { @Test public void removeChildGroupCommunityAdminTest() throws Exception { - CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); - Community community = null; - Group parentGroup = null; - Group childGroup = null; - - try { context.turnOffAuthorisationSystem(); - community = CommunityBuilder.createCommunity(context).build(); - parentGroup = GroupBuilder.createCommunityAdminGroup(context, community) + Community community = CommunityBuilder.createCommunity(context).build(); + Group parentGroup = GroupBuilder.createCommunityAdminGroup(context, community) .addMember(eperson) .build(); - childGroup = GroupBuilder.createGroup(context) + Group childGroup = GroupBuilder.createGroup(context) .withParent(parentGroup) .build(); - context.commit(); - - parentGroup = context.reloadEntity(parentGroup); - childGroup = context.reloadEntity(childGroup); - context.restoreAuthSystemState(); String authToken = getAuthToken(eperson.getEmail(), password); getClient(authToken).perform( @@ -1413,163 +1139,71 @@ public void removeChildGroupCommunityAdminTest() throws Exception { assertFalse( groupService.isMember(parentGroup, childGroup) ); - - } finally { - if (community != null) { - CommunityBuilder.deleteCommunity(community.getID()); - } - if (parentGroup != null) { - GroupBuilder.deleteGroup(parentGroup.getID()); - } - if (childGroup != null) { - GroupBuilder.deleteGroup(childGroup.getID()); - } - } } @Test public void removeChildGroupForbiddenTest() throws Exception { - GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); - - Group parentGroup = null; - Group childGroup = null; - - try { - context.turnOffAuthorisationSystem(); - - parentGroup = GroupBuilder.createGroup(context).build(); - childGroup = GroupBuilder.createGroup(context).build(); - - context.commit(); - - parentGroup = context.reloadEntity(parentGroup); - childGroup = context.reloadEntity(childGroup); + context.turnOffAuthorisationSystem(); - context.restoreAuthSystemState(); - String authToken = getAuthToken(eperson.getEmail(), password); - getClient(authToken).perform( - delete("/api/eperson/groups/" + parentGroup.getID() + "/subgroups/" + childGroup.getID()) - ).andExpect(status().isForbidden()); + Group parentGroup = GroupBuilder.createGroup(context).build(); + Group childGroup = GroupBuilder.createGroup(context).build(); - } finally { - if (parentGroup != null) { - GroupBuilder.deleteGroup(parentGroup.getID()); - } - if (childGroup != null) { - GroupBuilder.deleteGroup(childGroup.getID()); - } - } + context.restoreAuthSystemState(); + String authToken = getAuthToken(eperson.getEmail(), password); + getClient(authToken).perform( + delete("/api/eperson/groups/" + parentGroup.getID() + "/subgroups/" + childGroup.getID()) + ).andExpect(status().isForbidden()); } @Test public void removeChildGroupUnauthorizedTest() throws Exception { - GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); - - Group parentGroup = null; - Group childGroup = null; - - try { - context.turnOffAuthorisationSystem(); - - parentGroup = GroupBuilder.createGroup(context).build(); - childGroup = GroupBuilder.createGroup(context).build(); - - context.commit(); - - parentGroup = context.reloadEntity(parentGroup); - childGroup = context.reloadEntity(childGroup); + context.turnOffAuthorisationSystem(); - context.restoreAuthSystemState(); - getClient().perform( - delete("/api/eperson/groups/" + parentGroup.getID() + "/subgroups/" + childGroup.getID()) - ).andExpect(status().isUnauthorized()); + Group parentGroup = GroupBuilder.createGroup(context).build(); + Group childGroup = GroupBuilder.createGroup(context).build(); - } finally { - if (parentGroup != null) { - GroupBuilder.deleteGroup(parentGroup.getID()); - } - if (childGroup != null) { - GroupBuilder.deleteGroup(childGroup.getID()); - } - } + context.restoreAuthSystemState(); + getClient().perform( + delete("/api/eperson/groups/" + parentGroup.getID() + "/subgroups/" + childGroup.getID()) + ).andExpect(status().isUnauthorized()); } @Test public void removeChildGroupNotFoundTest() throws Exception { - GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); - - Group parentGroup = null; - Group childGroup = null; - - try { - context.turnOffAuthorisationSystem(); - - parentGroup = GroupBuilder.createGroup(context).build(); - childGroup = GroupBuilder.createGroup(context) - .withParent(parentGroup) - .build(); - - context.commit(); - - parentGroup = context.reloadEntity(parentGroup); - childGroup = context.reloadEntity(childGroup); + context.turnOffAuthorisationSystem(); - context.restoreAuthSystemState(); - String authToken = getAuthToken(admin.getEmail(), password); + Group parentGroup = GroupBuilder.createGroup(context).build(); + Group childGroup = GroupBuilder.createGroup(context) + .withParent(parentGroup) + .build(); - getClient(authToken).perform( - delete("/api/eperson/groups/" + UUID.randomUUID() + "/subgroups/" + childGroup.getID()) - ).andExpect(status().isNotFound()); + context.restoreAuthSystemState(); + String authToken = getAuthToken(admin.getEmail(), password); - } finally { - if (parentGroup != null) { - GroupBuilder.deleteGroup(parentGroup.getID()); - } - if (childGroup != null) { - GroupBuilder.deleteGroup(childGroup.getID()); - } - } + getClient(authToken).perform( + delete("/api/eperson/groups/" + UUID.randomUUID() + "/subgroups/" + childGroup.getID()) + ).andExpect(status().isNotFound()); } @Test public void removeChildGroupUnprocessableTest() throws Exception { - GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); - - Group parentGroup = null; - Group childGroup = null; - - try { - context.turnOffAuthorisationSystem(); - - parentGroup = GroupBuilder.createGroup(context).build(); - childGroup = GroupBuilder.createGroup(context) - .withParent(parentGroup) - .build(); - - context.commit(); - - parentGroup = context.reloadEntity(parentGroup); - childGroup = context.reloadEntity(childGroup); + context.turnOffAuthorisationSystem(); - context.restoreAuthSystemState(); - String authToken = getAuthToken(admin.getEmail(), password); + Group parentGroup = GroupBuilder.createGroup(context).build(); + Group childGroup = GroupBuilder.createGroup(context) + .withParent(parentGroup) + .build(); - getClient(authToken).perform( - delete("/api/eperson/groups/" + parentGroup.getID() + "/subgroups/" + UUID.randomUUID()) - ).andExpect(status().isUnprocessableEntity()); + context.restoreAuthSystemState(); + String authToken = getAuthToken(admin.getEmail(), password); - } finally { - if (parentGroup != null) { - GroupBuilder.deleteGroup(parentGroup.getID()); - } - if (childGroup != null) { - GroupBuilder.deleteGroup(childGroup.getID()); - } - } + getClient(authToken).perform( + delete("/api/eperson/groups/" + parentGroup.getID() + "/subgroups/" + UUID.randomUUID()) + ).andExpect(status().isUnprocessableEntity()); } @Test @@ -1613,31 +1247,19 @@ public void removeMemberTest() throws Exception { @Test public void removeMemberCommunityAdminTest() throws Exception { - CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); - EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); - - Community community = null; - Group parentGroup = null; - EPerson member = null; - try { context.turnOffAuthorisationSystem(); - community = CommunityBuilder.createCommunity(context).build(); - member = EPersonBuilder.createEPerson(context).build(); - parentGroup = GroupBuilder.createCommunityAdminGroup(context, community) + Community community = CommunityBuilder.createCommunity(context).build(); + EPerson member = EPersonBuilder.createEPerson(context).build(); + Group parentGroup = GroupBuilder.createCommunityAdminGroup(context, community) .addMember(member) .addMember(eperson) .build(); assertTrue(groupService.isMember(context, member, parentGroup)); - context.commit(); - - parentGroup = context.reloadEntity(parentGroup); - member = context.reloadEntity(member); - context.restoreAuthSystemState(); String authToken = getAuthToken(eperson.getEmail(), password); getClient(authToken).perform( @@ -1650,171 +1272,75 @@ public void removeMemberCommunityAdminTest() throws Exception { assertFalse( groupService.isMember(context, member, parentGroup) ); - - } finally { - if (community != null) { - CommunityBuilder.deleteCommunity(community.getID()); - } - if (parentGroup != null) { - GroupBuilder.deleteGroup(parentGroup.getID()); - } - if (member != null) { - EPersonBuilder.deleteEPerson(member.getID()); - } - } } @Test public void removeMemberForbiddenTest() throws Exception { - GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); - EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); - - Group parentGroup = null; - EPerson member = null; - - try { - context.turnOffAuthorisationSystem(); - - member = EPersonBuilder.createEPerson(context).build(); - parentGroup = GroupBuilder.createGroup(context) - .addMember(member) - .build(); - - context.commit(); - - parentGroup = context.reloadEntity(parentGroup); - member = context.reloadEntity(member); + context.turnOffAuthorisationSystem(); - context.restoreAuthSystemState(); - String authToken = getAuthToken(eperson.getEmail(), password); - getClient(authToken).perform( - delete("/api/eperson/groups/" + parentGroup.getID() + "/epersons/" + member.getID()) - ).andExpect(status().isForbidden()); + EPerson member = EPersonBuilder.createEPerson(context).build(); + Group parentGroup = GroupBuilder.createGroup(context) + .addMember(member) + .build(); - } finally { - if (parentGroup != null) { - GroupBuilder.deleteGroup(parentGroup.getID()); - } - if (member != null) { - EPersonBuilder.deleteEPerson(member.getID()); - } - } + context.restoreAuthSystemState(); + String authToken = getAuthToken(eperson.getEmail(), password); + getClient(authToken).perform( + delete("/api/eperson/groups/" + parentGroup.getID() + "/epersons/" + member.getID()) + ).andExpect(status().isForbidden()); } @Test public void removeMemberUnauthorizedTest() throws Exception { - GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); - EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); - - Group parentGroup = null; - EPerson member = null; - - try { - context.turnOffAuthorisationSystem(); - - member = EPersonBuilder.createEPerson(context).build(); - parentGroup = GroupBuilder.createGroup(context) - .addMember(member) - .build(); - - context.commit(); - - parentGroup = context.reloadEntity(parentGroup); - member = context.reloadEntity(member); + context.turnOffAuthorisationSystem(); - context.restoreAuthSystemState(); - getClient().perform( - delete("/api/eperson/groups/" + parentGroup.getID() + "/epersons/" + member.getID()) - ).andExpect(status().isUnauthorized()); + EPerson member = EPersonBuilder.createEPerson(context).build(); + Group parentGroup = GroupBuilder.createGroup(context) + .addMember(member) + .build(); - } finally { - if (parentGroup != null) { - GroupBuilder.deleteGroup(parentGroup.getID()); - } - if (member != null) { - EPersonBuilder.deleteEPerson(member.getID()); - } - } + context.restoreAuthSystemState(); + getClient().perform( + delete("/api/eperson/groups/" + parentGroup.getID() + "/epersons/" + member.getID()) + ).andExpect(status().isUnauthorized()); } @Test public void removeMemberNotFoundTest() throws Exception { - GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); - EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); - - Group parentGroup = null; - EPerson member = null; - - try { - context.turnOffAuthorisationSystem(); - - member = EPersonBuilder.createEPerson(context).build(); - parentGroup = GroupBuilder.createGroup(context) - .addMember(member) - .build(); - - context.commit(); - - parentGroup = context.reloadEntity(parentGroup); - member = context.reloadEntity(member); + context.turnOffAuthorisationSystem(); - context.restoreAuthSystemState(); - String authToken = getAuthToken(admin.getEmail(), password); + EPerson member = EPersonBuilder.createEPerson(context).build(); + Group parentGroup = GroupBuilder.createGroup(context) + .addMember(member) + .build(); - getClient(authToken).perform( - delete("/api/eperson/groups/" + UUID.randomUUID() + "/epersons/" + member.getID()) - ).andExpect(status().isNotFound()); + context.restoreAuthSystemState(); + String authToken = getAuthToken(admin.getEmail(), password); - } finally { - if (parentGroup != null) { - GroupBuilder.deleteGroup(parentGroup.getID()); - } - if (member != null) { - EPersonBuilder.deleteEPerson(member.getID()); - } - } + getClient(authToken).perform( + delete("/api/eperson/groups/" + UUID.randomUUID() + "/epersons/" + member.getID()) + ).andExpect(status().isNotFound()); } @Test public void removeMemberUnprocessableTest() throws Exception { - GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); - EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); - - Group parentGroup = null; - EPerson member = null; - - try { context.turnOffAuthorisationSystem(); - member = EPersonBuilder.createEPerson(context).build(); - parentGroup = GroupBuilder.createGroup(context) + EPerson member = EPersonBuilder.createEPerson(context).build(); + Group parentGroup = GroupBuilder.createGroup(context) .addMember(member) .build(); - context.commit(); - - parentGroup = context.reloadEntity(parentGroup); - member = context.reloadEntity(member); - context.restoreAuthSystemState(); String authToken = getAuthToken(admin.getEmail(), password); getClient(authToken).perform( delete("/api/eperson/groups/" + parentGroup.getID() + "/epersons/" + UUID.randomUUID()) ).andExpect(status().isUnprocessableEntity()); - - } finally { - if (parentGroup != null) { - GroupBuilder.deleteGroup(parentGroup.getID()); - } - if (member != null) { - EPersonBuilder.deleteEPerson(member.getID()); - } - } } @Test From 282d4db36bebb3e159a09f222e2aa13160a99468 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Fri, 6 Sep 2024 16:43:36 +0200 Subject: [PATCH 274/939] #9806: Align provider reg in CreateMissingIdentifiersIT with other tests VersionedHandlerIdentifierProviderIT uses this registerProvider method which looks more reliable and doesn't do a refresh/reload of applicationContext after (which I suspected might have an odd interaction with VersioningWithRelationshipsIT and its createBean() calls?) (cherry picked from commit 90536e443b7fedef0481a34326cafc58fb334396) --- .../general/CreateMissingIdentifiersIT.java | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java b/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java index 2a07799deee..8038a715332 100644 --- a/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java +++ b/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java @@ -10,6 +10,8 @@ import static org.junit.Assert.assertEquals; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.builder.CollectionBuilder; @@ -19,7 +21,10 @@ import org.dspace.content.Item; import org.dspace.core.factory.CoreServiceFactory; import org.dspace.curate.Curator; +import org.dspace.identifier.IdentifierProvider; +import org.dspace.identifier.IdentifierServiceImpl; import org.dspace.identifier.VersionedHandleIdentifierProviderWithCanonicalHandles; +import org.dspace.kernel.ServiceManager; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.junit.After; @@ -32,10 +37,23 @@ */ public class CreateMissingIdentifiersIT extends AbstractIntegrationTestWithDatabase { + private ServiceManager serviceManager; + private IdentifierServiceImpl identifierService; private static final String P_TASK_DEF = "plugin.named.org.dspace.curate.CurationTask"; private static final String TASK_NAME = "test"; + @Override + public void setUp() throws Exception { + super.setUp(); + context.turnOffAuthorisationSystem(); + + serviceManager = DSpaceServicesFactory.getInstance().getServiceManager(); + identifierService = serviceManager.getServicesByType(IdentifierServiceImpl.class).get(0); + // Clean out providers to avoid any being used for creation of community and collection + identifierService.setProviders(new ArrayList<>()); + } + @Test public void testPerform() throws IOException { @@ -67,11 +85,7 @@ public void testPerform() /* * Now install an incompatible provider to make the task fail. */ - DSpaceServicesFactory.getInstance() - .getServiceManager() - .registerServiceClass( - VersionedHandleIdentifierProviderWithCanonicalHandles.class.getCanonicalName(), - VersionedHandleIdentifierProviderWithCanonicalHandles.class); + registerProvider(VersionedHandleIdentifierProviderWithCanonicalHandles.class); curator.curate(context, item); System.out.format("With incompatible provider, result is '%s'.\n", @@ -86,4 +100,14 @@ public void destroy() throws Exception { super.destroy(); DSpaceServicesFactory.getInstance().getServiceManager().getApplicationContext().refresh(); } + + private void registerProvider(Class type) { + // Register our new provider + serviceManager.registerServiceClass(type.getName(), type); + IdentifierProvider identifierProvider = + (IdentifierProvider) serviceManager.getServiceByName(type.getName(), type); + + // Overwrite the identifier-service's providers with the new one to ensure only this provider is used + identifierService.setProviders(List.of(identifierProvider)); + } } From 48c8848fc21ed739c99045b5e3e2b690297ba3da Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Fri, 6 Sep 2024 16:45:05 +0200 Subject: [PATCH 275/939] #9806: Use builders for creation in VersioningWithRelationshipsIT I am a bit uncertain about the createBean() calls here, why do we not simply *get* the configured beans using the service manager instead, but will look at that in a separate change (cherry picked from commit 3521ab6d3598e716cc9fe8e938e883b302006580) --- .../VersioningWithRelationshipsIT.java | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsIT.java b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsIT.java index 44653300e0d..accc52d0d83 100644 --- a/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsIT.java +++ b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsIT.java @@ -49,6 +49,7 @@ import org.dspace.builder.ItemBuilder; import org.dspace.builder.RelationshipBuilder; import org.dspace.builder.RelationshipTypeBuilder; +import org.dspace.builder.VersionBuilder; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.InstallItemService; import org.dspace.content.service.ItemService; @@ -62,8 +63,6 @@ import org.dspace.kernel.ServiceManager; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.versioning.Version; -import org.dspace.versioning.factory.VersionServiceFactory; -import org.dspace.versioning.service.VersioningService; import org.hamcrest.Matcher; import org.junit.Assert; import org.junit.Before; @@ -74,8 +73,6 @@ public class VersioningWithRelationshipsIT extends AbstractIntegrationTestWithDa private final RelationshipService relationshipService = ContentServiceFactory.getInstance().getRelationshipService(); - private final VersioningService versioningService = - VersionServiceFactory.getInstance().getVersionService(); private final WorkspaceItemService workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService(); private final InstallItemService installItemService = @@ -291,7 +288,7 @@ public void test_createNewVersionOfItemOnLeftSideOfRelationships() throws Except // create a new version of the publication // ///////////////////////////////////////////// - Version newVersion = versioningService.createNewVersion(context, originalPublication); + Version newVersion = VersionBuilder.createVersion(context, originalPublication, "test").build(); Item newPublication = newVersion.getItem(); assertNotSame(originalPublication, newPublication); @@ -567,7 +564,7 @@ public void test_createNewVersionOfItemAndModifyRelationships() throws Exception // create a new version of the publication // ///////////////////////////////////////////// - Version newVersion = versioningService.createNewVersion(context, originalPublication); + Version newVersion = VersionBuilder.createVersion(context, originalPublication, "test").build(); Item newPublication = newVersion.getItem(); assertNotSame(originalPublication, newPublication); @@ -927,7 +924,7 @@ public void test_createNewVersionOfItemOnRightSideOfRelationships() throws Excep // create a new version of the person // //////////////////////////////////////// - Version newVersion = versioningService.createNewVersion(context, originalPerson); + Version newVersion = VersionBuilder.createVersion(context, originalPerson, "test").build(); Item newPerson = newVersion.getItem(); assertNotSame(originalPerson, newPerson); @@ -1300,7 +1297,7 @@ public void test_createNewVersionOfItemAndVerifyMetadataOrder() throws Exception // create new version of publication // /////////////////////////////////////// - Version newVersion = versioningService.createNewVersion(context, originalPublication); + Version newVersion = VersionBuilder.createVersion(context, originalPublication, "test").build(); Item newPublication = newVersion.getItem(); assertNotSame(originalPublication, newPublication); @@ -1463,7 +1460,7 @@ public void test_createNewVersionOfItemWithAddRemoveMove() throws Exception { // create a new version of the publication // ///////////////////////////////////////////// - Version newVersion = versioningService.createNewVersion(context, originalPublication); + Version newVersion = VersionBuilder.createVersion(context, originalPublication, "test").build(); Item newPublication = newVersion.getItem(); assertNotSame(originalPublication, newPublication); @@ -1782,7 +1779,7 @@ public void test_placeRecalculationAfterDelete() throws Exception { // create new version - volume 1.2 // ///////////////////////////////////// - Item v1_2 = versioningService.createNewVersion(context, v1_1).getItem(); + Item v1_2 = VersionBuilder.createVersion(context, v1_1, "test").build().getItem(); installItemService.installItem(context, workspaceItemService.findByItem(context, v1_2)); context.commit(); @@ -1790,7 +1787,7 @@ public void test_placeRecalculationAfterDelete() throws Exception { // create new version - issue 3.2 // //////////////////////////////////// - Item i3_2 = versioningService.createNewVersion(context, i3_1).getItem(); + Item i3_2 = VersionBuilder.createVersion(context, i3_1, "test").build().getItem(); installItemService.installItem(context, workspaceItemService.findByItem(context, i3_2)); context.commit(); @@ -2316,7 +2313,7 @@ public void test_placeRecalculationAfterDelete_complex() throws Exception { // create new version - person 3.2 // ///////////////////////////////////// - Item pe3_2 = versioningService.createNewVersion(context, pe3_1).getItem(); + Item pe3_2 = VersionBuilder.createVersion(context, pe3_1, "test").build().getItem(); installItemService.installItem(context, workspaceItemService.findByItem(context, pe3_2)); context.commit(); @@ -2324,7 +2321,7 @@ public void test_placeRecalculationAfterDelete_complex() throws Exception { // create new version - project 3.2 // ////////////////////////////////////// - Item pr3_2 = versioningService.createNewVersion(context, pr3_1).getItem(); + Item pr3_2 = VersionBuilder.createVersion(context, pr3_1, "test").build().getItem(); installItemService.installItem(context, workspaceItemService.findByItem(context, pr3_2)); context.commit(); @@ -3056,7 +3053,7 @@ public void test_placeRecalculationNoUseForPlace() throws Exception { // create new version - volume 1.2 // ///////////////////////////////////// - Item v1_2 = versioningService.createNewVersion(context, v1_1).getItem(); + Item v1_2 = VersionBuilder.createVersion(context, v1_1, "test").build().getItem(); installItemService.installItem(context, workspaceItemService.findByItem(context, v1_2)); context.commit(); @@ -3064,7 +3061,7 @@ public void test_placeRecalculationNoUseForPlace() throws Exception { // create new version - issue 3.2 // //////////////////////////////////// - Item i3_2 = versioningService.createNewVersion(context, i3_1).getItem(); + Item i3_2 = VersionBuilder.createVersion(context, i3_1, "test").build().getItem(); installItemService.installItem(context, workspaceItemService.findByItem(context, i3_2)); context.commit(); @@ -3509,7 +3506,7 @@ public void test_virtualMetadataPreserved() throws Exception { // create a new version of publication 1 and archive // /////////////////////////////////////////////////////// - Item publication1V2 = versioningService.createNewVersion(context, publication1V1).getItem(); + Item publication1V2 = VersionBuilder.createVersion(context, publication1V1, "test").build().getItem(); installItemService.installItem(context, workspaceItemService.findByItem(context, publication1V2)); context.dispatchEvents(); @@ -3517,7 +3514,7 @@ public void test_virtualMetadataPreserved() throws Exception { // create new version of person 1 // //////////////////////////////////// - Item person1V2 = versioningService.createNewVersion(context, person1V1).getItem(); + Item person1V2 = VersionBuilder.createVersion(context, person1V1, "test").build().getItem(); // update "Smith, Donald" to "Smith, D." itemService.replaceMetadata( context, person1V2, "person", "givenName", null, null, "D.", @@ -3853,7 +3850,7 @@ public void test_virtualMetadataPreserved() throws Exception { // create new version of person 2 // //////////////////////////////////// - Item person2V2 = versioningService.createNewVersion(context, person2V1).getItem(); + Item person2V2 = VersionBuilder.createVersion(context, person2V1, "test").build().getItem(); Relationship rel1 = getRelationship(publication1V2, isAuthorOfPublication, person2V2); assertNotNull(rel1); rel1.setRightwardValue("Doe, Jane Jr"); From 89796ece6b4e7ad3401c02ecab54e1f33926263c Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Fri, 6 Sep 2024 17:15:15 +0200 Subject: [PATCH 276/939] #9806: Set explicit id provider before VersioningWithRelationshipsIT (cherry picked from commit 4af690065038d9cb401d0fa1fc5fbfe0fa1fe2c7) --- .../VersioningWithRelationshipsIT.java | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsIT.java b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsIT.java index accc52d0d83..10cb30cd52b 100644 --- a/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsIT.java +++ b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsIT.java @@ -60,6 +60,9 @@ import org.dspace.content.virtual.VirtualMetadataPopulator; import org.dspace.core.Constants; import org.dspace.discovery.SolrSearchCore; +import org.dspace.identifier.IdentifierProvider; +import org.dspace.identifier.IdentifierServiceImpl; +import org.dspace.identifier.VersionedHandleIdentifierProvider; import org.dspace.kernel.ServiceManager; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.versioning.Version; @@ -81,7 +84,7 @@ public class VersioningWithRelationshipsIT extends AbstractIntegrationTestWithDa ContentServiceFactory.getInstance().getItemService(); private final SolrSearchCore solrSearchCore = DSpaceServicesFactory.getInstance().getServiceManager().getServicesByType(SolrSearchCore.class).get(0); - + private IdentifierServiceImpl identifierService; protected Community community; protected Collection collection; protected EntityType publicationEntityType; @@ -98,6 +101,22 @@ public class VersioningWithRelationshipsIT extends AbstractIntegrationTestWithDa protected RelationshipType isIssueOfJournalVolume; protected RelationshipType isProjectOfPerson; + private void registerProvider(Class type) { + // Register our new provider + IdentifierProvider identifierProvider = + (IdentifierProvider) DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName(type.getName(), type); + if (identifierProvider == null) { + DSpaceServicesFactory.getInstance().getServiceManager().registerServiceClass(type.getName(), type); + identifierProvider = (IdentifierProvider) DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName(type.getName(), type); + } + + // Overwrite the identifier-service's providers with the new one to ensure only this provider is used + identifierService = DSpaceServicesFactory.getInstance().getServiceManager() + .getServicesByType(IdentifierServiceImpl.class).get(0); + identifierService.setProviders(new ArrayList<>()); + identifierService.setProviders(List.of(identifierProvider)); + } + @Override @Before public void setUp() throws Exception { @@ -105,6 +124,9 @@ public void setUp() throws Exception { context.turnOffAuthorisationSystem(); + + registerProvider(VersionedHandleIdentifierProvider.class); + community = CommunityBuilder.createCommunity(context) .withName("community") .build(); From 123aedde04b0e05419d18767a59f6008126589a9 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Fri, 6 Sep 2024 17:58:23 +0200 Subject: [PATCH 277/939] #9806: Move cleanup of handle provider to destroy in VersionedHandleIdentifierProviderIT (cherry picked from commit f6cabe648dfcda786afdf7d5411dc3d7ad85c405) --- .../VersioningWithRelationshipsIT.java | 23 ------------------- .../VersionedHandleIdentifierProviderIT.java | 22 ++++++++++++++++-- 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsIT.java b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsIT.java index 10cb30cd52b..3acc4ca146e 100644 --- a/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsIT.java +++ b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsIT.java @@ -60,9 +60,6 @@ import org.dspace.content.virtual.VirtualMetadataPopulator; import org.dspace.core.Constants; import org.dspace.discovery.SolrSearchCore; -import org.dspace.identifier.IdentifierProvider; -import org.dspace.identifier.IdentifierServiceImpl; -import org.dspace.identifier.VersionedHandleIdentifierProvider; import org.dspace.kernel.ServiceManager; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.versioning.Version; @@ -84,7 +81,6 @@ public class VersioningWithRelationshipsIT extends AbstractIntegrationTestWithDa ContentServiceFactory.getInstance().getItemService(); private final SolrSearchCore solrSearchCore = DSpaceServicesFactory.getInstance().getServiceManager().getServicesByType(SolrSearchCore.class).get(0); - private IdentifierServiceImpl identifierService; protected Community community; protected Collection collection; protected EntityType publicationEntityType; @@ -101,22 +97,6 @@ public class VersioningWithRelationshipsIT extends AbstractIntegrationTestWithDa protected RelationshipType isIssueOfJournalVolume; protected RelationshipType isProjectOfPerson; - private void registerProvider(Class type) { - // Register our new provider - IdentifierProvider identifierProvider = - (IdentifierProvider) DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName(type.getName(), type); - if (identifierProvider == null) { - DSpaceServicesFactory.getInstance().getServiceManager().registerServiceClass(type.getName(), type); - identifierProvider = (IdentifierProvider) DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName(type.getName(), type); - } - - // Overwrite the identifier-service's providers with the new one to ensure only this provider is used - identifierService = DSpaceServicesFactory.getInstance().getServiceManager() - .getServicesByType(IdentifierServiceImpl.class).get(0); - identifierService.setProviders(new ArrayList<>()); - identifierService.setProviders(List.of(identifierProvider)); - } - @Override @Before public void setUp() throws Exception { @@ -124,9 +104,6 @@ public void setUp() throws Exception { context.turnOffAuthorisationSystem(); - - registerProvider(VersionedHandleIdentifierProvider.class); - community = CommunityBuilder.createCommunity(context) .withName("community") .build(); diff --git a/dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderIT.java b/dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderIT.java index 7e549f6cae3..57acf1f1c45 100644 --- a/dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderIT.java +++ b/dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderIT.java @@ -24,6 +24,7 @@ import org.dspace.content.Item; import org.dspace.kernel.ServiceManager; import org.dspace.services.factory.DSpaceServicesFactory; +import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -57,13 +58,30 @@ public void setUp() throws Exception { .build(); } + @After + @Override + public void destroy() throws Exception { + super.destroy(); + // After this test has finished running, refresh application context and + // set the expected 'default' versioned handle provider back to ensure other tests don't fail + DSpaceServicesFactory.getInstance().getServiceManager().getApplicationContext().refresh(); + } + private void registerProvider(Class type) { // Register our new provider - serviceManager.registerServiceClass(type.getName(), type); IdentifierProvider identifierProvider = - (IdentifierProvider) serviceManager.getServiceByName(type.getName(), type); + (IdentifierProvider) DSpaceServicesFactory.getInstance().getServiceManager() + .getServiceByName(type.getName(), type); + if (identifierProvider == null) { + DSpaceServicesFactory.getInstance().getServiceManager().registerServiceClass(type.getName(), type); + identifierProvider = (IdentifierProvider) DSpaceServicesFactory.getInstance().getServiceManager() + .getServiceByName(type.getName(), type); + } // Overwrite the identifier-service's providers with the new one to ensure only this provider is used + identifierService = DSpaceServicesFactory.getInstance().getServiceManager() + .getServicesByType(IdentifierServiceImpl.class).get(0); + identifierService.setProviders(new ArrayList<>()); identifierService.setProviders(List.of(identifierProvider)); } From 724f821bee86479663671f0eda94341c0e34ec85 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 25 Jun 2024 13:45:31 +0200 Subject: [PATCH 278/939] Fix request a copy link token generation Ensure DSpace URLs with extra segments are included fully in the generated link --- .../rest/repository/RequestItemRepository.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 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 5945d516600..a666b673530 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,6 +21,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.validator.routines.EmailValidator; import org.apache.http.client.utils.URIBuilder; import org.apache.logging.log4j.LogManager; @@ -287,19 +288,17 @@ public Class getDomainClass() { * Generate a link back to DSpace, to act on a request. * * @param token identifies the request. - * @return URL to the item request API, with the token as request parameter - * "token". + * @return URL to the item request API, with /request-a-copy/{token} as the last URL segments * @throws URISyntaxException passed through. * @throws MalformedURLException passed through. */ private String getLinkTokenEmail(String token) throws URISyntaxException, MalformedURLException { final String base = configurationService.getProperty("dspace.ui.url"); - - URI link = new URIBuilder(base) - .setPathSegments("request-a-copy", token) - .build(); - - return link.toURL().toExternalForm(); + URIBuilder uriBuilder = new URIBuilder(base); + // Add request-a-copy/token to the existing path (without breaking /sub/dir dspace URLs) + URI uri = uriBuilder.setPath(StringUtils.stripEnd(uriBuilder.getPath(), "") + + "/request-a-copy/" + token).build(); + return uri.toURL().toExternalForm(); } } From cdb255fecaf2fb06944137d359cc188a36e5ee66 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 25 Jun 2024 14:36:17 +0200 Subject: [PATCH 279/939] Make RequestItemRepository#getLinkTokenEmail public, write test --- .../repository/RequestItemRepository.java | 2 +- .../app/rest/RequestItemRepositoryIT.java | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+), 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 a666b673530..3a344461534 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 @@ -292,7 +292,7 @@ public Class getDomainClass() { * @throws URISyntaxException passed through. * @throws MalformedURLException passed through. */ - private String getLinkTokenEmail(String token) + public String getLinkTokenEmail(String token) throws URISyntaxException, MalformedURLException { final String base = configurationService.getProperty("dspace.ui.url"); URIBuilder uriBuilder = new URIBuilder(base); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java index 2fb7dbbc969..065660f2c87 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java @@ -29,6 +29,8 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URISyntaxException; import java.sql.SQLException; import java.time.temporal.ChronoUnit; import java.util.Date; @@ -56,10 +58,12 @@ import org.dspace.content.Bitstream; import org.dspace.content.Collection; import org.dspace.content.Item; +import org.dspace.services.ConfigurationService; import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; /** * @@ -82,6 +86,12 @@ public class RequestItemRepositoryIT @Autowired(required = true) RequestItemService requestItemService; + @Autowired + ApplicationContext applicationContext; + + @Autowired + private ConfigurationService configurationService; + private Collection collection; private Item item; @@ -610,4 +620,23 @@ public void testGetDomainClass() { Class instanceClass = instance.getDomainClass(); assertEquals("Wrong domain class", RequestItemRest.class, instanceClass); } + + /** + * Test that generated links include the correct base URL, even if it has extra URL segments + */ + @Test + public void testGetLinkTokenEmail() throws MalformedURLException, URISyntaxException { + RequestItemRepository instance = applicationContext.getBean( + RequestItemRest.CATEGORY + '.' + RequestItemRest.PLURAL_NAME, + RequestItemRepository.class); + String currentDspaceUrl = configurationService.getProperty("dspace.ui.url"); + String newDspaceUrl = currentDspaceUrl + "/subdir"; + // Add a /subdir to the url for this test + configurationService.setProperty("dspace.ui.url", newDspaceUrl); + String expectedUrl = newDspaceUrl + "/request-a-copy/token"; + String generatedLink = instance.getLinkTokenEmail("token"); + // The URLs should match + assertEquals(expectedUrl, generatedLink); + configurationService.reloadConfig(); + } } From 64255ffb05788898ee37baef7d42e1e89f98764a Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 10 Jul 2024 16:04:34 +0200 Subject: [PATCH 280/939] #9668: Ensure proper handling of non-subpath URLs in link tokens --- .../repository/RequestItemRepository.java | 5 +++-- .../app/rest/RequestItemRepositoryIT.java | 20 +++++++++++++++++-- 2 files changed, 21 insertions(+), 4 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 3a344461534..71c46b4dc84 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 @@ -297,8 +297,9 @@ public String getLinkTokenEmail(String token) final String base = configurationService.getProperty("dspace.ui.url"); URIBuilder uriBuilder = new URIBuilder(base); // Add request-a-copy/token to the existing path (without breaking /sub/dir dspace URLs) - URI uri = uriBuilder.setPath(StringUtils.stripEnd(uriBuilder.getPath(), "") - + "/request-a-copy/" + token).build(); + URI uri = uriBuilder.setPath( + (uriBuilder.getPath() == null ? "" : StringUtils.stripEnd(uriBuilder.getPath(), "")) + + "/request-a-copy/" + token).build(); return uri.toURL().toExternalForm(); } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java index 065660f2c87..f8732d0826e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java @@ -622,10 +622,10 @@ public void testGetDomainClass() { } /** - * Test that generated links include the correct base URL, even if it has extra URL segments + * Test that generated links include the correct base URL, where the UI URL has a subpath like /subdir */ @Test - public void testGetLinkTokenEmail() throws MalformedURLException, URISyntaxException { + public void testGetLinkTokenEmailWithSubPath() throws MalformedURLException, URISyntaxException { RequestItemRepository instance = applicationContext.getBean( RequestItemRest.CATEGORY + '.' + RequestItemRest.PLURAL_NAME, RequestItemRepository.class); @@ -639,4 +639,20 @@ public void testGetLinkTokenEmail() throws MalformedURLException, URISyntaxExcep assertEquals(expectedUrl, generatedLink); configurationService.reloadConfig(); } + + /** + * Test that generated links include the correct base URL, with NO subpath elements + */ + @Test + public void testGetLinkTokenEmailWithoutSubPath() throws MalformedURLException, URISyntaxException { + RequestItemRepository instance = applicationContext.getBean( + RequestItemRest.CATEGORY + '.' + RequestItemRest.PLURAL_NAME, + RequestItemRepository.class); + String currentDspaceUrl = configurationService.getProperty("dspace.ui.url"); + String expectedUrl = currentDspaceUrl + "/request-a-copy/token"; + String generatedLink = instance.getLinkTokenEmail("token"); + // The URLs should match + assertEquals(expectedUrl, generatedLink); + configurationService.reloadConfig(); + } } From 1914aac18b244c3b4053a56905c78d359b5e1910 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Sun, 1 Sep 2024 12:53:04 +0200 Subject: [PATCH 281/939] Improved URI build method as per review --- .../rest/repository/RequestItemRepository.java | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 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 71c46b4dc84..8be7971c84d 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 @@ -15,7 +15,11 @@ import java.net.URI; import java.net.URISyntaxException; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; import java.util.Date; +import java.util.LinkedList; +import java.util.List; import java.util.UUID; import javax.servlet.http.HttpServletRequest; @@ -295,11 +299,16 @@ public Class getDomainClass() { public String getLinkTokenEmail(String token) throws URISyntaxException, MalformedURLException { final String base = configurationService.getProperty("dspace.ui.url"); + URIBuilder uriBuilder = new URIBuilder(base); - // Add request-a-copy/token to the existing path (without breaking /sub/dir dspace URLs) - URI uri = uriBuilder.setPath( - (uriBuilder.getPath() == null ? "" : StringUtils.stripEnd(uriBuilder.getPath(), "")) - + "/request-a-copy/" + token).build(); + List segments = new LinkedList<>(); + if (StringUtils.isNotBlank(uriBuilder.getPath())) { + segments.add(StringUtils.strip(uriBuilder.getPath(), "/")); + } + segments.add("request-a-copy"); + segments.add(token); + URI uri = uriBuilder.setPathSegments(segments).build(); + return uri.toURL().toExternalForm(); } } From ab43b8606615ce6a77cb88333469611dc8411fb6 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 4 Sep 2024 14:04:20 +0200 Subject: [PATCH 282/939] Tidy implementation of link token generation --- .../dspace/app/rest/repository/RequestItemRepository.java | 5 +++-- 1 file changed, 3 insertions(+), 2 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 8be7971c84d..f7c986ff9a0 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 @@ -300,6 +300,7 @@ public String getLinkTokenEmail(String token) throws URISyntaxException, MalformedURLException { final String base = configurationService.getProperty("dspace.ui.url"); + // Construct the link, making sure to support sub-paths URIBuilder uriBuilder = new URIBuilder(base); List segments = new LinkedList<>(); if (StringUtils.isNotBlank(uriBuilder.getPath())) { @@ -307,8 +308,8 @@ public String getLinkTokenEmail(String token) } segments.add("request-a-copy"); segments.add(token); - URI uri = uriBuilder.setPathSegments(segments).build(); - return uri.toURL().toExternalForm(); + // Build and return the URL from segments (or throw exception) + return uriBuilder.setPathSegments(segments).build().toURL().toExternalForm(); } } From 7ec746d0052a01f5c5ed9f0351c988fe13f6aafa Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 4 Sep 2024 15:19:34 +0200 Subject: [PATCH 283/939] lint fixes (RequestItemRepository) --- .../org/dspace/app/rest/repository/RequestItemRepository.java | 3 --- 1 file changed, 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 f7c986ff9a0..10208b27fe7 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 @@ -12,11 +12,8 @@ import java.io.IOException; import java.net.MalformedURLException; -import java.net.URI; import java.net.URISyntaxException; import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collections; import java.util.Date; import java.util.LinkedList; import java.util.List; From 4ac4c911cf47ecf3540e05a9067550bba3faab25 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 6 Sep 2024 14:38:15 -0500 Subject: [PATCH 284/939] Singular name needed in 7.x --- .../java/org/dspace/app/rest/RequestItemRepositoryIT.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java index f8732d0826e..fbbd179fd28 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java @@ -627,7 +627,7 @@ public void testGetDomainClass() { @Test public void testGetLinkTokenEmailWithSubPath() throws MalformedURLException, URISyntaxException { RequestItemRepository instance = applicationContext.getBean( - RequestItemRest.CATEGORY + '.' + RequestItemRest.PLURAL_NAME, + RequestItemRest.CATEGORY + '.' + RequestItemRest.NAME, RequestItemRepository.class); String currentDspaceUrl = configurationService.getProperty("dspace.ui.url"); String newDspaceUrl = currentDspaceUrl + "/subdir"; @@ -646,7 +646,7 @@ public void testGetLinkTokenEmailWithSubPath() throws MalformedURLException, URI @Test public void testGetLinkTokenEmailWithoutSubPath() throws MalformedURLException, URISyntaxException { RequestItemRepository instance = applicationContext.getBean( - RequestItemRest.CATEGORY + '.' + RequestItemRest.PLURAL_NAME, + RequestItemRest.CATEGORY + '.' + RequestItemRest.NAME, RequestItemRepository.class); String currentDspaceUrl = configurationService.getProperty("dspace.ui.url"); String expectedUrl = currentDspaceUrl + "/request-a-copy/token"; From 9031322e7d4eccc73f057a5b3cf11c3c8cfc5361 Mon Sep 17 00:00:00 2001 From: Mikhail Schastlivtsev Date: Wed, 15 May 2024 10:08:46 +0300 Subject: [PATCH 285/939] add missing wosPublisherContrib key-ref in wos-integration.xml (#9579) (cherry picked from commit 6a8c76bbe1e7149785c17c7a7249e319199c3e0a) --- dspace/config/spring/api/wos-integration.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/dspace/config/spring/api/wos-integration.xml b/dspace/config/spring/api/wos-integration.xml index 17bd53c04a4..ba90363a2e6 100644 --- a/dspace/config/spring/api/wos-integration.xml +++ b/dspace/config/spring/api/wos-integration.xml @@ -35,6 +35,7 @@ + From a49ee624508f71c3e928d032b8448d1ec1316d6d Mon Sep 17 00:00:00 2001 From: Mikhail Schastlivtsev Date: Mon, 17 Jun 2024 14:06:53 +0300 Subject: [PATCH 286/939] add missing publisher metadatum in test (#9579) (cherry picked from commit cde892c8c7e1df8fff70a4cbfc693f378c2065d2) --- .../org/dspace/app/rest/WOSImportMetadataSourceServiceIT.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WOSImportMetadataSourceServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WOSImportMetadataSourceServiceIT.java index f1d3f5303ec..9f68d79c203 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WOSImportMetadataSourceServiceIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WOSImportMetadataSourceServiceIT.java @@ -136,6 +136,7 @@ private ArrayList getRecords() { MetadatumDTO subject15 = createMetadatumDTO("dc", "subject", null, "Coding concepts"); MetadatumDTO subject16 = createMetadatumDTO("dc", "subject", null, "Lesson design"); MetadatumDTO subject17 = createMetadatumDTO("dc", "subject", null, "Social Sciences"); + MetadatumDTO publisher = createMetadatumDTO("dc", "publisher", null, "SPRINGER"); MetadatumDTO other = createMetadatumDTO("dc", "identifier", "other", "WOS:000805105200003"); metadatums.add(edition); metadatums.add(date); @@ -166,6 +167,7 @@ private ArrayList getRecords() { metadatums.add(subject15); metadatums.add(subject16); metadatums.add(subject17); + metadatums.add(publisher); metadatums.add(other); ImportRecord firstrRecord = new ImportRecord(metadatums); @@ -205,6 +207,7 @@ private ArrayList getRecords() { MetadatumDTO subject26 = createMetadatumDTO("dc", "subject", null, "Social Sciences"); MetadatumDTO subject27 = createMetadatumDTO("dc", "subject", null, "Science & Technology"); MetadatumDTO subject28 = createMetadatumDTO("dc", "subject", null, "Life Sciences & Biomedicine"); + MetadatumDTO publisher2 = createMetadatumDTO("dc", "publisher", null, "NATURE PORTFOLIO"); MetadatumDTO other2 = createMetadatumDTO("dc", "identifier", "other", "WOS:000805100600001"); MetadatumDTO rid = createMetadatumDTO("person", "identifier", "rid", "C-6334-2011"); MetadatumDTO rid2 = createMetadatumDTO("person", "identifier", "rid", "B-1251-2008"); @@ -236,6 +239,7 @@ private ArrayList getRecords() { metadatums2.add(subject26); metadatums2.add(subject27); metadatums2.add(subject28); + metadatums2.add(publisher2); metadatums2.add(other2); metadatums2.add(rid); metadatums2.add(rid2); From 405397b8b061205253768db73924aeffa77c846d Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Wed, 3 Jul 2024 13:23:32 +0200 Subject: [PATCH 287/939] update eperson's attributes right after successful login (cherry picked from commit 428489ca5258ea7ac6942a722621e22c3371bfcf) --- .../authenticate/LDAPAuthentication.java | 69 +++++++++++-------- 1 file changed, 42 insertions(+), 27 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java index 585eaf9cd8b..2203937e75d 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java @@ -68,12 +68,8 @@ * @author Ivan Masár * @author Michael Plate */ -public class LDAPAuthentication - implements AuthenticationMethod { +public class LDAPAuthentication implements AuthenticationMethod { - /** - * log4j category - */ private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(LDAPAuthentication.class); @@ -130,7 +126,7 @@ public boolean allowSetPassword(Context context, return false; } - /* + /** * This is an explicit method. */ @Override @@ -138,7 +134,7 @@ public boolean isImplicit() { return false; } - /* + /** * Add authenticated users to the group defined in dspace.cfg by * the login.specialgroup key. */ @@ -177,7 +173,7 @@ public List getSpecialGroups(Context context, HttpServletRequest request) return Collections.EMPTY_LIST; } - /* + /** * Authenticate the given credentials. * This is the heart of the authentication method: test the * credentials for authenticity, and if accepted, attempt to match @@ -250,7 +246,7 @@ public int authenticate(Context context, } // Check a DN was found - if ((dn == null) || (dn.trim().equals(""))) { + if (StringUtils.isBlank(dn)) { log.info(LogHelper .getHeader(context, "failed_login", "no DN found for user " + netid)); return BAD_CREDENTIALS; @@ -269,6 +265,18 @@ public int authenticate(Context context, context.setCurrentUser(eperson); request.setAttribute(LDAP_AUTHENTICATED, true); + // update eperson's attributes + context.turnOffAuthorisationSystem(); + setEpersonAttributes(context, eperson, ldap, Optional.empty()); + try { + ePersonService.update(context, eperson); + context.dispatchEvents(); + } catch (AuthorizeException e) { + log.warn("update of eperson " + eperson.getID() + " failed", e); + } finally { + context.restoreAuthSystemState(); + } + // assign user to groups based on ldap dn assignGroups(dn, ldap.ldapGroup, context); @@ -313,14 +321,13 @@ public int authenticate(Context context, log.info(LogHelper.getHeader(context, "type=ldap-login", "type=ldap_but_already_email")); context.turnOffAuthorisationSystem(); - eperson.setNetid(netid.toLowerCase()); + setEpersonAttributes(context, eperson, ldap, Optional.of(netid)); ePersonService.update(context, eperson); context.dispatchEvents(); context.restoreAuthSystemState(); context.setCurrentUser(eperson); request.setAttribute(LDAP_AUTHENTICATED, true); - // assign user to groups based on ldap dn assignGroups(dn, ldap.ldapGroup, context); @@ -331,20 +338,7 @@ public int authenticate(Context context, try { context.turnOffAuthorisationSystem(); eperson = ePersonService.create(context); - if (StringUtils.isNotEmpty(email)) { - eperson.setEmail(email); - } - if (StringUtils.isNotEmpty(ldap.ldapGivenName)) { - eperson.setFirstName(context, ldap.ldapGivenName); - } - if (StringUtils.isNotEmpty(ldap.ldapSurname)) { - eperson.setLastName(context, ldap.ldapSurname); - } - if (StringUtils.isNotEmpty(ldap.ldapPhone)) { - ePersonService.setMetadataSingleValue(context, eperson, - MD_PHONE, ldap.ldapPhone, null); - } - eperson.setNetid(netid.toLowerCase()); + setEpersonAttributes(context, eperson, ldap, Optional.of(netid)); eperson.setCanLogIn(true); authenticationService.initEPerson(context, request, eperson); ePersonService.update(context, eperson); @@ -382,6 +376,27 @@ public int authenticate(Context context, return BAD_ARGS; } + /** + * Update eperson's attributes + */ + private void setEpersonAttributes(Context context, EPerson eperson, SpeakerToLDAP ldap, Optional netid) throws SQLException { + if (StringUtils.isNotEmpty(ldap.ldapEmail)) { + eperson.setEmail(ldap.ldapEmail); + } + if (StringUtils.isNotEmpty(ldap.ldapGivenName)) { + eperson.setFirstName(context, ldap.ldapGivenName); + } + if (StringUtils.isNotEmpty(ldap.ldapSurname)) { + eperson.setLastName(context, ldap.ldapSurname); + } + if (StringUtils.isNotEmpty(ldap.ldapPhone)) { + ePersonService.setMetadataSingleValue(context, eperson, MD_PHONE, ldap.ldapPhone, null); + } + if (netid.isPresent()) { + eperson.setNetid(netid.get().toLowerCase()); + } + } + /** * Internal class to manage LDAP query and results, mainly * because there are multiple values to return. @@ -671,7 +686,7 @@ protected boolean ldapAuthenticate(String netid, String password, } } - /* + /** * Returns the URL of an external login page which is not applicable for this authn method. * * Note: Prior to DSpace 7, this method return the page of login servlet. @@ -699,7 +714,7 @@ public String getName() { return "ldap"; } - /* + /** * Add authenticated users to the group defined in dspace.cfg by * the authentication-ldap.login.groupmap.* key. * From b0370a064b843db6d77efb58c5d54788e3872253 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Wed, 3 Jul 2024 13:36:45 +0200 Subject: [PATCH 288/939] add missing import (cherry picked from commit c5ad32a9b3ece7e64043f9f39d22b594e913737f) --- .../main/java/org/dspace/authenticate/LDAPAuthentication.java | 1 + 1 file changed, 1 insertion(+) diff --git a/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java index 2203937e75d..06bd6ae603d 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java @@ -17,6 +17,7 @@ import java.util.Hashtable; import java.util.Iterator; import java.util.List; +import java.util.Optional; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attribute; From 31af49ea725b5658f408e519cc577805b8f8c72d Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Wed, 3 Jul 2024 13:54:53 +0200 Subject: [PATCH 289/939] fix Checkstyle violations (cherry picked from commit aaa74b88c99af8ece67d43fa412a39a7406fe10c) --- .../org/dspace/authenticate/LDAPAuthentication.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java index 06bd6ae603d..93f0feb384c 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java @@ -184,7 +184,7 @@ public List getSpecialGroups(Context context, HttpServletRequest request) * @param context * DSpace context, will be modified (ePerson set) upon success. * - * @param username + * @param netid * Username (or email address) when method is explicit. Use null for * implicit method. * @@ -322,7 +322,7 @@ public int authenticate(Context context, log.info(LogHelper.getHeader(context, "type=ldap-login", "type=ldap_but_already_email")); context.turnOffAuthorisationSystem(); - setEpersonAttributes(context, eperson, ldap, Optional.of(netid)); + setEpersonAttributes(context, eperson, ldap, Optional.of(netid)); ePersonService.update(context, eperson); context.dispatchEvents(); context.restoreAuthSystemState(); @@ -380,7 +380,9 @@ public int authenticate(Context context, /** * Update eperson's attributes */ - private void setEpersonAttributes(Context context, EPerson eperson, SpeakerToLDAP ldap, Optional netid) throws SQLException { + private void setEpersonAttributes(Context context, EPerson eperson, SpeakerToLDAP ldap, Optional netid) + throws SQLException { + if (StringUtils.isNotEmpty(ldap.ldapEmail)) { eperson.setEmail(ldap.ldapEmail); } @@ -389,7 +391,7 @@ private void setEpersonAttributes(Context context, EPerson eperson, SpeakerToLDA } if (StringUtils.isNotEmpty(ldap.ldapSurname)) { eperson.setLastName(context, ldap.ldapSurname); - } + } if (StringUtils.isNotEmpty(ldap.ldapPhone)) { ePersonService.setMetadataSingleValue(context, eperson, MD_PHONE, ldap.ldapPhone, null); } From 702ff9cf4c0e9e2afeb1bab820087e6fb7584154 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Fri, 27 Sep 2024 12:07:52 +0200 Subject: [PATCH 290/939] minor fix in parameter description (cherry picked from commit 5758d9e90302d4da33ec869b6ec656e5c25b865a) --- dspace/config/emails/subscriptions_content | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace/config/emails/subscriptions_content b/dspace/config/emails/subscriptions_content index 9b8c91e559d..a1b107e8fc5 100644 --- a/dspace/config/emails/subscriptions_content +++ b/dspace/config/emails/subscriptions_content @@ -1,7 +1,7 @@ ## E-mail sent to designated address about updates on subscribed items ## -## Parameters: {0} Collections updates -## {1} Communities updates +## Parameters: {0} Communities updates +## {1} Collections updates #set($subject = "${config.get('dspace.name')} Subscriptions") This email is sent from ${config.get('dspace.name')} based on the chosen subscription preferences. From a721f8c41e92762d6d1af831b337b111a5ea9991 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Tue, 27 Aug 2024 10:33:01 +0200 Subject: [PATCH 291/939] fix failed first login attempt in HAL browser (cherry picked from commit 002e637d4fc69b31c2a2321230d96534e49ee33e) --- .../src/main/webapp/login.html | 128 ++++++++++-------- 1 file changed, 68 insertions(+), 60 deletions(-) diff --git a/dspace-server-webapp/src/main/webapp/login.html b/dspace-server-webapp/src/main/webapp/login.html index 50d95b80472..0597a66bfe9 100644 --- a/dspace-server-webapp/src/main/webapp/login.html +++ b/dspace-server-webapp/src/main/webapp/login.html @@ -32,7 +32,7 @@ border-radius: 5px; box-shadow: 0 1px 2px rgba(0, 0, 0, .05); } - .form-signin .form-signin-heading, .form-signin .checkbox { + .form-signin .form-signin-heading, .form-signin { margin-bottom: 10px; } .form-signin input[type="text"], .form-signin input[type="password"] { @@ -94,63 +94,71 @@

    Other login methods:

    "onclick" : function() { toastr.remove(); } } + // retrieves a valid CSRF token (please note that this method works both in DS 7 and DS 8) + // HTTP response code 403 is expected at this point (the response contains the DSPACE-XSRF-TOKEN header) + $.ajax({ + url : window.location.href.replace("login.html", "") + 'api/authn/login', + type : 'POST', + error : function(xhr) { + // Check for an update to the CSRF Token & save to a MyHalBrowserCsrfToken cookie (if found) + checkForUpdatedCSRFTokenInResponse(xhr); + } + }); + // When the login page loads, we do *two* AJAX requests. - // (1) Call GET /api/authn/status. This call has two purposes. First, it checks to see if you are logged in, - // (if not, WWW-Authenticate will return login options). Second, it retrieves the CSRF token, if a - // new one has been assigned (as a valid CSRF token is required for the POST call). + // (1) Call GET /api/authn/status. This call checks to see if you are logged in + // (if not, WWW-Authenticate will return login options). // (2) If that /api/authn/status call finds authentication data, call POST /api/authn/login. - // This scenario occurs when you login via an external authentication system (e.g. Shibboleth)... + // This scenario occurs when you log in via an external authentication system (e.g. Shibboleth) // in which case the main role of /api/authn/login is to simply ensure the "Authorization" header // is sent back to the client (based on your authentication data). $.ajax({ - url : window.location.href.replace("login.html", "") + 'api/authn/status', - type : 'GET', - success : function(result, status, xhr) { - // Check for an update to the CSRF Token & save to a MyHalBrowserCsrfToken cookie (if found) - checkForUpdatedCSRFTokenInResponse(xhr); + url : window.location.href.replace("login.html", "") + 'api/authn/status', + type : 'GET', + success : function(result, status, xhr) { - // Check for WWW-Authenticate header. If found, this means we are not yet authenticated, and - // therefore we need to display available authentication options. - var authenticate = xhr.getResponseHeader("WWW-Authenticate"); - if (authenticate !== null) { - var element = $('div.other-login-methods'); - var realms = authenticate.match(/(\w+ (\w+=((".*?")|[^,]*)(, )?)*)/g); - if (realms.length == 1){ - var loc = /location="([^,]*)"/.exec(authenticate); - if (loc !== null && loc.length === 2) { - document.location = loc[1]; - } - } else if (realms.length > 1){ - for (var i = 0; i < realms.length; i++){ - addLocationButton(realms[i], element); + // Check for WWW-Authenticate header. If found, this means we are not yet authenticated, and + // therefore we need to display available authentication options. + var authenticate = xhr.getResponseHeader("WWW-Authenticate"); + if (authenticate !== null && authenticate.contains('location=')) { + var element = $('div.other-login-methods'); + var realms = authenticate.match(/(\w+ (\w+=((".*?")|[^,]*)(, )?)*)/g); + if (realms.length === 1){ + var loc = /location="([^,]*)"/.exec(authenticate); + if (loc !== null && loc.length === 2) { + document.location = loc[1]; + } + } else if (realms.length > 1){ + for (var i = 0; i < realms.length; i++){ + addLocationButton(realms[i], element); + } } + } else { + // If Authentication data was found, do a POST /api/authn/login to ensure that data's JWT + // is sent back in the "Authorization" header. This simply completes an external authentication + // process (e.g. Shibboleth) + $.ajax({ + url : window.location.href.replace("login.html", "") + 'api/authn/login', + type : 'POST', + beforeSend: function (xhr) { + // If CSRF token found in cookie, send it back as X-XSRF-Token header + var csrfToken = getCSRFToken(); + if (csrfToken != null) { + xhr.setRequestHeader('X-XSRF-Token', csrfToken); + } + }, + success : successHandler, + error : function(xhr) { + // Check for an update to the CSRF Token & save to a MyHalBrowserCsrfToken cookie (if found) + checkForUpdatedCSRFTokenInResponse(xhr); + toastr.error('Failed to logged in. Please check for errors in Javascript console.', 'Login Failed'); + } + }); } - } else { - // If Authentication data was found, do a POST /api/authn/login to ensure that data's JWT - // is sent back in the "Authorization" header. This simply completes an external authentication - // process (e.g. Shibboleth) - $.ajax({ - url : window.location.href.replace("login.html", "") + 'api/authn/login', - type : 'POST', - beforeSend: function (xhr, settings) { - // If CSRF token found in cookie, send it back as X-XSRF-Token header - var csrfToken = getCSRFToken(); - if (csrfToken != null) { - xhr.setRequestHeader('X-XSRF-Token', csrfToken); - } - }, - success : successHandler, - error : function(xhr, textStatus, errorThrown) { - // Check for an update to the CSRF Token & save to a MyHalBrowserCsrfToken cookie (if found) - checkForUpdatedCSRFTokenInResponse(xhr); - toastr.error('Failed to logged in. Please check for errors in Javascript console.', 'Login Failed'); - } - }); + }, + error : function() { + toastr.error('Failed to connect with backend. Please check for errors in Javascript console.', 'Could Not Load'); } - }, - error : function(xhr, textStatus, errorThrown) { - toastr.error('Failed to connect with backend. Please check for errors in Javascript console.', 'Could Not Load'); - } }); function addLocationButton(realm, element){ @@ -166,22 +174,22 @@

    Other login methods:

    return string.charAt(0).toUpperCase() + string.slice(1); } - /** - * Check current response headers to see if the CSRF Token has changed. If a new value is found in headers, - * save the new value into our "MyHalBrowserCsrfToken" cookie. - **/ + /** + * Check current response headers to see if the CSRF Token has changed. If a new value is found in headers, + * save the new value into our "MyHalBrowserCsrfToken" cookie. + **/ function checkForUpdatedCSRFTokenInResponse(jqxhr) { // look for DSpace-XSRF-TOKEN header & save to our MyHalBrowserCsrfToken cookie (if found) var updatedCsrfToken = jqxhr.getResponseHeader('DSPACE-XSRF-TOKEN'); if (updatedCsrfToken != null) { - document.cookie = "MyHalBrowserCsrfToken=" + updatedCsrfToken; + document.cookie = "MyHalBrowserCsrfToken=" + updatedCsrfToken; } } - /** - * Get CSRF Token by parsing it out of the "MyHalBrowserCsrfToken" cookie. - * This cookie is set in login.html after a successful login occurs. - **/ + /** + * Get CSRF Token by parsing it out of the "MyHalBrowserCsrfToken" cookie. + * This cookie is set in login.html after a successful login occurs. + **/ function getCSRFToken() { var cookie = document.cookie.match('(^|;)\\s*' + 'MyHalBrowserCsrfToken' + '\\s*=\\s*([^;]+)'); if (cookie != null) { @@ -204,11 +212,11 @@

    Other login methods:

    user: $("#username").val(), password: $("#password").val() }, - beforeSend: function (xhr, settings) { + beforeSend: function (xhr) { // If CSRF token found in cookie, send it back as X-XSRF-Token header var csrfToken = getCSRFToken(); if (csrfToken != null) { - xhr.setRequestHeader('X-XSRF-Token', csrfToken); + xhr.setRequestHeader('X-XSRF-Token', csrfToken); } }, success : successHandler, From 4669d8f7605a980a3f038c6f10835f89b7d81bee Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Thu, 29 Aug 2024 16:24:38 +0200 Subject: [PATCH 292/939] applied change suggested by reviewer: use String.prototype.includes (cherry picked from commit 546afb189e1df8bdfbc9948e790004baa81ec894) --- dspace-server-webapp/src/main/webapp/login.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/webapp/login.html b/dspace-server-webapp/src/main/webapp/login.html index 0597a66bfe9..959190020a1 100644 --- a/dspace-server-webapp/src/main/webapp/login.html +++ b/dspace-server-webapp/src/main/webapp/login.html @@ -120,7 +120,7 @@

    Other login methods:

    // Check for WWW-Authenticate header. If found, this means we are not yet authenticated, and // therefore we need to display available authentication options. var authenticate = xhr.getResponseHeader("WWW-Authenticate"); - if (authenticate !== null && authenticate.contains('location=')) { + if (authenticate !== null && authenticate.includes('location=')) { var element = $('div.other-login-methods'); var realms = authenticate.match(/(\w+ (\w+=((".*?")|[^,]*)(, )?)*)/g); if (realms.length === 1){ From 1e47fa61d92c9c87aeabbb4d1c074cc360c721af Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 11 Oct 2024 14:38:47 -0500 Subject: [PATCH 293/939] Bump to Spring 5.3.39 and Spring Security 5.8.14 --- dspace-server-webapp/pom.xml | 21 +++++++++++++++++++++ pom.xml | 4 ++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index e0ec7ef5ed7..97bdaa241a4 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -362,6 +362,27 @@ org.springframework.boot spring-boot-starter-security ${spring-boot.version} + + + + org.springframework.security + spring-security-core + + + org.springframework.security + spring-security-web + + +
    + + org.springframework.security + spring-security-core + ${spring-security.version} + + + org.springframework.security + spring-security-web + ${spring-security.version} diff --git a/pom.xml b/pom.xml index b662e333225..6433b6f227d 100644 --- a/pom.xml +++ b/pom.xml @@ -19,9 +19,9 @@ 11 - 5.3.34 + 5.3.39 2.7.18 - 5.7.11 + 5.8.14 5.6.15.Final 6.2.5.Final 42.7.3 From 70dd8477599abf1e0b4c8933b45c80ddd49e7561 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 11 Oct 2024 16:41:40 -0500 Subject: [PATCH 294/939] Revert to Spring Security 5.7.12. Spring Security 5.8.x changes behavior of CSRF tokens --- dspace-server-webapp/pom.xml | 9 +++++++++ pom.xml | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 97bdaa241a4..5a4e1b32123 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -364,6 +364,10 @@ ${spring-boot.version} + + org.springframework.security + spring-security-config + org.springframework.security spring-security-core @@ -374,6 +378,11 @@
    + + org.springframework.security + spring-security-config + ${spring-security.version} + org.springframework.security spring-security-core diff --git a/pom.xml b/pom.xml index 6433b6f227d..02579c03294 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ 11 5.3.39 2.7.18 - 5.8.14 + 5.7.12 5.6.15.Final 6.2.5.Final 42.7.3 From 4a4a8bcb22796e5b22c9bbb1796e3458b48f1c07 Mon Sep 17 00:00:00 2001 From: Brian Keese Date: Tue, 15 Oct 2024 11:38:54 -0500 Subject: [PATCH 295/939] Fix full-text indexing for files over the character limit The error handler for files over the limit logged the correct message, but never actually added the full text to the index doc. --- .../indexobject/IndexFactoryImpl.java | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexFactoryImpl.java index f1ae137b916..c9a865ec85b 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexFactoryImpl.java @@ -118,20 +118,10 @@ protected void writeDocument(SolrInputDocument doc, FullTextContentStreams strea ParseContext tikaContext = new ParseContext(); // Use Apache Tika to parse the full text stream(s) + boolean extractionSucceeded = false; try (InputStream fullTextStreams = streams.getStream()) { tikaParser.parse(fullTextStreams, tikaHandler, tikaMetadata, tikaContext); - - // Write Tika metadata to "tika_meta_*" fields. - // This metadata is not very useful right now, - // but we'll keep it just in case it becomes more useful. - for (String name : tikaMetadata.names()) { - for (String value : tikaMetadata.getValues(name)) { - doc.addField("tika_meta_" + name, value); - } - } - - // Save (parsed) full text to "fulltext" field - doc.addField("fulltext", tikaHandler.toString()); + extractionSucceeded = true; } catch (SAXException saxe) { // Check if this SAXException is just a notice that this file was longer than the character limit. // Unfortunately there is not a unique, public exception type to catch here. This error is thrown @@ -141,6 +131,7 @@ protected void writeDocument(SolrInputDocument doc, FullTextContentStreams strea // log that we only indexed up to that configured limit log.info("Full text is larger than the configured limit (discovery.solr.fulltext.charLimit)." + " Only the first {} characters were indexed.", charLimit); + extractionSucceeded = true; } else { log.error("Tika parsing error. Could not index full text.", saxe); throw new IOException("Tika parsing error. Could not index full text.", saxe); @@ -148,11 +139,19 @@ protected void writeDocument(SolrInputDocument doc, FullTextContentStreams strea } catch (TikaException | IOException ex) { log.error("Tika parsing error. Could not index full text.", ex); throw new IOException("Tika parsing error. Could not index full text.", ex); - } finally { - // Add document to index - solr.add(doc); } - return; + if (extractionSucceeded) { + // Write Tika metadata to "tika_meta_*" fields. + // This metadata is not very useful right now, + // but we'll keep it just in case it becomes more useful. + for (String name : tikaMetadata.names()) { + for (String value : tikaMetadata.getValues(name)) { + doc.addField("tika_meta_" + name, value); + } + } + // Save (parsed) full text to "fulltext" field + doc.addField("fulltext", tikaHandler.toString()); + } } // Add document to index solr.add(doc); From 31d36e7abf89f188fffba004520e4cbe2c4890a0 Mon Sep 17 00:00:00 2001 From: Miika Nurminen Date: Tue, 27 Aug 2024 08:46:15 +0300 Subject: [PATCH 296/939] Bugfix: BitstreamRestController etag/content-length calculation does not consider cover page Ported Alphonse Bendt's PR #9666 to DSpace 7 branch (squashed 5 commits). This PR fixes a bug where the etag/content-length calculation did not respect the potential existence of a coverpage. The controller now will use the post processed pdf if coverpages are enabled. --- .../app/rest/BitstreamRestController.java | 14 +- .../app/rest/utils/BitstreamResource.java | 119 ++++++++++------ .../app/rest/BitstreamRestControllerIT.java | 129 +++++++++++++++++- 3 files changed, 209 insertions(+), 53 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java index 22b18724b90..242b231654c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java @@ -135,11 +135,16 @@ public ResponseEntity retrieve(@PathVariable UUID uuid, HttpServletResponse resp long filesize = bit.getSizeBytes(); Boolean citationEnabledForBitstream = citationDocumentService.isCitationEnabledForBitstream(bit, context); + var bitstreamResource = + new org.dspace.app.rest.utils.BitstreamResource(name, uuid, + currentUser != null ? currentUser.getID() : null, + context.getSpecialGroupUuids(), citationEnabledForBitstream); + HttpHeadersInitializer httpHeadersInitializer = new HttpHeadersInitializer() .withBufferSize(BUFFER_SIZE) .withFileName(name) - .withChecksum(bit.getChecksum()) - .withLength(bit.getSizeBytes()) + .withChecksum(bitstreamResource.getChecksum()) + .withLength(bitstreamResource.contentLength()) .withMimetype(mimetype) .with(request) .with(response); @@ -157,11 +162,6 @@ public ResponseEntity retrieve(@PathVariable UUID uuid, HttpServletResponse resp httpHeadersInitializer.withDisposition(HttpHeadersInitializer.CONTENT_DISPOSITION_ATTACHMENT); } - org.dspace.app.rest.utils.BitstreamResource bitstreamResource = - new org.dspace.app.rest.utils.BitstreamResource(name, uuid, - currentUser != null ? currentUser.getID() : null, - context.getSpecialGroupUuids(), citationEnabledForBitstream); - //We have all the data we need, close the connection to the database so that it doesn't stay open during //download/streaming context.complete(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/BitstreamResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/BitstreamResource.java index 4e5545fabc7..2f47dacc7cc 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/BitstreamResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/BitstreamResource.java @@ -15,7 +15,8 @@ import java.util.UUID; import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.tuple.Pair; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Bitstream; import org.dspace.content.factory.ContentServiceFactory; @@ -27,6 +28,7 @@ import org.dspace.eperson.service.EPersonService; import org.dspace.utils.DSpace; import org.springframework.core.io.AbstractResource; +import org.springframework.util.DigestUtils; /** * This class acts as a {@link AbstractResource} used by Spring's framework to send the data in a proper and @@ -36,18 +38,23 @@ */ public class BitstreamResource extends AbstractResource { - private String name; - private UUID uuid; - private UUID currentUserUUID; - private boolean shouldGenerateCoverPage; - private byte[] file; - private Set currentSpecialGroups; + private static final Logger LOG = LogManager.getLogger(BitstreamResource.class); - private BitstreamService bitstreamService = ContentServiceFactory.getInstance().getBitstreamService(); - private EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); - private CitationDocumentService citationDocumentService = + private final String name; + private final UUID uuid; + private final UUID currentUserUUID; + private final boolean shouldGenerateCoverPage; + private final Set currentSpecialGroups; + + private final BitstreamService bitstreamService = ContentServiceFactory.getInstance().getBitstreamService(); + private final EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); + private final CitationDocumentService citationDocumentService = new DSpace().getServiceManager() - .getServicesByType(CitationDocumentService.class).get(0); + .getServicesByType(CitationDocumentService.class).get(0); + + private String documentEtag; + private long documentLength; + private InputStream documentInputStream = null; public BitstreamResource(String name, UUID uuid, UUID currentUserUUID, Set currentSpecialGroups, boolean shouldGenerateCoverPage) { @@ -68,16 +75,14 @@ public BitstreamResource(String name, UUID uuid, UUID currentUserUUID, Set */ private byte[] getCoverpageByteArray(Context context, Bitstream bitstream) throws IOException, SQLException, AuthorizeException { - if (file == null) { - try { - Pair citedDocument = citationDocumentService.makeCitedDocument(context, bitstream); - this.file = citedDocument.getLeft(); - } catch (Exception e) { - // Return the original bitstream without the cover page - this.file = IOUtils.toByteArray(bitstreamService.retrieve(context, bitstream)); - } + try { + var citedDocument = citationDocumentService.makeCitedDocument(context, bitstream); + return citedDocument.getLeft(); + } catch (Exception e) { + LOG.warn("Could not generate cover page. Will fallback to original document", e); + // Return the original bitstream without the cover page + return IOUtils.toByteArray(bitstreamService.retrieve(context, bitstream)); } - return file; } @Override @@ -87,22 +92,9 @@ public String getDescription() { @Override public InputStream getInputStream() throws IOException { - try (Context context = initializeContext()) { - - Bitstream bitstream = bitstreamService.find(context, uuid); - InputStream out; - - if (shouldGenerateCoverPage) { - out = new ByteArrayInputStream(getCoverpageByteArray(context, bitstream)); - } else { - out = bitstreamService.retrieve(context, bitstream); - } + fetchDocument(); - this.file = null; - return out; - } catch (SQLException | AuthorizeException e) { - throw new IOException(e); - } + return this.documentInputStream; } @Override @@ -111,17 +103,63 @@ public String getFilename() { } @Override - public long contentLength() throws IOException { + public long contentLength() { + fetchDocument(); + + return this.documentLength; + } + + public String getChecksum() { + fetchDocument(); + + return this.documentEtag; + } + + private void fetchDocument() { + if (this.documentInputStream != null) { + return; + } + try (Context context = initializeContext()) { Bitstream bitstream = bitstreamService.find(context, uuid); if (shouldGenerateCoverPage) { - return getCoverpageByteArray(context, bitstream).length; + var coverPage = getCoverpageByteArray(context, bitstream); + + this.documentEtag = etag(bitstream); + this.documentLength = coverPage.length; + this.documentInputStream = new ByteArrayInputStream(coverPage); + } else { - return bitstream.getSizeBytes(); + + this.documentEtag = bitstream.getChecksum(); + this.documentLength = bitstream.getSizeBytes(); + this.documentInputStream = bitstreamService.retrieve(context, bitstream); + } - } catch (SQLException | AuthorizeException e) { - throw new IOException(e); + } catch (SQLException | AuthorizeException | IOException e) { + throw new RuntimeException(e); } + + LOG.debug("fetched document {} {} {}", shouldGenerateCoverPage, this.documentEtag, this.documentLength); + } + + private String etag(Bitstream bitstream) { + + /* Ideally we would calculate the md5 checksum based on the document with coverpage. + However it looks like the coverpage generation is not stable (e.g. if invoked twice it will return + different results). This means we cannot use it for etag calculation/comparison! + + Instead we will create the MD5 based off the original checksum plus fixed prefix. This ensures + that checksums will differ when coverpage is on/off. + However the checksum will _not_ change if the coverpage content changes. + */ + + var content = "coverpage:" + bitstream.getChecksum(); + + StringBuilder builder = new StringBuilder(37); + DigestUtils.appendMd5DigestAsHex(content.getBytes(), builder); + + return builder.toString(); } private Context initializeContext() throws SQLException { @@ -131,4 +169,5 @@ private Context initializeContext() throws SQLException { currentSpecialGroups.forEach(context::setSpecialGroup); return context; } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java index 855fedbaa2a..45f8565fbaf 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java @@ -23,6 +23,7 @@ import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; @@ -49,8 +50,10 @@ import java.io.InputStream; import java.io.StringWriter; import java.io.Writer; +import java.nio.file.Files; import java.util.UUID; +import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.CharEncoding; import org.apache.commons.lang3.StringUtils; @@ -90,6 +93,7 @@ import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; /** * Integration test to test the /api/core/bitstreams/[id]/* endpoints @@ -948,12 +952,11 @@ public void retrieveCitationCoverpageOfBitstream() throws Exception { //** THEN ** .andExpect(status().isOk()) - //The Content Length must match the full length + // exact content-length and etag values are verified in s separate test .andExpect(header().string("Content-Length", not(nullValue()))) + .andExpect(header().string("ETag", not(nullValue()))) //The server should indicate we support Range requests .andExpect(header().string("Accept-Ranges", "bytes")) - //The ETag has to be based on the checksum - .andExpect(header().string("ETag", "\"" + bitstream.getChecksum() + "\"")) //We expect the content type to match the bitstream mime type .andExpect(content().contentType("application/pdf;charset=UTF-8")) //THe bytes of the content must match the original content @@ -962,20 +965,22 @@ public void retrieveCitationCoverpageOfBitstream() throws Exception { // The citation cover page contains the item title. // We will now verify that the pdf text contains this title. String pdfText = extractPDFText(content); - System.out.println(pdfText); assertTrue(StringUtils.contains(pdfText,"Public item citation cover page test 1")); // The dspace-api/src/test/data/dspaceFolder/assetstore/ConstitutionofIreland.pdf file contains 64 pages, // manually counted + 1 citation cover page assertEquals(65,getNumberOfPdfPages(content)); + var etagHeader = getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) + .andReturn().getResponse().getHeader("ETag"); + //A If-None-Match HEAD request on the ETag must tell is the bitstream is not modified getClient().perform(head("/api/core/bitstreams/" + bitstream.getID() + "/content") - .header("If-None-Match", bitstream.getChecksum())) + .header("If-None-Match", etagHeader)) .andExpect(status().isNotModified()); //The download and head request should also be logged as a statistics record - checkNumberOfStatsRecords(bitstream, 2); + checkNumberOfStatsRecords(bitstream, 3); } private String extractPDFText(byte[] content) throws IOException { @@ -1383,4 +1388,116 @@ private void verifyBitstreamDownload(Bitstream file, String contentType, boolean header.contains("attachment")); } } + + @Test + public void contentLengthAndEtagUsesOriginalBitstream() throws Exception { + givenPdf(false, originalPdf -> { + var originalMd5 = md5Checksum(originalPdf); + long originalLength = Files.size(originalPdf.toPath()); + + assertThat(originalLength, greaterThan(0L)); + + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(header().longValue("Content-Length", originalLength)) + .andExpect(header().string("ETag", "\"" + originalMd5 + "\"")); + }); + } + + private static String md5Checksum(File file) throws IOException { + final String md5; + try (InputStream is = new FileInputStream(file)) { + md5 = DigestUtils.md5Hex(is); + } + return md5; + } + + @Test + public void withCoverPageContentLengthAndEtagChanges() throws Exception { + givenPdf(true, originalPdf -> { + var originalMd5 = md5Checksum(originalPdf); + long originalLength = Files.size(originalPdf.toPath()); + + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(header().string("Content-Length", not(Long.toString(originalLength)))) + .andExpect(header().string("ETag", not("\"" + originalMd5 + "\""))); + }); + } + + @Test + public void etagAndContentLengthIsStable() { + givenPdf(false, ignored -> { + var etag1 = getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) + .andReturn().getResponse().getHeader("Etag"); + + var etag2 = getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) + .andReturn().getResponse().getHeader("Etag"); + + assertThat(etag1, equalTo(etag2)); + }); + } + + @Test + public void withCoverPageEtagAndContentLengthIsStable() { + givenPdf(true, ignored -> { + var etag1 = getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) + .andReturn().getResponse().getHeader("Etag"); + + var etag2 = getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) + .andReturn().getResponse().getHeader("Etag"); + + assertThat(etag1, equalTo(etag2)); + }); + } + + @FunctionalInterface + interface ThrowingConsumer { + void accept(T t) throws Exception; + } + + private void givenPdf(boolean coverPageEnabled, ThrowingConsumer block) { + configurationService.setProperty("citation-page.enable_globally", coverPageEnabled); + + try { + citationDocumentService.afterPropertiesSet(); + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community and one collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + + //2. A public item with a bitstream + File originalPdf = new File(testProps.getProperty("test.bitstream")); + + try (InputStream is = new FileInputStream(originalPdf)) { + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item citation cover page test 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .build(); + + bitstream = BitstreamBuilder + .createBitstream(context, publicItem1, is) + .withName("Test bitstream") + .withDescription("This is a bitstream to test the citation cover page.") + .withMimeType("application/pdf") + .build(); + } + context.restoreAuthSystemState(); + + block.accept(originalPdf); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } From 6018f926d7a599b081664bf862531ededb645839 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 15 Oct 2024 23:23:24 +0200 Subject: [PATCH 297/939] Add Eclipse JDT .factorypath to .gitignore (cherry picked from commit 9ce645e08b50cd752e48a640e340b55466f019be) --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2fcb46b9932..529351edc5c 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ tags .project .classpath .checkstyle +.factorypath ## Ignore project files created by IntelliJ IDEA *.iml From 10a95ae92aa78fbd1998d439015eb91a7bc9b75c Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Wed, 23 Oct 2024 08:53:01 +0200 Subject: [PATCH 298/939] 119602: Add accessibility settings metadata field --- dspace/config/registries/dspace-types.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dspace/config/registries/dspace-types.xml b/dspace/config/registries/dspace-types.xml index fcdbc9b2af9..ebe021dad26 100644 --- a/dspace/config/registries/dspace-types.xml +++ b/dspace/config/registries/dspace-types.xml @@ -93,4 +93,12 @@ Stores the timestamp related to the user authentication on ORCID + + dspace + accessibility + settings + Metadata field storing the user-configured accessibility settings values for the EPerson. + + + From 68266cd3c19d27dbed4f181bc12017d46fc6f64c Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 6 Nov 2024 13:36:17 -0600 Subject: [PATCH 299/939] Move logging of test methods to Abstract*Test classes in dspace-api. That way they work for **both** dspace-server-webapp and dspace-api tests. (cherry picked from commit bd20c9262b1992ff15033701fa01738329864b1b) --- .../dspace/AbstractDSpaceIntegrationTest.java | 24 ++++++++++ .../java/org/dspace/AbstractDSpaceTest.java | 23 ++++++++++ .../AbstractControllerIntegrationTest.java | 4 -- .../AbstractWebClientIntegrationTest.java | 4 -- .../test/LoggingTestExecutionListener.java | 44 ------------------- 5 files changed, 47 insertions(+), 52 deletions(-) delete mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/test/LoggingTestExecutionListener.java diff --git a/dspace-api/src/test/java/org/dspace/AbstractDSpaceIntegrationTest.java b/dspace-api/src/test/java/org/dspace/AbstractDSpaceIntegrationTest.java index 5a5ce8bf6d4..791fdbc66ab 100644 --- a/dspace-api/src/test/java/org/dspace/AbstractDSpaceIntegrationTest.java +++ b/dspace-api/src/test/java/org/dspace/AbstractDSpaceIntegrationTest.java @@ -21,8 +21,12 @@ import org.dspace.discovery.SearchUtils; import org.dspace.servicemanager.DSpaceKernelImpl; import org.dspace.servicemanager.DSpaceKernelInit; +import org.junit.After; import org.junit.AfterClass; +import org.junit.Before; import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.rules.TestName; /** * Abstract Test class copied from DSpace API @@ -46,6 +50,12 @@ public class AbstractDSpaceIntegrationTest { */ protected static DSpaceKernelImpl kernelImpl; + /** + * Obtain the TestName from JUnit, so that we can print it out in the test logs (see below) + */ + @Rule + public TestName testName = new TestName(); + /** * Default constructor */ @@ -90,6 +100,20 @@ public static void initTestEnvironment() { } } + @Before + public void printTestMethodBefore() { + // Log the test method being executed. Put lines around it to make it stand out. + log.info("---"); + log.info("Starting execution of test method: {}()", testName.getMethodName()); + log.info("---"); + } + + @After + public void printTestMethodAfter() { + // Log the test method just completed. + log.info("Finished execution of test method: {}()", testName.getMethodName()); + } + /** * This method will be run after all tests finish as per @AfterClass. It * will clean resources initialized by the @BeforeClass methods. diff --git a/dspace-api/src/test/java/org/dspace/AbstractDSpaceTest.java b/dspace-api/src/test/java/org/dspace/AbstractDSpaceTest.java index 36477556d3d..136af83f076 100644 --- a/dspace-api/src/test/java/org/dspace/AbstractDSpaceTest.java +++ b/dspace-api/src/test/java/org/dspace/AbstractDSpaceTest.java @@ -18,9 +18,13 @@ import org.apache.logging.log4j.Logger; import org.dspace.servicemanager.DSpaceKernelImpl; import org.dspace.servicemanager.DSpaceKernelInit; +import org.junit.After; import org.junit.AfterClass; +import org.junit.Before; import org.junit.BeforeClass; import org.junit.Ignore; +import org.junit.Rule; +import org.junit.rules.TestName; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; @@ -62,6 +66,12 @@ protected AbstractDSpaceTest() { } */ protected static DSpaceKernelImpl kernelImpl; + /** + * Obtain the TestName from JUnit, so that we can print it out in the test logs (see below) + */ + @Rule + public TestName testName = new TestName(); + /** * This method will be run before the first test as per @BeforeClass. It will * initialize shared resources required for all tests of this class. @@ -94,6 +104,19 @@ public static void initKernel() { } } + @Before + public void printTestMethodBefore() { + // Log the test method being executed. Put lines around it to make it stand out. + log.info("---"); + log.info("Starting execution of test method: {}()", testName.getMethodName()); + log.info("---"); + } + + @After + public void printTestMethodAfter() { + // Log the test method just completed. + log.info("Finished execution of test method: {}()", testName.getMethodName()); + } /** * This method will be run after all tests finish as per @AfterClass. It diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java index 00339ba2e48..28c5560edf3 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java @@ -39,7 +39,6 @@ import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.web.WebAppConfiguration; @@ -75,9 +74,6 @@ @WebAppConfiguration // Load our src/test/resources/application-test.properties to override some settings in default application.properties @TestPropertySource(locations = "classpath:application-test.properties") -// Enable our custom Logging listener to log when each test method starts/stops -@TestExecutionListeners(listeners = {LoggingTestExecutionListener.class}, - mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS) public class AbstractControllerIntegrationTest extends AbstractIntegrationTestWithDatabase { protected static final String AUTHORIZATION_HEADER = "Authorization"; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java index 75b0143e3e6..df6afcb16e3 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java @@ -22,7 +22,6 @@ import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringRunner; @@ -52,9 +51,6 @@ @ContextConfiguration(initializers = { DSpaceKernelInitializer.class, DSpaceConfigurationInitializer.class }) // Load our src/test/resources/application-test.properties to override some settings in default application.properties @TestPropertySource(locations = "classpath:application-test.properties") -// Enable our custom Logging listener to log when each test method starts/stops -@TestExecutionListeners(listeners = {LoggingTestExecutionListener.class}, - mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS) public class AbstractWebClientIntegrationTest extends AbstractIntegrationTestWithDatabase { // (Random) port chosen for test web server @LocalServerPort diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/LoggingTestExecutionListener.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/LoggingTestExecutionListener.java deleted file mode 100644 index 2a04bab2041..00000000000 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/LoggingTestExecutionListener.java +++ /dev/null @@ -1,44 +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.app.rest.test; - -import org.apache.logging.log4j.Logger; -import org.springframework.test.context.TestContext; -import org.springframework.test.context.support.AbstractTestExecutionListener; - -/** - * Custom DSpace TestExecutionListener which logs messages whenever a specific Test Case (i.e. test method) has - * started or ended execution. This makes Test environment logs easier to read/understand as you know which method has - * caused errors, etc. - */ -public class LoggingTestExecutionListener extends AbstractTestExecutionListener { - - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(LoggingTestExecutionListener.class); - - /** - * Before each test method is run - * @param testContext - */ - @Override - public void beforeTestMethod(TestContext testContext) { - // Log the test method being executed. Put lines around it to make it stand out. - log.info("---"); - log.info("Starting execution of test method: {}()", testContext.getTestMethod().getName()); - log.info("---"); - } - - /** - * After each test method is run - * @param testContext - */ - @Override - public void afterTestMethod(TestContext testContext) { - // Log the test method just completed. - log.info("Finished execution of test method: {}()", testContext.getTestMethod().getName()); - } -} From 831db33018cdcfa132dc0b1da0f427aca0d03585 Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Mon, 14 Oct 2024 17:03:15 +0200 Subject: [PATCH 300/939] 118774: status of doi should be set to TO_BE_DELETED when related item is removed permanently (cherry picked from commit 352f4c21523237cefa5bf67c99f7d7b26a51d72b) --- .../src/main/java/org/dspace/content/ItemServiceImpl.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java index b2cc3e939d8..ad4156328ae 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -66,6 +66,7 @@ import org.dspace.harvest.HarvestedItem; import org.dspace.harvest.service.HarvestedItemService; import org.dspace.identifier.DOI; +import org.dspace.identifier.DOIIdentifierProvider; import org.dspace.identifier.IdentifierException; import org.dspace.identifier.service.DOIService; import org.dspace.identifier.service.IdentifierService; @@ -848,6 +849,7 @@ protected void rawDelete(Context context, Item item) throws AuthorizeException, DOI doi = doiService.findDOIByDSpaceObject(context, item); if (doi != null) { doi.setDSpaceObject(null); + doi.setStatus(DOIIdentifierProvider.TO_BE_DELETED); } // remove version attached to the item From fde825265db6b44ca0bd852ec6df56ca3f3eacfe Mon Sep 17 00:00:00 2001 From: Andrew Date: Fri, 27 Sep 2024 12:52:03 +0200 Subject: [PATCH 301/939] fix: performance of claiming workflow task (cherry picked from commit 27dd5a2ec58970256964f15f0879834c436aa542) --- .../storedcomponents/PoolTaskServiceImpl.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/PoolTaskServiceImpl.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/PoolTaskServiceImpl.java index fb673725e18..d3c8f6334d8 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/PoolTaskServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/PoolTaskServiceImpl.java @@ -13,6 +13,7 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.Optional; import java.util.Set; import org.apache.commons.collections4.CollectionUtils; @@ -100,12 +101,17 @@ public PoolTask findByWorkflowIdAndEPerson(Context context, XmlWorkflowItem work //If the user does not have a claimedtask yet, see whether one of the groups of the user has pooltasks //for this workflow item Set groups = groupService.allMemberGroupsSet(context, ePerson); - for (Group group : groups) { - poolTask = poolTaskDAO.findByWorkflowItemAndGroup(context, group, workflowItem); - if (poolTask != null) { - return poolTask; - } + List generalTasks = poolTaskDAO.findByWorkflowItem(context, workflowItem); + Optional firstClaimedTask = groups.stream() + .flatMap(group -> generalTasks.stream() + .filter(f -> f.getGroup().getID().equals(group.getID())) + .findFirst() + .stream()) + .findFirst(); + + if (firstClaimedTask.isPresent()) { + return firstClaimedTask.get(); } } } From c66dde2aa85540dfbfa36f18f581599a6457889e Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 11 Nov 2024 10:26:29 -0600 Subject: [PATCH 302/939] Add a job to test Docker deployment with the built images --- .github/workflows/docker.yml | 43 +++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index a9ff8760e76..12d49689b31 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -147,4 +147,45 @@ jobs: tags_flavor: suffix=-loadsql secrets: DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} - DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }} \ No newline at end of file + DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }} + + ######################################################################## + # Test Deployment via Docker to ensure images are working properly + ######################################################################## + docker-deploy: + # Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace' + if: github.repository == 'dspace/dspace' + runs-on: ubuntu-latest + # Must run after all major images are built + needs: [dspace-test, dspace-cli, dspace-postgres-pgcrypto, dspace-solr] + steps: + - name: Checkout codebase + uses: actions/checkout@v4 + # Start backend using our compose script in the codebase. + - name: Start backend in Docker + env: + # Override defaults dspace.server.url because backend starts at http://127.0.0.1:8080 + dspace__P__server__P__url: http://127.0.0.1:8080/server + run: | + docker compose -f docker-compose.yml up -d + sleep 10 + docker container ls + # Create a test admin account. Load test data from a simple set of AIPs as defined in cli.ingest.yml + - name: Load test data into Backend + run: | + docker compose -f docker-compose-cli.yml run --rm dspace-cli create-administrator -e test@test.edu -f admin -l user -p admin -c en + docker compose -f docker-compose-cli.yml -f dspace/src/main/docker-compose/cli.ingest.yml run --rm dspace-cli + # Verify backend started successfully. + # 1. Make sure root endpoint is responding (check for dspace.name defined in docker-compose.yml) + # 2. Also check /collections endpoint to ensure the test data loaded properly (check for a collection name in AIPs) + - name: Verify backend is responding properly + run: | + result=$(wget -O- -q http://127.0.0.1:8080/server/api) + echo "$result" + echo "$result" | grep -oE "\"DSpace Started with Docker Compose\"," + result=$(wget -O- -q http://127.0.0.1:8080/server/api/core/collections) + echo "$result" + echo "$result" | grep -oE "\"Dog in Yard\"," + - name: Shutdown Docker containers + run: | + docker compose -f docker-compose.yml down From 3ce85a2df3c548bf7842227cf86d8aa8bd2191b0 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 11 Nov 2024 14:10:44 -0600 Subject: [PATCH 303/939] Add a check that the Handle Server can be started & works properly --- .github/workflows/docker.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 12d49689b31..660c9eb817a 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -186,6 +186,25 @@ jobs: result=$(wget -O- -q http://127.0.0.1:8080/server/api/core/collections) echo "$result" echo "$result" | grep -oE "\"Dog in Yard\"," + # Verify Handle Server can be stared and is working properly + # 1. First generate the "[dspace]/handle-server" folder with the sitebndl.zip + # 2. Start the Handle Server (and wait 20 seconds to let it start up) + # 3. Verify logs do NOT include "Exception" in the text (as that means an error occurred) + # 4. Check that Handle Proxy HTML page is responding on default port (8000) + - name: Verify Handle Server is working properly + run: | + docker exec -i dspace /dspace/bin/make-handle-config + docker exec -i dspace /dspace/bin/start-handle-server + sleep 20 + echo "Checking for errors in handle-server.log..." + result=$(docker exec -i dspace cat /dspace/log/handle-server.log) + echo "$result" + echo "$result" | grep -vqz "Exception" + echo "Checking to see if Handle Proxy webpage is available..." + result=$(wget -O- -q http://127.0.0.1:8000/) + echo "$result" + echo "$result" | grep -oE "Handle Proxy" + # Shutdown our containers - name: Shutdown Docker containers run: | docker compose -f docker-compose.yml down From c1f168245b9756b6109bf6b53bc87317a66aa55c Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 12 Nov 2024 13:50:57 -0600 Subject: [PATCH 304/939] Ensure Docker images built from PRs are stored as artifacts. This allows us to use those new images when testing deployment (in docker-deploy) --- .github/workflows/docker.yml | 34 +++++++++--- .github/workflows/reusable-docker-build.yml | 58 +++++++++++++++++++-- 2 files changed, 81 insertions(+), 11 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 660c9eb817a..eed8de5a872 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -157,27 +157,47 @@ jobs: if: github.repository == 'dspace/dspace' runs-on: ubuntu-latest # Must run after all major images are built - needs: [dspace-test, dspace-cli, dspace-postgres-pgcrypto, dspace-solr] + needs: [dspace, dspace-test, dspace-cli, dspace-postgres-pgcrypto, dspace-solr] steps: + # Checkout our codebase (to get access to Docker Compose scripts) - name: Checkout codebase uses: actions/checkout@v4 - # Start backend using our compose script in the codebase. + # For PRs, download Docker image artifacts (built by reusable-docker-build.yml for all PRs) + - name: Download Docker image artifacts (for PRs) + if: github.event_name == 'pull_request' + uses: actions/download-artifact@v4 + with: + # Download all Docker images (TAR files) into the /tmp/docker directory + pattern: docker-image-* + path: /tmp/docker + merge-multiple: true + # For PRs, load each of the images into Docker by calling "docker image load" for each. + # This ensures we are using the images built from this PR & not the prior versions on DockerHub + - name: Load all downloaded Docker images (for PRs) + if: github.event_name == 'pull_request' + run: | + find /tmp/docker -type f -name "*.tar" -exec docker image load --input "{}" \; + docker image ls -a + # Start backend using our compose script in the codebase. - name: Start backend in Docker env: # Override defaults dspace.server.url because backend starts at http://127.0.0.1:8080 dspace__P__server__P__url: http://127.0.0.1:8080/server + # Force using "pr-testing" version of Docker images. The "pr-testing" tag is a temporary tag that we assign to + # all PR-built docker images in reusabe-docker-build.yml + DSPACE_VER: pr-testing run: | docker compose -f docker-compose.yml up -d sleep 10 docker container ls - # Create a test admin account. Load test data from a simple set of AIPs as defined in cli.ingest.yml + # Create a test admin account. Load test data from a simple set of AIPs as defined in cli.ingest.yml - name: Load test data into Backend run: | docker compose -f docker-compose-cli.yml run --rm dspace-cli create-administrator -e test@test.edu -f admin -l user -p admin -c en docker compose -f docker-compose-cli.yml -f dspace/src/main/docker-compose/cli.ingest.yml run --rm dspace-cli - # Verify backend started successfully. - # 1. Make sure root endpoint is responding (check for dspace.name defined in docker-compose.yml) - # 2. Also check /collections endpoint to ensure the test data loaded properly (check for a collection name in AIPs) + # Verify backend started successfully. + # 1. Make sure root endpoint is responding (check for dspace.name defined in docker-compose.yml) + # 2. Also check /collections endpoint to ensure the test data loaded properly (check for a collection name in AIPs) - name: Verify backend is responding properly run: | result=$(wget -O- -q http://127.0.0.1:8080/server/api) @@ -204,7 +224,7 @@ jobs: result=$(wget -O- -q http://127.0.0.1:8000/) echo "$result" echo "$result" | grep -oE "Handle Proxy" - # Shutdown our containers + # Shutdown our containers - name: Shutdown Docker containers run: | docker compose -f docker-compose.yml down diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml index 12aa0cfe286..83dfd74b9a1 100644 --- a/.github/workflows/reusable-docker-build.yml +++ b/.github/workflows/reusable-docker-build.yml @@ -113,6 +113,12 @@ jobs: - name: Set up QEMU emulation to build for multiple architectures uses: docker/setup-qemu-action@v3 + #------------------------------------------------------------ + # Build & deploy steps for new commits to a branch (non-PRs) + # + # These steps build the images, push to DockerHub, and + # (if necessary) redeploy demo/sandbox sites. + #------------------------------------------------------------ # https://github.com/docker/login-action - name: Login to DockerHub # Only login if not a PR, as PRs only trigger a Docker build and not a push @@ -125,6 +131,7 @@ jobs: # https://github.com/docker/metadata-action # Get Metadata for docker_build_deps step below - name: Sync metadata (tags, labels) from GitHub to Docker for image + if: ${{ ! matrix.isPr }} id: meta_build uses: docker/metadata-action@v5 with: @@ -133,7 +140,9 @@ jobs: flavor: ${{ env.TAGS_FLAVOR }} # https://github.com/docker/build-push-action - - name: Build and push image + - name: Build and push image to DockerHub + # Only build & push if not a PR + if: ${{ ! matrix.isPr }} id: docker_build uses: docker/build-push-action@v5 with: @@ -142,9 +151,7 @@ jobs: context: ${{ inputs.dockerfile_context }} file: ${{ inputs.dockerfile_path }} platforms: ${{ matrix.arch }} - # For pull requests, we run the Docker build (to ensure no PR changes break the build), - # but we ONLY do an image push to DockerHub if it's NOT a PR - push: ${{ ! matrix.isPr }} + push: true # Use tags / labels provided by 'docker/metadata-action' above tags: ${{ steps.meta_build.outputs.tags }} labels: ${{ steps.meta_build.outputs.labels }} @@ -189,11 +196,54 @@ jobs: run: | curl -X POST $REDEPLOY_DEMO_URL + #------------------------------------------------------------- + # Build steps for PRs only + # + # These steps build the images and store as a build artifact. + # These artifacts can then be used by later jobs to run the + # brand-new images for automated testing. + #-------------------------------------------------------------- + # Get Metadata for docker_build_deps step below + - name: Create metadata (tags, labels) for local Docker image + if: matrix.isPr + id: meta_build_pr + uses: docker/metadata-action@v5 + with: + images: ${{ env.IMAGE_NAME }} + # Hardcode to use custom "pr-testing" tag because that will allow us to spin up this PR + # for testing in docker.yml + tags: pr-testing + flavor: ${{ env.TAGS_FLAVOR }} + # Build local image and stores in a TAR file in /tmp directory + - name: Build and push image to local image + if: matrix.isPr + uses: docker/build-push-action@v5 + with: + build-contexts: | + ${{ inputs.dockerfile_additional_contexts }} + context: ${{ inputs.dockerfile_context }} + file: ${{ inputs.dockerfile_path }} + platforms: ${{ matrix.arch }} + tags: ${{ steps.meta_build_pr.outputs.tags }} + labels: ${{ steps.meta_build_pr.outputs.labels }} + # Export image to a local TAR file + outputs: type=docker,dest=/tmp/${{ inputs.build_id }}.tar + # Upload the local docker image (in TAR file) to a build Artifact + - name: Upload local image to artifact + if: matrix.isPr + uses: actions/upload-artifact@v4 + with: + name: docker-image-${{ inputs.build_id }} + path: /tmp/${{ inputs.build_id }}.tar + if-no-files-found: error + retention-days: 1 + # Merge Docker digests (from various architectures) into a manifest. # This runs after all Docker builds complete above, and it tells hub.docker.com # that these builds should be all included in the manifest for this tag. # (e.g. AMD64 and ARM64 should be listed as options under the same tagged Docker image) docker-build_manifest: + # Only run if this is NOT a PR if: ${{ github.event_name != 'pull_request' }} runs-on: ubuntu-latest needs: From 9853aa5bb45ceef6ba6e01a08515f0cdea68f92c Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 11 Nov 2024 16:45:59 -0600 Subject: [PATCH 305/939] Fix error in running Handle Server in GitHub Actions. Must exclude "spring-jcl" from dependencies as it conflicts with "commons-logging" (used by more of our dependencies) --- dspace-api/pom.xml | 7 +++++++ dspace-iiif/pom.xml | 5 +++++ dspace-oai/pom.xml | 5 +++++ dspace-rdf/pom.xml | 5 +++++ dspace-server-webapp/pom.xml | 5 +++++ dspace-services/pom.xml | 7 +++++++ dspace-sword/pom.xml | 5 +++++ dspace-swordv2/pom.xml | 5 +++++ pom.xml | 7 +++++++ 9 files changed, 51 insertions(+) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index bdf035179be..c3ef2c86044 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -396,6 +396,13 @@ org.springframework spring-orm + + + + org.springframework + spring-jcl + + diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index 64dd5106f0a..49ee02b5650 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -38,6 +38,11 @@ org.springframework.boot spring-boot-starter-logging
    + + + org.springframework + spring-jcl +
    diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index 28b610e996c..bfa2c8a2738 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -80,6 +80,11 @@ org.springframework.boot spring-boot-starter-logging
    + + + org.springframework + spring-jcl +
    diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index e656a920a1e..075f2d7038e 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -67,6 +67,11 @@ org.springframework.boot spring-boot-starter-logging + + + org.springframework + spring-jcl + diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 5a4e1b32123..35fa473fc17 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -405,6 +405,11 @@ org.springframework.boot spring-boot-starter-logging + + + org.springframework + spring-jcl + diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index 51c6dea6241..7c85ff7f0f0 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -85,6 +85,13 @@ spring-context-support ${spring.version} compile + + + + org.springframework + spring-jcl + + org.apache.commons diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index 1403c779e72..01aa68dfbfe 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -60,6 +60,11 @@ org.springframework.boot spring-boot-starter-logging + + + org.springframework + spring-jcl + diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index 47038888312..f575898c122 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -80,6 +80,11 @@ org.springframework.boot spring-boot-starter-logging + + + org.springframework + spring-jcl + diff --git a/pom.xml b/pom.xml index 02579c03294..b0d9d1819d3 100644 --- a/pom.xml +++ b/pom.xml @@ -1171,6 +1171,13 @@ org.springframework spring-orm ${spring.version} + + + + org.springframework + spring-jcl + + org.swordapp From faca14ad4059de0cb66c6d6ca0baf4161ac3aadd Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 13 Nov 2024 09:17:30 -0600 Subject: [PATCH 306/939] Ensure "host" command is installed in images, so "bin/make-handle-config" will work. --- Dockerfile | 5 +++++ Dockerfile.test | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/Dockerfile b/Dockerfile index 9d89710fe11..ffce09239d1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -55,6 +55,11 @@ FROM tomcat:9-jdk${JDK_VERSION} ENV DSPACE_INSTALL=/dspace # Copy the /dspace directory from 'ant_build' container to /dspace in this container COPY --from=ant_build /dspace $DSPACE_INSTALL +# Need host command for "[dspace]/bin/make-handle-config" +RUN apt-get update \ + && apt-get install -y --no-install-recommends host \ + && apt-get purge -y --auto-remove \ + && rm -rf /var/lib/apt/lists/* # Expose Tomcat port and AJP port EXPOSE 8080 8009 # Give java extra memory (2GB) diff --git a/Dockerfile.test b/Dockerfile.test index 031394ad256..d88699ca52a 100644 --- a/Dockerfile.test +++ b/Dockerfile.test @@ -54,6 +54,11 @@ ENV DSPACE_INSTALL=/dspace ENV TOMCAT_INSTALL=/usr/local/tomcat # Copy the /dspace directory from 'ant_build' containger to /dspace in this container COPY --from=ant_build /dspace $DSPACE_INSTALL +# Need host command for "[dspace]/bin/make-handle-config" +RUN apt-get update \ + && apt-get install -y --no-install-recommends host \ + && apt-get purge -y --auto-remove \ + && rm -rf /var/lib/apt/lists/* # Enable the AJP connector in Tomcat's server.xml # NOTE: secretRequired="false" should only be used when AJP is NOT accessible from an external network. But, secretRequired="true" isn't supported by mod_proxy_ajp until Apache 2.5 RUN sed -i '/Service name="Catalina".*/a \\n ' $TOMCAT_INSTALL/conf/server.xml From a0ed4a40eabab6296a577b3d7c8aa5a5fe024888 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 13 Nov 2024 10:37:53 -0600 Subject: [PATCH 307/939] Bug fixes. Ensure all steps of docker-deploy use the same environment variables. Ensure Handle Server HTTP port is open --- .github/workflows/docker.yml | 13 +++++++------ Dockerfile | 4 ++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index eed8de5a872..5197ed3bfe3 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -158,6 +158,12 @@ jobs: runs-on: ubuntu-latest # Must run after all major images are built needs: [dspace, dspace-test, dspace-cli, dspace-postgres-pgcrypto, dspace-solr] + env: + # Override defaults dspace.server.url because backend starts at http://127.0.0.1:8080 + dspace__P__server__P__url: http://127.0.0.1:8080/server + # Force using "pr-testing" version of all Docker images. The "pr-testing" tag is a temporary tag that we + # assign to all PR-built docker images in reusabe-docker-build.yml + DSPACE_VER: pr-testing steps: # Checkout our codebase (to get access to Docker Compose scripts) - name: Checkout codebase @@ -180,12 +186,6 @@ jobs: docker image ls -a # Start backend using our compose script in the codebase. - name: Start backend in Docker - env: - # Override defaults dspace.server.url because backend starts at http://127.0.0.1:8080 - dspace__P__server__P__url: http://127.0.0.1:8080/server - # Force using "pr-testing" version of Docker images. The "pr-testing" tag is a temporary tag that we assign to - # all PR-built docker images in reusabe-docker-build.yml - DSPACE_VER: pr-testing run: | docker compose -f docker-compose.yml up -d sleep 10 @@ -214,6 +214,7 @@ jobs: - name: Verify Handle Server is working properly run: | docker exec -i dspace /dspace/bin/make-handle-config + echo "Starting Handle Server..." docker exec -i dspace /dspace/bin/start-handle-server sleep 20 echo "Checking for errors in handle-server.log..." diff --git a/Dockerfile b/Dockerfile index ffce09239d1..adc5d6125f0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -60,8 +60,8 @@ RUN apt-get update \ && apt-get install -y --no-install-recommends host \ && apt-get purge -y --auto-remove \ && rm -rf /var/lib/apt/lists/* -# Expose Tomcat port and AJP port -EXPOSE 8080 8009 +# Expose Tomcat port (8080) and AJP port (8009) and Handle Server HTTP port (8000) +EXPOSE 8080 8009 8000 # Give java extra memory (2GB) ENV JAVA_OPTS=-Xmx2000m From d6d78298b31252155db7d00d7ae8eee8f2c0ac93 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 13 Nov 2024 11:45:03 -0600 Subject: [PATCH 308/939] Add check for Handle Server error.log --- .github/workflows/docker.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 5197ed3bfe3..1e839e89fce 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -217,6 +217,10 @@ jobs: echo "Starting Handle Server..." docker exec -i dspace /dspace/bin/start-handle-server sleep 20 + echo "Checking for errors in error.log" + result=$(docker exec -i dspace sh -c "cat /dspace/handle-server/logs/error.log* || echo ''") + echo "$result" + echo "$result" | grep -vqz "Exception" echo "Checking for errors in handle-server.log..." result=$(docker exec -i dspace cat /dspace/log/handle-server.log) echo "$result" From 5bb65c6b5647f74161244868c0864e9adb12d12a Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 13 Nov 2024 14:27:28 -0600 Subject: [PATCH 309/939] Fix error in Handle Server startup caused by having multiple versions of BouncyCastle in our classpath. Exclude the old version brought in by cnri-servlet-container --- dspace-api/pom.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index c3ef2c86044..c42895ab4c4 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -427,6 +427,16 @@ org.bouncycastle bcprov-jdk15on + + + org.bouncycastle + bcpkix-jdk15on + + + org.bouncycastle + bcprov-jdk15on + From aa71e4840be6fa9c6368e061d3e169e08a89c66a Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Mon, 5 Aug 2024 11:06:06 +0200 Subject: [PATCH 310/939] Fix 9734: Check configured workflow.reviewer.file-edit to show item edit functionality in workflow UI (cherry picked from commit e8ec0c1b1d20dd5e812f38593a24718fff1d8c6e) --- .../processingaction/ScoreReviewAction.java | 15 ++++++++++++++- .../processingaction/SingleUserReviewAction.java | 8 ++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreReviewAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreReviewAction.java index 43a3decacc7..419bb123640 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreReviewAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreReviewAction.java @@ -8,6 +8,7 @@ package org.dspace.xmlworkflow.state.actions.processingaction; import java.sql.SQLException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -20,6 +21,8 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.content.MetadataFieldName; import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.xmlworkflow.service.WorkflowRequirementsService; import org.dspace.xmlworkflow.state.Step; import org.dspace.xmlworkflow.state.actions.ActionAdvancedInfo; @@ -34,6 +37,9 @@ public class ScoreReviewAction extends ProcessingAction { private static final Logger log = LogManager.getLogger(ScoreReviewAction.class); + private final ConfigurationService configurationService + = DSpaceServicesFactory.getInstance().getConfigurationService(); + // Option(s) public static final String SUBMIT_SCORE = "submit_score"; @@ -114,7 +120,14 @@ private boolean checkRequestValid(int score, String review) { @Override public List getOptions() { - return List.of(SUBMIT_SCORE, RETURN_TO_POOL); + List options = new ArrayList<>(); + options.add(SUBMIT_SCORE); + if (configurationService.getBooleanProperty("workflow.reviewer.file-edit", false)) { + options.add(SUBMIT_EDIT_METADATA); + } + options.add(RETURN_TO_POOL); + + return options; } @Override diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SingleUserReviewAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SingleUserReviewAction.java index b3fe896ace2..0b8f2d13648 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SingleUserReviewAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SingleUserReviewAction.java @@ -21,6 +21,8 @@ import org.dspace.content.factory.ContentServiceFactory; import org.dspace.core.Context; import org.dspace.eperson.EPerson; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.workflow.WorkflowException; import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory; import org.dspace.xmlworkflow.state.Step; @@ -40,6 +42,9 @@ public class SingleUserReviewAction extends ProcessingAction { private static final Logger log = LogManager.getLogger(SingleUserReviewAction.class); + private final ConfigurationService configurationService + = DSpaceServicesFactory.getInstance().getConfigurationService(); + public static final int OUTCOME_REJECT = 1; protected static final String SUBMIT_DECLINE_TASK = "submit_decline_task"; @@ -95,6 +100,9 @@ public ActionResult processAccept(Context c, XmlWorkflowItem wfi) throws SQLExce public List getOptions() { List options = new ArrayList<>(); options.add(SUBMIT_APPROVE); + if (configurationService.getBooleanProperty("workflow.reviewer.file-edit", false)) { + options.add(SUBMIT_EDIT_METADATA); + } options.add(SUBMIT_REJECT); options.add(SUBMIT_DECLINE_TASK); return options; From 7fa31e21315db54a5f99e4d34dc91b29353a92b8 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 15 Nov 2024 08:59:19 -0600 Subject: [PATCH 311/939] Ensure we use "pr-testing" images for PRs, but use "latest" images for other builds (e.g. after PR is merged to a branch). (cherry picked from commit aa537c44902f458a684bcbb67b5ff702ded61363) --- .github/workflows/docker.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 1e839e89fce..a0aee14bea3 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -161,9 +161,10 @@ jobs: env: # Override defaults dspace.server.url because backend starts at http://127.0.0.1:8080 dspace__P__server__P__url: http://127.0.0.1:8080/server - # Force using "pr-testing" version of all Docker images. The "pr-testing" tag is a temporary tag that we - # assign to all PR-built docker images in reusabe-docker-build.yml - DSPACE_VER: pr-testing + # If this is a PR, force using "pr-testing" version of all Docker images. Otherwise, for branch commits, use the + # "latest" tag. NOTE: the "pr-testing" tag is a temporary tag that we assign to all PR-built docker images in + # reusabe-docker-build.yml + DSPACE_VER: ${{ github.event_name == 'pull_request' && 'pr-testing' || 'latest' }} steps: # Checkout our codebase (to get access to Docker Compose scripts) - name: Checkout codebase From f0c92ac96b6f6f22445020b9e9690886bb83830c Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 15 Nov 2024 16:06:10 -0600 Subject: [PATCH 312/939] Ensure only main branch uses "latest". Other branches should use the tag corresponding to the branch name (cherry picked from commit e0b7241acb167496ebc8c80c73ae69b9f6611a1c) --- .github/workflows/docker.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index a0aee14bea3..143b69a2f9f 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -161,10 +161,10 @@ jobs: env: # Override defaults dspace.server.url because backend starts at http://127.0.0.1:8080 dspace__P__server__P__url: http://127.0.0.1:8080/server - # If this is a PR, force using "pr-testing" version of all Docker images. Otherwise, for branch commits, use the - # "latest" tag. NOTE: the "pr-testing" tag is a temporary tag that we assign to all PR-built docker images in - # reusabe-docker-build.yml - DSPACE_VER: ${{ github.event_name == 'pull_request' && 'pr-testing' || 'latest' }} + # If this is a PR, force using "pr-testing" version of all Docker images. Otherwise, if on main branch, use the + # "latest" tag. Otherwise, use the branch name. NOTE: the "pr-testing" tag is a temporary tag that we assign to + # all PR-built docker images in reusabe-docker-build.yml + DSPACE_VER: ${{ (github.event_name == 'pull_request' && 'pr-testing') || (github.ref_name == github.event.repository.default_branch && 'latest') || github.ref_name }} steps: # Checkout our codebase (to get access to Docker Compose scripts) - name: Checkout codebase From d952fea6a2f6b5dc8a54b4e6ba3deecca2d7a4fe Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 15 Nov 2024 11:30:14 -0600 Subject: [PATCH 313/939] Remove parboiled-java and minor cleanup of unused OAI dependencies --- dspace-oai/pom.xml | 23 +------------------ .../xoai/app/CCElementItemCompilePlugin.java | 2 +- .../main/java/org/dspace/xoai/app/XOAI.java | 2 +- .../tests/integration/xoai/PipelineTest.java | 9 ++++---- 4 files changed, 8 insertions(+), 28 deletions(-) diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index bfa2c8a2738..2f8dae45b23 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -99,22 +99,9 @@ org.springframework.boot spring-boot-starter-web - - - org.parboiled - parboiled-java - - - - org.parboiled - parboiled-java - 1.3.1 - - org.dspace @@ -137,15 +124,7 @@ org.apache.logging.log4j log4j-core - - org.apache.logging.log4j - log4j-web - - - org.apache.logging.log4j - log4j-slf4j-impl - runtime - + org.apache.logging.log4j log4j-1.2-api diff --git a/dspace-oai/src/main/java/org/dspace/xoai/app/CCElementItemCompilePlugin.java b/dspace-oai/src/main/java/org/dspace/xoai/app/CCElementItemCompilePlugin.java index 225d56a4c98..370543029d8 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/app/CCElementItemCompilePlugin.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/app/CCElementItemCompilePlugin.java @@ -11,7 +11,7 @@ import com.lyncode.xoai.dataprovider.xml.xoai.Element; import com.lyncode.xoai.dataprovider.xml.xoai.Metadata; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.dspace.content.Item; import org.dspace.core.Context; import org.dspace.license.factory.LicenseServiceFactory; 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 4f842b8e944..c6aaaa34b53 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 @@ -9,7 +9,7 @@ import static com.lyncode.xoai.dataprovider.core.Granularity.Second; import static java.util.Objects.nonNull; -import static org.apache.commons.lang.StringUtils.EMPTY; +import static org.apache.commons.lang3.StringUtils.EMPTY; import static org.apache.solr.common.params.CursorMarkParams.CURSOR_MARK_PARAM; import static org.apache.solr.common.params.CursorMarkParams.CURSOR_MARK_START; import static org.dspace.xoai.util.ItemUtils.retrieveMetadata; diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/PipelineTest.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/PipelineTest.java index 0f48824159c..0f7ffde0bd0 100644 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/PipelineTest.java +++ b/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/PipelineTest.java @@ -13,13 +13,14 @@ import static org.hamcrest.MatcherAssert.assertThat; import java.io.InputStream; +import java.nio.charset.Charset; import javax.xml.transform.TransformerFactory; import javax.xml.transform.stream.StreamSource; import com.lyncode.xoai.util.XSLPipeline; +import org.apache.commons.io.IOUtils; import org.dspace.xoai.tests.support.XmlMatcherBuilder; import org.junit.Test; -import org.parboiled.common.FileUtils; public class PipelineTest { private static TransformerFactory factory = TransformerFactory.newInstance(); @@ -28,9 +29,9 @@ public class PipelineTest { public void pipelineTest() throws Exception { InputStream input = PipelineTest.class.getClassLoader().getResourceAsStream("item.xml"); InputStream xslt = PipelineTest.class.getClassLoader().getResourceAsStream("oai_dc.xsl"); - String output = FileUtils.readAllText(new XSLPipeline(input, true) - .apply(factory.newTemplates(new StreamSource(xslt))) - .getTransformed()); + String output = IOUtils.toString(new XSLPipeline(input, true) + .apply(factory.newTemplates(new StreamSource(xslt))) + .getTransformed(), Charset.defaultCharset()); assertThat(output, oai_dc().withXPath("/oai_dc:dc/dc:title", equalTo("Teste"))); From f3d15e5c04227ceb5425e671189ec4eb5ea42656 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 15 Nov 2024 12:02:20 -0600 Subject: [PATCH 314/939] Log4j cleanup. Remove last traces of log4j v1 (and remove log4j1 bridge to avoid them coming back). Create log4j2 settings for Handle Plugin. --- .../app/bulkedit/MetadataExportSearchIT.java | 5 +- .../dspace/builder/OrcidHistoryBuilder.java | 5 +- dspace-oai/pom.xml | 5 -- dspace-rdf/pom.xml | 4 -- dspace-rest/pom.xml | 4 -- .../rest/converter/SearchEventConverter.java | 5 +- .../bitstream/BitstreamLinksetProcessor.java | 5 +- .../BitstreamParentItemProcessor.java | 5 +- .../bitstream/BitstreamTypeProcessor.java | 5 +- .../processor/item/ItemAuthorProcessor.java | 5 +- .../item/ItemContentBitstreamsProcessor.java | 5 +- .../item/ItemDescribedbyProcessor.java | 5 +- .../processor/item/ItemLicenseProcessor.java | 5 +- .../processor/item/ItemLinksetProcessor.java | 5 +- .../processor/item/ItemTypeProcessor.java | 5 +- .../service/impl/LinksetServiceImpl.java | 5 +- dspace-sword/pom.xml | 4 -- dspace-swordv2/pom.xml | 4 -- dspace/bin/start-handle-server | 2 +- dspace/bin/start-handle-server.bat | 2 +- dspace/config/log4j-handle-plugin.properties | 34 ------------- dspace/config/log4j2-handle-plugin.xml | 48 +++++++++++++++++++ pom.xml | 25 ---------- 23 files changed, 89 insertions(+), 108 deletions(-) delete mode 100644 dspace/config/log4j-handle-plugin.properties create mode 100644 dspace/config/log4j2-handle-plugin.xml diff --git a/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataExportSearchIT.java b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataExportSearchIT.java index 3a972692efe..63a87a48f55 100644 --- a/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataExportSearchIT.java +++ b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataExportSearchIT.java @@ -23,7 +23,8 @@ import com.google.common.io.Files; import com.opencsv.CSVReader; import com.opencsv.exceptions.CsvException; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.app.launcher.ScriptLauncher; import org.dspace.app.scripts.handler.impl.TestDSpaceRunnableHandler; @@ -51,7 +52,7 @@ public class MetadataExportSearchIT extends AbstractIntegrationTestWithDatabase private Item[] itemsSubject2 = new Item[numberItemsSubject2]; private String filename; private Collection collection; - private Logger logger = Logger.getLogger(MetadataExportSearchIT.class); + private Logger logger = LogManager.getLogger(MetadataExportSearchIT.class); private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); private SearchService searchService; diff --git a/dspace-api/src/test/java/org/dspace/builder/OrcidHistoryBuilder.java b/dspace-api/src/test/java/org/dspace/builder/OrcidHistoryBuilder.java index 199f412f850..d811d03f535 100644 --- a/dspace-api/src/test/java/org/dspace/builder/OrcidHistoryBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/OrcidHistoryBuilder.java @@ -11,7 +11,8 @@ import java.sql.SQLException; import java.util.Date; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.content.Item; import org.dspace.core.Context; import org.dspace.orcid.OrcidHistory; @@ -24,7 +25,7 @@ */ public class OrcidHistoryBuilder extends AbstractBuilder { - private static final Logger log = Logger.getLogger(OrcidHistoryBuilder.class); + private static final Logger log = LogManager.getLogger(OrcidHistoryBuilder.class); private OrcidHistory orcidHistory; diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index 2f8dae45b23..f7acdae9929 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -124,11 +124,6 @@ org.apache.logging.log4j log4j-core - - - org.apache.logging.log4j - log4j-1.2-api - diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index 075f2d7038e..64d88b63c78 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -89,10 +89,6 @@ org.apache.logging.log4j log4j-core - - org.apache.logging.log4j - log4j-web - org.apache.commons diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index d1f3b95aafb..3f4bdaf1d0d 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -185,10 +185,6 @@ org.apache.logging.log4j log4j-core - - org.apache.logging.log4j - log4j-web - org.dspace dspace-services diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SearchEventConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SearchEventConverter.java index 126d37ba1ac..978ae2ca923 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SearchEventConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SearchEventConverter.java @@ -13,7 +13,8 @@ import java.util.List; import javax.servlet.http.HttpServletRequest; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.model.PageRest; import org.dspace.app.rest.model.SearchEventRest; import org.dspace.app.rest.model.SearchResultsRest; @@ -31,7 +32,7 @@ @Component public class SearchEventConverter { /* Log4j logger */ - private static final Logger log = Logger.getLogger(SearchEventConverter.class); + private static final Logger log = LogManager.getLogger(SearchEventConverter.class); @Autowired private ScopeResolver scopeResolver; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamLinksetProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamLinksetProcessor.java index c65191cb074..c9ee193b353 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamLinksetProcessor.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamLinksetProcessor.java @@ -10,7 +10,8 @@ import java.util.List; import javax.servlet.http.HttpServletRequest; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.app.rest.signposting.model.LinksetRelationType; import org.dspace.content.Bitstream; @@ -25,7 +26,7 @@ */ public class BitstreamLinksetProcessor extends BitstreamSignpostingProcessor { - private static final Logger log = Logger.getLogger(BitstreamLinksetProcessor.class); + private static final Logger log = LogManager.getLogger(BitstreamLinksetProcessor.class); private final BitstreamService bitstreamService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamParentItemProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamParentItemProcessor.java index 815d7817d4c..beb318a7348 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamParentItemProcessor.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamParentItemProcessor.java @@ -10,7 +10,8 @@ import java.util.List; import javax.servlet.http.HttpServletRequest; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.app.rest.signposting.model.LinksetRelationType; import org.dspace.content.Bitstream; @@ -28,7 +29,7 @@ */ public class BitstreamParentItemProcessor extends BitstreamSignpostingProcessor { - private static final Logger log = Logger.getLogger(BitstreamParentItemProcessor.class); + private static final Logger log = LogManager.getLogger(BitstreamParentItemProcessor.class); private final BitstreamService bitstreamService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamTypeProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamTypeProcessor.java index 005a8009836..1dabf398da9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamTypeProcessor.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/bitstream/BitstreamTypeProcessor.java @@ -11,7 +11,8 @@ import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.app.rest.signposting.model.LinksetRelationType; import org.dspace.content.Bitstream; @@ -28,7 +29,7 @@ */ public class BitstreamTypeProcessor extends BitstreamSignpostingProcessor { - private static final Logger log = Logger.getLogger(BitstreamTypeProcessor.class); + private static final Logger log = LogManager.getLogger(BitstreamTypeProcessor.class); @Autowired private SimpleMapConverter mapConverterDSpaceToSchemaOrgUri; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemAuthorProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemAuthorProcessor.java index 1bb215c4686..b96a41b00b2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemAuthorProcessor.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemAuthorProcessor.java @@ -16,7 +16,8 @@ import java.util.List; import javax.servlet.http.HttpServletRequest; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.app.rest.signposting.model.LinksetRelationType; import org.dspace.content.Item; @@ -37,7 +38,7 @@ public class ItemAuthorProcessor extends ItemSignpostingProcessor { /** * log4j category */ - private static final Logger log = Logger.getLogger(ItemAuthorProcessor.class); + private static final Logger log = LogManager.getLogger(ItemAuthorProcessor.class); private final ItemService itemService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemContentBitstreamsProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemContentBitstreamsProcessor.java index 61bf371adbd..40c3d96cf68 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemContentBitstreamsProcessor.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemContentBitstreamsProcessor.java @@ -11,7 +11,8 @@ import java.util.List; import javax.servlet.http.HttpServletRequest; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.app.rest.signposting.model.LinksetRelationType; import org.dspace.content.Bitstream; @@ -33,7 +34,7 @@ public class ItemContentBitstreamsProcessor extends ItemSignpostingProcessor { /** * log4j category */ - private static final Logger log = Logger.getLogger(ItemContentBitstreamsProcessor.class); + private static final Logger log = LogManager.getLogger(ItemContentBitstreamsProcessor.class); public ItemContentBitstreamsProcessor(FrontendUrlService frontendUrlService) { super(frontendUrlService); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemDescribedbyProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemDescribedbyProcessor.java index a16770c4d10..62374d7ee83 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemDescribedbyProcessor.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemDescribedbyProcessor.java @@ -10,7 +10,8 @@ import java.util.List; import javax.servlet.http.HttpServletRequest; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.app.rest.signposting.model.LinksetRelationType; import org.dspace.content.Item; @@ -23,7 +24,7 @@ */ public class ItemDescribedbyProcessor extends ItemSignpostingProcessor { - private static final Logger log = Logger.getLogger(ItemDescribedbyProcessor.class); + private static final Logger log = LogManager.getLogger(ItemDescribedbyProcessor.class); private final ConfigurationService configurationService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemLicenseProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemLicenseProcessor.java index 1a26fa7695b..a88e9eba37d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemLicenseProcessor.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemLicenseProcessor.java @@ -11,7 +11,8 @@ import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.app.rest.signposting.model.LinksetRelationType; import org.dspace.content.Item; @@ -25,7 +26,7 @@ */ public class ItemLicenseProcessor extends ItemSignpostingProcessor { - private static final Logger log = Logger.getLogger(ItemLicenseProcessor.class); + private static final Logger log = LogManager.getLogger(ItemLicenseProcessor.class); private final CreativeCommonsService creativeCommonsService = LicenseServiceFactory.getInstance().getCreativeCommonsService(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemLinksetProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemLinksetProcessor.java index 9008a28e29a..1c765047a62 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemLinksetProcessor.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemLinksetProcessor.java @@ -10,7 +10,8 @@ import java.util.List; import javax.servlet.http.HttpServletRequest; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.app.rest.signposting.model.LinksetRelationType; import org.dspace.content.Item; @@ -23,7 +24,7 @@ */ public class ItemLinksetProcessor extends ItemSignpostingProcessor { - private static final Logger log = Logger.getLogger(ItemLinksetProcessor.class); + private static final Logger log = LogManager.getLogger(ItemLinksetProcessor.class); private final ConfigurationService configurationService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemTypeProcessor.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemTypeProcessor.java index ddd2da12d59..6945c33619f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemTypeProcessor.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/processor/item/ItemTypeProcessor.java @@ -11,7 +11,8 @@ import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.app.rest.signposting.model.LinksetRelationType; import org.dspace.content.Item; @@ -27,7 +28,7 @@ */ public class ItemTypeProcessor extends ItemSignpostingProcessor { - private static final Logger log = Logger.getLogger(ItemTypeProcessor.class); + private static final Logger log = LogManager.getLogger(ItemTypeProcessor.class); private static final String ABOUT_PAGE_URI = "https://schema.org/AboutPage"; @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/service/impl/LinksetServiceImpl.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/service/impl/LinksetServiceImpl.java index 399b7bd1e6b..de555617355 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/service/impl/LinksetServiceImpl.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/service/impl/LinksetServiceImpl.java @@ -13,7 +13,8 @@ import java.util.List; import javax.servlet.http.HttpServletRequest; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.security.BitstreamMetadataReadPermissionEvaluatorPlugin; import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.app.rest.signposting.processor.bitstream.BitstreamSignpostingProcessor; @@ -37,7 +38,7 @@ @Service public class LinksetServiceImpl implements LinksetService { - private static final Logger log = Logger.getLogger(LinksetServiceImpl.class); + private static final Logger log = LogManager.getLogger(LinksetServiceImpl.class); @Autowired protected ItemService itemService; diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index 01aa68dfbfe..b72bee24db5 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -101,10 +101,6 @@ org.apache.logging.log4j log4j-core - - org.apache.logging.log4j - log4j-web - xom diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index f575898c122..be9430b4d12 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -97,10 +97,6 @@ org.apache.logging.log4j log4j-core - - org.apache.logging.log4j - log4j-web - + + + + + ${log4j:configParentLocation}/../log + + + + + + + + + yyyy-MM-dd + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml index b0d9d1819d3..9b4dcef37dc 100644 --- a/pom.xml +++ b/pom.xml @@ -1577,36 +1577,11 @@ log4j-api ${log4j.version} - - org.apache.logging.log4j - log4j-1.2-api - ${log4j.version} - org.apache.logging.log4j log4j-core ${log4j.version} - - org.apache.logging.log4j - log4j-web - ${log4j.version} - - - org.apache.logging.log4j - log4j-slf4j-impl - ${log4j.version} - - - org.apache.logging.log4j - log4j-jul - ${log4j.version} - - - org.slf4j - jul-to-slf4j - ${slf4j.version} - org.apache.pdfbox From 9f8240987b71f1f37d4f791854b85bcbe870b032 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 20 Nov 2024 12:45:05 -0600 Subject: [PATCH 315/939] Remove unused dependencies from several modules --- dspace-services/pom.xml | 5 ----- dspace-sword/pom.xml | 18 ------------------ dspace/modules/server/pom.xml | 5 ----- pom.xml | 14 +------------- 4 files changed, 1 insertion(+), 41 deletions(-) diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index 7c85ff7f0f0..6504ae2e4b2 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -131,10 +131,6 @@ 6.1.26 test - - org.apache.commons - commons-collections4 - org.apache.commons commons-configuration2 @@ -159,6 +155,5 @@ spring-boot-starter-log4j2 ${spring-boot.version} - diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index b72bee24db5..12325533c5c 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -25,15 +25,6 @@
    - - - - org.dspace dspace-api @@ -68,11 +59,6 @@ - - jaxen - jaxen - - commons-fileupload @@ -107,10 +93,6 @@ xom 1.3.9 - - commons-io - commons-io - diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index 5134ffb9473..cdbec2f60c8 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -262,11 +262,6 @@ just adding new jar in the classloader dspace-server-webapp war - - org.apache.solr - solr-solrj - ${solr.client.version} - diff --git a/pom.xml b/pom.xml index 9b4dcef37dc..c0127724c1d 100644 --- a/pom.xml +++ b/pom.xml @@ -1179,11 +1179,7 @@ - - org.swordapp - sword-common - 1.1 - + spring-core @@ -1484,14 +1480,6 @@ commons-collections4 4.4 - - - commons-collections - commons-collections - 3.2.2 - org.apache.commons commons-configuration2 From f44dba60cba8cacaa785e9bc5329b569f77a0c1e Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 22 Nov 2024 11:04:11 -0600 Subject: [PATCH 316/939] Enable all optional modules/controllers to test their deployment in Spring Boot (cherry picked from commit 98768d6f4fcc5181501d02d77ef5056641ac8cd3) --- .github/workflows/docker.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 143b69a2f9f..7013f1e71a0 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -161,6 +161,14 @@ jobs: env: # Override defaults dspace.server.url because backend starts at http://127.0.0.1:8080 dspace__P__server__P__url: http://127.0.0.1:8080/server + # Enable all optional modules / controllers for this test deployment. + # This helps check for errors in deploying these modules via Spring Boot + iiif__P__enabled: true + oai__P__enabled: true + rdf__P__enabled: true + signposting__P__enabled: true + sword-server__P__enabled: true + swordv2-server__P__enabled: true # If this is a PR, force using "pr-testing" version of all Docker images. Otherwise, if on main branch, use the # "latest" tag. Otherwise, use the branch name. NOTE: the "pr-testing" tag is a temporary tag that we assign to # all PR-built docker images in reusabe-docker-build.yml From 66a9782eeefc4e9211a4efdd1a10b1303640415e Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 22 Nov 2024 13:50:16 -0600 Subject: [PATCH 317/939] Fix syntax error in #10040. Env variables cannot have dashes or periods --- .github/workflows/docker.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 7013f1e71a0..daa940f215a 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -167,8 +167,8 @@ jobs: oai__P__enabled: true rdf__P__enabled: true signposting__P__enabled: true - sword-server__P__enabled: true - swordv2-server__P__enabled: true + sword__D__server__P__enabled: true + swordv2__D__server__P__enabled: true # If this is a PR, force using "pr-testing" version of all Docker images. Otherwise, if on main branch, use the # "latest" tag. Otherwise, use the branch name. NOTE: the "pr-testing" tag is a temporary tag that we assign to # all PR-built docker images in reusabe-docker-build.yml From cf99694a84714221ed506fa9afd54f262c11c1ce Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 22 Nov 2024 14:58:08 -0600 Subject: [PATCH 318/939] Fix startup errors for SWORDv2. Requires the log4jv1->v2 bridge to be installed. --- dspace-swordv2/pom.xml | 6 ++++++ pom.xml | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index be9430b4d12..7cae0567247 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -97,6 +97,12 @@ org.apache.logging.log4j log4j-core + + + org.apache.logging.log4j + log4j-1.2-api + + + org.apache.logging.log4j + log4j-1.2-api + ${log4j.version} + org.apache.pdfbox From 54a1c75cbc652bbb60e39157450043087c2d822b Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Thu, 28 Nov 2024 13:06:23 +0100 Subject: [PATCH 319/939] 109807: ArXiv mapping fix - author/name to dc.contributor.author https://info.arxiv.org/help/api/basics.html#using --- dspace/config/spring/api/arxiv-integration.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace/config/spring/api/arxiv-integration.xml b/dspace/config/spring/api/arxiv-integration.xml index e963e73a205..3f7f408dff4 100644 --- a/dspace/config/spring/api/arxiv-integration.xml +++ b/dspace/config/spring/api/arxiv-integration.xml @@ -94,7 +94,7 @@ - + From 38a71cc664fffc5e6bfd7809cc95960a552750eb Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Wed, 30 Oct 2024 13:37:53 +0100 Subject: [PATCH 320/939] 119960: Fixed NPE when retrieving a DSpace object with the api/dso/find endpoint without the required permissions (cherry picked from commit dd8b1d91cb74c5afadc1e545b2192b5ebb8848b1) --- .../app/rest/UUIDLookupRestController.java | 10 ++++- .../app/rest/UUIDLookupRestControllerIT.java | 37 +++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/UUIDLookupRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/UUIDLookupRestController.java index 40c0a79b97b..21631a6737a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/UUIDLookupRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/UUIDLookupRestController.java @@ -24,9 +24,11 @@ import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.rest.utils.DSpaceObjectUtils; import org.dspace.app.rest.utils.Utils; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.DSpaceObject; +import org.dspace.core.Constants; import org.dspace.core.Context; -import org.dspace.discovery.SearchServiceException; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.hateoas.Link; @@ -65,6 +67,9 @@ public class UUIDLookupRestController implements InitializingBean { @Autowired private DiscoverableEndpointsService discoverableEndpointsService; + @Autowired + private AuthorizeService authorizeService; + @Autowired private ConverterService converter; @@ -85,13 +90,14 @@ public void afterPropertiesSet() throws Exception { public void getDSObyIdentifier(HttpServletRequest request, HttpServletResponse response, @RequestParam(PARAM) UUID uuid) - throws IOException, SQLException, SearchServiceException { + throws IOException, SQLException, AuthorizeException { Context context = null; try { context = ContextUtil.obtainContext(request); DSpaceObject dso = dspaceObjectUtil.findDSpaceObject(context, uuid); if (dso != null) { + authorizeService.authorizeAction(context, dso, Constants.READ); DSpaceObjectRest dsor = converter.toRest(dso, utils.obtainProjection()); URI link = linkTo(dsor.getController(), dsor.getCategory(), dsor.getTypePlural()).slash(dsor.getId()) .toUri(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/UUIDLookupRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/UUIDLookupRestControllerIT.java index 8a6debce3ec..3b082164586 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/UUIDLookupRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/UUIDLookupRestControllerIT.java @@ -17,6 +17,8 @@ import org.apache.commons.codec.CharEncoding; import org.apache.commons.io.IOUtils; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.authorize.ResourcePolicy; +import org.dspace.authorize.service.ResourcePolicyService; import org.dspace.builder.BitstreamBuilder; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; @@ -31,6 +33,7 @@ import org.dspace.eperson.Group; import org.junit.Ignore; import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; /** * Integration test for the UUIDLookup endpoint @@ -39,6 +42,9 @@ */ public class UUIDLookupRestControllerIT extends AbstractControllerIntegrationTest { + @Autowired + ResourcePolicyService resourcePolicyService; + @Test /** * Test the proper redirection of a site's uuid @@ -307,4 +313,35 @@ public void testMissingIdentifierParameter() throws Exception { .andExpect(status().isUnprocessableEntity()); } + @Test + public void testUnauthorized() throws Exception { + context.turnOffAuthorisationSystem(); + Community community = CommunityBuilder.createCommunity(context) + .build(); + for (ResourcePolicy rp : resourcePolicyService.find(context, community)) { + resourcePolicyService.delete(context, rp); + } + context.restoreAuthSystemState(); + + getClient().perform(get("/api/dso/find") + .param("uuid", community.getID().toString())) + .andExpect(status().isUnauthorized()); + } + + @Test + public void testForbidden() throws Exception { + context.turnOffAuthorisationSystem(); + Community community = CommunityBuilder.createCommunity(context) + .build(); + for (ResourcePolicy rp : resourcePolicyService.find(context, community)) { + resourcePolicyService.delete(context, rp); + } + context.restoreAuthSystemState(); + + String authToken = getAuthToken(eperson.getEmail(), password); + getClient(authToken).perform(get("/api/dso/find") + .param("uuid", community.getID().toString())) + .andExpect(status().isForbidden()); + } + } From 2136dbf690389f881fc73373fda6be87b63249ab Mon Sep 17 00:00:00 2001 From: Nathan Buckingham Date: Tue, 4 Jun 2024 16:02:53 -0400 Subject: [PATCH 321/939] 110719: Port fix to checkLinks that works on redirects (cherry picked from commit 3dab2a7cea812420422af160d86371a59b48374f) --- .../ctask/general/BasicLinkChecker.java | 14 +++++-- .../impl/TestDSpaceRunnableHandler.java | 39 +++++++++++++++++ .../org/dspace/curate/CurationScriptIT.java | 42 +++++++++++++++++++ 3 files changed, 92 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java b/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java index fbc6eebdb5b..388036bd674 100644 --- a/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java +++ b/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java @@ -132,10 +132,18 @@ protected int getResponseStatus(String url) { try { URL theURL = new URL(url); HttpURLConnection connection = (HttpURLConnection) theURL.openConnection(); - int code = connection.getResponseCode(); - connection.disconnect(); + connection.setInstanceFollowRedirects(true); + int statusCode = connection.getResponseCode(); + if ((statusCode == HttpURLConnection.HTTP_MOVED_TEMP || statusCode == HttpURLConnection.HTTP_MOVED_PERM || + statusCode == HttpURLConnection.HTTP_SEE_OTHER)) { + connection.disconnect(); + String newUrl = connection.getHeaderField("Location"); + if (newUrl != null) { + return getResponseStatus(newUrl); + } - return code; + } + return statusCode; } catch (IOException ioe) { // Must be a bad URL diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/scripts/handler/impl/TestDSpaceRunnableHandler.java b/dspace-server-webapp/src/test/java/org/dspace/app/scripts/handler/impl/TestDSpaceRunnableHandler.java index 1b5b3fa7ac1..aced81cbdfd 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/scripts/handler/impl/TestDSpaceRunnableHandler.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/scripts/handler/impl/TestDSpaceRunnableHandler.java @@ -7,6 +7,9 @@ */ package org.dspace.app.scripts.handler.impl; +import java.util.ArrayList; +import java.util.List; + import org.dspace.scripts.handler.impl.CommandLineDSpaceRunnableHandler; /** @@ -17,6 +20,12 @@ public class TestDSpaceRunnableHandler extends CommandLineDSpaceRunnableHandler private Exception exception = null; + private final List infoMessages = new ArrayList<>(); + + private final List errorMessages = new ArrayList<>(); + + private final List warningMessages = new ArrayList<>(); + /** * We're overriding this method so that we can stop the script from doing the System.exit() if * an exception within the script is thrown @@ -33,4 +42,34 @@ public void handleException(String message, Exception e) { public Exception getException() { return exception; } + + @Override + public void logInfo(String message) { + super.logInfo(message); + infoMessages.add(message); + } + + @Override + public void logWarning(String message) { + super.logWarning(message); + warningMessages.add(message); + } + + @Override + public void logError(String message) { + super.logError(message); + errorMessages.add(message); + } + + public List getInfoMessages() { + return infoMessages; + } + + public List getErrorMessages() { + return errorMessages; + } + + public List getWarningMessages() { + return warningMessages; + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java b/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java index 3e40a855948..6bb9acb9248 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java @@ -9,6 +9,7 @@ import static com.jayway.jsonpath.JsonPath.read; import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -21,6 +22,7 @@ import java.util.stream.Collectors; import com.fasterxml.jackson.databind.ObjectMapper; +import org.dspace.app.launcher.ScriptLauncher; import org.dspace.app.rest.converter.DSpaceRunnableParameterConverter; import org.dspace.app.rest.matcher.ProcessMatcher; import org.dspace.app.rest.model.ParameterValueRest; @@ -28,6 +30,7 @@ import org.dspace.app.rest.model.ScriptRest; import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.app.scripts.handler.impl.TestDSpaceRunnableHandler; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.EPersonBuilder; @@ -41,7 +44,9 @@ import org.dspace.content.factory.ContentServiceFactory; import org.dspace.eperson.EPerson; import org.dspace.scripts.DSpaceCommandLineParameter; +import org.dspace.scripts.DSpaceRunnable; import org.dspace.scripts.configuration.ScriptConfiguration; +import org.dspace.scripts.factory.ScriptServiceFactory; import org.dspace.scripts.service.ScriptService; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -640,4 +645,41 @@ public void securityCurateTest() throws Exception { ProcessBuilder.deleteProcess(idItemRef.get()); } } + + @Test + public void testURLRedirectCurateTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withMetadata("dc", "identifier", "uri", "https://tinyurl.com/2rxpte5s") + .withSubject("ExtraEntry") + .build(); + + String[] args = new String[] {"curate", "-t", "checklinks", "-i", publicItem1.getHandle()}; + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + + ScriptService scriptService = ScriptServiceFactory.getInstance().getScriptService(); + ScriptConfiguration scriptConfiguration = scriptService.getScriptConfiguration(args[0]); + + DSpaceRunnable script = null; + if (scriptConfiguration != null) { + script = scriptService.createDSpaceRunnableForScriptConfiguration(scriptConfiguration); + } + if (script != null) { + script.initialize(args, testDSpaceRunnableHandler, admin); + script.run(); + } + + assertTrue(testDSpaceRunnableHandler.getInfoMessages().contains("200 - OK")); + } } From be905a088750911928b9515f36cc2a24fc2b518f Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Thu, 13 Jun 2024 20:48:31 +0200 Subject: [PATCH 322/939] 110719: IT checking redirect links accepted by checklinks curate task (cherry picked from commit e826660cb0bd926de390e04e466d54ceedb67a1e) --- .../org/dspace/curate/CurationScriptIT.java | 35 ++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java b/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java index 6bb9acb9248..8c0744a09cc 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java @@ -9,6 +9,7 @@ import static com.jayway.jsonpath.JsonPath.read; import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -22,7 +23,7 @@ import java.util.stream.Collectors; import com.fasterxml.jackson.databind.ObjectMapper; -import org.dspace.app.launcher.ScriptLauncher; +import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.converter.DSpaceRunnableParameterConverter; import org.dspace.app.rest.matcher.ProcessMatcher; import org.dspace.app.rest.model.ParameterValueRest; @@ -661,12 +662,19 @@ public void testURLRedirectCurateTest() throws Exception { .withTitle("Public item 1") .withIssueDate("2017-10-17") .withAuthor("Smith, Donald").withAuthor("Doe, John") - .withMetadata("dc", "identifier", "uri", "https://tinyurl.com/2rxpte5s") + // Value not starting with http or https + .withMetadata("dc", "identifier", "uri", "demo.dspace.org/home") + // MetadataValueLinkChecker uri field with regular link + .withMetadata("dc", "description", null, "https://google.com") + // MetadataValueLinkChecker uri field with redirect link + .withMetadata("dc", "description", "uri", "https://demo7.dspace.org/handle/123456789/1") + // MetadataValueLinkChecker uri field with non resolving link + .withMetadata("dc", "description", "uri", "https://www.atmire.com/broken-link") .withSubject("ExtraEntry") .build(); String[] args = new String[] {"curate", "-t", "checklinks", "-i", publicItem1.getHandle()}; - TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler(); ScriptService scriptService = ScriptServiceFactory.getInstance().getScriptService(); ScriptConfiguration scriptConfiguration = scriptService.getScriptConfiguration(args[0]); @@ -676,10 +684,27 @@ public void testURLRedirectCurateTest() throws Exception { script = scriptService.createDSpaceRunnableForScriptConfiguration(scriptConfiguration); } if (script != null) { - script.initialize(args, testDSpaceRunnableHandler, admin); + script.initialize(args, handler, admin); script.run(); } - assertTrue(testDSpaceRunnableHandler.getInfoMessages().contains("200 - OK")); + // field that should be ignored + assertFalse(checkIfInfoTextLoggedByHandler(handler, "demo.dspace.org/home")); + // redirect links in field that should not be ignored (https) => expect OK + assertTrue(checkIfInfoTextLoggedByHandler(handler, "https://demo7.dspace.org/handle/123456789/1 = 200 - OK")); + // regular link in field that should not be ignored (http) => expect OK + assertTrue(checkIfInfoTextLoggedByHandler(handler, "https://google.com = 200 - OK")); + // nonexistent link in field that should not be ignored => expect 404 + assertTrue(checkIfInfoTextLoggedByHandler(handler, "https://www.atmire.com/broken-link = 404 - FAILED")); } + + boolean checkIfInfoTextLoggedByHandler(TestDSpaceRunnableHandler handler, String text) { + for (String message: handler.getInfoMessages()) { + if (StringUtils.containsIgnoreCase(message, text)) { + return true; + } + } + return false; + } + } From 3ff38ec4b44acbae43292b0b26623623aac64200 Mon Sep 17 00:00:00 2001 From: Nathan Buckingham Date: Tue, 12 Nov 2024 15:59:17 -0500 Subject: [PATCH 323/939] 115778: Adjust redirect check to only follow a limited amount defined in curate.cfg (cherry picked from commit ef381aa1516df0d45227cd6c752b32658a69509b) --- .../dspace/ctask/general/BasicLinkChecker.java | 16 ++++++++++++---- dspace/config/modules/curate.cfg | 3 +++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java b/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java index 388036bd674..a302159ea9a 100644 --- a/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java +++ b/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java @@ -19,6 +19,8 @@ import org.dspace.content.MetadataValue; import org.dspace.curate.AbstractCurationTask; import org.dspace.curate.Curator; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; /** * A basic link checker that is designed to be extended. By default this link checker @@ -42,6 +44,9 @@ public class BasicLinkChecker extends AbstractCurationTask { // The log4j logger for this class private static Logger log = org.apache.logging.log4j.LogManager.getLogger(BasicLinkChecker.class); + protected static final ConfigurationService configurationService + = DSpaceServicesFactory.getInstance().getConfigurationService(); + /** * Perform the link checking. @@ -110,7 +115,8 @@ protected List getURLs(Item item) { */ protected boolean checkURL(String url, StringBuilder results) { // Link check the URL - int httpStatus = getResponseStatus(url); + int redirects = 0; + int httpStatus = getResponseStatus(url, redirects); if ((httpStatus >= 200) && (httpStatus < 300)) { results.append(" - " + url + " = " + httpStatus + " - OK\n"); @@ -128,18 +134,20 @@ protected boolean checkURL(String url, StringBuilder results) { * @param url The url to open * @return The HTTP response code (e.g. 200 / 301 / 404 / 500) */ - protected int getResponseStatus(String url) { + protected int getResponseStatus(String url, int redirects) { try { URL theURL = new URL(url); HttpURLConnection connection = (HttpURLConnection) theURL.openConnection(); connection.setInstanceFollowRedirects(true); int statusCode = connection.getResponseCode(); + int maxRedirect = configurationService.getIntProperty("curate.checklinks.max-redirect", 0); if ((statusCode == HttpURLConnection.HTTP_MOVED_TEMP || statusCode == HttpURLConnection.HTTP_MOVED_PERM || statusCode == HttpURLConnection.HTTP_SEE_OTHER)) { connection.disconnect(); String newUrl = connection.getHeaderField("Location"); - if (newUrl != null) { - return getResponseStatus(newUrl); + if (newUrl != null && (maxRedirect >= redirects || maxRedirect == -1)) { + redirects++; + return getResponseStatus(newUrl, redirects); } } diff --git a/dspace/config/modules/curate.cfg b/dspace/config/modules/curate.cfg index 1d7b87960df..6e75738de54 100644 --- a/dspace/config/modules/curate.cfg +++ b/dspace/config/modules/curate.cfg @@ -26,3 +26,6 @@ curate.taskqueue.dir = ${dspace.dir}/ctqueues # (optional) directory location of scripted (non-java) tasks # curate.script.dir = ${dspace.dir}/ctscripts + +# Maximum amount of redirects set to 0 for none and -1 for unlimited +curate.checklinks.max-redirect = 0 From 25fb8111f1aa18cd45b331d169dce32789a97068 Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Tue, 23 Jul 2024 18:28:43 +0200 Subject: [PATCH 324/939] 116609: Improve running process observability - keep temp process log files in [dspace]/log/processes/ instead of temp dir - reformat file names of process logs - ensure that running and scheduled processes are cleaned up during startup (cherry picked from commit d80f49e0238981c22703abbb1423aa2896eca694) --- .../dspace/scripts/ProcessServiceImpl.java | 56 +++++++++++++++---- .../app/rest/ProcessRestRepositoryIT.java | 8 +-- 2 files changed, 49 insertions(+), 15 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java b/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java index f242ec7acda..f48fd02a920 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java @@ -45,14 +45,15 @@ import org.dspace.core.LogHelper; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; -import org.dspace.eperson.service.EPersonService; import org.dspace.scripts.service.ProcessService; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; /** * The implementation for the {@link ProcessService} class */ -public class ProcessServiceImpl implements ProcessService { +public class ProcessServiceImpl implements ProcessService, InitializingBean { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(ProcessService.class); @@ -72,7 +73,26 @@ public class ProcessServiceImpl implements ProcessService { private MetadataFieldService metadataFieldService; @Autowired - private EPersonService ePersonService; + private ConfigurationService configurationService; + + @Override + public void afterPropertiesSet() throws Exception { + Context context = new Context(); + + // Processes that were running or scheduled when tomcat crashed, should be cleaned up during startup. + List processesToBeFailed = findByStatusAndCreationTimeOlderThan( + context, List.of(ProcessStatus.RUNNING, ProcessStatus.SCHEDULED), new Date()); + for (Process process : processesToBeFailed) { + context.setCurrentUser(process.getEPerson()); + // Fail the process. + log.info("Process with ID {} did not complete before tomcat shutdown, failing it now.", process.getID()); + fail(context, process); + // But still attach its log to the process. + createLogBitstream(context, process); + } + + context.complete(); + } @Override public Process create(Context context, EPerson ePerson, String scriptName, @@ -293,8 +313,8 @@ public int countSearch(Context context, ProcessQueryParameterContainer processQu @Override public void appendLog(int processId, String scriptName, String output, ProcessLogLevel processLogLevel) throws IOException { - File tmpDir = FileUtils.getTempDirectory(); - File tempFile = new File(tmpDir, scriptName + processId + ".log"); + File logsDir = getLogsDirectory(); + File tempFile = new File(logsDir, processId + "-" + scriptName + ".log"); FileWriter out = new FileWriter(tempFile, true); try { try (BufferedWriter writer = new BufferedWriter(out)) { @@ -309,12 +329,15 @@ public void appendLog(int processId, String scriptName, String output, ProcessLo @Override public void createLogBitstream(Context context, Process process) throws IOException, SQLException, AuthorizeException { - File tmpDir = FileUtils.getTempDirectory(); - File tempFile = new File(tmpDir, process.getName() + process.getID() + ".log"); - FileInputStream inputStream = FileUtils.openInputStream(tempFile); - appendFile(context, process, inputStream, Process.OUTPUT_TYPE, process.getName() + process.getID() + ".log"); - inputStream.close(); - tempFile.delete(); + File logsDir = getLogsDirectory(); + File tempFile = new File(logsDir, process.getID() + "-" + process.getName() + ".log"); + if (tempFile.exists()) { + FileInputStream inputStream = FileUtils.openInputStream(tempFile); + appendFile(context, process, inputStream, Process.OUTPUT_TYPE, + process.getID() + "-" + process.getName() + ".log"); + inputStream.close(); + tempFile.delete(); + } } @Override @@ -343,4 +366,15 @@ private String formatLogLine(int processId, String scriptName, String output, Pr return sb.toString(); } + private File getLogsDirectory() { + String pathStr = configurationService.getProperty("dspace.dir") + + File.separator + "log" + File.separator + "processes"; + File logsDir = new File(pathStr); + if (!logsDir.exists()) { + if (!logsDir.mkdirs()) { + throw new RuntimeException("Couldn't create [dspace.dir]/log/processes/ directory."); + } + } + return logsDir; + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ProcessRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ProcessRestRepositoryIT.java index 670d8e2f35b..6c018df6d07 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ProcessRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ProcessRestRepositoryIT.java @@ -856,10 +856,10 @@ public void getProcessOutput() throws Exception { getClient(token).perform(get("/api/system/processes/" + process1.getID() + "/output")) .andExpect(status().isOk()) .andExpect(jsonPath("$.name", - is(process1.getName() + process1.getID() + ".log"))) + is(process1.getID() + "-" + process1.getName() + ".log"))) .andExpect(jsonPath("$.type", is("bitstream"))) .andExpect(jsonPath("$.metadata['dc.title'][0].value", - is(process1.getName() + process1.getID() + ".log"))) + is(process1.getID() + "-" + process1.getName() + ".log"))) .andExpect(jsonPath("$.metadata['dspace.process.filetype'][0].value", is("script_output"))); @@ -869,10 +869,10 @@ public void getProcessOutput() throws Exception { .perform(get("/api/system/processes/" + process1.getID() + "/output")) .andExpect(status().isOk()) .andExpect(jsonPath("$.name", - is(process1.getName() + process1.getID() + ".log"))) + is(process1.getID() + "-" + process1.getName() + ".log"))) .andExpect(jsonPath("$.type", is("bitstream"))) .andExpect(jsonPath("$.metadata['dc.title'][0].value", - is(process1.getName() + process1.getID() + ".log"))) + is(process1.getID() + "-" + process1.getName() + ".log"))) .andExpect(jsonPath("$.metadata['dspace.process.filetype'][0].value", is("script_output"))); From 1ba7ca3bdda705d26d7dbccf33aa19933e810bde Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Thu, 25 Jul 2024 09:33:50 +0200 Subject: [PATCH 325/939] 116609: Add tomcat shutdown line to process log (cherry picked from commit 156ad471b575dfc1c2b643ec4b2f677d6f89a314) --- .../src/main/java/org/dspace/scripts/ProcessServiceImpl.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java b/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java index f48fd02a920..c34705a64d5 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java @@ -88,6 +88,9 @@ public void afterPropertiesSet() throws Exception { log.info("Process with ID {} did not complete before tomcat shutdown, failing it now.", process.getID()); fail(context, process); // But still attach its log to the process. + appendLog(process.getID(), process.getName(), + "Process did not complete before tomcat shutdown.", + ProcessLogLevel.ERROR); createLogBitstream(context, process); } From 3f836ae7a58a635163ca6a3a0a62cf9c514700ad Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Thu, 25 Jul 2024 14:13:16 +0200 Subject: [PATCH 326/939] 116687: Never handle exception with null message (cherry picked from commit bdf7069cb752782a4f8df62885c9ae6116be9dde) --- .../rest/scripts/handler/impl/RestDSpaceRunnableHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/scripts/handler/impl/RestDSpaceRunnableHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/scripts/handler/impl/RestDSpaceRunnableHandler.java index 596ab442909..ee67baa8ab3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/scripts/handler/impl/RestDSpaceRunnableHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/scripts/handler/impl/RestDSpaceRunnableHandler.java @@ -130,7 +130,7 @@ public void handleCompletion() { @Override public void handleException(Exception e) { - handleException(null, e); + handleException(e.getMessage(), e); } @Override From 8bc100375af69d02ea4ead19561f80effbf6362e Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Mon, 29 Jul 2024 13:48:32 +0200 Subject: [PATCH 327/939] 116609: Add try catch to init method in ProcessServiceImpl (cherry picked from commit 070fe689d70d8dbfcde63899d377f2524f38d6db) --- .../dspace/scripts/ProcessServiceImpl.java | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java b/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java index c34705a64d5..ab5147221cf 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java @@ -77,24 +77,29 @@ public class ProcessServiceImpl implements ProcessService, InitializingBean { @Override public void afterPropertiesSet() throws Exception { - Context context = new Context(); - - // Processes that were running or scheduled when tomcat crashed, should be cleaned up during startup. - List processesToBeFailed = findByStatusAndCreationTimeOlderThan( - context, List.of(ProcessStatus.RUNNING, ProcessStatus.SCHEDULED), new Date()); - for (Process process : processesToBeFailed) { - context.setCurrentUser(process.getEPerson()); - // Fail the process. - log.info("Process with ID {} did not complete before tomcat shutdown, failing it now.", process.getID()); - fail(context, process); - // But still attach its log to the process. - appendLog(process.getID(), process.getName(), - "Process did not complete before tomcat shutdown.", - ProcessLogLevel.ERROR); - createLogBitstream(context, process); - } + try { + Context context = new Context(); + + // Processes that were running or scheduled when tomcat crashed, should be cleaned up during startup. + List processesToBeFailed = findByStatusAndCreationTimeOlderThan( + context, List.of(ProcessStatus.RUNNING, ProcessStatus.SCHEDULED), new Date()); + for (Process process : processesToBeFailed) { + context.setCurrentUser(process.getEPerson()); + // Fail the process. + log.info("Process with ID {} did not complete before tomcat shutdown, failing it now.", + process.getID()); + fail(context, process); + // But still attach its log to the process. + appendLog(process.getID(), process.getName(), + "Process did not complete before tomcat shutdown.", + ProcessLogLevel.ERROR); + createLogBitstream(context, process); + } - context.complete(); + context.complete(); + } catch (Exception e) { + log.error("Unable to clean up Processes: ", e); + } } @Override From f8bf278f06af860fe47c92947f15f9be7a68326a Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 3 Dec 2024 12:07:18 -0600 Subject: [PATCH 328/939] Ensure login occurs *before* setup-buildx, as some buildx commands appear to be unauthenticated. --- .github/workflows/reusable-docker-build.yml | 36 ++++++++++----------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml index 83dfd74b9a1..d5964454a53 100644 --- a/.github/workflows/reusable-docker-build.yml +++ b/.github/workflows/reusable-docker-build.yml @@ -105,29 +105,29 @@ jobs: - name: Checkout codebase uses: actions/checkout@v4 - # https://github.com/docker/setup-buildx-action - - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v3 + # https://github.com/docker/login-action + - name: Login to DockerHub + # Only login if not a PR, as PRs only trigger a Docker build and not a push + if: ${{ ! matrix.isPr }} + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_ACCESS_TOKEN }} # https://github.com/docker/setup-qemu-action - name: Set up QEMU emulation to build for multiple architectures uses: docker/setup-qemu-action@v3 + # https://github.com/docker/setup-buildx-action + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v3 + #------------------------------------------------------------ # Build & deploy steps for new commits to a branch (non-PRs) # # These steps build the images, push to DockerHub, and # (if necessary) redeploy demo/sandbox sites. #------------------------------------------------------------ - # https://github.com/docker/login-action - - name: Login to DockerHub - # Only login if not a PR, as PRs only trigger a Docker build and not a push - if: ${{ ! matrix.isPr }} - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_ACCESS_TOKEN }} - # https://github.com/docker/metadata-action # Get Metadata for docker_build_deps step below - name: Sync metadata (tags, labels) from GitHub to Docker for image @@ -257,6 +257,12 @@ jobs: pattern: digests-${{ inputs.build_id }}-* merge-multiple: true + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_ACCESS_TOKEN }} + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -268,12 +274,6 @@ jobs: tags: ${{ env.IMAGE_TAGS }} flavor: ${{ env.TAGS_FLAVOR }} - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_ACCESS_TOKEN }} - - name: Create manifest list from digests and push working-directory: /tmp/digests run: | From 8d34f06396328c888ff3695230a92ceb9d181eab Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 5 Dec 2024 10:39:34 -0600 Subject: [PATCH 329/939] Update Actions to always deploy the locally created image. non-PRs do not need to redownload images from DockerHub. --- .github/workflows/docker.yml | 24 ++++++++++----------- .github/workflows/reusable-docker-build.yml | 21 ++++++++++++++---- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index daa940f215a..080ffc1c080 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -149,9 +149,9 @@ jobs: DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }} - ######################################################################## - # Test Deployment via Docker to ensure images are working properly - ######################################################################## + ################################################################################# + # Test Deployment via Docker to ensure newly built images are working properly + ################################################################################# docker-deploy: # Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace' if: github.repository == 'dspace/dspace' @@ -171,25 +171,23 @@ jobs: swordv2__D__server__P__enabled: true # If this is a PR, force using "pr-testing" version of all Docker images. Otherwise, if on main branch, use the # "latest" tag. Otherwise, use the branch name. NOTE: the "pr-testing" tag is a temporary tag that we assign to - # all PR-built docker images in reusabe-docker-build.yml + # all PR-built docker images in reusable-docker-build.yml DSPACE_VER: ${{ (github.event_name == 'pull_request' && 'pr-testing') || (github.ref_name == github.event.repository.default_branch && 'latest') || github.ref_name }} steps: # Checkout our codebase (to get access to Docker Compose scripts) - name: Checkout codebase uses: actions/checkout@v4 - # For PRs, download Docker image artifacts (built by reusable-docker-build.yml for all PRs) - - name: Download Docker image artifacts (for PRs) - if: github.event_name == 'pull_request' + # Download Docker image artifacts (which were just built by reusable-docker-build.yml) + - name: Download Docker image artifacts uses: actions/download-artifact@v4 with: - # Download all Docker images (TAR files) into the /tmp/docker directory - pattern: docker-image-* + # Download all amd64 Docker images (TAR files) into the /tmp/docker directory + pattern: docker-image-*-linux-amd64 path: /tmp/docker merge-multiple: true - # For PRs, load each of the images into Docker by calling "docker image load" for each. - # This ensures we are using the images built from this PR & not the prior versions on DockerHub - - name: Load all downloaded Docker images (for PRs) - if: github.event_name == 'pull_request' + # Load each of the images into Docker by calling "docker image load" for each. + # This ensures we are using the images just built & not any prior versions on DockerHub + - name: Load all downloaded Docker images run: | find /tmp/docker -type f -name "*.tar" -exec docker image load --input "{}" \; docker image ls -a diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml index d5964454a53..e6f24bb64f4 100644 --- a/.github/workflows/reusable-docker-build.yml +++ b/.github/workflows/reusable-docker-build.yml @@ -130,7 +130,7 @@ jobs: #------------------------------------------------------------ # https://github.com/docker/metadata-action # Get Metadata for docker_build_deps step below - - name: Sync metadata (tags, labels) from GitHub to Docker for image + - name: Extract metadata (tags, labels) from GitHub for Docker image if: ${{ ! matrix.isPr }} id: meta_build uses: docker/metadata-action@v5 @@ -151,10 +151,23 @@ jobs: context: ${{ inputs.dockerfile_context }} file: ${{ inputs.dockerfile_path }} platforms: ${{ matrix.arch }} - push: true # Use tags / labels provided by 'docker/metadata-action' above tags: ${{ steps.meta_build.outputs.tags }} labels: ${{ steps.meta_build.outputs.labels }} + # Export image to both Docker registry & to a local TAR file + outputs: | + registry + type=docker,dest=/tmp/${{ inputs.build_id }}.tar + + # Upload the local docker image (in TAR file) to a build Artifact + - name: Upload local image to artifact + if: ${{ ! matrix.isPr }} + uses: actions/upload-artifact@v4 + with: + name: docker-image-${{ inputs.build_id }}-${{ env.ARCH_NAME }} + path: /tmp/${{ inputs.build_id }}.tar + if-no-files-found: error + retention-days: 1 # Export the digest of Docker build locally (for non PRs only) - name: Export Docker build digest @@ -204,7 +217,7 @@ jobs: # brand-new images for automated testing. #-------------------------------------------------------------- # Get Metadata for docker_build_deps step below - - name: Create metadata (tags, labels) for local Docker image + - name: Extract metadata (tags, labels) for local Docker image if: matrix.isPr id: meta_build_pr uses: docker/metadata-action@v5 @@ -233,7 +246,7 @@ jobs: if: matrix.isPr uses: actions/upload-artifact@v4 with: - name: docker-image-${{ inputs.build_id }} + name: docker-image-${{ inputs.build_id }}-${{ env.ARCH_NAME }} path: /tmp/${{ inputs.build_id }}.tar if-no-files-found: error retention-days: 1 From 1f5defe643bdcf27cca1ea51a00d3a78b937124b Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 5 Dec 2024 11:24:31 -0600 Subject: [PATCH 330/939] Ensure non-PRs run image build twice. First for a DockerHub image, then to export a local tarball. Since these are different image types they cannot be combined --- .github/workflows/reusable-docker-build.yml | 41 ++++++++++++++------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml index e6f24bb64f4..b8f8d0f9171 100644 --- a/.github/workflows/reusable-docker-build.yml +++ b/.github/workflows/reusable-docker-build.yml @@ -151,23 +151,10 @@ jobs: context: ${{ inputs.dockerfile_context }} file: ${{ inputs.dockerfile_path }} platforms: ${{ matrix.arch }} + push: true # Use tags / labels provided by 'docker/metadata-action' above tags: ${{ steps.meta_build.outputs.tags }} labels: ${{ steps.meta_build.outputs.labels }} - # Export image to both Docker registry & to a local TAR file - outputs: | - registry - type=docker,dest=/tmp/${{ inputs.build_id }}.tar - - # Upload the local docker image (in TAR file) to a build Artifact - - name: Upload local image to artifact - if: ${{ ! matrix.isPr }} - uses: actions/upload-artifact@v4 - with: - name: docker-image-${{ inputs.build_id }}-${{ env.ARCH_NAME }} - path: /tmp/${{ inputs.build_id }}.tar - if-no-files-found: error - retention-days: 1 # Export the digest of Docker build locally (for non PRs only) - name: Export Docker build digest @@ -187,6 +174,32 @@ jobs: if-no-files-found: error retention-days: 1 + # Build local image (again) and store in a TAR file in /tmp directory + # NOTE: This cannot be combined with push to DockerHub registry above as it's a different type of output. + - name: Build and push image to local image + if: ${{ ! matrix.isPr }} + uses: docker/build-push-action@v5 + with: + build-contexts: | + ${{ inputs.dockerfile_additional_contexts }} + context: ${{ inputs.dockerfile_context }} + file: ${{ inputs.dockerfile_path }} + platforms: ${{ matrix.arch }} + tags: ${{ steps.meta_build.outputs.tags }} + labels: ${{ steps.meta_build.outputs.labels }} + # Export image to a local TAR file + outputs: type=docker,dest=/tmp/${{ inputs.build_id }}.tar + + # Upload the local docker image (in TAR file) to a build Artifact + - name: Upload local image to artifact + if: ${{ ! matrix.isPr }} + uses: actions/upload-artifact@v4 + with: + name: docker-image-${{ inputs.build_id }}-${{ env.ARCH_NAME }} + path: /tmp/${{ inputs.build_id }}.tar + if-no-files-found: error + retention-days: 1 + # If this build is NOT a PR and passed in a REDEPLOY_SANDBOX_URL secret, # Then redeploy https://sandbox.dspace.org if this build is for our deployment architecture and 'main' branch. - name: Redeploy sandbox.dspace.org (based on main branch) From 0fc0f181fd8b49c1b6e278b4bf8aa5b7f8711332 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 6 Dec 2024 11:26:59 -0600 Subject: [PATCH 331/939] Enable caching of Docker builds using GitHub Actions cache --- .github/workflows/reusable-docker-build.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml index b8f8d0f9171..92dd08623c0 100644 --- a/.github/workflows/reusable-docker-build.yml +++ b/.github/workflows/reusable-docker-build.yml @@ -155,6 +155,10 @@ jobs: # Use tags / labels provided by 'docker/metadata-action' above tags: ${{ steps.meta_build.outputs.tags }} labels: ${{ steps.meta_build.outputs.labels }} + # Use GitHub cache to load cached Docker images and cache the results of this build + # This decreases the number of images we need to fetch from DockerHub + cache-from: type=gha + cache-to: type=gha,mode=max # Export the digest of Docker build locally (for non PRs only) - name: Export Docker build digest @@ -240,6 +244,7 @@ jobs: # for testing in docker.yml tags: pr-testing flavor: ${{ env.TAGS_FLAVOR }} + # Build local image and stores in a TAR file in /tmp directory - name: Build and push image to local image if: matrix.isPr @@ -252,8 +257,13 @@ jobs: platforms: ${{ matrix.arch }} tags: ${{ steps.meta_build_pr.outputs.tags }} labels: ${{ steps.meta_build_pr.outputs.labels }} + # Use GitHub cache to load cached Docker images and cache the results of this build + # This decreases the number of images we need to fetch from DockerHub + cache-from: type=gha + cache-to: type=gha,mode=max # Export image to a local TAR file outputs: type=docker,dest=/tmp/${{ inputs.build_id }}.tar + # Upload the local docker image (in TAR file) to a build Artifact - name: Upload local image to artifact if: matrix.isPr From 46c169fb7947acfc559ef07f67d0672880da7d0e Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 6 Dec 2024 12:51:07 -0600 Subject: [PATCH 332/939] Refactor to use the same local TAR image build for PRs and non-PRs. These TAR images act as a cache for our current build. --- .github/workflows/docker.yml | 4 -- .github/workflows/reusable-docker-build.yml | 76 ++++++--------------- 2 files changed, 20 insertions(+), 60 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 080ffc1c080..6195aadb4eb 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -169,10 +169,6 @@ jobs: signposting__P__enabled: true sword__D__server__P__enabled: true swordv2__D__server__P__enabled: true - # If this is a PR, force using "pr-testing" version of all Docker images. Otherwise, if on main branch, use the - # "latest" tag. Otherwise, use the branch name. NOTE: the "pr-testing" tag is a temporary tag that we assign to - # all PR-built docker images in reusable-docker-build.yml - DSPACE_VER: ${{ (github.event_name == 'pull_request' && 'pr-testing') || (github.ref_name == github.event.repository.default_branch && 'latest') || github.ref_name }} steps: # Checkout our codebase (to get access to Docker Compose scripts) - name: Checkout codebase diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml index 92dd08623c0..3c497410a27 100644 --- a/.github/workflows/reusable-docker-build.yml +++ b/.github/workflows/reusable-docker-build.yml @@ -122,16 +122,9 @@ jobs: - name: Setup Docker Buildx uses: docker/setup-buildx-action@v3 - #------------------------------------------------------------ - # Build & deploy steps for new commits to a branch (non-PRs) - # - # These steps build the images, push to DockerHub, and - # (if necessary) redeploy demo/sandbox sites. - #------------------------------------------------------------ # https://github.com/docker/metadata-action - # Get Metadata for docker_build_deps step below + # Extract metadata used for Docker images in all build steps below - name: Extract metadata (tags, labels) from GitHub for Docker image - if: ${{ ! matrix.isPr }} id: meta_build uses: docker/metadata-action@v5 with: @@ -139,6 +132,12 @@ jobs: tags: ${{ env.IMAGE_TAGS }} flavor: ${{ env.TAGS_FLAVOR }} + #------------------------------------------------------------ + # Build & deploy steps for new commits to a branch (non-PRs) + # + # These steps build the images, push to DockerHub, and + # (if necessary) redeploy demo/sandbox sites. + #------------------------------------------------------------ # https://github.com/docker/build-push-action - name: Build and push image to DockerHub # Only build & push if not a PR @@ -178,32 +177,6 @@ jobs: if-no-files-found: error retention-days: 1 - # Build local image (again) and store in a TAR file in /tmp directory - # NOTE: This cannot be combined with push to DockerHub registry above as it's a different type of output. - - name: Build and push image to local image - if: ${{ ! matrix.isPr }} - uses: docker/build-push-action@v5 - with: - build-contexts: | - ${{ inputs.dockerfile_additional_contexts }} - context: ${{ inputs.dockerfile_context }} - file: ${{ inputs.dockerfile_path }} - platforms: ${{ matrix.arch }} - tags: ${{ steps.meta_build.outputs.tags }} - labels: ${{ steps.meta_build.outputs.labels }} - # Export image to a local TAR file - outputs: type=docker,dest=/tmp/${{ inputs.build_id }}.tar - - # Upload the local docker image (in TAR file) to a build Artifact - - name: Upload local image to artifact - if: ${{ ! matrix.isPr }} - uses: actions/upload-artifact@v4 - with: - name: docker-image-${{ inputs.build_id }}-${{ env.ARCH_NAME }} - path: /tmp/${{ inputs.build_id }}.tar - if-no-files-found: error - retention-days: 1 - # If this build is NOT a PR and passed in a REDEPLOY_SANDBOX_URL secret, # Then redeploy https://sandbox.dspace.org if this build is for our deployment architecture and 'main' branch. - name: Redeploy sandbox.dspace.org (based on main branch) @@ -227,27 +200,19 @@ jobs: curl -X POST $REDEPLOY_DEMO_URL #------------------------------------------------------------- - # Build steps for PRs only + # Shared Build steps. + # These are used for PRs as well as new commits to a branch (non-PRs) # - # These steps build the images and store as a build artifact. - # These artifacts can then be used by later jobs to run the - # brand-new images for automated testing. + # These steps build the images and cache/store as a build artifact. + # These artifacts can then be used by later jobs to install the + # brand-new images for automated testing. For non-PRs, this cache is + # also used to avoid pulling the images we just built from DockerHub. #-------------------------------------------------------------- - # Get Metadata for docker_build_deps step below - - name: Extract metadata (tags, labels) for local Docker image - if: matrix.isPr - id: meta_build_pr - uses: docker/metadata-action@v5 - with: - images: ${{ env.IMAGE_NAME }} - # Hardcode to use custom "pr-testing" tag because that will allow us to spin up this PR - # for testing in docker.yml - tags: pr-testing - flavor: ${{ env.TAGS_FLAVOR }} - # Build local image and stores in a TAR file in /tmp directory - - name: Build and push image to local image - if: matrix.isPr + # Build local image (again) and store in a TAR file in /tmp directory + # NOTE: This build is run for both PRs and non-PRs as it's used to "cache" our built images as artifacts. + # NOTE #2: This cannot be combined with push to DockerHub registry above as it's a different type of output. + - name: Build and push image to local TAR file uses: docker/build-push-action@v5 with: build-contexts: | @@ -255,8 +220,8 @@ jobs: context: ${{ inputs.dockerfile_context }} file: ${{ inputs.dockerfile_path }} platforms: ${{ matrix.arch }} - tags: ${{ steps.meta_build_pr.outputs.tags }} - labels: ${{ steps.meta_build_pr.outputs.labels }} + tags: ${{ steps.meta_build.outputs.tags }} + labels: ${{ steps.meta_build.outputs.labels }} # Use GitHub cache to load cached Docker images and cache the results of this build # This decreases the number of images we need to fetch from DockerHub cache-from: type=gha @@ -265,8 +230,7 @@ jobs: outputs: type=docker,dest=/tmp/${{ inputs.build_id }}.tar # Upload the local docker image (in TAR file) to a build Artifact - - name: Upload local image to artifact - if: matrix.isPr + - name: Upload local image TAR to artifact uses: actions/upload-artifact@v4 with: name: docker-image-${{ inputs.build_id }}-${{ env.ARCH_NAME }} From 020fec96d8206572b2517dcd9dc67fa920c10b23 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 6 Dec 2024 14:12:28 -0600 Subject: [PATCH 333/939] Ensure PRs are tagging their images with same tag as the base branch the PR was created against --- .github/workflows/reusable-docker-build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml index 3c497410a27..723c1c77256 100644 --- a/.github/workflows/reusable-docker-build.yml +++ b/.github/workflows/reusable-docker-build.yml @@ -54,10 +54,13 @@ env: # For a new commit on default branch (main), use the literal tag 'latest' on Docker image. # For a new commit on other branches, use the branch name as the tag for Docker image. # For a new tag, copy that tag name as the tag for Docker image. + # For a pull request, use the name of the base branch that the PR was created against or "latest" (for main). + # e.g. PR against 'main' will use "latest". a PR against 'dspace-7_x' will use 'dspace-7_x'. IMAGE_TAGS: | type=raw,value=latest,enable=${{ github.ref_name == github.event.repository.default_branch }} type=ref,event=branch,enable=${{ github.ref_name != github.event.repository.default_branch }} type=ref,event=tag + type=raw,value=${{ (github.event.pull_request.base.ref == github.event.repository.default_branch && 'latest') || github.event.pull_request.base.ref }},enable=${{ github.event_name == 'pull_request' }} # Define default tag "flavor" for docker/metadata-action per # https://github.com/docker/metadata-action#flavor-input # We manage the 'latest' tag ourselves to the 'main' branch (see settings above) From 3c0e7158a26b13ae01de500298e94d65e3601c97 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 6 Dec 2024 15:01:08 -0600 Subject: [PATCH 334/939] Ensure each image has a separate cache. This allows later builds of that same image to inherit that cache. --- .github/workflows/reusable-docker-build.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml index 723c1c77256..d9f4c5342b4 100644 --- a/.github/workflows/reusable-docker-build.yml +++ b/.github/workflows/reusable-docker-build.yml @@ -159,8 +159,8 @@ jobs: labels: ${{ steps.meta_build.outputs.labels }} # Use GitHub cache to load cached Docker images and cache the results of this build # This decreases the number of images we need to fetch from DockerHub - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: type=gha,scope=${{ inputs.build_id }} + cache-to: type=gha,scope=${{ inputs.build_id }},mode=max # Export the digest of Docker build locally (for non PRs only) - name: Export Docker build digest @@ -227,8 +227,8 @@ jobs: labels: ${{ steps.meta_build.outputs.labels }} # Use GitHub cache to load cached Docker images and cache the results of this build # This decreases the number of images we need to fetch from DockerHub - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: type=gha,scope=${{ inputs.build_id }} + cache-to: type=gha,scope=${{ inputs.build_id }},mode=max # Export image to a local TAR file outputs: type=docker,dest=/tmp/${{ inputs.build_id }}.tar From 78855cdbbab98038f61e2bd94922e4fa3db9ad18 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 9 Dec 2024 10:18:32 -0600 Subject: [PATCH 335/939] Ensure we use the main Docker image, and not the "-test" image. --- .github/workflows/docker.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 6195aadb4eb..45d6fd6b027 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -169,6 +169,9 @@ jobs: signposting__P__enabled: true sword__D__server__P__enabled: true swordv2__D__server__P__enabled: true + # If this is a PR, used the base branch name. If on main branch, use the "latest" tag. Otherwise, use branch name. + # NOTE: DSPACE_VER is used because our docker compose scripts default to using the "-test" image. + DSPACE_VER: ${{ (github.event_name == 'pull_request' && github.event.pull_request.base.ref) || (github.ref_name == github.event.repository.default_branch && 'latest') || github.ref_name }} steps: # Checkout our codebase (to get access to Docker Compose scripts) - name: Checkout codebase From b8c88ef284f95e2a8cb8873f00dcc227fcf58e74 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 9 Dec 2024 12:06:14 -0600 Subject: [PATCH 336/939] Ensure PRs against main also use "latest" tag --- .github/workflows/docker.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 45d6fd6b027..ebbd8cc5098 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -169,9 +169,12 @@ jobs: signposting__P__enabled: true sword__D__server__P__enabled: true swordv2__D__server__P__enabled: true - # If this is a PR, used the base branch name. If on main branch, use the "latest" tag. Otherwise, use branch name. + # If this is a PR against main (default branch), use "latest". + # Else if this is a PR against a different branch, used the base branch name. + # Else if this is a commit on main (default branch), use the "latest" tag. + # Else, just use the branch name. # NOTE: DSPACE_VER is used because our docker compose scripts default to using the "-test" image. - DSPACE_VER: ${{ (github.event_name == 'pull_request' && github.event.pull_request.base.ref) || (github.ref_name == github.event.repository.default_branch && 'latest') || github.ref_name }} + DSPACE_VER: ${{ (github.event_name == 'pull_request' && github.event.pull_request.base.ref == github.event.repository.default_branch && 'latest') || (github.event_name == 'pull_request' && github.event.pull_request.base.ref) || (github.ref_name == github.event.repository.default_branch && 'latest') || github.ref_name }} steps: # Checkout our codebase (to get access to Docker Compose scripts) - name: Checkout codebase From c017a662e0642d32ae59017451ea8994de48ab44 Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Wed, 11 Dec 2024 21:26:42 +0100 Subject: [PATCH 337/939] 121971: #9669 - Remove unsupported OpenAIRE date types --- .../oai/metadataFormats/oai_openaire.xsl | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl b/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl index 3a1d75eb56c..b6abd9daa05 100644 --- a/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl +++ b/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl @@ -1039,6 +1039,8 @@ + + @@ -1050,24 +1052,6 @@ Available - - Collected - - - Copyrighted - - - Created - - - Submitted - - - Updated - - - Valid - From 04953b94d919bf5e0135aa3311b70767cba20160 Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Wed, 11 Dec 2024 21:39:08 +0100 Subject: [PATCH 338/939] 121971: #9715 - Only dc.date.issued should have date types Accepted and Issued --- dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl b/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl index b6abd9daa05..f92367646d1 100644 --- a/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl +++ b/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl @@ -731,7 +731,7 @@ From 31c79500ceec1c8e36228195b802a289ac9c30c9 Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Wed, 11 Dec 2024 21:49:37 +0100 Subject: [PATCH 339/939] 121971: #9716 - Only dc.date.embargo should have date type Available --- dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl b/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl index f92367646d1..7b141e48b3a 100644 --- a/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl +++ b/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl @@ -1049,7 +1049,9 @@ - + + + Available From 52e5b35c0653afc17ebd47ce1272549ddf205469 Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Thu, 12 Dec 2024 09:16:21 +0100 Subject: [PATCH 340/939] 121971: #9664 - Make cclicense step required in openaire submission form --- dspace/config/item-submission.xml | 4 ++++ dspace/config/submission-forms.xml | 23 ----------------------- 2 files changed, 4 insertions(+), 23 deletions(-) diff --git a/dspace/config/item-submission.xml b/dspace/config/item-submission.xml index a6cd49bdf1e..866426afd61 100644 --- a/dspace/config/item-submission.xml +++ b/dspace/config/item-submission.xml @@ -379,6 +379,10 @@ + + + + diff --git a/dspace/config/submission-forms.xml b/dspace/config/submission-forms.xml index 39a4778356c..13c8fc208f6 100644 --- a/dspace/config/submission-forms.xml +++ b/dspace/config/submission-forms.xml @@ -1133,29 +1133,6 @@ - - - dc - rights - false - - dropdown - Select the access type of the resource. - - - - - - oaire - license - condition - false - - dropdown - Select the license of the resource. - - - dc From e5401685944b6135795462748c791ed7ba7bab7d Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Thu, 12 Dec 2024 09:18:40 +0100 Subject: [PATCH 341/939] 121971: #9867 - Remove objectType attribute from openaire crosswalk --- .../crosswalks/oai/metadataFormats/oai_openaire.xsl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl b/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl index 7b141e48b3a..6c6c369071a 100644 --- a/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl +++ b/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl @@ -888,13 +888,13 @@ - + + fulltext - + other - + --> From a6d2c4897b46062cca59add6946eb7a86c648585 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 4 Dec 2024 13:44:44 -0600 Subject: [PATCH 342/939] Add Docker registry to all scripts. Allow it to be configurable for DSpace images (only). Other minor Dockerfile cleanup --- Dockerfile | 17 +++++++++++------ Dockerfile.cli | 17 +++++++++++------ Dockerfile.dependencies | 2 +- Dockerfile.test | 17 +++++++++++------ docker-compose-cli.yml | 2 +- docker-compose.yml | 6 +++--- .../dspace-postgres-pgcrypto-curl/Dockerfile | 2 +- .../docker/dspace-postgres-pgcrypto/Dockerfile | 2 +- .../main/docker/dspace-shibboleth/Dockerfile | 2 +- dspace/src/main/docker/dspace-solr/Dockerfile | 2 +- 10 files changed, 42 insertions(+), 27 deletions(-) diff --git a/Dockerfile b/Dockerfile index adc5d6125f0..ff35f2a2e3a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,9 +6,14 @@ # This Dockerfile uses JDK11 by default, but has also been tested with JDK17. # To build with JDK17, use "--build-arg JDK_VERSION=17" ARG JDK_VERSION=11 +# The Docker version tag to build from +ARG DSPACE_VERSION=dspace-7_x +# The Docker registry to use for DSpace images. Defaults to "docker.io" +# NOTE: non-DSpace images are hardcoded to use "docker.io" and are not impacted by this build argument +ARG DOCKER_REGISTRY=docker.io # Step 1 - Run Maven Build -FROM dspace/dspace-dependencies:dspace-7_x AS build +FROM ${DOCKER_REGISTRY}/dspace/dspace-dependencies:${DSPACE_VERSION} AS build ARG TARGET_DIR=dspace-installer WORKDIR /app # The dspace-installer directory will be written to /install @@ -28,15 +33,15 @@ RUN mvn --no-transfer-progress package ${MAVEN_FLAGS} && \ mvn clean # Step 2 - Run Ant Deploy -FROM eclipse-temurin:${JDK_VERSION} AS ant_build +FROM docker.io/eclipse-temurin:${JDK_VERSION} AS ant_build ARG TARGET_DIR=dspace-installer # COPY the /install directory from 'build' container to /dspace-src in this container COPY --from=build /install /dspace-src WORKDIR /dspace-src # Create the initial install deployment using ANT -ENV ANT_VERSION 1.10.13 -ENV ANT_HOME /tmp/ant-$ANT_VERSION -ENV PATH $ANT_HOME/bin:$PATH +ENV ANT_VERSION=1.10.13 +ENV ANT_HOME=/tmp/ant-$ANT_VERSION +ENV PATH=$ANT_HOME/bin:$PATH # Need wget to install ant RUN apt-get update \ && apt-get install -y --no-install-recommends wget \ @@ -50,7 +55,7 @@ RUN ant init_installation update_configs update_code update_webapps # Step 3 - Run tomcat # Create a new tomcat image that does not retain the the build directory contents -FROM tomcat:9-jdk${JDK_VERSION} +FROM docker.io/tomcat:9-jdk${JDK_VERSION} # NOTE: DSPACE_INSTALL must align with the "dspace.dir" default configuration. ENV DSPACE_INSTALL=/dspace # Copy the /dspace directory from 'ant_build' container to /dspace in this container diff --git a/Dockerfile.cli b/Dockerfile.cli index 8a69b32e2dc..be03e8922b2 100644 --- a/Dockerfile.cli +++ b/Dockerfile.cli @@ -6,9 +6,14 @@ # This Dockerfile uses JDK11 by default, but has also been tested with JDK17. # To build with JDK17, use "--build-arg JDK_VERSION=17" ARG JDK_VERSION=11 +# The Docker version tag to build from +ARG DSPACE_VERSION=dspace-7_x +# The Docker registry to use for DSpace images. Defaults to "docker.io" +# NOTE: non-DSpace images are hardcoded to use "docker.io" and are not impacted by this build argument +ARG DOCKER_REGISTRY=docker.io # Step 1 - Run Maven Build -FROM dspace/dspace-dependencies:dspace-7_x AS build +FROM ${DOCKER_REGISTRY}/dspace/dspace-dependencies:${DSPACE_VERSION} AS build ARG TARGET_DIR=dspace-installer WORKDIR /app # The dspace-installer directory will be written to /install @@ -24,15 +29,15 @@ RUN mvn --no-transfer-progress package && \ mvn clean # Step 2 - Run Ant Deploy -FROM eclipse-temurin:${JDK_VERSION} AS ant_build +FROM docker.io/eclipse-temurin:${JDK_VERSION} AS ant_build ARG TARGET_DIR=dspace-installer # COPY the /install directory from 'build' container to /dspace-src in this container COPY --from=build /install /dspace-src WORKDIR /dspace-src # Create the initial install deployment using ANT -ENV ANT_VERSION 1.10.13 -ENV ANT_HOME /tmp/ant-$ANT_VERSION -ENV PATH $ANT_HOME/bin:$PATH +ENV ANT_VERSION=1.10.13 +ENV ANT_HOME=/tmp/ant-$ANT_VERSION +ENV PATH=$ANT_HOME/bin:$PATH # Need wget to install ant RUN apt-get update \ && apt-get install -y --no-install-recommends wget \ @@ -45,7 +50,7 @@ RUN mkdir $ANT_HOME && \ RUN ant init_installation update_configs update_code # Step 3 - Run jdk -FROM eclipse-temurin:${JDK_VERSION} +FROM docker.io/eclipse-temurin:${JDK_VERSION} # NOTE: DSPACE_INSTALL must align with the "dspace.dir" default configuration. ENV DSPACE_INSTALL=/dspace # Copy the /dspace directory from 'ant_build' container to /dspace in this container diff --git a/Dockerfile.dependencies b/Dockerfile.dependencies index 123206ea588..794dfa9a66a 100644 --- a/Dockerfile.dependencies +++ b/Dockerfile.dependencies @@ -7,7 +7,7 @@ ARG JDK_VERSION=11 # Step 1 - Run Maven Build -FROM maven:3-eclipse-temurin-${JDK_VERSION} AS build +FROM docker.io/maven:3-eclipse-temurin-${JDK_VERSION} AS build ARG TARGET_DIR=dspace-installer WORKDIR /app # Create the 'dspace' user account & home directory diff --git a/Dockerfile.test b/Dockerfile.test index d88699ca52a..08b6b3018b8 100644 --- a/Dockerfile.test +++ b/Dockerfile.test @@ -8,9 +8,14 @@ # This Dockerfile uses JDK11 by default, but has also been tested with JDK17. # To build with JDK17, use "--build-arg JDK_VERSION=17" ARG JDK_VERSION=11 +# The Docker version tag to build from +ARG DSPACE_VERSION=dspace-7_x +# The Docker registry to use for DSpace images. Defaults to "docker.io" +# NOTE: non-DSpace images are hardcoded to use "docker.io" and are not impacted by this build argument +ARG DOCKER_REGISTRY=docker.io # Step 1 - Run Maven Build -FROM dspace/dspace-dependencies:dspace-7_x AS build +FROM ${DOCKER_REGISTRY}/dspace/dspace-dependencies:${DSPACE_VERSION} AS build ARG TARGET_DIR=dspace-installer WORKDIR /app # The dspace-installer directory will be written to /install @@ -27,15 +32,15 @@ RUN mvn --no-transfer-progress package -Pdspace-rest && \ mvn clean # Step 2 - Run Ant Deploy -FROM eclipse-temurin:${JDK_VERSION} AS ant_build +FROM docker.io/eclipse-temurin:${JDK_VERSION} AS ant_build ARG TARGET_DIR=dspace-installer # COPY the /install directory from 'build' container to /dspace-src in this container COPY --from=build /install /dspace-src WORKDIR /dspace-src # Create the initial install deployment using ANT -ENV ANT_VERSION 1.10.12 -ENV ANT_HOME /tmp/ant-$ANT_VERSION -ENV PATH $ANT_HOME/bin:$PATH +ENV ANT_VERSION=1.10.12 +ENV ANT_HOME=/tmp/ant-$ANT_VERSION +ENV PATH=$ANT_HOME/bin:$PATH # Need wget to install ant RUN apt-get update \ && apt-get install -y --no-install-recommends wget \ @@ -49,7 +54,7 @@ RUN ant init_installation update_configs update_code update_webapps # Step 3 - Run tomcat # Create a new tomcat image that does not retain the the build directory contents -FROM tomcat:9-jdk${JDK_VERSION} +FROM docker.io/tomcat:9-jdk${JDK_VERSION} ENV DSPACE_INSTALL=/dspace ENV TOMCAT_INSTALL=/usr/local/tomcat # Copy the /dspace directory from 'ant_build' containger to /dspace in this container diff --git a/docker-compose-cli.yml b/docker-compose-cli.yml index d6a194617e0..ce7676bdc6c 100644 --- a/docker-compose-cli.yml +++ b/docker-compose-cli.yml @@ -6,7 +6,7 @@ networks: external: true services: dspace-cli: - image: "${DOCKER_OWNER:-dspace}/dspace-cli:${DSPACE_VER:-dspace-7_x}" + image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-cli:${DSPACE_VER:-dspace-7_x}" container_name: dspace-cli build: context: . diff --git a/docker-compose.yml b/docker-compose.yml index 23fce37db24..a16c3677503 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,7 +28,7 @@ services: # from the host machine. This IP range MUST correspond to the 'dspacenet' subnet defined above. proxies__P__trusted__P__ipranges: '172.23.0' LOGGING_CONFIG: /dspace/config/log4j2-container.xml - image: "${DOCKER_OWNER:-dspace}/dspace:${DSPACE_VER:-dspace-7_x-test}" + image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace:${DSPACE_VER:-dspace-7_x-test}" build: context: . dockerfile: Dockerfile.test @@ -66,7 +66,7 @@ services: dspacedb: container_name: dspacedb # Uses a custom Postgres image with pgcrypto installed - image: "${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-dspace-7_x}" + image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-dspace-7_x}" build: # Must build out of subdirectory to have access to install script for pgcrypto context: ./dspace/src/main/docker/dspace-postgres-pgcrypto/ @@ -86,7 +86,7 @@ services: # DSpace Solr container dspacesolr: container_name: dspacesolr - image: "${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-dspace-7_x}" + image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-dspace-7_x}" build: context: ./dspace/src/main/docker/dspace-solr/ # Provide path to Solr configs necessary to build Docker image diff --git a/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/Dockerfile b/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/Dockerfile index aabf87df3ed..791242f5bdc 100644 --- a/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/Dockerfile +++ b/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/Dockerfile @@ -13,7 +13,7 @@ ARG POSTGRES_VERSION=15 ARG POSTGRES_PASSWORD=dspace -FROM postgres:${POSTGRES_VERSION} +FROM docker.io/postgres:${POSTGRES_VERSION} ENV POSTGRES_DB dspace ENV POSTGRES_USER dspace diff --git a/dspace/src/main/docker/dspace-postgres-pgcrypto/Dockerfile b/dspace/src/main/docker/dspace-postgres-pgcrypto/Dockerfile index 2298cd4e76e..116d2dbcda3 100644 --- a/dspace/src/main/docker/dspace-postgres-pgcrypto/Dockerfile +++ b/dspace/src/main/docker/dspace-postgres-pgcrypto/Dockerfile @@ -13,7 +13,7 @@ ARG POSTGRES_VERSION=15 ARG POSTGRES_PASSWORD=dspace -FROM postgres:${POSTGRES_VERSION} +FROM docker.io/postgres:${POSTGRES_VERSION} ENV POSTGRES_DB dspace ENV POSTGRES_USER dspace diff --git a/dspace/src/main/docker/dspace-shibboleth/Dockerfile b/dspace/src/main/docker/dspace-shibboleth/Dockerfile index 79f2921bd7d..25fb510e3d3 100644 --- a/dspace/src/main/docker/dspace-shibboleth/Dockerfile +++ b/dspace/src/main/docker/dspace-shibboleth/Dockerfile @@ -10,7 +10,7 @@ # Build from Ubuntu as it has easy Apache tooling (e.g. a2enmod script is debian only). # Apache & mod_shib are required for DSpace to act as an SP # See also https://wiki.lyrasis.org/display/DSDOC7x/Authentication+Plugins#AuthenticationPlugins-ShibbolethAuthentication -FROM ubuntu:20.04 +FROM docker.io/ubuntu:20.04 # Apache ENVs (default values) ENV APACHE_RUN_USER www-data diff --git a/dspace/src/main/docker/dspace-solr/Dockerfile b/dspace/src/main/docker/dspace-solr/Dockerfile index eb8e93493fa..0c011d43105 100644 --- a/dspace/src/main/docker/dspace-solr/Dockerfile +++ b/dspace/src/main/docker/dspace-solr/Dockerfile @@ -12,7 +12,7 @@ ARG SOLR_VERSION=8.11 -FROM solr:${SOLR_VERSION}-slim +FROM docker.io/solr:${SOLR_VERSION}-slim ENV AUTHORITY_CONFIGSET_PATH=/opt/solr/server/solr/configsets/authority/conf \ OAI_CONFIGSET_PATH=/opt/solr/server/solr/configsets/oai/conf \ From 2ee328ff18aa973dd2ed82abd062d8a923cbddf5 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 4 Dec 2024 13:45:33 -0600 Subject: [PATCH 343/939] Minor Dockerfile cleanup. Use new syntax for ENV variables --- .../docker/dspace-postgres-pgcrypto-curl/Dockerfile | 6 +++--- .../main/docker/dspace-postgres-pgcrypto/Dockerfile | 6 +++--- dspace/src/main/docker/dspace-shibboleth/Dockerfile | 12 ++++++------ 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/Dockerfile b/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/Dockerfile index 791242f5bdc..f314fdb79e8 100644 --- a/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/Dockerfile +++ b/dspace/src/main/docker/dspace-postgres-pgcrypto-curl/Dockerfile @@ -15,9 +15,9 @@ ARG POSTGRES_PASSWORD=dspace FROM docker.io/postgres:${POSTGRES_VERSION} -ENV POSTGRES_DB dspace -ENV POSTGRES_USER dspace -ENV POSTGRES_PASSWORD ${POSTGRES_PASSWORD} +ENV POSTGRES_DB=dspace +ENV POSTGRES_USER=dspace +ENV POSTGRES_PASSWORD=${POSTGRES_PASSWORD} # Install curl which is necessary to load SQL file RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* diff --git a/dspace/src/main/docker/dspace-postgres-pgcrypto/Dockerfile b/dspace/src/main/docker/dspace-postgres-pgcrypto/Dockerfile index 116d2dbcda3..4f639f36102 100644 --- a/dspace/src/main/docker/dspace-postgres-pgcrypto/Dockerfile +++ b/dspace/src/main/docker/dspace-postgres-pgcrypto/Dockerfile @@ -15,9 +15,9 @@ ARG POSTGRES_PASSWORD=dspace FROM docker.io/postgres:${POSTGRES_VERSION} -ENV POSTGRES_DB dspace -ENV POSTGRES_USER dspace -ENV POSTGRES_PASSWORD ${POSTGRES_PASSWORD} +ENV POSTGRES_DB=dspace +ENV POSTGRES_USER=dspace +ENV POSTGRES_PASSWORD=${POSTGRES_PASSWORD} # Copy over script which will initialize database and install pgcrypto extension COPY install-pgcrypto.sh /docker-entrypoint-initdb.d/ diff --git a/dspace/src/main/docker/dspace-shibboleth/Dockerfile b/dspace/src/main/docker/dspace-shibboleth/Dockerfile index 25fb510e3d3..15a436d7b6f 100644 --- a/dspace/src/main/docker/dspace-shibboleth/Dockerfile +++ b/dspace/src/main/docker/dspace-shibboleth/Dockerfile @@ -13,12 +13,12 @@ FROM docker.io/ubuntu:20.04 # Apache ENVs (default values) -ENV APACHE_RUN_USER www-data -ENV APACHE_RUN_GROUP www-data -ENV APACHE_LOCK_DIR /var/lock/apache2 -ENV APACHE_LOG_DIR /var/log/apache2 -ENV APACHE_PID_FILE /var/run/apache2/apache2.pid -ENV APACHE_SERVER_NAME localhost +ENV APACHE_RUN_USER=www-data +ENV APACHE_RUN_GROUP=www-data +ENV APACHE_LOCK_DIR=/var/lock/apache2 +ENV APACHE_LOG_DIR=/var/log/apache2 +ENV APACHE_PID_FILE=/var/run/apache2/apache2.pid +ENV APACHE_SERVER_NAME=localhost # Ensure Apache2, mod_shib & shibboleth daemon are installed. # Also install ssl-cert to provide a local SSL cert for use with Apache From 2a6c60e1392cf643b4ae4c8512ce92c7aba4b2eb Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 12 Dec 2024 16:50:02 -0600 Subject: [PATCH 344/939] Refactor Docker build process to use ghcr.io for build, and then copy to docker.io once build completes --- .github/workflows/docker.yml | 1 + .github/workflows/reusable-docker-build.yml | 189 ++++++++++++++------ 2 files changed, 131 insertions(+), 59 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index ebbd8cc5098..fceea6403d2 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -15,6 +15,7 @@ on: permissions: contents: read # to fetch code (actions/checkout) + packages: write # to write images to GitHub Container Registry (GHCR) jobs: #################################################### diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml index d9f4c5342b4..36546af3a5d 100644 --- a/.github/workflows/reusable-docker-build.yml +++ b/.github/workflows/reusable-docker-build.yml @@ -74,7 +74,11 @@ env: # Current DSpace maintenance branch (and architecture) which is deployed to demo.dspace.org / sandbox.dspace.org # (NOTE: No deployment branch specified for sandbox.dspace.org as it uses the default_branch) DEPLOY_DEMO_BRANCH: 'dspace-8_x' + DEPLOY_SANDBOX_BRANCH: 'main' DEPLOY_ARCH: 'linux/amd64' + # Registry used during building of Docker images. (All images are later copied to docker.io registry) + # We use GitHub's Container Registry to avoid aggressive rate limits at DockerHub. + DOCKER_BUILD_REGISTRY: ghcr.io jobs: docker-build: @@ -99,6 +103,7 @@ jobs: # This step converts the slashes in the "arch" matrix values above into dashes & saves to env.ARCH_NAME # E.g. "linux/amd64" becomes "linux-amd64" # This is necessary because all upload artifacts CANNOT have special chars (like slashes) + # NOTE: The regex-like syntax below is Bash Parameter Substitution - name: Prepare run: | platform=${{ matrix.arch }} @@ -109,13 +114,14 @@ jobs: uses: actions/checkout@v4 # https://github.com/docker/login-action - - name: Login to DockerHub - # Only login if not a PR, as PRs only trigger a Docker build and not a push + - name: Login to ${{ env.DOCKER_BUILD_REGISTRY }} + # Only login if not a PR, as PRs only trigger a Docker build and not a push if: ${{ ! matrix.isPr }} uses: docker/login-action@v3 with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_ACCESS_TOKEN }} + registry: ${{ env.DOCKER_BUILD_REGISTRY }} + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} # https://github.com/docker/setup-qemu-action - name: Set up QEMU emulation to build for multiple architectures @@ -131,19 +137,20 @@ jobs: id: meta_build uses: docker/metadata-action@v5 with: - images: ${{ env.IMAGE_NAME }} + images: ${{ env.DOCKER_BUILD_REGISTRY }}/${{ env.IMAGE_NAME }} tags: ${{ env.IMAGE_TAGS }} flavor: ${{ env.TAGS_FLAVOR }} - #------------------------------------------------------------ - # Build & deploy steps for new commits to a branch (non-PRs) + #-------------------------------------------------------------------- + # First, for all branch commits (non-PRs) we build the image & upload + # to GitHub Container Registry (GHCR). After uploading the image + # to GHCR, we store the image digest in an artifact, so we can + # create a merged manifest later (see 'docker-build_manifest' job). # - # These steps build the images, push to DockerHub, and - # (if necessary) redeploy demo/sandbox sites. - #------------------------------------------------------------ + # NOTE: We use GHCR in order to avoid aggressive rate limits at DockerHub. + #-------------------------------------------------------------------- # https://github.com/docker/build-push-action - - name: Build and push image to DockerHub - # Only build & push if not a PR + - name: Build and push image to ${{ env.DOCKER_BUILD_REGISTRY }} if: ${{ ! matrix.isPr }} id: docker_build uses: docker/build-push-action@v5 @@ -152,6 +159,9 @@ jobs: ${{ inputs.dockerfile_additional_contexts }} context: ${{ inputs.dockerfile_context }} file: ${{ inputs.dockerfile_path }} + # Tell DSpace's Docker files to use the build registry instead of DockerHub + build-args: + DOCKER_REGISTRY=${{ env.DOCKER_BUILD_REGISTRY }} platforms: ${{ matrix.arch }} push: true # Use tags / labels provided by 'docker/metadata-action' above @@ -162,7 +172,7 @@ jobs: cache-from: type=gha,scope=${{ inputs.build_id }} cache-to: type=gha,scope=${{ inputs.build_id }},mode=max - # Export the digest of Docker build locally (for non PRs only) + # Export the digest of Docker build locally - name: Export Docker build digest if: ${{ ! matrix.isPr }} run: | @@ -170,7 +180,8 @@ jobs: digest="${{ steps.docker_build.outputs.digest }}" touch "/tmp/digests/${digest#sha256:}" - # Upload digest to an artifact, so that it can be used in manifest below + # Upload digest to an artifact, so that it can be used in combined manifest below + # (The purpose of the combined manifest is to list both amd64 and arm64 builds under same tag) - name: Upload Docker build digest to artifact if: ${{ ! matrix.isPr }} uses: actions/upload-artifact@v4 @@ -180,48 +191,31 @@ jobs: if-no-files-found: error retention-days: 1 - # If this build is NOT a PR and passed in a REDEPLOY_SANDBOX_URL secret, - # Then redeploy https://sandbox.dspace.org if this build is for our deployment architecture and 'main' branch. - - name: Redeploy sandbox.dspace.org (based on main branch) - if: | - !matrix.isPR && - env.REDEPLOY_SANDBOX_URL != '' && - matrix.arch == env.DEPLOY_ARCH && - github.ref_name == github.event.repository.default_branch - run: | - curl -X POST $REDEPLOY_SANDBOX_URL - - # If this build is NOT a PR and passed in a REDEPLOY_DEMO_URL secret, - # Then redeploy https://demo.dspace.org if this build is for our deployment architecture and demo branch. - - name: Redeploy demo.dspace.org (based on maintenance branch) - if: | - !matrix.isPR && - env.REDEPLOY_DEMO_URL != '' && - matrix.arch == env.DEPLOY_ARCH && - github.ref_name == env.DEPLOY_DEMO_BRANCH - run: | - curl -X POST $REDEPLOY_DEMO_URL - - #------------------------------------------------------------- - # Shared Build steps. - # These are used for PRs as well as new commits to a branch (non-PRs) + #------------------------------------------------------------------------------ + # Second, we build the image again in order to store it in a local TAR file. + # This TAR of the image is cached/saved as an artifact, so that it can be used + # by later jobs to install the brand-new images for automated testing. + # This TAR build is performed BOTH for PRs and for branch commits (non-PRs). # - # These steps build the images and cache/store as a build artifact. - # These artifacts can then be used by later jobs to install the - # brand-new images for automated testing. For non-PRs, this cache is - # also used to avoid pulling the images we just built from DockerHub. - #-------------------------------------------------------------- - + # (This approach has the advantage of avoiding having to download the newly built + # image from DockerHub or GHCR during automated testing.) + # + # See the 'docker-deploy' job in docker.yml as an example of where this TAR is used. + #------------------------------------------------------------------------------- # Build local image (again) and store in a TAR file in /tmp directory - # NOTE: This build is run for both PRs and non-PRs as it's used to "cache" our built images as artifacts. - # NOTE #2: This cannot be combined with push to DockerHub registry above as it's a different type of output. + # This step is only done for AMD64, as that's the only image we use in our automated testing (at this time). + # NOTE: This step cannot be combined with the build above as it's a different type of output. - name: Build and push image to local TAR file + if: ${{ matrix.arch == 'linux/amd64'}} uses: docker/build-push-action@v5 with: build-contexts: | ${{ inputs.dockerfile_additional_contexts }} context: ${{ inputs.dockerfile_context }} file: ${{ inputs.dockerfile_path }} + # Tell DSpace's Docker files to use the build registry instead of DockerHub + build-args: + DOCKER_REGISTRY=${{ env.DOCKER_BUILD_REGISTRY }} platforms: ${{ matrix.arch }} tags: ${{ steps.meta_build.outputs.tags }} labels: ${{ steps.meta_build.outputs.labels }} @@ -233,7 +227,9 @@ jobs: outputs: type=docker,dest=/tmp/${{ inputs.build_id }}.tar # Upload the local docker image (in TAR file) to a build Artifact + # This step is only done for AMD64, as that's the only image we use in our automated testing (at this time). - name: Upload local image TAR to artifact + if: ${{ matrix.arch == 'linux/amd64'}} uses: actions/upload-artifact@v4 with: name: docker-image-${{ inputs.build_id }}-${{ env.ARCH_NAME }} @@ -241,10 +237,12 @@ jobs: if-no-files-found: error retention-days: 1 - # Merge Docker digests (from various architectures) into a manifest. - # This runs after all Docker builds complete above, and it tells hub.docker.com - # that these builds should be all included in the manifest for this tag. - # (e.g. AMD64 and ARM64 should be listed as options under the same tagged Docker image) + ########################################################################################## + # Merge Docker digests (from various architectures) into a single manifest. + # This runs after all Docker builds complete above. The purpose is to include all builds + # under a single manifest for this tag. + # (e.g. both linux/amd64 and linux/arm64 should be listed under the same tagged Docker image) + ########################################################################################## docker-build_manifest: # Only run if this is NOT a PR if: ${{ github.event_name != 'pull_request' }} @@ -260,11 +258,12 @@ jobs: pattern: digests-${{ inputs.build_id }}-* merge-multiple: true - - name: Login to Docker Hub + - name: Login to ${{ env.DOCKER_BUILD_REGISTRY }} uses: docker/login-action@v3 with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_ACCESS_TOKEN }} + registry: ${{ env.DOCKER_BUILD_REGISTRY }} + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -273,16 +272,88 @@ jobs: id: meta uses: docker/metadata-action@v5 with: - images: ${{ env.IMAGE_NAME }} + images: ${{ env.DOCKER_BUILD_REGISTRY }}/${{ env.IMAGE_NAME }} tags: ${{ env.IMAGE_TAGS }} flavor: ${{ env.TAGS_FLAVOR }} - - name: Create manifest list from digests and push + - name: Create manifest list from digests and push to ${{ env.DOCKER_BUILD_REGISTRY }} working-directory: /tmp/digests run: | docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ - $(printf '${{ env.IMAGE_NAME }}@sha256:%s ' *) + $(printf '${{ env.DOCKER_BUILD_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *) + + - name: Inspect manifest in ${{ env.DOCKER_BUILD_REGISTRY }} + run: | + docker buildx imagetools inspect ${{ env.DOCKER_BUILD_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }} + + ########################################################################################## + # Copy images / manifest to DockerHub. + # This MUST run after *both* images (AMD64 and ARM64) are built and uploaded to GitHub + # Container Registry (GHCR). Attempting to run this in parallel to GHCR builds can result + # in a race condition...i.e. the copy to DockerHub may fail if GHCR image has been updated + # at the moment when the copy occurs. + ########################################################################################## + docker-copy_to_dockerhub: + # Only run if this is NOT a PR + if: ${{ github.event_name != 'pull_request' }} + runs-on: ubuntu-latest + needs: + - docker-build_manifest + + steps: + # 'regctl' is used to more easily copy the image to DockerHub and obtain the digest from DockerHub + # See https://github.com/regclient/regclient/blob/main/docs/regctl.md + - name: Install regctl for Docker registry tools + uses: regclient/actions/regctl-installer@main + with: + release: 'v0.8.0' - - name: Inspect image + # This recreates Docker tags for DockerHub + - name: Add Docker metadata for image + id: meta_dockerhub + uses: docker/metadata-action@v5 + with: + images: ${{ env.IMAGE_NAME }} + tags: ${{ env.IMAGE_TAGS }} + flavor: ${{ env.TAGS_FLAVOR }} + + # Login to source registry first, as this is where we are copying *from* + - name: Login to ${{ env.DOCKER_BUILD_REGISTRY }} + uses: docker/login-action@v3 + with: + registry: ${{ env.DOCKER_BUILD_REGISTRY }} + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Login to DockerHub, since this is where we are copying *to* + - name: Login to DockerHub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_ACCESS_TOKEN }} + + # Copy the image from source to DockerHub + - name: Copy image from ${{ env.DOCKER_BUILD_REGISTRY }} to docker.io + run: | + regctl image copy ${{ env.DOCKER_BUILD_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta_dockerhub.outputs.version }} docker.io/${{ env.IMAGE_NAME }}:${{ steps.meta_dockerhub.outputs.version }} + + #-------------------------------------------------------------------- + # Finally, check whether demo.dspace.org or sandbox.dspace.org need + # to be redeployed based on these new DockerHub images. + #-------------------------------------------------------------------- + # If this build is for the branch that Sandbox uses and passed in a REDEPLOY_SANDBOX_URL secret, + # Then redeploy https://sandbox.dspace.org + - name: Redeploy sandbox.dspace.org (based on main branch) + if: | + env.REDEPLOY_SANDBOX_URL != '' && + github.ref_name == env.DEPLOY_SANDBOX_BRANCH + run: | + curl -X POST $REDEPLOY_SANDBOX_URL + # If this build is for the branch that Demo uses and passed in a REDEPLOY_DEMO_URL secret, + # Then redeploy https://demo.dspace.org + - name: Redeploy demo.dspace.org (based on maintenance branch) + if: | + env.REDEPLOY_DEMO_URL != '' && + github.ref_name == env.DEPLOY_DEMO_BRANCH run: | - docker buildx imagetools inspect ${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }} + curl -X POST $REDEPLOY_DEMO_URL \ No newline at end of file From 296c9a12f489d779c15bf07fd8c38fac6bc6d233 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 16 Dec 2024 12:05:41 -0600 Subject: [PATCH 345/939] PRs must also login to ghcr.io in order to read private images for the build process --- .github/workflows/reusable-docker-build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml index 36546af3a5d..3b74f250b53 100644 --- a/.github/workflows/reusable-docker-build.yml +++ b/.github/workflows/reusable-docker-build.yml @@ -114,9 +114,9 @@ jobs: uses: actions/checkout@v4 # https://github.com/docker/login-action + # NOTE: This login occurs for BOTH non-PRs or PRs. PRs *must* also login to access private images from GHCR + # during the build process - name: Login to ${{ env.DOCKER_BUILD_REGISTRY }} - # Only login if not a PR, as PRs only trigger a Docker build and not a push - if: ${{ ! matrix.isPr }} uses: docker/login-action@v3 with: registry: ${{ env.DOCKER_BUILD_REGISTRY }} From a27f1ed1757b193cf6f831a0fd27585b655bacc2 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 16 Dec 2024 12:28:16 -0600 Subject: [PATCH 346/939] Ensure "docker-deploy" job also uses ghcr.io by default. --- .github/workflows/docker.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index fceea6403d2..815aec5cf6b 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -176,6 +176,9 @@ jobs: # Else, just use the branch name. # NOTE: DSPACE_VER is used because our docker compose scripts default to using the "-test" image. DSPACE_VER: ${{ (github.event_name == 'pull_request' && github.event.pull_request.base.ref == github.event.repository.default_branch && 'latest') || (github.event_name == 'pull_request' && github.event.pull_request.base.ref) || (github.ref_name == github.event.repository.default_branch && 'latest') || github.ref_name }} + # Docker Registry to use for Docker compose scripts below. + # We use GitHub's Container Registry to avoid aggressive rate limits at DockerHub. + DOCKER_REGISTRY: ghcr.io steps: # Checkout our codebase (to get access to Docker Compose scripts) - name: Checkout codebase From 64cb3bda0034d8e3051c31290a3eeea8a560d939 Mon Sep 17 00:00:00 2001 From: autavares-dev Date: Wed, 7 Aug 2024 16:34:38 -0300 Subject: [PATCH 347/939] Changes Group2GroupCache computation --- .../org/dspace/eperson/Group2GroupCache.java | 3 +- .../org/dspace/eperson/GroupServiceImpl.java | 72 +++++++++--------- .../eperson/dao/Group2GroupCacheDAO.java | 74 +++++++++++++++++-- .../dao/impl/Group2GroupCacheDAOImpl.java | 34 +++++++++ 4 files changed, 143 insertions(+), 40 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/eperson/Group2GroupCache.java b/dspace-api/src/main/java/org/dspace/eperson/Group2GroupCache.java index 09bdf34d4ca..58781cd402f 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/Group2GroupCache.java +++ b/dspace-api/src/main/java/org/dspace/eperson/Group2GroupCache.java @@ -14,6 +14,7 @@ import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Table; +import javax.persistence.UniqueConstraint; import org.hibernate.proxy.HibernateProxyHelper; @@ -23,7 +24,7 @@ * @author kevinvandevelde at atmire.com */ @Entity -@Table(name = "group2groupcache") +@Table(name = "group2groupcache", uniqueConstraints = { @UniqueConstraint(columnNames = {"parent_id", "child_id"}) }) public class Group2GroupCache implements Serializable { @Id diff --git a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java index 3fb20e2f1e6..b52f17a5c69 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java @@ -20,6 +20,7 @@ import java.util.UUID; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.SetUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.LogManager; @@ -673,15 +674,14 @@ protected boolean isEPersonInGroup(Context context, Group group, EPerson ePerson /** - * Regenerate the group cache AKA the group2groupcache table in the database - - * meant to be called when a group is added or removed from another group + * Returns a set with pairs of parent and child group UUIDs, representing the new cache table rows. * - * @param context The relevant DSpace Context. - * @param flushQueries flushQueries Flush all pending queries + * @param context The relevant DSpace Context. + * @param flushQueries flushQueries Flush all pending queries + * @return Pairs of parent and child group UUID of the new cache. * @throws SQLException An exception that provides information on a database access error or other errors. */ - protected void rethinkGroupCache(Context context, boolean flushQueries) throws SQLException { - + private Set> computeNewCache(Context context, boolean flushQueries) throws SQLException { Map> parents = new HashMap<>(); List> group2groupResults = groupDAO.getGroup2GroupResults(context, flushQueries); @@ -689,19 +689,8 @@ protected void rethinkGroupCache(Context context, boolean flushQueries) throws S UUID parent = group2groupResult.getLeft(); UUID child = group2groupResult.getRight(); - // if parent doesn't have an entry, create one - if (!parents.containsKey(parent)) { - Set children = new HashSet<>(); - - // add child id to the list - children.add(child); - parents.put(parent, children); - } else { - // parent has an entry, now add the child to the parent's record - // of children - Set children = parents.get(parent); - children.add(child); - } + parents.putIfAbsent(parent, new HashSet<>()); + parents.get(parent).add(child); } // now parents is a hash of all of the IDs of groups that are parents @@ -714,27 +703,42 @@ protected void rethinkGroupCache(Context context, boolean flushQueries) throws S parent.getValue().addAll(myChildren); } - // empty out group2groupcache table - group2GroupCacheDAO.deleteAll(context); - - // write out new one + // write out new cache IN MEMORY ONLY and returns it + Set> newCache = new HashSet<>(); for (Map.Entry> parent : parents.entrySet()) { UUID key = parent.getKey(); - for (UUID child : parent.getValue()) { + newCache.add(Pair.of(key, child)); + } + } + return newCache; + } - Group parentGroup = find(context, key); - Group childGroup = find(context, child); + /** + * Regenerate the group cache AKA the group2groupcache table in the database - + * meant to be called when a group is added or removed from another group + * + * @param context The relevant DSpace Context. + * @param flushQueries flushQueries Flush all pending queries + * @throws SQLException An exception that provides information on a database access error or other errors. + */ + protected void rethinkGroupCache(Context context, boolean flushQueries) throws SQLException { + // current cache in the database + var oldCache = group2GroupCacheDAO.getCache(context); - if (parentGroup != null && childGroup != null && group2GroupCacheDAO - .find(context, parentGroup, childGroup) == null) { - Group2GroupCache group2GroupCache = group2GroupCacheDAO.create(context, new Group2GroupCache()); - group2GroupCache.setParent(parentGroup); - group2GroupCache.setChild(childGroup); - group2GroupCacheDAO.save(context, group2GroupCache); - } - } + // correct cache, computed from the Group table + var newCache = computeNewCache(context, flushQueries); + + var toDelete = SetUtils.difference(oldCache, newCache); + var toCreate = SetUtils.difference(newCache, oldCache); + + for (var pair : toDelete ) { + group2GroupCacheDAO.deleteFromCache(context, pair.getLeft(), pair.getRight()); + } + + for (var pair : toCreate ) { + group2GroupCacheDAO.addToCache(context, pair.getLeft(), pair.getRight()); } } diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/Group2GroupCacheDAO.java b/dspace-api/src/main/java/org/dspace/eperson/dao/Group2GroupCacheDAO.java index 7db569a59e2..d41d52c7e61 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/Group2GroupCacheDAO.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/Group2GroupCacheDAO.java @@ -9,7 +9,10 @@ import java.sql.SQLException; import java.util.List; +import java.util.Set; +import java.util.UUID; +import org.apache.commons.lang3.tuple.Pair; import org.dspace.core.Context; import org.dspace.core.GenericDAO; import org.dspace.eperson.Group; @@ -25,13 +28,74 @@ */ public interface Group2GroupCacheDAO extends GenericDAO { - public List findByParent(Context context, Group group) throws SQLException; + /** + * Returns the current cache table as a set of UUID pairs. + * @param context The relevant DSpace Context. + * @return Set of UUID pairs, where the first element is the parent UUID and the second one is the child UUID. + * @throws SQLException An exception that provides information on a database access error or other errors. + */ + Set> getCache(Context context) throws SQLException; - public List findByChildren(Context context, Iterable groups) throws SQLException; + /** + * Returns all cache entities that are children of a given parent Group entity. + * @param context The relevant DSpace Context. + * @param group Parent group to perform the search. + * @return List of cached groups that are children of the parent group. + * @throws SQLException An exception that provides information on a database access error or other errors. + */ + List findByParent(Context context, Group group) throws SQLException; - public Group2GroupCache findByParentAndChild(Context context, Group parent, Group child) throws SQLException; + /** + * Returns all cache entities that are parents of at least one group from a children groups list. + * @param context The relevant DSpace Context. + * @param groups Children groups to perform the search. + * @return List of cached groups that are parents of at least one group from the children groups list. + * @throws SQLException An exception that provides information on a database access error or other errors. + */ + List findByChildren(Context context, Iterable groups) throws SQLException; - public Group2GroupCache find(Context context, Group parent, Group child) throws SQLException; + /** + * Returns the cache entity given specific parent and child groups. + * @param context The relevant DSpace Context. + * @param parent Parent group. + * @param child Child gruoup. + * @return Cached group. + * @throws SQLException An exception that provides information on a database access error or other errors. + */ + Group2GroupCache findByParentAndChild(Context context, Group parent, Group child) throws SQLException; - public void deleteAll(Context context) throws SQLException; + /** + * Returns the cache entity given specific parent and child groups. + * @param context The relevant DSpace Context. + * @param parent Parent group. + * @param child Child gruoup. + * @return Cached group. + * @throws SQLException An exception that provides information on a database access error or other errors. + */ + Group2GroupCache find(Context context, Group parent, Group child) throws SQLException; + + /** + * Completely deletes the current cache table. + * @param context The relevant DSpace Context. + * @throws SQLException An exception that provides information on a database access error or other errors. + */ + void deleteAll(Context context) throws SQLException; + + /** + * Deletes a specific cache row given parent and child groups UUIDs. + * @param context The relevant DSpace Context. + * @param parent Parent group UUID. + * @param child Child group UUID. + * @throws SQLException An exception that provides information on a database access error or other errors. + */ + void deleteFromCache(Context context, UUID parent, UUID child) throws SQLException; + + /** + * Adds a single row to the cache table given parent and child groups UUIDs. + * @param context The relevant DSpace Context. + * @param parent Parent group UUID. + * @param child Child group UUID. + * @throws SQLException An exception that provides information on a database access error or other errors. + */ + void addToCache(Context context, UUID parent, UUID child) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/Group2GroupCacheDAOImpl.java b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/Group2GroupCacheDAOImpl.java index 83fb48aaf03..42ca2bb5d48 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/Group2GroupCacheDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/Group2GroupCacheDAOImpl.java @@ -8,14 +8,18 @@ package org.dspace.eperson.dao.impl; import java.sql.SQLException; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import java.util.Set; +import java.util.UUID; import javax.persistence.Query; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; +import org.apache.commons.lang3.tuple.Pair; import org.dspace.core.AbstractHibernateDAO; import org.dspace.core.Context; import org.dspace.eperson.Group; @@ -35,6 +39,16 @@ protected Group2GroupCacheDAOImpl() { super(); } + @Override + public Set> getCache(Context context) throws SQLException { + Query query = createQuery( + context, + "SELECT new org.apache.commons.lang3.tuple.ImmutablePair(g.parent.id, g.child.id) FROM Group2GroupCache g" + ); + List> results = query.getResultList(); + return new HashSet>(results); + } + @Override public List findByParent(Context context, Group group) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); @@ -90,4 +104,24 @@ public Group2GroupCache find(Context context, Group parent, Group child) throws public void deleteAll(Context context) throws SQLException { createQuery(context, "delete from Group2GroupCache").executeUpdate(); } + + @Override + public void deleteFromCache(Context context, UUID parent, UUID child) throws SQLException { + Query query = getHibernateSession(context).createNativeQuery( + "delete from group2groupcache g WHERE g.parent_id = :parent AND g.child_id = :child" + ); + query.setParameter("parent", parent); + query.setParameter("child", child); + query.executeUpdate(); + } + + @Override + public void addToCache(Context context, UUID parent, UUID child) throws SQLException { + Query query = getHibernateSession(context).createNativeQuery( + "insert into group2groupcache (parent_id, child_id) VALUES (:parent, :child)" + ); + query.setParameter("parent", parent); + query.setParameter("child", child); + query.executeUpdate(); + } } From 05bac14716e1b011ab56abc7b9d662e9d689e546 Mon Sep 17 00:00:00 2001 From: autavares-dev Date: Thu, 8 Aug 2024 15:52:03 -0300 Subject: [PATCH 348/939] Refactor 'var' variables to explicit types --- .../java/org/dspace/eperson/GroupServiceImpl.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java index b52f17a5c69..4cec4c9c0d9 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java @@ -725,19 +725,19 @@ private Set> computeNewCache(Context context, boolean flushQuer */ protected void rethinkGroupCache(Context context, boolean flushQueries) throws SQLException { // current cache in the database - var oldCache = group2GroupCacheDAO.getCache(context); + Set> oldCache = group2GroupCacheDAO.getCache(context); // correct cache, computed from the Group table - var newCache = computeNewCache(context, flushQueries); + Set> newCache = computeNewCache(context, flushQueries); - var toDelete = SetUtils.difference(oldCache, newCache); - var toCreate = SetUtils.difference(newCache, oldCache); + SetUtils.SetView> toDelete = SetUtils.difference(oldCache, newCache); + SetUtils.SetView> toCreate = SetUtils.difference(newCache, oldCache); - for (var pair : toDelete ) { + for (Pair pair : toDelete ) { group2GroupCacheDAO.deleteFromCache(context, pair.getLeft(), pair.getRight()); } - for (var pair : toCreate ) { + for (Pair pair : toCreate ) { group2GroupCacheDAO.addToCache(context, pair.getLeft(), pair.getRight()); } } From aa027aefae2e39558273fcdbd511c288f8b0def3 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 17 Dec 2024 13:11:43 -0600 Subject: [PATCH 349/939] Improve Apache Ant download process. Switch to using curl so that we can retry the request if it initially fails. (cherry picked from commit e236634a4c758a6c0d81e929b2f36d49e81b0835) --- Dockerfile | 10 ++++------ Dockerfile.cli | 10 ++++------ Dockerfile.test | 10 ++++------ 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/Dockerfile b/Dockerfile index ff35f2a2e3a..102f00abe1f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -42,14 +42,12 @@ WORKDIR /dspace-src ENV ANT_VERSION=1.10.13 ENV ANT_HOME=/tmp/ant-$ANT_VERSION ENV PATH=$ANT_HOME/bin:$PATH -# Need wget to install ant -RUN apt-get update \ - && apt-get install -y --no-install-recommends wget \ - && apt-get purge -y --auto-remove \ - && rm -rf /var/lib/apt/lists/* # Download and install 'ant' RUN mkdir $ANT_HOME && \ - wget -qO- "https://archive.apache.org/dist/ant/binaries/apache-ant-$ANT_VERSION-bin.tar.gz" | tar -zx --strip-components=1 -C $ANT_HOME + curl --silent --show-error --location --fail --retry 5 --output /tmp/apache-ant.tar.gz \ + https://archive.apache.org/dist/ant/binaries/apache-ant-${ANT_VERSION}-bin.tar.gz && \ + tar -zx --strip-components=1 -f /tmp/apache-ant.tar.gz -C $ANT_HOME && \ + rm /tmp/apache-ant.tar.gz # Run necessary 'ant' deploy scripts RUN ant init_installation update_configs update_code update_webapps diff --git a/Dockerfile.cli b/Dockerfile.cli index be03e8922b2..e208e3d9214 100644 --- a/Dockerfile.cli +++ b/Dockerfile.cli @@ -38,14 +38,12 @@ WORKDIR /dspace-src ENV ANT_VERSION=1.10.13 ENV ANT_HOME=/tmp/ant-$ANT_VERSION ENV PATH=$ANT_HOME/bin:$PATH -# Need wget to install ant -RUN apt-get update \ - && apt-get install -y --no-install-recommends wget \ - && apt-get purge -y --auto-remove \ - && rm -rf /var/lib/apt/lists/* # Download and install 'ant' RUN mkdir $ANT_HOME && \ - wget -qO- "https://archive.apache.org/dist/ant/binaries/apache-ant-$ANT_VERSION-bin.tar.gz" | tar -zx --strip-components=1 -C $ANT_HOME + curl --silent --show-error --location --fail --retry 5 --output /tmp/apache-ant.tar.gz \ + https://archive.apache.org/dist/ant/binaries/apache-ant-${ANT_VERSION}-bin.tar.gz && \ + tar -zx --strip-components=1 -f /tmp/apache-ant.tar.gz -C $ANT_HOME && \ + rm /tmp/apache-ant.tar.gz # Run necessary 'ant' deploy scripts RUN ant init_installation update_configs update_code diff --git a/Dockerfile.test b/Dockerfile.test index 08b6b3018b8..cc73c655b63 100644 --- a/Dockerfile.test +++ b/Dockerfile.test @@ -41,14 +41,12 @@ WORKDIR /dspace-src ENV ANT_VERSION=1.10.12 ENV ANT_HOME=/tmp/ant-$ANT_VERSION ENV PATH=$ANT_HOME/bin:$PATH -# Need wget to install ant -RUN apt-get update \ - && apt-get install -y --no-install-recommends wget \ - && apt-get purge -y --auto-remove \ - && rm -rf /var/lib/apt/lists/* # Download and install 'ant' RUN mkdir $ANT_HOME && \ - wget -qO- "https://archive.apache.org/dist/ant/binaries/apache-ant-$ANT_VERSION-bin.tar.gz" | tar -zx --strip-components=1 -C $ANT_HOME + curl --silent --show-error --location --fail --retry 5 --output /tmp/apache-ant.tar.gz \ + https://archive.apache.org/dist/ant/binaries/apache-ant-${ANT_VERSION}-bin.tar.gz && \ + tar -zx --strip-components=1 -f /tmp/apache-ant.tar.gz -C $ANT_HOME && \ + rm /tmp/apache-ant.tar.gz # Run necessary 'ant' deploy scripts RUN ant init_installation update_configs update_code update_webapps From 58af9fd224ba37a8f3a9c8b354c04d772d6f3ee2 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 17 Dec 2024 14:13:21 -0600 Subject: [PATCH 350/939] Significantly speed up build of dspace-dependencies by only copying over POM files (cherry picked from commit 6d7a3fcb725bbb5e42790ba5c57292477f2f3f01) --- Dockerfile.dependencies | 58 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/Dockerfile.dependencies b/Dockerfile.dependencies index 794dfa9a66a..d3ca9d5e3c8 100644 --- a/Dockerfile.dependencies +++ b/Dockerfile.dependencies @@ -6,7 +6,7 @@ # To build with JDK17, use "--build-arg JDK_VERSION=17" ARG JDK_VERSION=11 -# Step 1 - Run Maven Build +# Step 1 - Download all Dependencies FROM docker.io/maven:3-eclipse-temurin-${JDK_VERSION} AS build ARG TARGET_DIR=dspace-installer WORKDIR /app @@ -19,16 +19,60 @@ RUN chown -Rv dspace: /app # Switch to dspace user & run below commands as that user USER dspace -# Copy the DSpace source code (from local machine) into the workdir (excluding .dockerignore contents) -ADD --chown=dspace . /app/ +# This next part may look odd, but it speeds up the build of this image *significantly*. +# Copy ONLY the POMs to this image (from local machine). This will allow us to download all dependencies *without* +# performing any code compilation steps. + +# Parent POM +ADD --chown=dspace pom.xml /app/ +RUN mkdir -p /app/dspace + +# 'dspace' module POM. Includes 'additions' ONLY, as it's the only submodule that is required to exist. +ADD --chown=dspace dspace/pom.xml /app/dspace/ +RUN mkdir -p /app/dspace/modules/ +ADD --chown=dspace dspace/modules/pom.xml /app/dspace/modules/ +RUN mkdir -p /app/dspace/modules/additions +ADD --chown=dspace dspace/modules/additions/pom.xml /app/dspace/modules/additions/ + +# 'dspace-api' module POM +RUN mkdir -p /app/dspace-api +ADD --chown=dspace dspace-api/pom.xml /app/dspace-api/ + +# 'dspace-iiif' module POM +RUN mkdir -p /app/dspace-iiif +ADD --chown=dspace dspace-iiif/pom.xml /app/dspace-iiif/ + +# 'dspace-oai' module POM +RUN mkdir -p /app/dspace-oai +ADD --chown=dspace dspace-oai/pom.xml /app/dspace-oai/ + +# 'dspace-rdf' module POM +RUN mkdir -p /app/dspace-rdf +ADD --chown=dspace dspace-rdf/pom.xml /app/dspace-rdf/ + +# 'dspace-server-webapp' module POM +RUN mkdir -p /app/dspace-server-webapp +ADD --chown=dspace dspace-server-webapp/pom.xml /app/dspace-server-webapp/ + +# 'dspace-services' module POM +RUN mkdir -p /app/dspace-services +ADD --chown=dspace dspace-services/pom.xml /app/dspace-services/ + +# 'dspace-sword' module POM +RUN mkdir -p /app/dspace-sword +ADD --chown=dspace dspace-sword/pom.xml /app/dspace-sword/ + +# 'dspace-swordv2' module POM +RUN mkdir -p /app/dspace-swordv2 +ADD --chown=dspace dspace-swordv2/pom.xml /app/dspace-swordv2/ # Trigger the installation of all maven dependencies (hide download progress messages) # Maven flags here ensure that we skip final assembly, skip building test environment and skip all code verification checks. -# These flags speed up this installation as much as reasonably possible. -ENV MAVEN_FLAGS="-P-assembly -P-test-environment -Denforcer.skip=true -Dcheckstyle.skip=true -Dlicense.skip=true -Dxml.skip=true" -RUN mvn --no-transfer-progress install ${MAVEN_FLAGS} +# These flags speed up this installation and skip tasks we cannot perform as we don't have the full source code. +ENV MAVEN_FLAGS="-P-assembly -P-test-environment -Denforcer.skip=true -Dcheckstyle.skip=true -Dlicense.skip=true -Dxjc.skip=true -Dxml.skip=true" +RUN mvn --no-transfer-progress verify ${MAVEN_FLAGS} -# Clear the contents of the /app directory (including all maven builds), so no artifacts remain. +# Clear the contents of the /app directory (including all maven target folders), so no artifacts remain. # This ensures when dspace:dspace is built, it will use the Maven local cache (~/.m2) for dependencies USER root RUN rm -rf /app/* From e5568157c87370531e6b86cbf582e160504858fb Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 17 Sep 2024 08:47:54 -0400 Subject: [PATCH 351/939] More information about failed DOI registrations. (cherry picked from commit b8f4ab0eb3e90b080eaae057fb5ee673d3477dc2) --- .../dspace/identifier/doi/DOIOrganiser.java | 3 ++- .../identifier/doi/DataCiteConnector.java | 22 ++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java b/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java index 088e2b1cbc8..12f6d7c4fca 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java +++ b/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java @@ -577,7 +577,8 @@ public void update(DOI doiRow) { } } catch (IdentifierException ex) { if (!(ex instanceof DOIIdentifierException)) { - LOG.error("It wasn't possible to register the identifier online. ", ex); + LOG.error("Registering DOI {} for object {}: the registrar returned an error.", + doiRow.getDoi(), dso.getID(), ex); } DOIIdentifierException doiIdentifierException = (DOIIdentifierException) ex; diff --git a/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java b/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java index a15e3f7fdbf..5bb37add2d9 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java +++ b/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java @@ -464,6 +464,10 @@ public void reserveDOI(Context context, DSpaceObject dso, String doi) log.warn("While reserving the DOI {}, we got a http status code " + "{} and the message \"{}\".", doi, Integer.toString(resp.statusCode), resp.getContent()); + Format format = Format.getCompactFormat(); + format.setEncoding("UTF-8"); + XMLOutputter xout = new XMLOutputter(format); + log.info("We send the following XML:\n{}", xout.outputString(root)); throw new DOIIdentifierException("Unable to parse an answer from " + "DataCite API. Please have a look into DSpace logs.", DOIIdentifierException.BAD_ANSWER); @@ -635,6 +639,14 @@ protected DataCiteResponse sendGetRequest(String doi, String path) return sendHttpRequest(httpget, doi); } + /** + * Send a DataCite metadata document to the registrar. + * + * @param doi identify the object. + * @param metadataRoot describe the object. The root element of the document. + * @return the registrar's response. + * @throws DOIIdentifierException passed through. + */ protected DataCiteResponse sendMetadataPostRequest(String doi, Element metadataRoot) throws DOIIdentifierException { Format format = Format.getCompactFormat(); @@ -643,6 +655,14 @@ protected DataCiteResponse sendMetadataPostRequest(String doi, Element metadataR return sendMetadataPostRequest(doi, xout.outputString(new Document(metadataRoot))); } + /** + * Send a DataCite metadata document to the registrar. + * + * @param doi identify the object. + * @param metadata describe the object. + * @return the registrar's response. + * @throws DOIIdentifierException passed through. + */ protected DataCiteResponse sendMetadataPostRequest(String doi, String metadata) throws DOIIdentifierException { // post mds/metadata/ @@ -690,7 +710,7 @@ protected DataCiteResponse sendMetadataPostRequest(String doi, String metadata) * properties such as request URI and method type. * @param doi DOI string to operate on * @return response from DataCite - * @throws DOIIdentifierException if DOI error + * @throws DOIIdentifierException if registrar returns an error. */ protected DataCiteResponse sendHttpRequest(HttpUriRequest req, String doi) throws DOIIdentifierException { From fcc650e1a6fbc1311e905a349fc5ed3957dd1db7 Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Sat, 26 Oct 2024 23:33:13 +0200 Subject: [PATCH 352/939] Add limit, offset, and a new parameter to calculate the total entry count in the Solr query used for the metadata navigation index (cherry picked from commit e71de8a4d075d897f68c145233ac19ba0ec3718b) --- .../java/org/dspace/browse/BrowseEngine.java | 30 ++-------------- .../java/org/dspace/browse/SolrBrowseDAO.java | 32 ++++++++++++----- .../org/dspace/discovery/DiscoverResult.java | 11 ++++++ .../org/dspace/discovery/SolrServiceImpl.java | 36 ++++++++++++++++++- 4 files changed, 73 insertions(+), 36 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java b/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java index 351c3624820..be7a34086a4 100644 --- a/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java +++ b/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java @@ -422,9 +422,6 @@ private BrowseInfo browseByValue(BrowserScope bs) } } - // this is the total number of results in answer to the query - int total = getTotalResults(true); - // set the ordering field (there is only one option) dao.setOrderField("sort_value"); @@ -444,6 +441,9 @@ private BrowseInfo browseByValue(BrowserScope bs) dao.setOffset(offset); dao.setLimit(scope.getResultsPerPage()); + // this is the total number of results in answer to the query + int total = getTotalResults(true); + // Holder for the results List results = null; @@ -680,33 +680,9 @@ private int getTotalResults(boolean distinct) // tell the browse query whether we are distinct dao.setDistinct(distinct); - // ensure that the select is set to "*" - String[] select = {"*"}; - dao.setCountValues(select); - - // FIXME: it would be nice to have a good way of doing this in the DAO - // now reset all of the fields that we don't want to have constraining - // our count, storing them locally to reinstate later - String focusField = dao.getJumpToField(); - String focusValue = dao.getJumpToValue(); - int limit = dao.getLimit(); - int offset = dao.getOffset(); - - dao.setJumpToField(null); - dao.setJumpToValue(null); - dao.setLimit(-1); - dao.setOffset(-1); - // perform the query and get the result int count = dao.doCountQuery(); - // now put back the values we removed for this method - dao.setJumpToField(focusField); - dao.setJumpToValue(focusValue); - dao.setLimit(limit); - dao.setOffset(offset); - dao.setCountValues(null); - log.debug(LogHelper.getHeader(context, "get_total_results_return", "return=" + count)); return count; diff --git a/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java b/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java index f99aab852bf..1917dec423e 100644 --- a/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java +++ b/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java @@ -13,6 +13,8 @@ import java.util.Comparator; import java.util.List; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.apache.solr.client.solrj.util.ClientUtils; @@ -180,18 +182,33 @@ private DiscoverResult getSolrResponse() throws BrowseException { addDefaultFilterQueries(query); if (distinct) { DiscoverFacetField dff; + + // To get the number of distinct values we use the next "json.facet" query param + // {"entries_count": {"type":"terms","field": "_filter", "limit":0, "numBuckets":true}}" + ObjectNode jsonFacet = JsonNodeFactory.instance.objectNode(); + ObjectNode entriesCount = JsonNodeFactory.instance.objectNode(); + entriesCount.put("type", "terms"); + entriesCount.put("field", facetField + "_filter"); + entriesCount.put("limit", 0); + entriesCount.put("numBuckets", true); + jsonFacet.set("entries_count", entriesCount); + if (StringUtils.isNotBlank(startsWith)) { dff = new DiscoverFacetField(facetField, - DiscoveryConfigurationParameters.TYPE_TEXT, -1, - DiscoveryConfigurationParameters.SORT.VALUE, startsWith); + DiscoveryConfigurationParameters.TYPE_TEXT, limit, + DiscoveryConfigurationParameters.SORT.VALUE, startsWith, offset); + + // Add the prefix to the json facet query + entriesCount.put("prefix", startsWith); } else { dff = new DiscoverFacetField(facetField, - DiscoveryConfigurationParameters.TYPE_TEXT, -1, - DiscoveryConfigurationParameters.SORT.VALUE); + DiscoveryConfigurationParameters.TYPE_TEXT, limit, + DiscoveryConfigurationParameters.SORT.VALUE, offset); } query.addFacetField(dff); query.setFacetMinCount(1); query.setMaxResults(0); + query.addProperty("json.facet", jsonFacet.toString()); } else { query.setMaxResults(limit/* > 0 ? limit : 20*/); if (offset > 0) { @@ -248,8 +265,7 @@ public int doCountQuery() throws BrowseException { DiscoverResult resp = getSolrResponse(); int count = 0; if (distinct) { - List facetResults = resp.getFacetResult(facetField); - count = facetResults.size(); + count = (int) resp.getTotalEntries(); } else { // we need to cast to int to respect the BrowseDAO contract... count = (int) resp.getTotalSearchResults(); @@ -266,8 +282,8 @@ public List doValueQuery() throws BrowseException { DiscoverResult resp = getSolrResponse(); List facet = resp.getFacetResult(facetField); int count = doCountQuery(); - int start = offset > 0 ? offset : 0; - int max = limit > 0 ? limit : count; //if negative, return everything + int start = 0; + int max = facet.size(); List result = new ArrayList<>(); if (ascending) { for (int i = start; i < (start + max) && i < count; i++) { diff --git a/dspace-api/src/main/java/org/dspace/discovery/DiscoverResult.java b/dspace-api/src/main/java/org/dspace/discovery/DiscoverResult.java index 00236d2bfe3..a56804e3e7e 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/DiscoverResult.java +++ b/dspace-api/src/main/java/org/dspace/discovery/DiscoverResult.java @@ -32,6 +32,9 @@ public class DiscoverResult { private List indexableObjects; private Map> facetResults; + // Total count of facet entries calculated for a metadata browsing query + private long totalEntries; + /** * A map that contains all the documents sougth after, the key is a string representation of the Indexable Object */ @@ -64,6 +67,14 @@ public void setTotalSearchResults(long totalSearchResults) { this.totalSearchResults = totalSearchResults; } + public long getTotalEntries() { + return totalEntries; + } + + public void setTotalEntries(long totalEntries) { + this.totalEntries = totalEntries; + } + public int getStart() { return start; } diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java index cd3797e3e34..9339b574b57 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java @@ -1055,6 +1055,8 @@ protected DiscoverResult retrieveResult(Context context, DiscoverQuery query) } //Resolve our facet field values resolveFacetFields(context, query, result, skipLoadingResponse, solrQueryResponse); + //Add total entries count for metadata browsing + resolveEntriesCount(result, solrQueryResponse); } // If any stale entries are found in the current page of results, // we remove those stale entries and rerun the same query again. @@ -1080,7 +1082,39 @@ protected DiscoverResult retrieveResult(Context context, DiscoverQuery query) return result; } - + /** + * Stores the total count of entries for metadata index browsing. The count is calculated by the + * json.facet parameter with the following value: + * + *
    
    +     * {
    +     *     "entries_count": {
    +     *         "type": "terms",
    +     *         "field": "facetNameField_filter",
    +     *         "limit": 0,
    +     *         "prefix": "prefix_value",
    +     *         "numBuckets": true
    +     *     }
    +     * }
    +     * 
    + * + * This value is returned in the facets field of the Solr response. + * + * @param result DiscoverResult object where the total entries count will be stored + * @param solrQueryResponse QueryResponse object containing the solr response + */ + private void resolveEntriesCount(DiscoverResult result, QueryResponse solrQueryResponse) { + + Object facetsObj = solrQueryResponse.getResponse().get("facets"); + if (facetsObj instanceof NamedList) { + NamedList facets = (NamedList) facetsObj; + Object bucketsInfoObj = facets.get("entries_count"); + if (bucketsInfoObj instanceof NamedList) { + NamedList bucketsInfo = (NamedList) bucketsInfoObj; + result.setTotalEntries((int) bucketsInfo.get("numBuckets")); + } + } + } private void resolveFacetFields(Context context, DiscoverQuery query, DiscoverResult result, boolean skipLoadingResponse, QueryResponse solrQueryResponse) throws SQLException { From 95742fe4f38842fc0747188ec941b05364ec7797 Mon Sep 17 00:00:00 2001 From: Jukka Lipka <3710455+jlipka@users.noreply.github.com> Date: Mon, 21 Oct 2024 18:11:08 +0200 Subject: [PATCH 353/939] fix(submission): Submission scope naming fixed According to the documentation, the value for the property is 'submission' in the 'submission-forms.xml'. Without this change, an empty input field will never be marked as an error, even if the field is marked as 'required'. (cherry picked from commit 02f52c7d5c245eaae09dbd636fc8c7935d7cc242) --- dspace-api/src/main/java/org/dspace/app/util/DCInput.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/app/util/DCInput.java b/dspace-api/src/main/java/org/dspace/app/util/DCInput.java index 11f9aadd869..a3d1ba208b5 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/DCInput.java +++ b/dspace-api/src/main/java/org/dspace/app/util/DCInput.java @@ -163,7 +163,7 @@ public class DCInput { * The scope of the input sets, this restricts hidden metadata fields from * view by the end user during submission. */ - public static final String SUBMISSION_SCOPE = "submit"; + public static final String SUBMISSION_SCOPE = "submission"; /** * Class constructor for creating a DCInput object based on the contents of From 31faf809d15d968a021a64720b620faf5f47bf0e Mon Sep 17 00:00:00 2001 From: Jukka Lipka <3710455+jlipka@users.noreply.github.com> Date: Mon, 9 Dec 2024 10:50:59 +0100 Subject: [PATCH 354/939] fix(submission): Submission scope naming fixed Corrected wording in related code comment (cherry picked from commit ec2187ea6532d7db5f5ffa38e20e971a49b90871) --- dspace-api/src/main/java/org/dspace/app/util/DCInput.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/app/util/DCInput.java b/dspace-api/src/main/java/org/dspace/app/util/DCInput.java index a3d1ba208b5..0a1e77ee729 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/DCInput.java +++ b/dspace-api/src/main/java/org/dspace/app/util/DCInput.java @@ -262,7 +262,7 @@ protected void initRegex(String regex) { /** * Is this DCInput for display in the given scope? The scope should be - * either "workflow" or "submit", as per the input forms definition. If the + * either "workflow" or "submission", as per the input forms definition. If the * internal visibility is set to "null" then this will always return true. * * @param scope String identifying the scope that this input's visibility From 973beee2ce5f8dc993ef5eb91711b595c7786789 Mon Sep 17 00:00:00 2001 From: igorbaptist4 Date: Tue, 10 Sep 2024 18:44:15 -0300 Subject: [PATCH 355/939] fix: properly type in field id, adjust use of getProperty and add error handling when dbPath is null (when property usage-statistics.dbfile is commented (cherry picked from commit 412d5751f29f31bbdd872820a62b74970333f066) --- .../org/dspace/statistics/util/StatisticsImporter.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/statistics/util/StatisticsImporter.java b/dspace-api/src/main/java/org/dspace/statistics/util/StatisticsImporter.java index 95736a8bd6d..1bd97a6f5ec 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/util/StatisticsImporter.java +++ b/dspace-api/src/main/java/org/dspace/statistics/util/StatisticsImporter.java @@ -357,7 +357,7 @@ protected void load(String filename, Context context, boolean verbose) { SolrInputDocument sid = new SolrInputDocument(); sid.addField("ip", ip); sid.addField("type", dso.getType()); - sid.addField("id", dso.getID()); + sid.addField("id", dso.getID().toString()); sid.addField("time", DateFormatUtils.format(date, SolrLoggerServiceImpl.DATE_FORMAT_8601)); sid.addField("continent", continent); sid.addField("country", country); @@ -471,13 +471,13 @@ public static void main(String[] args) throws Exception { boolean verbose = line.hasOption('v'); // Find our solr server - String sserver = configurationService.getProperty("solr-statistics", "server"); + String sserver = configurationService.getProperty("solr-statistics.server"); if (verbose) { System.out.println("Writing to solr server at: " + sserver); } solr = new HttpSolrClient.Builder(sserver).build(); - String dbPath = configurationService.getProperty("usage-statistics", "dbfile"); + String dbPath = configurationService.getProperty("usage-statistics.dbfile"); try { File dbFile = new File(dbPath); geoipLookup = new DatabaseReader.Builder(dbFile).build(); @@ -492,6 +492,10 @@ public static void main(String[] args) throws Exception { "Unable to load GeoLite Database file (" + dbPath + ")! You may need to reinstall it. See the DSpace " + "installation instructions for more details.", e); + } catch (NullPointerException e) { + log.error( + "The value of the property usage-statistics.dbfile is null. You may need to install the GeoLite Database file and/or uncomment the property in the config file!", + e); } From 3a223e3fefec9172538e7defa34ce982b0d72762 Mon Sep 17 00:00:00 2001 From: igorbaptist4 Date: Tue, 10 Sep 2024 20:16:47 -0300 Subject: [PATCH 356/939] fix line length checkstyle (cherry picked from commit 338f3b1d3efaa0f7f3852654121cf1f93adeae7e) --- .../java/org/dspace/statistics/util/StatisticsImporter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/statistics/util/StatisticsImporter.java b/dspace-api/src/main/java/org/dspace/statistics/util/StatisticsImporter.java index 1bd97a6f5ec..354c803fe2a 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/util/StatisticsImporter.java +++ b/dspace-api/src/main/java/org/dspace/statistics/util/StatisticsImporter.java @@ -494,7 +494,8 @@ public static void main(String[] args) throws Exception { e); } catch (NullPointerException e) { log.error( - "The value of the property usage-statistics.dbfile is null. You may need to install the GeoLite Database file and/or uncomment the property in the config file!", + "The value of the property usage-statistics.dbfile is null. You may need to install the GeoLite " + + "Database file and/or uncomment the property in the config file!", e); } From c86d082d5f6fe4a76eae018bbe6890cc0b72ff33 Mon Sep 17 00:00:00 2001 From: igorbaptist4 Date: Tue, 10 Sep 2024 13:10:14 -0300 Subject: [PATCH 357/939] fix: set default configFile (cherry picked from commit a5e8d7aa15bc56c268ba67f19657f19edaa45253) --- .../java/org/dspace/app/statistics/LogAnalyser.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/statistics/LogAnalyser.java b/dspace-api/src/main/java/org/dspace/app/statistics/LogAnalyser.java index 2e4ed69b268..c787261419f 100644 --- a/dspace-api/src/main/java/org/dspace/app/statistics/LogAnalyser.java +++ b/dspace-api/src/main/java/org/dspace/app/statistics/LogAnalyser.java @@ -281,10 +281,14 @@ public class LogAnalyser { */ private static String fileTemplate = "dspace\\.log.*"; + private static final ConfigurationService configurationService = + DSpaceServicesFactory.getInstance().getConfigurationService(); + /** * the configuration file from which to configure the analyser */ - private static String configFile; + private static String configFile = configurationService.getProperty("dspace.dir") + + File.separator + "config" + File.separator + "dstat.cfg"; /** * the output file to which to write aggregation data @@ -616,8 +620,6 @@ public static String processLogs(Context context, String myLogDir, } // now do the host name and url lookup - ConfigurationService configurationService - = DSpaceServicesFactory.getInstance().getConfigurationService(); hostName = Utils.getHostName(configurationService.getProperty("dspace.ui.url")); name = configurationService.getProperty("dspace.name").trim(); url = configurationService.getProperty("dspace.ui.url").trim(); @@ -658,8 +660,6 @@ public static void setParameters(String myLogDir, String myFileTemplate, String myConfigFile, String myOutFile, Date myStartDate, Date myEndDate, boolean myLookUp) { - ConfigurationService configurationService - = DSpaceServicesFactory.getInstance().getConfigurationService(); if (myLogDir != null) { logDir = myLogDir; @@ -673,9 +673,6 @@ public static void setParameters(String myLogDir, String myFileTemplate, if (myConfigFile != null) { configFile = myConfigFile; - } else { - configFile = configurationService.getProperty("dspace.dir") - + File.separator + "config" + File.separator + "dstat.cfg"; } if (myStartDate != null) { From 1ce4e08333559bffadde8d266a1009c1c875df33 Mon Sep 17 00:00:00 2001 From: DSpace Bot <68393067+dspace-bot@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:45:16 -0600 Subject: [PATCH 358/939] [Port dspace-7_x] Fix issue with submission sections visibility (#10140) * README.md: v8 is the current release, not v7 (cherry picked from commit 2b698eff609d510c487ad2331d4e11cd28f64e9a) (cherry picked from commit 83460afb3700a2ddaabce16069b2094e4edbe8fa) * Update README.md (cherry picked from commit 671234b08f909810d798dd950f87d1818b098363) (cherry picked from commit 7a6785b1c353cbc45c4dfaad89f5252064ef1e4e) * [DURACOM-291] Expose section scope attribute (cherry picked from commit 4107f937fda1b49da2b6a81dc91026ebf4b1cd37) * README.md: v8 is the current release, not v7 (cherry picked from commit 2b698eff609d510c487ad2331d4e11cd28f64e9a) (cherry picked from commit d98499a39416d026c0b45199e90ddb9bbfc1cd9c) * Update README.md (cherry picked from commit 671234b08f909810d798dd950f87d1818b098363) (cherry picked from commit 6a707548ffa6929e8a36775063ce05e2dc02a586) --------- Co-authored-by: Christian Clauss Co-authored-by: Giuseppe Digilio --- .../dspace/app/rest/converter/SubmissionSectionConverter.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionSectionConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionSectionConverter.java index 0391cbce7a2..3cd263493b5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionSectionConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionSectionConverter.java @@ -10,6 +10,7 @@ import java.sql.SQLException; import org.apache.logging.log4j.Logger; +import org.dspace.app.rest.model.ScopeEnum; import org.dspace.app.rest.model.SubmissionSectionRest; import org.dspace.app.rest.model.SubmissionVisibilityRest; import org.dspace.app.rest.model.VisibilityEnum; @@ -41,6 +42,7 @@ public SubmissionSectionRest convert(SubmissionStepConfig step, Projection proje sp.setHeader(step.getHeading()); sp.setSectionType(step.getType()); sp.setId(step.getId()); + sp.setScope(ScopeEnum.fromString(step.getScope())); sp.setVisibility(new SubmissionVisibilityRest(VisibilityEnum.fromString(step.getVisibility()), VisibilityEnum.fromString(step.getVisibilityOutside()))); return sp; From 51df5c1fe67b7660dafa85d57f688347ca7f024d Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 2 Dec 2024 15:35:10 +0100 Subject: [PATCH 359/939] 119664: Search event scope fix (cherry picked from commit 48956d90b7619bdb336632b454ac62ca62967a39) --- .../org/dspace/app/rest/converter/SearchEventConverter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SearchEventConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SearchEventConverter.java index 978ae2ca923..f2fb12f2bda 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SearchEventConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SearchEventConverter.java @@ -67,8 +67,8 @@ public UsageSearchEvent convert(Context context, HttpServletRequest request, Sea if (searchEventRest.getScope() != null) { IndexableObject scopeObject = scopeResolver.resolveScope(context, String.valueOf(searchEventRest.getScope())); - if (scopeObject instanceof DSpaceObject) { - usageSearchEvent.setScope((DSpaceObject) scopeObject); + if (scopeObject != null && scopeObject.getIndexedObject() instanceof DSpaceObject) { + usageSearchEvent.setScope((DSpaceObject) scopeObject.getIndexedObject()); } } usageSearchEvent.setConfiguration(searchEventRest.getConfiguration()); From 8849212895d987abaca92865178c6b4d2f99452d Mon Sep 17 00:00:00 2001 From: Chris Wilper Date: Wed, 28 Apr 2021 09:29:36 -0400 Subject: [PATCH 360/939] Add Context method to uncache all entities (cherry picked from commit 8ea664adb2161b04ab8b6171bbb426d8875fe27f) --- .../main/java/org/dspace/core/Context.java | 14 +++++++- .../java/org/dspace/core/DBConnection.java | 32 ++++++++++++------- .../dspace/core/HibernateDBConnection.java | 5 +++ .../java/org/dspace/core/ContextTest.java | 25 +++++++++++++++ .../core/HibernateDBConnectionTest.java | 22 +++++++++++++ 5 files changed, 86 insertions(+), 12 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/core/Context.java b/dspace-api/src/main/java/org/dspace/core/Context.java index 02a3fee09f8..dab6ab7fbd6 100644 --- a/dspace-api/src/main/java/org/dspace/core/Context.java +++ b/dspace-api/src/main/java/org/dspace/core/Context.java @@ -883,7 +883,19 @@ public E reloadEntity(E entity) throws SQLException } /** - * Remove an entity from the cache. This is necessary when batch processing a large number of items. + * Remove all entities from the cache and reload the current user entity. This is useful when batch processing + * a large number of entities when the calling code requires the cache to be completely cleared before continuing. + * + * @throws SQLException if a database error occurs. + */ + public void uncacheEntities() throws SQLException { + dbConnection.uncacheEntities(); + reloadContextBoundEntities(); + } + + /** + * Remove an entity from the cache. This is useful when batch processing a large number of entities + * when the calling code needs to retain some items in the cache while removing others. * * @param entity The entity to reload * @param The class of the entity. The entity must implement the {@link ReloadableEntity} interface. diff --git a/dspace-api/src/main/java/org/dspace/core/DBConnection.java b/dspace-api/src/main/java/org/dspace/core/DBConnection.java index 66e4a65dbfe..c9c4ce0953e 100644 --- a/dspace-api/src/main/java/org/dspace/core/DBConnection.java +++ b/dspace-api/src/main/java/org/dspace/core/DBConnection.java @@ -124,28 +124,38 @@ public interface DBConnection { public long getCacheSize() throws SQLException; /** - * Reload a DSpace object from the database. This will make sure the object + * Reload an entity from the database. This will make sure the object * is valid and stored in the cache. The returned object should be used * henceforth instead of the passed object. * - * @param type of {@link entity} - * @param entity The DSpace object to reload + * @param type of entity. + * @param entity The entity to reload. * @return the reloaded entity. - * @throws java.sql.SQLException passed through. + * @throws SQLException passed through. */ public E reloadEntity(E entity) throws SQLException; /** - * Remove a DSpace object from the session cache when batch processing a - * large number of objects. + * Remove all entities from the session cache. * - *

    Objects removed from cache are not saved in any way. Therefore, if you - * have modified an object, you should be sure to {@link commit()} changes + *

    Entities removed from cache are not saved in any way. Therefore, if you + * have modified any entities, you should be sure to {@link #commit()} changes * before calling this method. * - * @param Type of {@link entity} - * @param entity The DSpace object to decache. - * @throws java.sql.SQLException passed through. + * @throws SQLException passed through. + */ + public void uncacheEntities() throws SQLException; + + /** + * Remove an entity from the session cache. + * + *

    Entities removed from cache are not saved in any way. Therefore, if you + * have modified the entity, you should be sure to {@link #commit()} changes + * before calling this method. + * + * @param Type of entity. + * @param entity The entity to decache. + * @throws SQLException passed through. */ public void uncacheEntity(E entity) throws SQLException; diff --git a/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java b/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java index b371af80eed..bd00b844ba9 100644 --- a/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java +++ b/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java @@ -243,6 +243,11 @@ private void configureDatabaseMode() throws SQLException { } } + @Override + public void uncacheEntities() throws SQLException { + getSession().clear(); + } + /** * Evict an entity from the hibernate cache. *

    diff --git a/dspace-api/src/test/java/org/dspace/core/ContextTest.java b/dspace-api/src/test/java/org/dspace/core/ContextTest.java index c6cd849d211..ccc1d2f732c 100644 --- a/dspace-api/src/test/java/org/dspace/core/ContextTest.java +++ b/dspace-api/src/test/java/org/dspace/core/ContextTest.java @@ -558,4 +558,29 @@ protected void init() { cleanupContext(instance); } + @Test + public void testUncacheEntities() throws Throwable { + // To set up the test, ensure the cache contains more than the current user entity + groupService.findByName(context, Group.ANONYMOUS); + assertTrue("Cache size should be greater than one", context.getDBConnection().getCacheSize() > 1); + + context.uncacheEntities(); + + assertThat("Cache size should be one (current user)", context.getDBConnection().getCacheSize(), equalTo(1L)); + context.reloadEntity(context.getCurrentUser()); + assertThat("Cache should only contain the current user", context.getDBConnection().getCacheSize(), equalTo(1L)); + } + + @Test + public void testUncacheEntity() throws Throwable { + // Remember the cache size after loading an entity + Group group = groupService.findByName(context, Group.ANONYMOUS); + long oldCacheSize = context.getDBConnection().getCacheSize(); + + // Uncache the entity + context.uncacheEntity(group); + + long newCacheSize = context.getDBConnection().getCacheSize(); + assertThat("Cache size should be reduced by one", newCacheSize, equalTo(oldCacheSize - 1)); + } } diff --git a/dspace-api/src/test/java/org/dspace/core/HibernateDBConnectionTest.java b/dspace-api/src/test/java/org/dspace/core/HibernateDBConnectionTest.java index 093f693d567..302844ce62a 100644 --- a/dspace-api/src/test/java/org/dspace/core/HibernateDBConnectionTest.java +++ b/dspace-api/src/test/java/org/dspace/core/HibernateDBConnectionTest.java @@ -205,6 +205,28 @@ public void testReloadEntityAfterCommit() throws SQLException { .contains(person)); } + /** + * Test of uncacheEntities method + */ + @Test + public void testUncacheEntities() throws SQLException { + // Get DBConnection associated with DSpace Context + HibernateDBConnection dbConnection = (HibernateDBConnection) context.getDBConnection(); + EPerson person = context.getCurrentUser(); + + assertTrue("Current user should be cached in session", dbConnection.getSession() + .contains(person)); + + dbConnection.uncacheEntities(); + assertFalse("Current user should be gone from cache", dbConnection.getSession() + .contains(person)); + + // Test ability to reload an uncached entity + person = dbConnection.reloadEntity(person); + assertTrue("Current user should be cached back in session", dbConnection.getSession() + .contains(person)); + } + /** * Test of uncacheEntity method */ From e856ae3291831feac74f01133f7e235d8513cba0 Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Sun, 27 Oct 2024 08:56:10 +0100 Subject: [PATCH 361/939] Uncache all entities during OAI indexing to reduce memory usage (cherry picked from commit 9af2e2e17cf289a2a6921d27a21d90db6ca8b9f9) --- dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java | 5 +++++ 1 file changed, 5 insertions(+) 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 c6aaaa34b53..2d267957780 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 @@ -334,6 +334,11 @@ private int index(Iterator iterator) throws DSpaceSolrIndexerException { server.add(list); server.commit(); list.clear(); + try { + context.uncacheEntities(); + } catch (SQLException ex) { + log.error("Error uncaching entities", ex); + } } } System.out.println("Total: " + i + " items"); From 964e4bf476b9cb49ce07c5d05242c63e9ae638d4 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Mon, 23 Dec 2024 12:26:58 +0100 Subject: [PATCH 362/939] remove usage of deprecated constructor call (cherry picked from commit 45cdb4d9d47d3151a4672e99152a0b3bedb6cd18) --- dspace-api/src/main/java/org/dspace/curate/Curation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/curate/Curation.java b/dspace-api/src/main/java/org/dspace/curate/Curation.java index 4d70286e79e..ea47d817c07 100644 --- a/dspace-api/src/main/java/org/dspace/curate/Curation.java +++ b/dspace-api/src/main/java/org/dspace/curate/Curation.java @@ -185,7 +185,7 @@ private Curator initCurator() throws FileNotFoundException { Curator curator = new Curator(handler); OutputStream reporterStream; if (null == this.reporter) { - reporterStream = new NullOutputStream(); + reporterStream = NullOutputStream.NULL_OUTPUT_STREAM; } else if ("-".equals(this.reporter)) { reporterStream = System.out; } else { From 214c669e8063f93eca2f878610647cc3b87346bd Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Wed, 11 Dec 2024 15:35:26 +0100 Subject: [PATCH 363/939] fix missing +1 offset (cherry picked from commit ab00de05b46fe98306e0047af7d6d98bba9d0077) --- dspace-oai/src/main/resources/static/style.xsl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/dspace-oai/src/main/resources/static/style.xsl b/dspace-oai/src/main/resources/static/style.xsl index 17eb865e8f1..67aeb975b26 100644 --- a/dspace-oai/src/main/resources/static/style.xsl +++ b/dspace-oai/src/main/resources/static/style.xsl @@ -522,15 +522,14 @@ - + - - + - - + From 6c82e2dba40e50e72ab8459995802a5d7f526a53 Mon Sep 17 00:00:00 2001 From: Koen Pauwels Date: Tue, 7 Jan 2025 15:51:28 +0100 Subject: [PATCH 364/939] Bugfix: Enforce unique item id in workspace table (#9340) * 106798 Enforce values in item_id column of workspaceitem table to be unique, both at database level and at WorkspaceItemService level * 106798 Removed Oracle SQL migration * 106798 workspaceitem table migration: delete duplicate rows before introducing uniqueness constraint * 106798: update migration for H2 --------- Co-authored-by: Koen Pauwels Co-authored-by: wout --- .../content/WorkspaceItemServiceImpl.java | 8 +++ ...paceitem_add_item_id_unique_constraint.sql | 21 +++++++ ...paceitem_add_item_id_unique_constraint.sql | 21 +++++++ .../org/dspace/content/WorkspaceItemTest.java | 12 ++++ .../org/dspace/workflow/MockWorkflowItem.java | 62 +++++++++++++++++++ 5 files changed, 124 insertions(+) create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2024.12.17__workspaceitem_add_item_id_unique_constraint.sql create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2024.12.17__workspaceitem_add_item_id_unique_constraint.sql create mode 100644 dspace-api/src/test/java/org/dspace/workflow/MockWorkflowItem.java diff --git a/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java index 1da9e6e44a6..543f5a55efe 100644 --- a/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java @@ -178,6 +178,14 @@ public WorkspaceItem create(Context context, Collection collection, UUID uuid, b @Override public WorkspaceItem create(Context c, WorkflowItem workflowItem) throws SQLException, AuthorizeException { + WorkspaceItem potentialDuplicate = findByItem(c, workflowItem.getItem()); + if (potentialDuplicate != null) { + throw new IllegalArgumentException(String.format( + "A workspace item referring to item %s already exists (%d)", + workflowItem.getItem().getID(), + potentialDuplicate.getID() + )); + } WorkspaceItem workspaceItem = workspaceItemDAO.create(c, new WorkspaceItem()); workspaceItem.setItem(workflowItem.getItem()); workspaceItem.setCollection(workflowItem.getCollection()); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2024.12.17__workspaceitem_add_item_id_unique_constraint.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2024.12.17__workspaceitem_add_item_id_unique_constraint.sql new file mode 100644 index 00000000000..38389bf2d19 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2024.12.17__workspaceitem_add_item_id_unique_constraint.sql @@ -0,0 +1,21 @@ +-- +-- 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/ +-- + +-- In the workspaceitem table, if there are multiple rows referring to the same item ID, keep only the first of them. +DELETE FROM workspaceitem WHERE EXISTS ( + SELECT item_id + FROM workspaceitem + GROUP BY item_id + HAVING COUNT(workspace_item_id) > 1 +) AND workspaceitem.workspace_item_id NOT IN ( + SELECT MIN(workspace_item_id) AS workspace_item_id + FROM workspaceitem + GROUP BY item_id +); +-- Identify which rows have duplicates, and compute their replacements. +ALTER TABLE workspaceitem ADD CONSTRAINT unique_item_id UNIQUE(item_id); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2024.12.17__workspaceitem_add_item_id_unique_constraint.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2024.12.17__workspaceitem_add_item_id_unique_constraint.sql new file mode 100644 index 00000000000..20eb0f9119d --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2024.12.17__workspaceitem_add_item_id_unique_constraint.sql @@ -0,0 +1,21 @@ +-- +-- 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/ +-- + +-- In the workspaceitem table, if there are multiple rows referring to the same item ID, keep only the first of them. +WITH dedup AS ( + SELECT item_id, MIN(workspace_item_id) AS workspace_item_id + FROM workspaceitem + GROUP BY item_id + HAVING COUNT(workspace_item_id) > 1 +) +DELETE FROM workspaceitem +USING dedup +WHERE workspaceitem.item_id = dedup.item_id AND workspaceitem.workspace_item_id <> dedup.workspace_item_id; + +-- Enforce uniqueness of item_id in workspaceitem table. +ALTER TABLE workspaceitem ADD CONSTRAINT unique_item_id UNIQUE(item_id); diff --git a/dspace-api/src/test/java/org/dspace/content/WorkspaceItemTest.java b/dspace-api/src/test/java/org/dspace/content/WorkspaceItemTest.java index d018a15f976..15d4720c937 100644 --- a/dspace-api/src/test/java/org/dspace/content/WorkspaceItemTest.java +++ b/dspace-api/src/test/java/org/dspace/content/WorkspaceItemTest.java @@ -12,6 +12,7 @@ import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; @@ -39,6 +40,7 @@ import org.dspace.eperson.EPerson; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.EPersonService; +import org.dspace.workflow.MockWorkflowItem; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -468,4 +470,14 @@ public void testSetPublishedBefore() { assertTrue("testSetPublishedBefore 0", wi.isPublishedBefore()); } + @Test + public void testDuplicateItemID() throws Exception { + context.turnOffAuthorisationSystem(); + Item item = wi.getItem(); + MockWorkflowItem wfItem = new MockWorkflowItem(); + wfItem.item = item; + wfItem.collection = collection; + assertThrows(IllegalArgumentException.class, () -> workspaceItemService.create(context, wfItem)); + context.restoreAuthSystemState(); + } } diff --git a/dspace-api/src/test/java/org/dspace/workflow/MockWorkflowItem.java b/dspace-api/src/test/java/org/dspace/workflow/MockWorkflowItem.java new file mode 100644 index 00000000000..d36ecf7331b --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/workflow/MockWorkflowItem.java @@ -0,0 +1,62 @@ +/** + * 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.workflow; + +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.eperson.EPerson; + +public class MockWorkflowItem implements WorkflowItem { + public Integer id; + public Item item; + public Collection collection; + public EPerson submitter; + boolean hasMultipleFiles; + boolean hasMultipleTitles; + boolean isPublishedBefore; + + public Integer getID() { + return id; + } + + public Item getItem() { + return item; + } + + public Collection getCollection() { + return collection; + } + + public EPerson getSubmitter() { + return submitter; + } + + public boolean hasMultipleFiles() { + return hasMultipleFiles; + } + + public void setMultipleFiles(boolean b) { + hasMultipleFiles = b; + } + + public boolean hasMultipleTitles() { + return hasMultipleTitles; + } + + public void setMultipleTitles(boolean b) { + hasMultipleTitles = b; + } + + public boolean isPublishedBefore() { + return isPublishedBefore; + } + + public void setPublishedBefore(boolean b) { + isPublishedBefore = b; + } +} From 22511a17b67c8034b039634e7bf98d9e9a58ae5d Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 6 Nov 2024 12:03:31 -0600 Subject: [PATCH 365/939] Refactor identifier ITs to ensure they unregister all utilized IdentifierProviders which are non-default. Cannot use "getApplicationContext().refresh()" as that seems to result in empty test database in Hibernate 6.6. (cherry picked from commit cfca2adbb1d597761b3169a5a46170de8cabfa4f) --- .../general/CreateMissingIdentifiersIT.java | 48 +++---------- .../AbstractIdentifierProviderIT.java | 68 +++++++++++++++++++ .../VersionedHandleIdentifierProviderIT.java | 49 ++----------- 3 files changed, 84 insertions(+), 81 deletions(-) create mode 100644 dspace-api/src/test/java/org/dspace/identifier/AbstractIdentifierProviderIT.java diff --git a/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java b/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java index 8038a715332..4934404c3b3 100644 --- a/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java +++ b/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java @@ -10,10 +10,7 @@ import static org.junit.Assert.assertEquals; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.ItemBuilder; @@ -21,13 +18,11 @@ import org.dspace.content.Item; import org.dspace.core.factory.CoreServiceFactory; import org.dspace.curate.Curator; -import org.dspace.identifier.IdentifierProvider; -import org.dspace.identifier.IdentifierServiceImpl; +import org.dspace.identifier.AbstractIdentifierProviderIT; +import org.dspace.identifier.VersionedHandleIdentifierProvider; import org.dspace.identifier.VersionedHandleIdentifierProviderWithCanonicalHandles; -import org.dspace.kernel.ServiceManager; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; -import org.junit.After; import org.junit.Test; /** @@ -36,30 +31,19 @@ * @author mwood */ public class CreateMissingIdentifiersIT - extends AbstractIntegrationTestWithDatabase { - private ServiceManager serviceManager; - private IdentifierServiceImpl identifierService; + extends AbstractIdentifierProviderIT { + private static final String P_TASK_DEF = "plugin.named.org.dspace.curate.CurationTask"; private static final String TASK_NAME = "test"; - @Override - public void setUp() throws Exception { - super.setUp(); - context.turnOffAuthorisationSystem(); - - serviceManager = DSpaceServicesFactory.getInstance().getServiceManager(); - identifierService = serviceManager.getServicesByType(IdentifierServiceImpl.class).get(0); - // Clean out providers to avoid any being used for creation of community and collection - identifierService.setProviders(new ArrayList<>()); - } + private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); @Test public void testPerform() throws IOException { // Must remove any cached named plugins before creating a new one CoreServiceFactory.getInstance().getPluginService().clearNamedPluginClasses(); - ConfigurationService configurationService = kernelImpl.getConfigurationService(); // Define a new task dynamically configurationService.setProperty(P_TASK_DEF, CreateMissingIdentifiers.class.getCanonicalName() + " = " + TASK_NAME); @@ -76,7 +60,7 @@ public void testPerform() .build(); /* - * Curate with regular test configuration -- should succeed. + * Curate with default Handle Provider */ curator.curate(context, item); int status = curator.getStatus(TASK_NAME); @@ -92,22 +76,10 @@ public void testPerform() curator.getResult(TASK_NAME)); assertEquals("Curation should fail", Curator.CURATE_ERROR, curator.getStatus(TASK_NAME)); - } - - @Override - @After - public void destroy() throws Exception { - super.destroy(); - DSpaceServicesFactory.getInstance().getServiceManager().getApplicationContext().refresh(); - } - - private void registerProvider(Class type) { - // Register our new provider - serviceManager.registerServiceClass(type.getName(), type); - IdentifierProvider identifierProvider = - (IdentifierProvider) serviceManager.getServiceByName(type.getName(), type); - // Overwrite the identifier-service's providers with the new one to ensure only this provider is used - identifierService.setProviders(List.of(identifierProvider)); + // Unregister this non-default provider + unregisterProvider(VersionedHandleIdentifierProviderWithCanonicalHandles.class); + // Re-register the default provider (for later tests which may depend on it) + registerProvider(VersionedHandleIdentifierProvider.class); } } diff --git a/dspace-api/src/test/java/org/dspace/identifier/AbstractIdentifierProviderIT.java b/dspace-api/src/test/java/org/dspace/identifier/AbstractIdentifierProviderIT.java new file mode 100644 index 00000000000..6ec9efddf5e --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/identifier/AbstractIdentifierProviderIT.java @@ -0,0 +1,68 @@ +/** + * 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.identifier; + +import java.util.ArrayList; +import java.util.List; + +import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.kernel.ServiceManager; +import org.dspace.services.factory.DSpaceServicesFactory; + +/** + * AbstractIdentifierProviderIT which contains a few useful utility methods for IdentifierProvider Integration Tests + */ +public class AbstractIdentifierProviderIT extends AbstractIntegrationTestWithDatabase { + + protected final ServiceManager serviceManager = DSpaceServicesFactory.getInstance().getServiceManager(); + protected final IdentifierServiceImpl identifierService = + serviceManager.getServicesByType(IdentifierServiceImpl.class).get(0); + + /** + * Register a specific IdentifierProvider into the current IdentifierService (replacing any existing providers). + * This method will also ensure the IdentifierProvider service is registered in the DSpace Service Manager. + * @param type IdentifierProvider Class + */ + protected void registerProvider(Class type) { + // Register our new provider + IdentifierProvider identifierProvider = + (IdentifierProvider) DSpaceServicesFactory.getInstance().getServiceManager() + .getServiceByName(type.getName(), type); + if (identifierProvider == null) { + DSpaceServicesFactory.getInstance().getServiceManager().registerServiceClass(type.getName(), type); + identifierProvider = (IdentifierProvider) DSpaceServicesFactory.getInstance().getServiceManager() + .getServiceByName(type.getName(), type); + } + + identifierService.setProviders(List.of(identifierProvider)); + } + + /** + * Unregister a specific IdentifierProvider from the current IdentifierService (removing all existing providers). + * This method will also ensure the IdentifierProvider service is unregistered in the DSpace Service Manager, + * which ensures it does not conflict with other IdentifierProvider services. + * @param type IdentifierProvider Class + */ + protected void unregisterProvider(Class type) { + // Find the provider service + IdentifierProvider identifierProvider = + (IdentifierProvider) DSpaceServicesFactory.getInstance().getServiceManager() + .getServiceByName(type.getName(), type); + // If found, unregister it + if (identifierProvider == null) { + DSpaceServicesFactory.getInstance().getServiceManager().unregisterService(type.getName()); + } + + // Overwrite the identifier-service's providers with an empty list + identifierService.setProviders(new ArrayList<>()); + } + +} + + + diff --git a/dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderIT.java b/dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderIT.java index 57acf1f1c45..8c0f7e5b5e1 100644 --- a/dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderIT.java +++ b/dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderIT.java @@ -11,10 +11,7 @@ import static org.junit.Assert.assertTrue; import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.authorize.AuthorizeException; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; @@ -22,15 +19,10 @@ import org.dspace.builder.VersionBuilder; import org.dspace.content.Collection; import org.dspace.content.Item; -import org.dspace.kernel.ServiceManager; -import org.dspace.services.factory.DSpaceServicesFactory; -import org.junit.After; import org.junit.Before; import org.junit.Test; -public class VersionedHandleIdentifierProviderIT extends AbstractIntegrationTestWithDatabase { - private ServiceManager serviceManager; - private IdentifierServiceImpl identifierService; +public class VersionedHandleIdentifierProviderIT extends AbstractIdentifierProviderIT { private String firstHandle; @@ -44,12 +36,6 @@ public class VersionedHandleIdentifierProviderIT extends AbstractIntegrationTest public void setUp() throws Exception { super.setUp(); context.turnOffAuthorisationSystem(); - - serviceManager = DSpaceServicesFactory.getInstance().getServiceManager(); - identifierService = serviceManager.getServicesByType(IdentifierServiceImpl.class).get(0); - // Clean out providers to avoid any being used for creation of community and collection - identifierService.setProviders(new ArrayList<>()); - parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") .build(); @@ -58,33 +44,6 @@ public void setUp() throws Exception { .build(); } - @After - @Override - public void destroy() throws Exception { - super.destroy(); - // After this test has finished running, refresh application context and - // set the expected 'default' versioned handle provider back to ensure other tests don't fail - DSpaceServicesFactory.getInstance().getServiceManager().getApplicationContext().refresh(); - } - - private void registerProvider(Class type) { - // Register our new provider - IdentifierProvider identifierProvider = - (IdentifierProvider) DSpaceServicesFactory.getInstance().getServiceManager() - .getServiceByName(type.getName(), type); - if (identifierProvider == null) { - DSpaceServicesFactory.getInstance().getServiceManager().registerServiceClass(type.getName(), type); - identifierProvider = (IdentifierProvider) DSpaceServicesFactory.getInstance().getServiceManager() - .getServiceByName(type.getName(), type); - } - - // Overwrite the identifier-service's providers with the new one to ensure only this provider is used - identifierService = DSpaceServicesFactory.getInstance().getServiceManager() - .getServicesByType(IdentifierServiceImpl.class).get(0); - identifierService.setProviders(new ArrayList<>()); - identifierService.setProviders(List.of(identifierProvider)); - } - private void createVersions() throws SQLException, AuthorizeException { itemV1 = ItemBuilder.createItem(context, collection) .withTitle("First version") @@ -96,7 +55,6 @@ private void createVersions() throws SQLException, AuthorizeException { @Test public void testDefaultVersionedHandleProvider() throws Exception { - registerProvider(VersionedHandleIdentifierProvider.class); createVersions(); // Confirm the original item only has its original handle @@ -125,6 +83,11 @@ public void testCanonicalVersionedHandleProvider() throws Exception { assertEquals(firstHandle, itemV3.getHandle()); assertEquals(2, itemV3.getHandles().size()); containsHandle(itemV3, firstHandle + ".3"); + + // Unregister this non-default provider + unregisterProvider(VersionedHandleIdentifierProviderWithCanonicalHandles.class); + // Re-register the default provider (for later tests) + registerProvider(VersionedHandleIdentifierProvider.class); } private void containsHandle(Item item, String handle) { From 41207d5dee23c78d9f8a8ef93be89ed331607b13 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 8 Nov 2024 10:17:31 -0600 Subject: [PATCH 366/939] Update CreateMissingIdentifiers to better identify when CanonicalHandles provider is enabled. Update CreateMissingIdentifiersIT to verify that we are accurately resetting to our default IdentifierProvider (cherry picked from commit 2385c13f2d48048306a1280d88da5d8c022cc24f) --- .../general/CreateMissingIdentifiers.java | 28 +++++++++---------- .../identifier/IdentifierServiceImpl.java | 5 ++++ .../identifier/service/IdentifierService.java | 6 ++++ .../general/CreateMissingIdentifiersIT.java | 17 +++++------ 4 files changed, 33 insertions(+), 23 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/ctask/general/CreateMissingIdentifiers.java b/dspace-api/src/main/java/org/dspace/ctask/general/CreateMissingIdentifiers.java index 9639461426e..0734d60946b 100644 --- a/dspace-api/src/main/java/org/dspace/ctask/general/CreateMissingIdentifiers.java +++ b/dspace-api/src/main/java/org/dspace/ctask/general/CreateMissingIdentifiers.java @@ -10,6 +10,7 @@ import java.io.IOException; import java.sql.SQLException; +import java.util.List; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -25,7 +26,6 @@ import org.dspace.identifier.VersionedHandleIdentifierProviderWithCanonicalHandles; import org.dspace.identifier.factory.IdentifierServiceFactory; import org.dspace.identifier.service.IdentifierService; -import org.dspace.services.factory.DSpaceServicesFactory; /** * Ensure that an object has all of the identifiers that it should, minting them @@ -45,20 +45,6 @@ public int perform(DSpaceObject dso) return Curator.CURATE_SKIP; } - // XXX Temporary escape when an incompatible provider is configured. - // XXX Remove this when the provider is fixed. - boolean compatible = DSpaceServicesFactory - .getInstance() - .getServiceManager() - .getServiceByName( - VersionedHandleIdentifierProviderWithCanonicalHandles.class.getCanonicalName(), - IdentifierProvider.class) == null; - if (!compatible) { - setResult("This task is not compatible with VersionedHandleIdentifierProviderWithCanonicalHandles"); - return Curator.CURATE_ERROR; - } - // XXX End of escape - String typeText = Constants.typeText[dso.getType()]; // Get a Context @@ -75,6 +61,18 @@ public int perform(DSpaceObject dso) .getInstance() .getIdentifierService(); + // XXX Temporary escape when an incompatible provider is configured. + // XXX Remove this when the provider is fixed. + List providerList = identifierService.getProviders(); + boolean compatible = + providerList.stream().noneMatch(p -> p instanceof VersionedHandleIdentifierProviderWithCanonicalHandles); + + if (!compatible) { + setResult("This task is not compatible with VersionedHandleIdentifierProviderWithCanonicalHandles"); + return Curator.CURATE_ERROR; + } + // XXX End of escape + // Register any missing identifiers. try { identifierService.register(context, dso); diff --git a/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java b/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java index b98aea24fa0..e6dcfcdda69 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java @@ -57,6 +57,11 @@ public void setProviders(List providers) { } } + @Override + public List getProviders() { + return this.providers; + } + /** * Reserves identifiers for the item * diff --git a/dspace-api/src/main/java/org/dspace/identifier/service/IdentifierService.java b/dspace-api/src/main/java/org/dspace/identifier/service/IdentifierService.java index 23005b65750..45bf3c6dea8 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/service/IdentifierService.java +++ b/dspace-api/src/main/java/org/dspace/identifier/service/IdentifierService.java @@ -19,6 +19,7 @@ import org.dspace.identifier.IdentifierException; import org.dspace.identifier.IdentifierNotFoundException; import org.dspace.identifier.IdentifierNotResolvableException; +import org.dspace.identifier.IdentifierProvider; /** * @author Fabio Bolognesi (fabio at atmire dot com) @@ -194,4 +195,9 @@ void register(Context context, DSpaceObject dso, String identifier) void delete(Context context, DSpaceObject dso, String identifier) throws AuthorizeException, SQLException, IdentifierException; + /** + * Get List of currently enabled IdentifierProviders + * @return List of enabled IdentifierProvider objects. + */ + List getProviders(); } diff --git a/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java b/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java index 4934404c3b3..3b50258a5a2 100644 --- a/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java +++ b/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java @@ -60,14 +60,7 @@ public void testPerform() .build(); /* - * Curate with default Handle Provider - */ - curator.curate(context, item); - int status = curator.getStatus(TASK_NAME); - assertEquals("Curation should succeed", Curator.CURATE_SUCCESS, status); - - /* - * Now install an incompatible provider to make the task fail. + * First, install an incompatible provider to make the task fail. */ registerProvider(VersionedHandleIdentifierProviderWithCanonicalHandles.class); @@ -81,5 +74,13 @@ public void testPerform() unregisterProvider(VersionedHandleIdentifierProviderWithCanonicalHandles.class); // Re-register the default provider (for later tests which may depend on it) registerProvider(VersionedHandleIdentifierProvider.class); + + /* + * Now, verify curate with default Handle Provider works + * (and that our re-registration of the default provider above was successful) + */ + curator.curate(context, item); + int status = curator.getStatus(TASK_NAME); + assertEquals("Curation should succeed", Curator.CURATE_SUCCESS, status); } } From 97a5439e3aa23bfde4f9741282d48c10a873b387 Mon Sep 17 00:00:00 2001 From: "Gantner, Florian Klaus" Date: Thu, 9 Jan 2025 14:09:24 +0100 Subject: [PATCH 367/939] switch IT search core to MockSolrSearchCore https://github.com/DSpace/DSpace/issues/10188 (cherry picked from commit 6d781e8f83a912863af8d998d982e030db64a2ce) --- .../org/dspace/content/VersioningWithRelationshipsIT.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsIT.java b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsIT.java index 3acc4ca146e..9ff452f7895 100644 --- a/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsIT.java +++ b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsIT.java @@ -59,7 +59,7 @@ import org.dspace.content.virtual.VirtualMetadataConfiguration; import org.dspace.content.virtual.VirtualMetadataPopulator; import org.dspace.core.Constants; -import org.dspace.discovery.SolrSearchCore; +import org.dspace.discovery.MockSolrSearchCore; import org.dspace.kernel.ServiceManager; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.versioning.Version; @@ -79,8 +79,9 @@ public class VersioningWithRelationshipsIT extends AbstractIntegrationTestWithDa ContentServiceFactory.getInstance().getInstallItemService(); private final ItemService itemService = ContentServiceFactory.getInstance().getItemService(); - private final SolrSearchCore solrSearchCore = - DSpaceServicesFactory.getInstance().getServiceManager().getServicesByType(SolrSearchCore.class).get(0); + private final MockSolrSearchCore solrSearchCore = + DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName(null, MockSolrSearchCore.class); + protected Community community; protected Collection collection; protected EntityType publicationEntityType; From 49b3deef77055a18daba21c0bf2812bb2daa3265 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 9 Jan 2025 15:42:25 -0600 Subject: [PATCH 368/939] Refactor AbstractIntegrationTestWithDatabase to use Builders to create test EPersons. (cherry picked from commit 0b8b7be22b88a411898bcb975e238e7cd96f40ab) --- .../AbstractIntegrationTestWithDatabase.java | 48 ++++++++----------- .../AbstractWebClientIntegrationTest.java | 16 +++++++ 2 files changed, 37 insertions(+), 27 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/AbstractIntegrationTestWithDatabase.java b/dspace-api/src/test/java/org/dspace/AbstractIntegrationTestWithDatabase.java index e27fb19a68e..d5bb6ab8a4c 100644 --- a/dspace-api/src/test/java/org/dspace/AbstractIntegrationTestWithDatabase.java +++ b/dspace-api/src/test/java/org/dspace/AbstractIntegrationTestWithDatabase.java @@ -17,8 +17,8 @@ import org.dspace.app.scripts.handler.impl.TestDSpaceRunnableHandler; import org.dspace.authority.AuthoritySearchService; import org.dspace.authority.MockAuthoritySolrServiceImpl; -import org.dspace.authorize.AuthorizeException; import org.dspace.builder.AbstractBuilder; +import org.dspace.builder.EPersonBuilder; import org.dspace.content.Community; import org.dspace.core.Context; import org.dspace.core.I18nUtil; @@ -114,19 +114,16 @@ public void setUp() throws Exception { EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); eperson = ePersonService.findByEmail(context, "test@email.com"); if (eperson == null) { - // This EPerson creation should only happen once (i.e. for first test run) - log.info("Creating initial EPerson (email=test@email.com) for Unit Tests"); - eperson = ePersonService.create(context); - eperson.setFirstName(context, "first"); - eperson.setLastName(context, "last"); - eperson.setEmail("test@email.com"); - eperson.setCanLogIn(true); - eperson.setLanguage(context, I18nUtil.getDefaultLocale().getLanguage()); - ePersonService.setPassword(eperson, password); - // actually save the eperson to unit testing DB - ePersonService.update(context, eperson); + // Create test EPerson for usage in all tests + log.info("Creating Test EPerson (email=test@email.com) for Integration Tests"); + eperson = EPersonBuilder.createEPerson(context) + .withNameInMetadata("first", "last") + .withEmail("test@email.com") + .withCanLogin(true) + .withLanguage(I18nUtil.getDefaultLocale().getLanguage()) + .withPassword(password) + .build(); } - // Set our global test EPerson as the current user in DSpace context.setCurrentUser(eperson); @@ -135,26 +132,23 @@ public void setUp() throws Exception { admin = ePersonService.findByEmail(context, "admin@email.com"); if (admin == null) { - // This EPerson creation should only happen once (i.e. for first test run) - log.info("Creating initial EPerson (email=admin@email.com) for Unit Tests"); - admin = ePersonService.create(context); - admin.setFirstName(context, "first (admin)"); - admin.setLastName(context, "last (admin)"); - admin.setEmail("admin@email.com"); - admin.setCanLogIn(true); - admin.setLanguage(context, I18nUtil.getDefaultLocale().getLanguage()); - ePersonService.setPassword(admin, password); - // actually save the eperson to unit testing DB - ePersonService.update(context, admin); + // Create test Administrator for usage in all tests + log.info("Creating Test Admin EPerson (email=admin@email.com) for Integration Tests"); + admin = EPersonBuilder.createEPerson(context) + .withNameInMetadata("first (admin)", "last (admin)") + .withEmail("admin@email.com") + .withCanLogin(true) + .withLanguage(I18nUtil.getDefaultLocale().getLanguage()) + .withPassword(password) + .build(); + + // Add Test Administrator to the ADMIN group in test database GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); Group adminGroup = groupService.findByName(context, Group.ADMIN); groupService.addMember(context, adminGroup, admin); } context.restoreAuthSystemState(); - } catch (AuthorizeException ex) { - log.error("Error creating initial eperson or default groups", ex); - fail("Error creating initial eperson or default groups in AbstractUnitTest init()"); } catch (SQLException ex) { log.error(ex.getMessage(), ex); fail("SQL Error on AbstractUnitTest init()"); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java index df6afcb16e3..fadebff7de5 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java @@ -12,6 +12,7 @@ import org.dspace.app.rest.Application; import org.dspace.app.rest.utils.DSpaceConfigurationInitializer; import org.dspace.app.rest.utils.DSpaceKernelInitializer; +import org.junit.Before; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -64,6 +65,21 @@ public class AbstractWebClientIntegrationTest extends AbstractIntegrationTestWit @Autowired protected ApplicationContext applicationContext; + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + + // Because AbstractWebClientIntegrationTest starts a new webserver, we need to ensure *everything* created by + // the "super.setUp()" script is committed to the test database. Otherwise, the new webserver may not see the + // created test data in the database. + // NOTE: This commit() does NOT occur in AbstractIntegrationTestDatabase because it will remove newly created + // Hibernate entities from the current Hibernate session. For most ITs we don't want that as it may result + // in "could not initialize proxy - no Session" errors when using those entities in other tests (or other tests + // would need to reload each test entity back into the Hibernate session) + context.commit(); + } + /** * Get client TestRestTemplate for making HTTP requests to test webserver * @return TestRestTemplate From 5973c67eff7180496ee761a32fa1f9636b02c93e Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Tue, 9 Jul 2024 16:58:00 +0200 Subject: [PATCH 369/939] put DOIs in dc.identifier.doi (cherry picked from commit 3d1bef9d0e3fe820da9705427c129b5cb49d66fb) --- dspace/config/spring/api/scopus-integration.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace/config/spring/api/scopus-integration.xml b/dspace/config/spring/api/scopus-integration.xml index 47c5a4fbb90..6f7556574d0 100644 --- a/dspace/config/spring/api/scopus-integration.xml +++ b/dspace/config/spring/api/scopus-integration.xml @@ -128,7 +128,7 @@ - + @@ -264,4 +264,4 @@ - \ No newline at end of file + From 5d0e9871e9ec4cbacda577f9f2ae8cefcd961e27 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Tue, 9 Jul 2024 17:37:07 +0200 Subject: [PATCH 370/939] fix broken test (cherry picked from commit 2eff833fab361d4ed3c7b602b681823911f989af) --- .../dspace/app/rest/ScopusImportMetadataSourceServiceIT.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScopusImportMetadataSourceServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScopusImportMetadataSourceServiceIT.java index 7f6cb53ce40..e81f662ad5a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScopusImportMetadataSourceServiceIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScopusImportMetadataSourceServiceIT.java @@ -124,7 +124,7 @@ private ArrayList getRecords() { ArrayList records = new ArrayList<>(); //define first record List metadatums = new ArrayList(); - MetadatumDTO doi = createMetadatumDTO("dc", "identifier", null, "10.3934/mine.2023004"); + MetadatumDTO doi = createMetadatumDTO("dc", "identifier", "doi", "10.3934/mine.2023004"); MetadatumDTO title = createMetadatumDTO("dc","title", null, "Hardy potential versus lower order terms in Dirichlet problems: regularizing effects"); MetadatumDTO type = createMetadatumDTO("dc", "type", null, "Journal"); @@ -171,7 +171,7 @@ private ArrayList getRecords() { //define second record List metadatums2 = new ArrayList(); - MetadatumDTO doi2 = createMetadatumDTO("dc", "identifier", null, "10.3934/mine.2023001"); + MetadatumDTO doi2 = createMetadatumDTO("dc", "identifier", "doi", "10.3934/mine.2023001"); MetadatumDTO title2 = createMetadatumDTO("dc","title", null, "Large deviations for a binary collision model: energy evaporation"); MetadatumDTO date2 = createMetadatumDTO("dc", "date", "issued", "2023-01-01"); From 09a722a029ebc6e50835c360af8977d97cb1c033 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Thu, 17 Oct 2024 20:17:06 +0200 Subject: [PATCH 371/939] use dc.relation.hasversion for externally generated DOIs (cherry picked from commit 29067b6572b538650e4686e983edafcc355d87c5) --- dspace/config/spring/api/scopus-integration.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace/config/spring/api/scopus-integration.xml b/dspace/config/spring/api/scopus-integration.xml index 6f7556574d0..d3e8150c74b 100644 --- a/dspace/config/spring/api/scopus-integration.xml +++ b/dspace/config/spring/api/scopus-integration.xml @@ -128,7 +128,7 @@ - + From d109eac2a0ad170b656e36028d84fc294ae6eb35 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Thu, 17 Oct 2024 20:19:04 +0200 Subject: [PATCH 372/939] use dc.relation.hasversion instead of dc.identifier.doi (cherry picked from commit d61dc8d911a4d226362d9a554226da5c304ad722) --- .../dspace/app/rest/ScopusImportMetadataSourceServiceIT.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScopusImportMetadataSourceServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScopusImportMetadataSourceServiceIT.java index e81f662ad5a..f9164ddc599 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScopusImportMetadataSourceServiceIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScopusImportMetadataSourceServiceIT.java @@ -124,7 +124,7 @@ private ArrayList getRecords() { ArrayList records = new ArrayList<>(); //define first record List metadatums = new ArrayList(); - MetadatumDTO doi = createMetadatumDTO("dc", "identifier", "doi", "10.3934/mine.2023004"); + MetadatumDTO doi = createMetadatumDTO("dc", "relation", "hasversion", "10.3934/mine.2023004"); MetadatumDTO title = createMetadatumDTO("dc","title", null, "Hardy potential versus lower order terms in Dirichlet problems: regularizing effects"); MetadatumDTO type = createMetadatumDTO("dc", "type", null, "Journal"); @@ -171,7 +171,7 @@ private ArrayList getRecords() { //define second record List metadatums2 = new ArrayList(); - MetadatumDTO doi2 = createMetadatumDTO("dc", "identifier", "doi", "10.3934/mine.2023001"); + MetadatumDTO doi2 = createMetadatumDTO("dc", "relation", "hasversion", "10.3934/mine.2023001"); MetadatumDTO title2 = createMetadatumDTO("dc","title", null, "Large deviations for a binary collision model: energy evaporation"); MetadatumDTO date2 = createMetadatumDTO("dc", "date", "issued", "2023-01-01"); From 62596ad86313053f56fa316fba62ff1156cbeb37 Mon Sep 17 00:00:00 2001 From: "Gantner, Florian Klaus" Date: Tue, 8 Oct 2024 13:42:07 +0200 Subject: [PATCH 373/939] option to exclude the submitter being indexed to solr in archived items https://github.com/DSpace/DSpace/issues/9660 --- .../indexobject/ItemIndexFactoryImpl.java | 8 +- .../org/dspace/discovery/DiscoveryIT.java | 109 ++++++++++++++++++ dspace/config/modules/discovery.cfg | 7 +- 3 files changed, 120 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java index 7cdb8b93d80..5fb2f779783 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java @@ -154,9 +154,11 @@ public SolrInputDocument buildDocument(Context context, IndexableItem indexableI doc.addField("latestVersion", isLatestVersion(context, item)); EPerson submitter = item.getSubmitter(); - if (submitter != null) { - addFacetIndex(doc, "submitter", submitter.getID().toString(), - submitter.getFullName()); + if (submitter != null && !(DSpaceServicesFactory.getInstance().getConfigurationService().getBooleanProperty( + "discovery.index.item.submitter.enabled", false))) { + doc.addField("submitter_authority", submitter.getID().toString()); + } else if (submitter != null) { + addFacetIndex(doc, "submitter", submitter.getID().toString(), submitter.getFullName()); } // Add the item metadata diff --git a/dspace-api/src/test/java/org/dspace/discovery/DiscoveryIT.java b/dspace-api/src/test/java/org/dspace/discovery/DiscoveryIT.java index 55be531418a..159f7a0ac5a 100644 --- a/dspace-api/src/test/java/org/dspace/discovery/DiscoveryIT.java +++ b/dspace-api/src/test/java/org/dspace/discovery/DiscoveryIT.java @@ -8,6 +8,10 @@ package org.dspace.discovery; import static org.dspace.discovery.SolrServiceWorkspaceWorkflowRestrictionPlugin.DISCOVER_WORKSPACE_CONFIGURATION_NAME; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -21,6 +25,10 @@ import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; +import org.apache.solr.client.solrj.SolrQuery; +import org.apache.solr.client.solrj.SolrServerException; +import org.apache.solr.client.solrj.response.QueryResponse; +import org.apache.solr.common.SolrDocument; import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.app.launcher.ScriptLauncher; import org.dspace.app.scripts.handler.impl.TestDSpaceRunnableHandler; @@ -99,6 +107,9 @@ public class DiscoveryIT extends AbstractIntegrationTestWithDatabase { MetadataAuthorityService metadataAuthorityService = ContentAuthorityServiceFactory.getInstance() .getMetadataAuthorityService(); + MockSolrSearchCore solrSearchCore = DSpaceServicesFactory.getInstance().getServiceManager() + .getServiceByName(null, MockSolrSearchCore.class); + @Override @Before public void setUp() throws Exception { @@ -796,6 +807,104 @@ public void searchWithDefaultSortServiceTest() throws SearchServiceException { } } + /** + * Test designed to check if the submitter is not indexed in all in solr documents for items + * and the submitter authority is still indexed + * @throws SearchServiceException + */ + @Test + public void searchWithNoSubmitterTest() throws SearchServiceException { + + configurationService.setProperty("discovery.index.item.submitter.enabled", false); + DiscoveryConfiguration defaultConf = SearchUtils.getDiscoveryConfiguration(context, "default", null); + + // Populate the testing objects: create items in eperson's workspace and perform search in it + int numberItems = 10; + context.turnOffAuthorisationSystem(); + EPerson submitter = null; + try { + submitter = EPersonBuilder.createEPerson(context).withEmail("submitter@example.org") + .withNameInMetadata("Peter", "Funny").build(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + context.setCurrentUser(submitter); + Community community = CommunityBuilder.createCommunity(context).build(); + Collection collection = CollectionBuilder.createCollection(context, community).build(); + for (int i = 0; i < numberItems; i++) { + ItemBuilder.createItem(context, collection) + .withTitle("item " + i) + .build(); + } + context.restoreAuthSystemState(); + + // Build query with default parameters (except for workspaceConf) + QueryResponse result = null; + try { + result = solrSearchCore.getSolr().query(new SolrQuery(String.format( + "search.resourcetype:\"Item\""))); + } catch (SolrServerException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + assertEquals(result.getResults().size(), numberItems); + for (SolrDocument doc : result.getResults()) { + assertThat(doc.getFieldNames(), + not(hasItems("submitter_keyword", "submitter_ac", "submitter_acid", "submitter_filter"))); + assertThat(doc.getFieldNames(), hasItem("submitter_authority")); + } + } + + /** + * Test designed to check if the submitter is indexed in all in solr documents for items + * @throws SearchServiceException + */ + @Test + public void searchWithSubmitterTest() throws SearchServiceException { + + configurationService.setProperty("discovery.index.item.submitter.enabled", true); + DiscoveryConfiguration defaultConf = SearchUtils.getDiscoveryConfiguration(context, "default", null); + + // Populate the testing objects: create items in eperson's workspace and perform search in it + int numberItems = 10; + context.turnOffAuthorisationSystem(); + EPerson submitter = null; + try { + submitter = EPersonBuilder.createEPerson(context).withEmail("submitter@example.org") + .withNameInMetadata("Peter", "Funny").build(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + context.setCurrentUser(submitter); + Community community = CommunityBuilder.createCommunity(context).build(); + Collection collection = CollectionBuilder.createCollection(context, community).build(); + for (int i = 0; i < numberItems; i++) { + ItemBuilder.createItem(context, collection) + .withTitle("item " + i) + .build(); + } + context.restoreAuthSystemState(); + + // Build query with default parameters (except for workspaceConf) + QueryResponse result = null; + try { + result = solrSearchCore.getSolr().query(new SolrQuery(String.format( + "search.resourcetype:\"Item\""))); + } catch (SolrServerException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + assertEquals(result.getResults().size(), numberItems); + for (SolrDocument doc : result.getResults()) { + for (String fieldname : doc.getFieldNames()) { + assertThat(doc.getFieldNames(), hasItems("submitter_keyword","submitter_ac", "submitter_filter", + "submitter_authority")); + } + } + } + private void assertSearchQuery(String resourceType, int size) throws SearchServiceException { assertSearchQuery(resourceType, size, size, 0, -1); } diff --git a/dspace/config/modules/discovery.cfg b/dspace/config/modules/discovery.cfg index 72088ddc49f..cd8e8636c2e 100644 --- a/dspace/config/modules/discovery.cfg +++ b/dspace/config/modules/discovery.cfg @@ -24,6 +24,11 @@ discovery.search.server = ${solr.server}/${solr.multicorePrefix}search # discovery.index.ignore-authority = false discovery.index.projection=dc.title,dc.contributor.*,dc.date.issued +# Restricts the indexing of the submitter for archived items +# By default the submitter information from the corresponding eperson is not indexed. +# If you set this value to true, than the submitter information is indexed and you will need to reindex search core +# discovery.index.item.submitter.enabled = false + # Allow auto-reindexing. # If any database migrations are applied to your database (via Flyway), then a # reindex flag is always written to '[dspace]/solr/search/conf/reindex.flag'. @@ -48,4 +53,4 @@ discovery.facet.namedtype.workflow.pooled = 004workflow\n|||\nWaiting for Contro # Set the number of retry of a query when stale objects are found. # Set to -1 if stale objects should be ignored. Set to 0 if you want to avoid extra query but take the chance to cleanup # the index each time that stale objects are found. Default 3 -discovery.removestale.attempts = 3 \ No newline at end of file +discovery.removestale.attempts = 3 From e840ca27315ce8fb2772e8bcdf6c34c042a6cc99 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 28 Oct 2024 14:55:30 -0500 Subject: [PATCH 374/939] Create dependabot.yml --- .github/dependabot.yml | 115 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000000..6f4f3ae601a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,115 @@ +#------------------- +# DSpace's dependabot rules. Enables maven updates for all dependencies on a weekly basis +# for main and any maintenance branches. Security updates only apply to main. +#------------------- +version: 2 +updates: + - package-ecosystem: "maven" + directory: "/" + schedule: + interval: "weekly" + # Allow up to 10 open PRs for dependencies + open-pull-requests-limit: 10 + # Group together some upgrades in a single PR + groups: + # Group together all Build Tools in a single PR + build-tools: + applies-to: version-updates + patterns: + - "org.apache.maven.plugins:*" + - "*:*-maven-plugin" + - "*:maven-*-plugin" + - "com.github.spotbugs:spotbugs" + - "com.google.code.findbugs:*" + - "com.google.errorprone:*" + - "com.puppycrawl.tools:checkstyle" + - "org.sonatype.plugins:*" + update-types: + - "minor" + - "patch" + test-tools: + applies-to: version-updates + patterns: + - "junit:*" + - "com.github.stefanbirker:system-rules" + - "com.h2database:*" + - "io.findify:s3mock*" + - "io.netty:*" + - "org.hamcrest:*" + - "org.mock-server:*" + - "org.mockito:*" + update-types: + - "minor" + - "patch" + # Group together all Apache Commons deps in a single PR + apache-commons: + applies-to: version-updates + patterns: + - "org.apache.commons:*" + - "commons-*:commons-*" + update-types: + - "minor" + - "patch" + # Group together all fasterxml deps in a single PR + fasterxml: + applies-to: version-updates + patterns: + - "com.fasterxml:*" + - "com.fasterxml.*:*" + update-types: + - "minor" + - "patch" + # Group together all Hibernate deps in a single PR + hibernate: + applies-to: version-updates + patterns: + - "org.hibernate.*:*" + update-types: + - "minor" + - "patch" + # Group together all Jakarta deps in a single PR + jakarta: + applies-to: version-updates + patterns: + - "jakarta.*:*" + - "org.eclipse.angus:jakarta.mail" + - "org.glassfish.jaxb:jaxb-runtime" + update-types: + - "minor" + - "patch" + # Group together all Google deps in a single PR + google-apis: + applies-to: version-updates + patterns: + - "com.google.apis:*" + - "com.google.api-client:*" + - "com.google.http-client:*" + - "com.google.oauth-client:*" + update-types: + - "minor" + - "patch" + # Group together all Spring deps in a single PR + spring: + applies-to: version-updates + patterns: + - "org.springframework:*" + - "org.springframework.*:*" + update-types: + - "minor" + - "patch" + # Group together all WebJARs deps in a single PR + webjars: + applies-to: version-updates + patterns: + - "org.webjars:*" + - "org.webjars.*:*" + update-types: + - "minor" + - "patch" + ignore: + # Don't try to auto-update any DSpace dependencies + - dependency-name: "org.dspace:*" + - dependency-name: "org.dspace.*:*" + # Ignore all major version updates for all dependencies. We'll only automate minor/patch updates. + - dependency-name: "*" + update-types: ["version-update:semver-major"] From fe28962e9672c01477059bcc2d2c8787a781dadf Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 29 Oct 2024 15:01:41 -0500 Subject: [PATCH 375/939] Exclude spring from build-tools group in dependabot.yml --- .github/dependabot.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 6f4f3ae601a..b6412b25b66 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -24,6 +24,9 @@ updates: - "com.google.errorprone:*" - "com.puppycrawl.tools:checkstyle" - "org.sonatype.plugins:*" + exclude-patterns: + # Exclude anything from Spring, as that is in a separate group + - "org.springframework.*:*" update-types: - "minor" - "patch" From 0b46a0963c5cfe8189fe3b8764eb6450ec41664a Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Tue, 2 Apr 2024 11:15:49 +0200 Subject: [PATCH 376/939] 113811: cli logs should be written to a different file (cherry picked from commit d30468a09fbbd288df6cf2556e1a630f040801d1) --- dspace/bin/dspace | 2 + dspace/config/log4j2-cli.xml | 105 +++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 dspace/config/log4j2-cli.xml diff --git a/dspace/bin/dspace b/dspace/bin/dspace index 7d5d678d0f6..24644aae911 100644 --- a/dspace/bin/dspace +++ b/dspace/bin/dspace @@ -39,6 +39,8 @@ if [ "$JAVA_OPTS" = "" ]; then JAVA_OPTS="-Xmx256m -Dfile.encoding=UTF-8" fi +export JAVA_OPTS="$JAVA_OPTS -Dlog4j2.configurationFile=$DSPACEDIR/config/log4j2-cli.xml" + # Now invoke Java java $JAVA_OPTS \ -classpath $FULLPATH \ diff --git a/dspace/config/log4j2-cli.xml b/dspace/config/log4j2-cli.xml new file mode 100644 index 00000000000..24d36fd12ef --- /dev/null +++ b/dspace/config/log4j2-cli.xml @@ -0,0 +1,105 @@ + + + + + + + ${log4j:configParentLocation}/../log + + + INFO + + + INFO + + + + + + + + + yyyy-MM-dd + + + + + + + + + yyyy-MM-dd + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 04ec6381cba0472f4fdbc0918588180393a89c5d Mon Sep 17 00:00:00 2001 From: Kevin Van de Velde Date: Fri, 6 Sep 2024 10:15:08 +0200 Subject: [PATCH 377/939] Modifying it so that the cli file content is stored in a file using the date (cherry picked from commit 529c3a77c15f401fe623b2c9d04a438754a1d72d) --- dspace/config/log4j2-cli.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/dspace/config/log4j2-cli.xml b/dspace/config/log4j2-cli.xml index 24d36fd12ef..73acab877f1 100644 --- a/dspace/config/log4j2-cli.xml +++ b/dspace/config/log4j2-cli.xml @@ -25,8 +25,6 @@ From 1a6088388fa4bc0d01ecdea0b1fee7108a27bce5 Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Wed, 4 Dec 2024 11:08:37 +0100 Subject: [PATCH 378/939] apply fix to windows env and remove duplicate logging for checksum checker (cherry picked from commit 9f39a3d6a53f9a815509d6a47abd176b07a6a86c) --- dspace/bin/dspace.bat | 3 +++ dspace/config/log4j2.xml | 27 --------------------------- 2 files changed, 3 insertions(+), 27 deletions(-) diff --git a/dspace/bin/dspace.bat b/dspace/bin/dspace.bat index 2288127c447..768b1061762 100644 --- a/dspace/bin/dspace.bat +++ b/dspace/bin/dspace.bat @@ -38,6 +38,9 @@ REM If JAVA_OPTS specified, use those options REM Otherwise, default Java to using 256MB of memory if "%JAVA_OPTS%"=="" set "JAVA_OPTS=-Xmx256m -Dfile.encoding=UTF-8" +REM Add log4j2 configuration to JAVA_OPTS +set "JAVA_OPTS=%JAVA_OPTS% -Dlog4j2.configurationFile=%cd%\config\log4j2-cli.xml" + REM Execute Java java %JAVA_OPTS% -classpath "%DSPACE_CLASSPATH%" org.dspace.app.launcher.ScriptLauncher %* diff --git a/dspace/config/log4j2.xml b/dspace/config/log4j2.xml index 6e9a43e4f0f..4f212d128dc 100644 --- a/dspace/config/log4j2.xml +++ b/dspace/config/log4j2.xml @@ -44,26 +44,6 @@ --> - - - - - - yyyy-MM-dd - - - @@ -75,13 +55,6 @@ - - - - - From 1492ed7f61b5685e0e8e0f4365460887465bef88 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 21:01:16 +0000 Subject: [PATCH 379/939] Bump the build-tools group with 21 updates Bumps the build-tools group with 21 updates: | Package | From | To | | --- | --- | --- | | [com.google.errorprone:error_prone_core](https://github.com/google/error-prone) | `2.10.0` | `2.36.0` | | [com.puppycrawl.tools:checkstyle](https://github.com/checkstyle/checkstyle) | `8.38` | `8.45.1` | | [com.github.spotbugs:spotbugs](https://github.com/spotbugs/spotbugs) | `4.8.2` | `4.8.6` | | [org.apache.maven.plugins:maven-enforcer-plugin](https://github.com/apache/maven-enforcer) | `3.0.0-M3` | `3.5.0` | | [org.apache.maven.plugins:maven-compiler-plugin](https://github.com/apache/maven-compiler-plugin) | `3.8.1` | `3.13.0` | | [org.apache.maven.plugins:maven-jar-plugin](https://github.com/apache/maven-jar-plugin) | `3.2.0` | `3.4.2` | | [org.apache.maven.plugins:maven-war-plugin](https://github.com/apache/maven-war-plugin) | `3.2.3` | `3.4.0` | | [org.apache.maven.plugins:maven-checkstyle-plugin](https://github.com/apache/maven-checkstyle-plugin) | `3.3.1` | `3.6.0` | | [com.github.spotbugs:spotbugs-maven-plugin](https://github.com/spotbugs/spotbugs-maven-plugin) | `4.8.2.0` | `4.8.6.6` | | [org.apache.maven.plugins:maven-clean-plugin](https://github.com/apache/maven-clean-plugin) | `3.1.0` | `3.4.0` | | [org.apache.maven.plugins:maven-assembly-plugin](https://github.com/apache/maven-assembly-plugin) | `3.6.0` | `3.7.1` | | [org.apache.maven.plugins:maven-dependency-plugin](https://github.com/apache/maven-dependency-plugin) | `3.1.2` | `3.8.1` | | [org.apache.maven.plugins:maven-resources-plugin](https://github.com/apache/maven-resources-plugin) | `3.1.0` | `3.3.1` | | org.sonatype.plugins:nexus-staging-maven-plugin | `1.6.8` | `1.7.0` | | [org.apache.maven.plugins:maven-javadoc-plugin](https://github.com/apache/maven-javadoc-plugin) | `3.2.0` | `3.11.2` | | [org.apache.maven.plugins:maven-source-plugin](https://github.com/apache/maven-source-plugin) | `3.2.1` | `3.3.1` | | [org.jacoco:jacoco-maven-plugin](https://github.com/jacoco/jacoco) | `0.8.5` | `0.8.12` | | [org.codehaus.mojo:xml-maven-plugin](https://github.com/mojohaus/xml-maven-plugin) | `1.0.2` | `1.1.0` | | [org.codehaus.mojo:license-maven-plugin](https://github.com/mojohaus/license-maven-plugin) | `2.0.0` | `2.5.0` | | [org.codehaus.mojo:build-helper-maven-plugin](https://github.com/mojohaus/build-helper-maven-plugin) | `3.4.0` | `3.6.0` | | [org.codehaus.mojo:buildnumber-maven-plugin](https://github.com/mojohaus/buildnumber-maven-plugin) | `3.2.0` | `3.2.1` | Updates `com.google.errorprone:error_prone_core` from 2.10.0 to 2.36.0 - [Release notes](https://github.com/google/error-prone/releases) - [Commits](https://github.com/google/error-prone/compare/v2.10.0...v2.36.0) Updates `com.puppycrawl.tools:checkstyle` from 8.38 to 8.45.1 - [Release notes](https://github.com/checkstyle/checkstyle/releases) - [Commits](https://github.com/checkstyle/checkstyle/compare/checkstyle-8.38...checkstyle-8.45.1) Updates `com.github.spotbugs:spotbugs` from 4.8.2 to 4.8.6 - [Release notes](https://github.com/spotbugs/spotbugs/releases) - [Changelog](https://github.com/spotbugs/spotbugs/blob/master/CHANGELOG.md) - [Commits](https://github.com/spotbugs/spotbugs/compare/4.8.2...4.8.6) Updates `org.apache.maven.plugins:maven-enforcer-plugin` from 3.0.0-M3 to 3.5.0 - [Release notes](https://github.com/apache/maven-enforcer/releases) - [Commits](https://github.com/apache/maven-enforcer/compare/enforcer-3.0.0-M3...enforcer-3.5.0) Updates `org.apache.maven.plugins:maven-compiler-plugin` from 3.8.1 to 3.13.0 - [Release notes](https://github.com/apache/maven-compiler-plugin/releases) - [Commits](https://github.com/apache/maven-compiler-plugin/compare/maven-compiler-plugin-3.8.1...maven-compiler-plugin-3.13.0) Updates `org.apache.maven.plugins:maven-jar-plugin` from 3.2.0 to 3.4.2 - [Release notes](https://github.com/apache/maven-jar-plugin/releases) - [Commits](https://github.com/apache/maven-jar-plugin/compare/maven-jar-plugin-3.2.0...maven-jar-plugin-3.4.2) Updates `org.apache.maven.plugins:maven-war-plugin` from 3.2.3 to 3.4.0 - [Commits](https://github.com/apache/maven-war-plugin/compare/maven-war-plugin-3.2.3...maven-war-plugin-3.4.0) Updates `org.apache.maven.plugins:maven-checkstyle-plugin` from 3.3.1 to 3.6.0 - [Commits](https://github.com/apache/maven-checkstyle-plugin/compare/maven-checkstyle-plugin-3.3.1...maven-checkstyle-plugin-3.6.0) Updates `com.github.spotbugs:spotbugs-maven-plugin` from 4.8.2.0 to 4.8.6.6 - [Release notes](https://github.com/spotbugs/spotbugs-maven-plugin/releases) - [Commits](https://github.com/spotbugs/spotbugs-maven-plugin/compare/spotbugs-maven-plugin-4.8.2.0...spotbugs-maven-plugin-4.8.6.6) Updates `org.apache.maven.plugins:maven-clean-plugin` from 3.1.0 to 3.4.0 - [Release notes](https://github.com/apache/maven-clean-plugin/releases) - [Commits](https://github.com/apache/maven-clean-plugin/compare/maven-clean-plugin-3.1.0...maven-clean-plugin-3.4.0) Updates `org.apache.maven.plugins:maven-assembly-plugin` from 3.6.0 to 3.7.1 - [Release notes](https://github.com/apache/maven-assembly-plugin/releases) - [Commits](https://github.com/apache/maven-assembly-plugin/compare/maven-assembly-plugin-3.6.0...maven-assembly-plugin-3.7.1) Updates `org.apache.maven.plugins:maven-dependency-plugin` from 3.1.2 to 3.8.1 - [Release notes](https://github.com/apache/maven-dependency-plugin/releases) - [Commits](https://github.com/apache/maven-dependency-plugin/compare/maven-dependency-plugin-3.1.2...maven-dependency-plugin-3.8.1) Updates `org.apache.maven.plugins:maven-resources-plugin` from 3.1.0 to 3.3.1 - [Release notes](https://github.com/apache/maven-resources-plugin/releases) - [Commits](https://github.com/apache/maven-resources-plugin/compare/maven-resources-plugin-3.1.0...maven-resources-plugin-3.3.1) Updates `org.sonatype.plugins:nexus-staging-maven-plugin` from 1.6.8 to 1.7.0 Updates `org.apache.maven.plugins:maven-javadoc-plugin` from 3.2.0 to 3.11.2 - [Release notes](https://github.com/apache/maven-javadoc-plugin/releases) - [Commits](https://github.com/apache/maven-javadoc-plugin/compare/maven-javadoc-plugin-3.2.0...maven-javadoc-plugin-3.11.2) Updates `org.apache.maven.plugins:maven-source-plugin` from 3.2.1 to 3.3.1 - [Release notes](https://github.com/apache/maven-source-plugin/releases) - [Commits](https://github.com/apache/maven-source-plugin/compare/maven-source-plugin-3.2.1...maven-source-plugin-3.3.1) Updates `org.jacoco:jacoco-maven-plugin` from 0.8.5 to 0.8.12 - [Release notes](https://github.com/jacoco/jacoco/releases) - [Commits](https://github.com/jacoco/jacoco/compare/v0.8.5...v0.8.12) Updates `org.codehaus.mojo:xml-maven-plugin` from 1.0.2 to 1.1.0 - [Release notes](https://github.com/mojohaus/xml-maven-plugin/releases) - [Commits](https://github.com/mojohaus/xml-maven-plugin/compare/xml-maven-plugin-1.0.2...1.1.0) Updates `org.codehaus.mojo:license-maven-plugin` from 2.0.0 to 2.5.0 - [Release notes](https://github.com/mojohaus/license-maven-plugin/releases) - [Commits](https://github.com/mojohaus/license-maven-plugin/compare/license-maven-plugin-2.0.0...2.5.0) Updates `org.codehaus.mojo:build-helper-maven-plugin` from 3.4.0 to 3.6.0 - [Release notes](https://github.com/mojohaus/build-helper-maven-plugin/releases) - [Commits](https://github.com/mojohaus/build-helper-maven-plugin/compare/3.4.0...3.6.0) Updates `org.codehaus.mojo:buildnumber-maven-plugin` from 3.2.0 to 3.2.1 - [Release notes](https://github.com/mojohaus/buildnumber-maven-plugin/releases) - [Commits](https://github.com/mojohaus/buildnumber-maven-plugin/compare/3.2.0...3.2.1) --- updated-dependencies: - dependency-name: com.google.errorprone:error_prone_core dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools - dependency-name: com.puppycrawl.tools:checkstyle dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools - dependency-name: com.github.spotbugs:spotbugs dependency-type: direct:production update-type: version-update:semver-patch dependency-group: build-tools - dependency-name: org.apache.maven.plugins:maven-enforcer-plugin dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools - dependency-name: org.apache.maven.plugins:maven-compiler-plugin dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools - dependency-name: org.apache.maven.plugins:maven-jar-plugin dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools - dependency-name: org.apache.maven.plugins:maven-war-plugin dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools - dependency-name: org.apache.maven.plugins:maven-checkstyle-plugin dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools - dependency-name: com.github.spotbugs:spotbugs-maven-plugin dependency-type: direct:production update-type: version-update:semver-patch dependency-group: build-tools - dependency-name: org.apache.maven.plugins:maven-clean-plugin dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools - dependency-name: org.apache.maven.plugins:maven-assembly-plugin dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools - dependency-name: org.apache.maven.plugins:maven-dependency-plugin dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools - dependency-name: org.apache.maven.plugins:maven-resources-plugin dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools - dependency-name: org.sonatype.plugins:nexus-staging-maven-plugin dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools - dependency-name: org.apache.maven.plugins:maven-javadoc-plugin dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools - dependency-name: org.apache.maven.plugins:maven-source-plugin dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools - dependency-name: org.jacoco:jacoco-maven-plugin dependency-type: direct:production update-type: version-update:semver-patch dependency-group: build-tools - dependency-name: org.codehaus.mojo:xml-maven-plugin dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools - dependency-name: org.codehaus.mojo:license-maven-plugin dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools - dependency-name: org.codehaus.mojo:build-helper-maven-plugin dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools - dependency-name: org.codehaus.mojo:buildnumber-maven-plugin dependency-type: direct:production update-type: version-update:semver-patch dependency-group: build-tools ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 4 ++-- pom.xml | 38 +++++++++++++++++++------------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index c42895ab4c4..cf81408a265 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -102,7 +102,7 @@ org.codehaus.mojo build-helper-maven-plugin - 3.4.0 + 3.6.0 validate @@ -116,7 +116,7 @@ org.codehaus.mojo buildnumber-maven-plugin - 3.2.0 + 3.2.1 UNKNOWN_REVISION diff --git a/pom.xml b/pom.xml index 50faf1f7372..f496ed7e879 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ 8.11.3 3.10.8 - 2.10.0 + 2.36.0 2.16.0 2.16.0 @@ -89,7 +89,7 @@ org.apache.maven.plugins maven-enforcer-plugin - 3.0.0-M3 + 3.5.0 enforce-java @@ -140,7 +140,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.13.0 11 @@ -173,7 +173,7 @@ org.apache.maven.plugins maven-jar-plugin - 3.2.0 + 3.4.2 @@ -187,7 +187,7 @@ org.apache.maven.plugins maven-war-plugin - 3.2.3 + 3.4.0 false @@ -257,7 +257,7 @@ org.apache.maven.plugins maven-checkstyle-plugin - 3.3.1 + 3.6.0 verify-style @@ -287,14 +287,14 @@ com.puppycrawl.tools checkstyle - 8.38 + 8.45.1 com.github.spotbugs spotbugs-maven-plugin - 4.8.2.0 + 4.8.6.6 Max Low @@ -304,7 +304,7 @@ com.github.spotbugs spotbugs - 4.8.2 + 4.8.6 @@ -320,7 +320,7 @@ maven-clean-plugin - 3.1.0 + 3.4.0 @@ -334,17 +334,17 @@ maven-assembly-plugin - 3.6.0 + 3.7.1 org.apache.maven.plugins maven-dependency-plugin - 3.1.2 + 3.8.1 org.apache.maven.plugins maven-resources-plugin - 3.1.0 + 3.3.1 @@ -356,13 +356,13 @@ org.sonatype.plugins nexus-staging-maven-plugin - 1.6.8 + 1.7.0 org.apache.maven.plugins maven-javadoc-plugin - 3.2.0 + 3.11.2 false @@ -372,7 +372,7 @@ org.apache.maven.plugins maven-source-plugin - 3.2.1 + 3.3.1 @@ -391,7 +391,7 @@ org.jacoco jacoco-maven-plugin - 0.8.5 + 0.8.12 @@ -471,7 +471,7 @@ org.codehaus.mojo xml-maven-plugin - 1.0.2 + 1.1.0 validate-ALL-xml-and-xsl @@ -679,7 +679,7 @@ org.codehaus.mojo license-maven-plugin - 2.0.0 + 2.5.0 false From d7b0e26179f9b0812afb722fd13efa00eae4fa57 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 21:02:16 +0000 Subject: [PATCH 380/939] Bump the apache-commons group with 11 updates Bumps the apache-commons group with 11 updates: | Package | From | To | | --- | --- | --- | | commons-beanutils:commons-beanutils | `1.9.4` | `1.10.0` | | commons-cli:commons-cli | `1.6.0` | `1.9.0` | | [commons-codec:commons-codec](https://github.com/apache/commons-codec) | `1.16.0` | `1.17.2` | | org.apache.commons:commons-configuration2 | `2.10.1` | `2.11.0` | | org.apache.commons:commons-dbcp2 | `2.11.0` | `2.13.0` | | commons-io:commons-io | `2.15.1` | `2.18.0` | | org.apache.commons:commons-lang3 | `3.14.0` | `3.17.0` | | commons-logging:commons-logging | `1.3.0` | `1.3.4` | | org.apache.commons:commons-compress | `1.26.0` | `1.27.1` | | org.apache.commons:commons-text | `1.10.0` | `1.13.0` | | commons-validator:commons-validator | `1.7` | `1.9.0` | Updates `commons-beanutils:commons-beanutils` from 1.9.4 to 1.10.0 Updates `commons-cli:commons-cli` from 1.6.0 to 1.9.0 Updates `commons-codec:commons-codec` from 1.16.0 to 1.17.2 - [Changelog](https://github.com/apache/commons-codec/blob/master/RELEASE-NOTES.txt) - [Commits](https://github.com/apache/commons-codec/compare/rel/commons-codec-1.16.0...rel/commons-codec-1.17.2) Updates `org.apache.commons:commons-configuration2` from 2.10.1 to 2.11.0 Updates `org.apache.commons:commons-dbcp2` from 2.11.0 to 2.13.0 Updates `commons-io:commons-io` from 2.15.1 to 2.18.0 Updates `org.apache.commons:commons-lang3` from 3.14.0 to 3.17.0 Updates `commons-logging:commons-logging` from 1.3.0 to 1.3.4 Updates `org.apache.commons:commons-compress` from 1.26.0 to 1.27.1 Updates `org.apache.commons:commons-text` from 1.10.0 to 1.13.0 Updates `commons-validator:commons-validator` from 1.7 to 1.9.0 --- updated-dependencies: - dependency-name: commons-beanutils:commons-beanutils dependency-type: direct:production update-type: version-update:semver-minor dependency-group: apache-commons - dependency-name: commons-cli:commons-cli dependency-type: direct:production update-type: version-update:semver-minor dependency-group: apache-commons - dependency-name: commons-codec:commons-codec dependency-type: direct:production update-type: version-update:semver-minor dependency-group: apache-commons - dependency-name: org.apache.commons:commons-configuration2 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: apache-commons - dependency-name: org.apache.commons:commons-dbcp2 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: apache-commons - dependency-name: commons-io:commons-io dependency-type: direct:production update-type: version-update:semver-minor dependency-group: apache-commons - dependency-name: org.apache.commons:commons-lang3 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: apache-commons - dependency-name: commons-logging:commons-logging dependency-type: direct:production update-type: version-update:semver-patch dependency-group: apache-commons - dependency-name: org.apache.commons:commons-compress dependency-type: direct:production update-type: version-update:semver-minor dependency-group: apache-commons - dependency-name: org.apache.commons:commons-text dependency-type: direct:production update-type: version-update:semver-minor dependency-group: apache-commons - dependency-name: commons-validator:commons-validator dependency-type: direct:production update-type: version-update:semver-minor dependency-group: apache-commons ... Signed-off-by: dependabot[bot] --- pom.xml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index 50faf1f7372..242c03d1b17 100644 --- a/pom.xml +++ b/pom.xml @@ -1463,17 +1463,17 @@ commons-beanutils commons-beanutils - 1.9.4 + 1.10.0 commons-cli commons-cli - 1.6.0 + 1.9.0 commons-codec commons-codec - 1.16.0 + 1.17.2 org.apache.commons @@ -1483,12 +1483,12 @@ org.apache.commons commons-configuration2 - 2.10.1 + 2.11.0 org.apache.commons commons-dbcp2 - 2.11.0 + 2.13.0 commons-fileupload @@ -1498,24 +1498,24 @@ commons-io commons-io - 2.15.1 + 2.18.0 org.apache.commons commons-lang3 - 3.14.0 + 3.17.0 commons-logging commons-logging - 1.3.0 + 1.3.4 org.apache.commons commons-compress - 1.26.0 + 1.27.1 org.apache.commons @@ -1525,12 +1525,12 @@ org.apache.commons commons-text - 1.10.0 + 1.13.0 commons-validator commons-validator - 1.7 + 1.9.0 joda-time From 4a20a4c37fa43c33eb6ba11ea49422e15e786212 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 21:02:28 +0000 Subject: [PATCH 381/939] Bump the fasterxml group with 4 updates Bumps the fasterxml group with 4 updates: [com.fasterxml:classmate](https://github.com/FasterXML/java-classmate), [com.fasterxml.jackson.core:jackson-annotations](https://github.com/FasterXML/jackson), [com.fasterxml.jackson.core:jackson-core](https://github.com/FasterXML/jackson-core) and [com.fasterxml.jackson.core:jackson-databind](https://github.com/FasterXML/jackson). Updates `com.fasterxml:classmate` from 1.6.0 to 1.7.0 - [Commits](https://github.com/FasterXML/java-classmate/compare/classmate-1.6.0...classmate-1.7.0) Updates `com.fasterxml.jackson.core:jackson-annotations` from 2.16.0 to 2.18.2 - [Commits](https://github.com/FasterXML/jackson/commits) Updates `com.fasterxml.jackson.core:jackson-core` from 2.16.0 to 2.18.2 - [Commits](https://github.com/FasterXML/jackson-core/compare/jackson-core-2.16.0...jackson-core-2.18.2) Updates `com.fasterxml.jackson.core:jackson-core` from 2.16.0 to 2.18.2 - [Commits](https://github.com/FasterXML/jackson-core/compare/jackson-core-2.16.0...jackson-core-2.18.2) Updates `com.fasterxml.jackson.core:jackson-databind` from 2.16.0 to 2.18.2 - [Commits](https://github.com/FasterXML/jackson/commits) --- updated-dependencies: - dependency-name: com.fasterxml:classmate dependency-type: direct:production update-type: version-update:semver-minor dependency-group: fasterxml - dependency-name: com.fasterxml.jackson.core:jackson-annotations dependency-type: direct:production update-type: version-update:semver-minor dependency-group: fasterxml - dependency-name: com.fasterxml.jackson.core:jackson-core dependency-type: direct:production update-type: version-update:semver-minor dependency-group: fasterxml - dependency-name: com.fasterxml.jackson.core:jackson-core dependency-type: direct:production update-type: version-update:semver-minor dependency-group: fasterxml - dependency-name: com.fasterxml.jackson.core:jackson-databind dependency-type: direct:production update-type: version-update:semver-minor dependency-group: fasterxml ... Signed-off-by: dependabot[bot] --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 50faf1f7372..a7a87a7938f 100644 --- a/pom.xml +++ b/pom.xml @@ -30,8 +30,8 @@ 3.10.8 2.10.0 - 2.16.0 - 2.16.0 + 2.18.2 + 2.18.2 1.3.2 2.3.1 2.3.9 @@ -1742,7 +1742,7 @@ com.fasterxml classmate - 1.6.0 + 1.7.0 com.fasterxml.jackson.core From bec1ee84e51e6a22766c58348791292fe9d6f36d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 21:03:40 +0000 Subject: [PATCH 382/939] Bump org.webjars.npm:json-editor__json-editor in the webjars group Bumps the webjars group with 1 update: [org.webjars.npm:json-editor__json-editor](https://github.com/json-editor/json-editor). Updates `org.webjars.npm:json-editor__json-editor` from 2.6.1 to 2.15.1 - [Changelog](https://github.com/json-editor/json-editor/blob/master/CHANGELOG.md) - [Commits](https://github.com/json-editor/json-editor/compare/2.6.1...2.15.1) --- updated-dependencies: - dependency-name: org.webjars.npm:json-editor__json-editor dependency-type: direct:production update-type: version-update:semver-minor dependency-group: webjars ... Signed-off-by: dependabot[bot] --- dspace-server-webapp/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 35fa473fc17..2715338ed0f 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -345,7 +345,7 @@ org.webjars.npm json-editor__json-editor - 2.6.1 + 2.15.1 From d9945b27b94f372979ac3268d2c85887edb00fa3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 21:03:55 +0000 Subject: [PATCH 384/939] Bump jersey.version from 2.39.1 to 2.46 Bumps `jersey.version` from 2.39.1 to 2.46. Updates `org.glassfish.jersey.core:jersey-client` from 2.39.1 to 2.46 Updates `org.glassfish.jersey.inject:jersey-hk2` from 2.39.1 to 2.46 Updates `org.glassfish.jersey.core:jersey-server` from 2.39.1 to 2.46 Updates `org.glassfish.jersey.containers:jersey-container-servlet` from 2.39.1 to 2.46 Updates `org.glassfish.jersey.media:jersey-media-json-jackson` from 2.39.1 to 2.46 Updates `org.glassfish.jersey.media:jersey-media-jaxb` from 2.39.1 to 2.46 Updates `org.glassfish.jersey.ext:jersey-spring5` from 2.39.1 to 2.46 --- updated-dependencies: - dependency-name: org.glassfish.jersey.core:jersey-client dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.glassfish.jersey.inject:jersey-hk2 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.glassfish.jersey.core:jersey-server dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.glassfish.jersey.containers:jersey-container-servlet dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.glassfish.jersey.media:jersey-media-json-jackson dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.glassfish.jersey.media:jersey-media-jaxb dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.glassfish.jersey.ext:jersey-spring5 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 50faf1f7372..1e83cbbb9bb 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,7 @@ https://jena.apache.org/documentation/migrate_jena2_jena3.html --> 2.13.0 - 2.39.1 + 2.46 UTF-8 From 9d17009cf390ea403e439bc5287bf6c118c88836 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 21:04:17 +0000 Subject: [PATCH 385/939] Bump jetty.version from 9.4.54.v20240208 to 9.4.57.v20241219 Bumps `jetty.version` from 9.4.54.v20240208 to 9.4.57.v20241219. Updates `org.eclipse.jetty:jetty-server` from 9.4.54.v20240208 to 9.4.57.v20241219 Updates `org.eclipse.jetty:jetty-alpn-java-server` from 9.4.54.v20240208 to 9.4.57.v20241219 Updates `org.eclipse.jetty:jetty-deploy` from 9.4.54.v20240208 to 9.4.57.v20241219 Updates `org.eclipse.jetty:jetty-http` from 9.4.54.v20240208 to 9.4.57.v20241219 Updates `org.eclipse.jetty:jetty-io` from 9.4.54.v20240208 to 9.4.57.v20241219 Updates `org.eclipse.jetty:jetty-servlet` from 9.4.54.v20240208 to 9.4.57.v20241219 Updates `org.eclipse.jetty:jetty-servlets` from 9.4.54.v20240208 to 9.4.57.v20241219 Updates `org.eclipse.jetty:jetty-util` from 9.4.54.v20240208 to 9.4.57.v20241219 Updates `org.eclipse.jetty:jetty-webapp` from 9.4.54.v20240208 to 9.4.57.v20241219 Updates `org.eclipse.jetty:jetty-xml` from 9.4.54.v20240208 to 9.4.57.v20241219 Updates `org.eclipse.jetty.http2:http2-common` from 9.4.54.v20240208 to 9.4.57.v20241219 Updates `org.eclipse.jetty.http2:http2-server` from 9.4.54.v20240208 to 9.4.57.v20241219 --- updated-dependencies: - dependency-name: org.eclipse.jetty:jetty-server dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.jetty:jetty-alpn-java-server dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.jetty:jetty-deploy dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.jetty:jetty-http dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.jetty:jetty-io dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.jetty:jetty-servlet dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.jetty:jetty-servlets dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.jetty:jetty-util dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.jetty:jetty-webapp dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.jetty:jetty-xml dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.jetty.http2:http2-common dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.jetty.http2:http2-server dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 50faf1f7372..6575c7e5277 100644 --- a/pom.xml +++ b/pom.xml @@ -37,7 +37,7 @@ 2.3.9 1.1.1 - 9.4.54.v20240208 + 9.4.57.v20241219 2.23.1 2.0.31 1.19.0 From a01983c23027642163f7a8f88525e5638784f428 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 29 Oct 2024 17:02:14 -0500 Subject: [PATCH 386/939] Fix checkstyle.xml syntax for bump to 8.45.1 --- checkstyle.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/checkstyle.xml b/checkstyle.xml index e0fa808d83c..a33fc483195 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -92,7 +92,7 @@ For more information on CheckStyle configurations below, see: http://checkstyle. - + From eee743a72d779d7af940a0ecf83393f26c2b4de2 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 29 Oct 2024 17:02:46 -0500 Subject: [PATCH 387/939] Minor checkstyle fixes after bump to 8.45.1. All are indentation / spacing fixes which are more strict now. --- .../rest/AuthenticationRestController.java | 2 +- .../app/rest/model/AuthorizationRest.java | 6 +-- .../dspace/app/rest/model/BitstreamRest.java | 15 ++----- .../app/rest/model/BrowseIndexRest.java | 10 +---- .../org/dspace/app/rest/model/BundleRest.java | 15 ++----- .../app/rest/model/ClaimedTaskRest.java | 5 +-- .../dspace/app/rest/model/CollectionRest.java | 40 ++++------------- .../dspace/app/rest/model/CommunityRest.java | 25 +++-------- .../dspace/app/rest/model/EPersonRest.java | 5 +-- .../dspace/app/rest/model/EntityTypeRest.java | 5 +-- .../app/rest/model/ExternalSourceRest.java | 5 +-- .../org/dspace/app/rest/model/GroupRest.java | 15 ++----- .../org/dspace/app/rest/model/ItemRest.java | 45 ++++--------------- .../app/rest/model/OrcidHistoryRest.java | 2 +- .../dspace/app/rest/model/PoolTaskRest.java | 5 +-- .../dspace/app/rest/model/ProcessRest.java | 15 ++----- .../app/rest/model/RelationshipRest.java | 5 +-- .../app/rest/model/ResearcherProfileRest.java | 4 +- .../app/rest/model/VersionHistoryRest.java | 10 +---- .../dspace/app/rest/model/VersionRest.java | 10 +---- .../model/VocabularyEntryDetailsRest.java | 6 +-- .../dspace/app/rest/model/VocabularyRest.java | 4 +- .../rest/model/WorkflowDefinitionRest.java | 10 +---- .../app/rest/model/WorkflowItemRest.java | 20 ++------- .../app/rest/model/WorkflowStepRest.java | 5 +-- .../app/rest/model/WorkspaceItemRest.java | 20 ++------- .../org/dspace/app/rest/utils/RegexUtils.java | 2 +- .../app/rest/matcher/RegistrationMatcher.java | 2 +- 28 files changed, 70 insertions(+), 243 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/AuthenticationRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/AuthenticationRestController.java index e8b8eb8e70d..2fbc116843c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/AuthenticationRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/AuthenticationRestController.java @@ -224,7 +224,7 @@ private AuthenticationTokenResource shortLivedTokenResponse(HttpServletRequest r * @return ResponseEntity */ @RequestMapping(value = "/login", method = { RequestMethod.GET, RequestMethod.PUT, RequestMethod.PATCH, - RequestMethod.DELETE }) + RequestMethod.DELETE }) public ResponseEntity login() { return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED).body("Only POST is allowed for login requests."); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorizationRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorizationRest.java index 95f28883130..7aec2cb4b76 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorizationRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorizationRest.java @@ -18,9 +18,9 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ @LinksRest(links = { - @LinkRest(method = "getEperson", name = AuthorizationRest.EPERSON), - @LinkRest(method = "getFeature", name = AuthorizationRest.FEATURE), - @LinkRest(method = "getObject", name = AuthorizationRest.OBJECT) + @LinkRest(method = "getEperson", name = AuthorizationRest.EPERSON), + @LinkRest(method = "getFeature", name = AuthorizationRest.FEATURE), + @LinkRest(method = "getObject", name = AuthorizationRest.OBJECT) }) public class AuthorizationRest extends BaseObjectRest { public static final String NAME = "authorization"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BitstreamRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BitstreamRest.java index 8e9efc2680b..77b558c0faa 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BitstreamRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BitstreamRest.java @@ -16,18 +16,9 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ @LinksRest(links = { - @LinkRest( - name = BitstreamRest.BUNDLE, - method = "getBundle" - ), - @LinkRest( - name = BitstreamRest.FORMAT, - method = "getFormat" - ), - @LinkRest( - name = BitstreamRest.THUMBNAIL, - method = "getThumbnail" - ) + @LinkRest(name = BitstreamRest.BUNDLE, method = "getBundle"), + @LinkRest(name = BitstreamRest.FORMAT, method = "getFormat"), + @LinkRest(name = BitstreamRest.THUMBNAIL, method = "getThumbnail") }) public class BitstreamRest extends DSpaceObjectRest { public static final String PLURAL_NAME = "bitstreams"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BrowseIndexRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BrowseIndexRest.java index f7978f00fdf..176b268e4be 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BrowseIndexRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BrowseIndexRest.java @@ -20,14 +20,8 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ @LinksRest(links = { - @LinkRest( - name = BrowseIndexRest.LINK_ITEMS, - method = "listBrowseItems" - ), - @LinkRest( - name = BrowseIndexRest.LINK_ENTRIES, - method = "listBrowseEntries" - ) + @LinkRest(name = BrowseIndexRest.LINK_ITEMS, method = "listBrowseItems"), + @LinkRest(name = BrowseIndexRest.LINK_ENTRIES, method = "listBrowseEntries") }) public class BrowseIndexRest extends BaseObjectRest { private static final long serialVersionUID = -4870333170249999559L; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BundleRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BundleRest.java index dd4a80d488a..7ef1ceed92c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BundleRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BundleRest.java @@ -16,18 +16,9 @@ * @author Jelle Pelgrims (jelle.pelgrims at atmire.com) */ @LinksRest(links = { - @LinkRest( - name = BundleRest.ITEM, - method = "getItem" - ), - @LinkRest( - name = BundleRest.BITSTREAMS, - method = "getBitstreams" - ), - @LinkRest( - name = BundleRest.PRIMARY_BITSTREAM, - method = "getPrimaryBitstream" - ) + @LinkRest(name = BundleRest.ITEM, method = "getItem"), + @LinkRest(name = BundleRest.BITSTREAMS, method = "getBitstreams"), + @LinkRest(name = BundleRest.PRIMARY_BITSTREAM, method = "getPrimaryBitstream") }) public class BundleRest extends DSpaceObjectRest { public static final String NAME = "bundle"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ClaimedTaskRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ClaimedTaskRest.java index dd97fef44d0..d9c90769757 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ClaimedTaskRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ClaimedTaskRest.java @@ -16,10 +16,7 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ @LinksRest(links = { - @LinkRest( - name = ClaimedTaskRest.STEP, - method = "getStep" - ) + @LinkRest(name = ClaimedTaskRest.STEP, method = "getStep") }) public class ClaimedTaskRest extends BaseObjectRest { public static final String NAME = "claimedtask"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CollectionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CollectionRest.java index 3f5ae3bb34c..28ff9bcceb6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CollectionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CollectionRest.java @@ -15,38 +15,14 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ @LinksRest(links = { - @LinkRest( - name = CollectionRest.LICENSE, - method = "getLicense" - ), - @LinkRest( - name = CollectionRest.LOGO, - method = "getLogo" - ), - @LinkRest( - name = CollectionRest.MAPPED_ITEMS, - method = "getMappedItems" - ), - @LinkRest( - name = CollectionRest.PARENT_COMMUNITY, - method = "getParentCommunity" - ), - @LinkRest( - name = CollectionRest.ADMIN_GROUP, - method = "getAdminGroup" - ), - @LinkRest( - name = CollectionRest.SUBMITTERS_GROUP, - method = "getSubmittersGroup" - ), - @LinkRest( - name = CollectionRest.ITEM_READ_GROUP, - method = "getItemReadGroup" - ), - @LinkRest( - name = CollectionRest.BITSTREAM_READ_GROUP, - method = "getBitstreamReadGroup" - ), + @LinkRest(name = CollectionRest.LICENSE, method = "getLicense"), + @LinkRest(name = CollectionRest.LOGO, method = "getLogo"), + @LinkRest(name = CollectionRest.MAPPED_ITEMS, method = "getMappedItems"), + @LinkRest(name = CollectionRest.PARENT_COMMUNITY, method = "getParentCommunity"), + @LinkRest(name = CollectionRest.ADMIN_GROUP, method = "getAdminGroup"), + @LinkRest(name = CollectionRest.SUBMITTERS_GROUP, method = "getSubmittersGroup"), + @LinkRest(name = CollectionRest.ITEM_READ_GROUP, method = "getItemReadGroup"), + @LinkRest(name = CollectionRest.BITSTREAM_READ_GROUP, method = "getBitstreamReadGroup"), }) public class CollectionRest extends DSpaceObjectRest { public static final String NAME = "collection"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CommunityRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CommunityRest.java index 86dc4b2c390..08758ef9cc2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CommunityRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CommunityRest.java @@ -15,26 +15,11 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ @LinksRest(links = { - @LinkRest( - name = CommunityRest.COLLECTIONS, - method = "getCollections" - ), - @LinkRest( - name = CommunityRest.LOGO, - method = "getLogo" - ), - @LinkRest( - name = CommunityRest.SUBCOMMUNITIES, - method = "getSubcommunities" - ), - @LinkRest( - name = CommunityRest.PARENT_COMMUNITY, - method = "getParentCommunity" - ), - @LinkRest( - name = CommunityRest.ADMIN_GROUP, - method = "getAdminGroup" - ) + @LinkRest(name = CommunityRest.COLLECTIONS, method = "getCollections"), + @LinkRest(name = CommunityRest.LOGO, method = "getLogo"), + @LinkRest(name = CommunityRest.SUBCOMMUNITIES, method = "getSubcommunities"), + @LinkRest(name = CommunityRest.PARENT_COMMUNITY, method = "getParentCommunity"), + @LinkRest(name = CommunityRest.ADMIN_GROUP, method = "getAdminGroup") }) public class CommunityRest extends DSpaceObjectRest { public static final String NAME = "community"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EPersonRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EPersonRest.java index 7b4c683322a..0c4f4d7b290 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EPersonRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EPersonRest.java @@ -20,10 +20,7 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ @LinksRest(links = { - @LinkRest( - name = EPersonRest.GROUPS, - method = "getGroups" - ) + @LinkRest(name = EPersonRest.GROUPS, method = "getGroups") }) public class EPersonRest extends DSpaceObjectRest { public static final String NAME = "eperson"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EntityTypeRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EntityTypeRest.java index f777b3a29c1..f62ef2767ea 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EntityTypeRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EntityTypeRest.java @@ -15,10 +15,7 @@ * Refer to {@link org.dspace.content.EntityType} for explanation of the properties */ @LinksRest(links = { - @LinkRest( - name = EntityTypeRest.RELATION_SHIP_TYPES, - method = "getEntityTypeRelationship" - ) + @LinkRest(name = EntityTypeRest.RELATION_SHIP_TYPES, method = "getEntityTypeRelationship") }) public class EntityTypeRest extends BaseObjectRest { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ExternalSourceRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ExternalSourceRest.java index 639c2cf72e2..2cd3712bdd5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ExternalSourceRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ExternalSourceRest.java @@ -13,10 +13,7 @@ * This class serves as a REST representation for an External Source */ @LinksRest(links = { - @LinkRest( - name = ExternalSourceRest.ENTITY_TYPES, - method = "getSupportedEntityTypes" - ) + @LinkRest(name = ExternalSourceRest.ENTITY_TYPES, method = "getSupportedEntityTypes") }) public class ExternalSourceRest extends BaseObjectRest { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/GroupRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/GroupRest.java index 3f60b2d61fd..332f5a5055f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/GroupRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/GroupRest.java @@ -18,18 +18,9 @@ */ @JsonIgnoreProperties(ignoreUnknown = true) @LinksRest(links = { - @LinkRest( - name = GroupRest.SUBGROUPS, - method = "getGroups" - ), - @LinkRest( - name = GroupRest.EPERSONS, - method = "getMembers" - ), - @LinkRest( - name = GroupRest.OBJECT, - method = "getParentObject" - ) + @LinkRest(name = GroupRest.SUBGROUPS, method = "getGroups"), + @LinkRest(name = GroupRest.EPERSONS, method = "getMembers"), + @LinkRest(name = GroupRest.OBJECT, method = "getParentObject") }) public class GroupRest extends DSpaceObjectRest { public static final String NAME = "group"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java index 1254ef8f937..bd0530be5dc 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java @@ -17,42 +17,15 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ @LinksRest(links = { - @LinkRest( - name = ItemRest.ACCESS_STATUS, - method = "getAccessStatus" - ), - @LinkRest( - name = ItemRest.BUNDLES, - method = "getBundles" - ), - @LinkRest( - name = ItemRest.IDENTIFIERS, - method = "getIdentifiers" - ), - @LinkRest( - name = ItemRest.MAPPED_COLLECTIONS, - method = "getMappedCollections" - ), - @LinkRest( - name = ItemRest.OWNING_COLLECTION, - method = "getOwningCollection" - ), - @LinkRest( - name = ItemRest.RELATIONSHIPS, - method = "getRelationships" - ), - @LinkRest( - name = ItemRest.VERSION, - method = "getItemVersion" - ), - @LinkRest( - name = ItemRest.TEMPLATE_ITEM_OF, - method = "getTemplateItemOf" - ), - @LinkRest( - name = ItemRest.THUMBNAIL, - method = "getThumbnail" - ) + @LinkRest(name = ItemRest.ACCESS_STATUS, method = "getAccessStatus"), + @LinkRest(name = ItemRest.BUNDLES, method = "getBundles"), + @LinkRest(name = ItemRest.IDENTIFIERS, method = "getIdentifiers"), + @LinkRest(name = ItemRest.MAPPED_COLLECTIONS, method = "getMappedCollections"), + @LinkRest(name = ItemRest.OWNING_COLLECTION, method = "getOwningCollection"), + @LinkRest(name = ItemRest.RELATIONSHIPS, method = "getRelationships"), + @LinkRest(name = ItemRest.VERSION, method = "getItemVersion"), + @LinkRest(name = ItemRest.TEMPLATE_ITEM_OF, method = "getTemplateItemOf"), + @LinkRest(name = ItemRest.THUMBNAIL, method = "getThumbnail") }) public class ItemRest extends DSpaceObjectRest { public static final String NAME = "item"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OrcidHistoryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OrcidHistoryRest.java index 02e0f470620..abfc0a31dc6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OrcidHistoryRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/OrcidHistoryRest.java @@ -38,7 +38,7 @@ public class OrcidHistoryRest extends BaseObjectRest { private String responseMessage; - public OrcidHistoryRest(){} + public OrcidHistoryRest() {} @Override @JsonProperty(access = JsonProperty.Access.READ_ONLY) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PoolTaskRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PoolTaskRest.java index c32c2c95783..320558f9467 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PoolTaskRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PoolTaskRest.java @@ -17,10 +17,7 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ @LinksRest(links = { - @LinkRest( - name = PoolTaskRest.STEP, - method = "getStep" - ) + @LinkRest(name = PoolTaskRest.STEP, method = "getStep") }) public class PoolTaskRest extends BaseObjectRest { public static final String NAME = "pooltask"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ProcessRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ProcessRest.java index 8216e161719..f7d8088d8a3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ProcessRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ProcessRest.java @@ -21,18 +21,9 @@ * This class serves as a REST representation for the {@link Process} class */ @LinksRest(links = { - @LinkRest( - name = ProcessRest.FILES, - method = "getFilesFromProcess" - ), - @LinkRest( - name = ProcessRest.FILE_TYPES, - method = "getFileTypesFromProcess" - ), - @LinkRest( - name = ProcessRest.OUTPUT, - method = "getOutputFromProcess" - ) + @LinkRest(name = ProcessRest.FILES, method = "getFilesFromProcess"), + @LinkRest(name = ProcessRest.FILE_TYPES, method = "getFileTypesFromProcess"), + @LinkRest(name = ProcessRest.OUTPUT, method = "getOutputFromProcess") }) public class ProcessRest extends BaseObjectRest { public static final String NAME = "process"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipRest.java index dd35a0726e9..d4050cda9a3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipRest.java @@ -19,10 +19,7 @@ * Refer to {@link org.dspace.content.Relationship} for explanation about the properties */ @LinksRest(links = { - @LinkRest( - name = RelationshipRest.RELATIONSHIP_TYPE, - method = "getRelationshipType" - ) + @LinkRest(name = RelationshipRest.RELATIONSHIP_TYPE, method = "getRelationshipType") }) public class RelationshipRest extends BaseObjectRest { public static final String NAME = "relationship"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResearcherProfileRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResearcherProfileRest.java index 4224cfeeb92..1ebde12e854 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResearcherProfileRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResearcherProfileRest.java @@ -20,8 +20,8 @@ * */ @LinksRest(links = { - @LinkRest(name = ResearcherProfileRest.ITEM, method = "getItem"), - @LinkRest(name = ResearcherProfileRest.EPERSON, method = "getEPerson") + @LinkRest(name = ResearcherProfileRest.ITEM, method = "getItem"), + @LinkRest(name = ResearcherProfileRest.EPERSON, method = "getEPerson") }) public class ResearcherProfileRest extends BaseObjectRest { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionHistoryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionHistoryRest.java index e93e131aadb..51d926082e3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionHistoryRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionHistoryRest.java @@ -13,14 +13,8 @@ * The REST object for the {@link org.dspace.versioning.VersionHistory} object */ @LinksRest(links = { - @LinkRest( - name = VersionHistoryRest.VERSIONS, - method = "getVersions" - ), - @LinkRest( - name = VersionHistoryRest.DRAFT_VERSION, - method = "getDraftVersion" - ) + @LinkRest(name = VersionHistoryRest.VERSIONS, method = "getVersions"), + @LinkRest(name = VersionHistoryRest.DRAFT_VERSION, method = "getDraftVersion") }) public class VersionHistoryRest extends BaseObjectRest { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionRest.java index abdc64295fd..b4a7ce2b016 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionRest.java @@ -16,14 +16,8 @@ * The REST object for the {@link org.dspace.versioning.Version} objects */ @LinksRest(links = { - @LinkRest( - name = VersionRest.VERSION_HISTORY, - method = "getVersionHistory" - ), - @LinkRest( - name = VersionRest.ITEM, - method = "getVersionItem" - ) + @LinkRest(name = VersionRest.VERSION_HISTORY, method = "getVersionHistory"), + @LinkRest(name = VersionRest.ITEM, method = "getVersionItem") }) public class VersionRest extends BaseObjectRest { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java index 30e5eb71cbf..964c67dbbe8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java @@ -18,9 +18,9 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ @LinksRest(links = { - @LinkRest(name = VocabularyEntryDetailsRest.PARENT, method = "getParent"), - @LinkRest(name = VocabularyEntryDetailsRest.CHILDREN, method = "getChildren") - }) + @LinkRest(name = VocabularyEntryDetailsRest.PARENT, method = "getParent"), + @LinkRest(name = VocabularyEntryDetailsRest.CHILDREN, method = "getChildren") +}) public class VocabularyEntryDetailsRest extends BaseObjectRest { public static final String PLURAL_NAME = "vocabularyEntryDetails"; public static final String NAME = "vocabularyEntryDetail"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyRest.java index cc848b945b2..45d13076acb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyRest.java @@ -15,9 +15,7 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ @LinksRest(links = { - @LinkRest(name = VocabularyRest.ENTRIES, - method = "filter" - ), + @LinkRest(name = VocabularyRest.ENTRIES, method = "filter"), }) public class VocabularyRest extends BaseObjectRest { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowDefinitionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowDefinitionRest.java index 7c2de7071bd..9ea4e2ae8c2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowDefinitionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowDefinitionRest.java @@ -18,14 +18,8 @@ * @author Maria Verdonck (Atmire) on 11/12/2019 */ @LinksRest(links = { - @LinkRest( - name = WorkflowDefinitionRest.COLLECTIONS_MAPPED_TO, - method = "getCollections" - ), - @LinkRest( - name = WorkflowDefinitionRest.STEPS, - method = "getSteps" - ) + @LinkRest(name = WorkflowDefinitionRest.COLLECTIONS_MAPPED_TO, method = "getCollections"), + @LinkRest(name = WorkflowDefinitionRest.STEPS, method = "getSteps") }) public class WorkflowDefinitionRest extends BaseObjectRest { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowItemRest.java index 4a840a0bb77..e94cfd54644 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowItemRest.java @@ -15,22 +15,10 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ @LinksRest(links = { - @LinkRest( - name = WorkflowItemRest.STEP, - method = "getStep" - ), - @LinkRest( - name = WorkflowItemRest.SUBMITTER, - method = "getWorkflowItemSubmitter" - ), - @LinkRest( - name = WorkflowItemRest.ITEM, - method = "getWorkflowItemItem" - ), - @LinkRest( - name = WorkflowItemRest.COLLECTION, - method = "getWorkflowItemCollection" - ) + @LinkRest(name = WorkflowItemRest.STEP, method = "getStep"), + @LinkRest(name = WorkflowItemRest.SUBMITTER, method = "getWorkflowItemSubmitter"), + @LinkRest(name = WorkflowItemRest.ITEM, method = "getWorkflowItemItem"), + @LinkRest(name = WorkflowItemRest.COLLECTION, method = "getWorkflowItemCollection") }) public class WorkflowItemRest extends AInprogressSubmissionRest { public static final String NAME = "workflowitem"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowStepRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowStepRest.java index 648cffbca80..91128fd05a7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowStepRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowStepRest.java @@ -18,10 +18,7 @@ * @author Maria Verdonck (Atmire) on 10/01/2020 */ @LinksRest(links = { - @LinkRest( - name = WorkflowStepRest.ACTIONS, - method = "getActions" - ), + @LinkRest(name = WorkflowStepRest.ACTIONS, method = "getActions"), }) public class WorkflowStepRest extends BaseObjectRest { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkspaceItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkspaceItemRest.java index b40d1a4494f..7693771fb9f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkspaceItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkspaceItemRest.java @@ -15,22 +15,10 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) */ @LinksRest(links = { - @LinkRest( - name = WorkspaceItemRest.SUPERVISION_ORDERS, - method = "getSupervisionOrders" - ), - @LinkRest( - name = WorkspaceItemRest.SUBMITTER, - method = "getWorkspaceItemSubmitter" - ), - @LinkRest( - name = WorkspaceItemRest.ITEM, - method = "getWorkspaceItemItem" - ), - @LinkRest( - name = WorkspaceItemRest.COLLECTION, - method = "getWorkspaceItemCollection" - ) + @LinkRest(name = WorkspaceItemRest.SUPERVISION_ORDERS, method = "getSupervisionOrders"), + @LinkRest(name = WorkspaceItemRest.SUBMITTER, method = "getWorkspaceItemSubmitter"), + @LinkRest(name = WorkspaceItemRest.ITEM, method = "getWorkspaceItemItem"), + @LinkRest(name = WorkspaceItemRest.COLLECTION, method = "getWorkspaceItemCollection") }) public class WorkspaceItemRest extends AInprogressSubmissionRest { public static final String NAME = "workspaceitem"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/RegexUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/RegexUtils.java index 8739f6b8d57..856e3645705 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/RegexUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/RegexUtils.java @@ -13,7 +13,7 @@ */ public class RegexUtils { - private RegexUtils(){} + private RegexUtils() {} /** * Regular expression in the request mapping to accept UUID as identifier diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RegistrationMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RegistrationMatcher.java index a154091a2ef..2a4cee8375b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RegistrationMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RegistrationMatcher.java @@ -17,7 +17,7 @@ public class RegistrationMatcher { - private RegistrationMatcher(){} + private RegistrationMatcher() {} public static Matcher matchRegistration(String email, UUID epersonUuid) { return allOf( From 5d7b42603d5d68bbffef0226fca3116e7029e401 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 3 Dec 2024 09:34:02 -0600 Subject: [PATCH 388/939] Add newly required "should-stop" flag to errorprone config. See https://errorprone.info/docs/installation --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index f496ed7e879..f3b09f3a9dd 100644 --- a/pom.xml +++ b/pom.xml @@ -147,6 +147,7 @@ true -XDcompilePolicy=simple + --should-stop=ifError=FLOW -Xplugin:ErrorProne -J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED -J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED From f7ee509423591c33621e9b6abf80135c08f16522 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 3 Dec 2024 09:44:56 -0600 Subject: [PATCH 389/939] Fix duplicate code warning from errorprone. This "else if" clause is the same as the "else" and can be removed --- .../src/main/java/org/dspace/discovery/SolrServiceImpl.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java index 9339b574b57..34efe96ae7f 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java @@ -1445,8 +1445,6 @@ protected String transformFacetField(DiscoverFacetField facetFieldConfig, String } else { return field + "_acid"; } - } else if (facetFieldConfig.getType().equals(DiscoveryConfigurationParameters.TYPE_STANDARD)) { - return field; } else { return field; } From 745e9c468a083b916f89682f414126e0f044bc7c Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Thu, 16 Jan 2025 12:01:13 +0100 Subject: [PATCH 390/939] 124362: Fix issue with the VersionedHandleIdentifierProviderWithCanonicalHandles and creating communities / collections --- ...dentifierProviderWithCanonicalHandles.java | 38 ++++++++++++------- .../config/spring/api/identifier-service.xml | 9 ++--- 2 files changed, 29 insertions(+), 18 deletions(-) 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 78ad6b7b79b..7be5cbc0d8a 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java +++ b/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java @@ -15,11 +15,14 @@ import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Collection; +import org.dspace.content.Community; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.MetadataValue; -import org.dspace.content.service.ItemService; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.DSpaceObjectService; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.LogHelper; @@ -64,9 +67,6 @@ public class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident @Autowired(required = true) private HandleService handleService; - @Autowired(required = true) - private ItemService itemService; - /** * After all the properties are set check that the versioning is enabled * @@ -173,6 +173,16 @@ public String register(Context context, DSpaceObject dso) { throw new RuntimeException("The current user is not authorized to change this item.", ex); } } + if (dso instanceof Collection || dso instanceof Community) { + try { + // Update the metadata with the handle for collections and communities. + modifyHandleMetadata(context, dso, getCanonical(id)); + } catch (SQLException ex) { + throw new RuntimeException("A problem with the database connection occured.", ex); + } catch (AuthorizeException ex) { + throw new RuntimeException("The current user is not authorized to change this item.", ex); + } + } return id; } @@ -490,27 +500,29 @@ protected String getCanonical(String identifier) { * Remove all handles from an item's metadata and add the supplied handle instead. * * @param context The relevant DSpace Context. - * @param item which item to modify + * @param dso which dso to modify * @param handle which handle to add * @throws SQLException if database error * @throws AuthorizeException if authorization error */ - protected void modifyHandleMetadata(Context context, Item item, String handle) + protected void modifyHandleMetadata(Context context, DSpaceObject dso, String handle) throws SQLException, AuthorizeException { // we want to exchange the old handle against the new one. To do so, we // load all identifiers, clear the metadata field, re add all // identifiers which are not from type handle and add the new handle. String handleref = handleService.getCanonicalForm(handle); - List identifiers = itemService - .getMetadata(item, MetadataSchemaEnum.DC.getName(), "identifier", "uri", Item.ANY); - itemService.clearMetadata(context, item, MetadataSchemaEnum.DC.getName(), "identifier", "uri", Item.ANY); + DSpaceObjectService dSpaceObjectService = + ContentServiceFactory.getInstance().getDSpaceObjectService(dso); + List identifiers = dSpaceObjectService + .getMetadata(dso, MetadataSchemaEnum.DC.getName(), "identifier", "uri", Item.ANY); + dSpaceObjectService.clearMetadata(context, dso, MetadataSchemaEnum.DC.getName(), "identifier", "uri", Item.ANY); for (MetadataValue identifier : identifiers) { if (this.supports(identifier.getValue())) { // ignore handles continue; } - itemService.addMetadata(context, - item, + dSpaceObjectService.addMetadata(context, + dso, identifier.getMetadataField(), identifier.getLanguage(), identifier.getValue(), @@ -518,9 +530,9 @@ protected void modifyHandleMetadata(Context context, Item item, String handle) identifier.getConfidence()); } if (!StringUtils.isEmpty(handleref)) { - itemService.addMetadata(context, item, MetadataSchemaEnum.DC.getName(), + dSpaceObjectService.addMetadata(context, dso, MetadataSchemaEnum.DC.getName(), "identifier", "uri", null, handleref); } - itemService.update(context, item); + dSpaceObjectService.update(context, dso); } } diff --git a/dspace/config/spring/api/identifier-service.xml b/dspace/config/spring/api/identifier-service.xml index 0c58cc1de93..9e599527444 100644 --- a/dspace/config/spring/api/identifier-service.xml +++ b/dspace/config/spring/api/identifier-service.xml @@ -17,9 +17,9 @@ The VersionedHandleIdentifierProvider creates a new versioned handle for every new version. --> - - - + + + - - - - + + + + - + 2.16.0 2.16.0 From c657f7770f30309b1656018771dd44e28b8c1c02 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 17 Jan 2025 14:12:38 -0600 Subject: [PATCH 396/939] Sync a few dependency exclusions from dspace-8_x to dspace-7_x to fix convergence issues --- dspace-server-webapp/pom.xml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 35fa473fc17..9f630cf60df 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -316,6 +316,13 @@ org.webjars.bowergithub.codeseven toastr 2.1.4 + + + + org.webjars.bowergithub.jquery + jquery-dist + + @@ -337,6 +344,13 @@ org.webjars.bowergithub.jashkenas backbone 1.4.1 + + + + org.webjars.bowergithub.jashkenas + underscore + + + + net.minidev + json-smart + + + + + + + net.minidev + json-smart + 2.5.0 From 4c46bc33be2692684ca87cb572c628dfca544ae2 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 17 Jan 2025 15:07:10 -0600 Subject: [PATCH 397/939] Remove unused dependency --- dspace-sword/pom.xml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index 12325533c5c..c4826645460 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -36,11 +36,6 @@ - - org.dspace - dspace-api-lang - - org.springframework.boot From 2b5db943b228e9fb6efe286743669604a644c29d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Jan 2025 21:12:56 +0000 Subject: [PATCH 398/939] Bump org.scala-lang:scala-library from 2.13.11 to 2.13.16 Bumps [org.scala-lang:scala-library](https://github.com/scala/scala) from 2.13.11 to 2.13.16. - [Release notes](https://github.com/scala/scala/releases) - [Commits](https://github.com/scala/scala/compare/v2.13.11...v2.13.16) --- updated-dependencies: - dependency-name: org.scala-lang:scala-library dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index c42895ab4c4..943e0d6d005 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -943,7 +943,7 @@ org.scala-lang scala-library - 2.13.11 + 2.13.16 test From bd16f7a9821e1acfbdf5691ed3a9f2562533159e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Jan 2025 21:13:00 +0000 Subject: [PATCH 399/939] Bump org.apache.bcel:bcel from 6.7.0 to 6.10.0 Bumps [org.apache.bcel:bcel](https://github.com/apache/commons-bcel) from 6.7.0 to 6.10.0. - [Changelog](https://github.com/apache/commons-bcel/blob/master/RELEASE-NOTES.txt) - [Commits](https://github.com/apache/commons-bcel/compare/rel/commons-bcel-6.7.0...rel/commons-bcel-6.10.0) --- updated-dependencies: - dependency-name: org.apache.bcel:bcel dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index c42895ab4c4..17031911579 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -823,7 +823,7 @@ org.apache.bcel bcel - 6.7.0 + 6.10.0 test From 80e39f7669edf1fbe195d2860845ca1b04ab383f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Jan 2025 21:13:06 +0000 Subject: [PATCH 400/939] Bump log4j.version from 2.23.1 to 2.24.3 Bumps `log4j.version` from 2.23.1 to 2.24.3. Updates `org.apache.logging.log4j:log4j-api` from 2.23.1 to 2.24.3 Updates `org.apache.logging.log4j:log4j-core` from 2.23.1 to 2.24.3 Updates `org.apache.logging.log4j:log4j-1.2-api` from 2.23.1 to 2.24.3 --- updated-dependencies: - dependency-name: org.apache.logging.log4j:log4j-api dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.apache.logging.log4j:log4j-core dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.apache.logging.log4j:log4j-1.2-api dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2a97e8767bf..fcf03e61655 100644 --- a/pom.xml +++ b/pom.xml @@ -38,7 +38,7 @@ 1.1.1 9.4.57.v20241219 - 2.23.1 + 2.24.3 2.0.31 1.19.0 1.7.36 From 6b2e4c185e3af93c1a9e8cd660ad9a089b129df9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 18 Jan 2025 01:09:25 +0000 Subject: [PATCH 401/939] Bump com.github.spotbugs:spotbugs Bumps the build-tools group with 1 update in the / directory: [com.github.spotbugs:spotbugs](https://github.com/spotbugs/spotbugs). Updates `com.github.spotbugs:spotbugs` from 4.8.6 to 4.9.0 - [Release notes](https://github.com/spotbugs/spotbugs/releases) - [Changelog](https://github.com/spotbugs/spotbugs/blob/master/CHANGELOG.md) - [Commits](https://github.com/spotbugs/spotbugs/compare/4.8.6...4.9.0) --- updated-dependencies: - dependency-name: com.github.spotbugs:spotbugs dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 523f50c3830..6ebf3ebcf72 100644 --- a/pom.xml +++ b/pom.xml @@ -305,7 +305,7 @@ com.github.spotbugs spotbugs - 4.8.6 + 4.9.0 From 6c99c25d34a34d176c77a10039e86fe801f24ab3 Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Mon, 20 Jan 2025 13:26:39 +0100 Subject: [PATCH 402/939] [CST-14901][DSC-1357][#8662] Adds IT for isLatestVersion --- .../indexobject/ItemIndexFactoryImpl.java | 43 +------------------ .../dspace/content/service/ItemServiceIT.java | 38 ++++++++++++++++ 2 files changed, 40 insertions(+), 41 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java index 7cdb8b93d80..682f87fbcec 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java @@ -67,8 +67,6 @@ import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.util.MultiFormatDateParser; import org.dspace.util.SolrUtils; -import org.dspace.versioning.Version; -import org.dspace.versioning.VersionHistory; import org.dspace.versioning.service.VersionHistoryService; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; import org.dspace.xmlworkflow.storedcomponents.service.XmlWorkflowItemService; @@ -151,7 +149,7 @@ public SolrInputDocument buildDocument(Context context, IndexableItem indexableI doc.addField("withdrawn", item.isWithdrawn()); doc.addField("discoverable", item.isDiscoverable()); doc.addField("lastModified", SolrUtils.getDateFormatter().format(item.getLastModified())); - doc.addField("latestVersion", isLatestVersion(context, item)); + doc.addField("latestVersion", itemService.isLatestVersion(context, item)); EPerson submitter = item.getSubmitter(); if (submitter != null) { @@ -175,43 +173,6 @@ public SolrInputDocument buildDocument(Context context, IndexableItem indexableI return doc; } - /** - * Check whether the given item is the latest version. - * If the latest item cannot be determined, because either the version history or the latest version is not present, - * assume the item is latest. - * @param context the DSpace context. - * @param item the item that should be checked. - * @return true if the item is the latest version, false otherwise. - */ - protected boolean isLatestVersion(Context context, Item item) throws SQLException { - VersionHistory history = versionHistoryService.findByItem(context, item); - if (history == null) { - // not all items have a version history - // if an item does not have a version history, it is by definition the latest version - return true; - } - - // start with the very latest version of the given item (may still be in workspace) - Version latestVersion = versionHistoryService.getLatestVersion(context, history); - - // find the latest version of the given item that is archived - while (latestVersion != null && !latestVersion.getItem().isArchived()) { - latestVersion = versionHistoryService.getPrevious(context, history, latestVersion); - } - - // could not find an archived version of the given item - if (latestVersion == null) { - // this scenario should never happen, but let's err on the side of showing too many items vs. to little - // (see discovery.xml, a lot of discovery configs filter out all items that are not the latest version) - return true; - } - - // sanity check - assert latestVersion.getItem().isArchived(); - - return item.equals(latestVersion.getItem()); - } - @Override public SolrInputDocument buildNewDocument(Context context, IndexableItem indexableItem) throws SQLException, IOException { @@ -704,7 +665,7 @@ public List getIndexableObjects(Context context, Item item) throws SQLException return List.copyOf(workflowItemIndexFactory.getIndexableObjects(context, xmlWorkflowItem)); } - if (!isLatestVersion(context, item)) { + if (!itemService.isLatestVersion(context, item)) { // the given item is an older version of another item return List.of(new IndexableItem(item)); } diff --git a/dspace-api/src/test/java/org/dspace/content/service/ItemServiceIT.java b/dspace-api/src/test/java/org/dspace/content/service/ItemServiceIT.java index 2ff7dd21da5..d31acd85451 100644 --- a/dspace-api/src/test/java/org/dspace/content/service/ItemServiceIT.java +++ b/dspace-api/src/test/java/org/dspace/content/service/ItemServiceIT.java @@ -11,7 +11,9 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasSize; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.ByteArrayInputStream; @@ -912,4 +914,40 @@ private void assertMetadataValue(String authorQualifier, String contributorEleme assertThat(metadataValue.getAuthority(), equalTo(authority)); assertThat(metadataValue.getPlace(), equalTo(place)); } + + + @Test + public void testIsLatestVersion() throws Exception { + assertTrue("Original should be the latest version", this.itemService.isLatestVersion(context, item)); + + context.turnOffAuthorisationSystem(); + + Version firstVersion = versioningService.createNewVersion(context, item); + Item firstPublication = firstVersion.getItem(); + WorkspaceItem firstPublicationWSI = workspaceItemService.findByItem(context, firstPublication); + installItemService.installItem(context, firstPublicationWSI); + + context.commit(); + context.restoreAuthSystemState(); + + assertTrue("First version should be valid", this.itemService.isLatestVersion(context, firstPublication)); + assertFalse("Original version should not be valid", this.itemService.isLatestVersion(context, item)); + + context.turnOffAuthorisationSystem(); + + Version secondVersion = versioningService.createNewVersion(context, item); + Item secondPublication = secondVersion.getItem(); + WorkspaceItem secondPublicationWSI = workspaceItemService.findByItem(context, secondPublication); + installItemService.installItem(context, secondPublicationWSI); + + context.commit(); + context.restoreAuthSystemState(); + + assertTrue("Second version should be valid", this.itemService.isLatestVersion(context, secondPublication)); + assertFalse("First version should not be valid", this.itemService.isLatestVersion(context, firstPublication)); + assertFalse("Original version should not be valid", this.itemService.isLatestVersion(context, item)); + + context.turnOffAuthorisationSystem(); + } + } From 75e291d8677e10bc52af399665242392b576a32a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 22:43:27 +0000 Subject: [PATCH 403/939] Bump the test-tools group with 8 updates Bumps the test-tools group with 8 updates: | Package | From | To | | --- | --- | --- | | [com.h2database:h2](https://github.com/h2database/h2database) | `2.2.224` | `2.3.232` | | [org.mock-server:mockserver-junit-rule](https://github.com/jamesdbloom/mockservice) | `5.11.2` | `5.15.0` | | [io.netty:netty-buffer](https://github.com/netty/netty) | `4.1.106.Final` | `4.1.117.Final` | | [io.netty:netty-transport](https://github.com/netty/netty) | `4.1.106.Final` | `4.1.117.Final` | | [io.netty:netty-transport-native-unix-common](https://github.com/netty/netty) | `4.1.106.Final` | `4.1.117.Final` | | [io.netty:netty-common](https://github.com/netty/netty) | `4.1.106.Final` | `4.1.117.Final` | | [io.netty:netty-handler](https://github.com/netty/netty) | `4.1.106.Final` | `4.1.117.Final` | | [io.netty:netty-codec](https://github.com/netty/netty) | `4.1.106.Final` | `4.1.117.Final` | Updates `com.h2database:h2` from 2.2.224 to 2.3.232 - [Release notes](https://github.com/h2database/h2database/releases) - [Commits](https://github.com/h2database/h2database/compare/version-2.2.224...version-2.3.232) Updates `org.mock-server:mockserver-junit-rule` from 5.11.2 to 5.15.0 - [Changelog](https://github.com/mock-server/mockserver/blob/master/changelog.md) - [Commits](https://github.com/jamesdbloom/mockservice/compare/mockserver-5.11.2...mockserver-5.15.0) Updates `io.netty:netty-buffer` from 4.1.106.Final to 4.1.117.Final - [Commits](https://github.com/netty/netty/compare/netty-4.1.106.Final...netty-4.1.117.Final) Updates `io.netty:netty-transport` from 4.1.106.Final to 4.1.117.Final - [Commits](https://github.com/netty/netty/compare/netty-4.1.106.Final...netty-4.1.117.Final) Updates `io.netty:netty-transport-native-unix-common` from 4.1.106.Final to 4.1.117.Final - [Commits](https://github.com/netty/netty/compare/netty-4.1.106.Final...netty-4.1.117.Final) Updates `io.netty:netty-common` from 4.1.106.Final to 4.1.117.Final - [Commits](https://github.com/netty/netty/compare/netty-4.1.106.Final...netty-4.1.117.Final) Updates `io.netty:netty-handler` from 4.1.106.Final to 4.1.117.Final - [Commits](https://github.com/netty/netty/compare/netty-4.1.106.Final...netty-4.1.117.Final) Updates `io.netty:netty-codec` from 4.1.106.Final to 4.1.117.Final - [Commits](https://github.com/netty/netty/compare/netty-4.1.106.Final...netty-4.1.117.Final) --- updated-dependencies: - dependency-name: com.h2database:h2 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: test-tools - dependency-name: org.mock-server:mockserver-junit-rule dependency-type: direct:development update-type: version-update:semver-minor dependency-group: test-tools - dependency-name: io.netty:netty-buffer dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-transport dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-transport-native-unix-common dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-common dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-handler dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-codec dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 14 +++++++------- pom.xml | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index cf81408a265..32ab56aa450 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -844,7 +844,7 @@ org.mock-server mockserver-junit-rule - 5.11.2 + 5.15.0 test @@ -882,32 +882,32 @@ io.netty netty-buffer - 4.1.106.Final + 4.1.117.Final io.netty netty-transport - 4.1.106.Final + 4.1.117.Final io.netty netty-transport-native-unix-common - 4.1.106.Final + 4.1.117.Final io.netty netty-common - 4.1.106.Final + 4.1.117.Final io.netty netty-handler - 4.1.106.Final + 4.1.117.Final io.netty netty-codec - 4.1.106.Final + 4.1.117.Final org.apache.velocity diff --git a/pom.xml b/pom.xml index 523f50c3830..a92a91c8122 100644 --- a/pom.xml +++ b/pom.xml @@ -1685,7 +1685,7 @@ com.h2database h2 - 2.2.224 + 2.3.232 test From 9dcc4e6f7ce32e22c8492b3429077f1a991f1d1f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 22:43:38 +0000 Subject: [PATCH 404/939] Bump the spring group with 4 updates Bumps the spring group with 4 updates: [org.springframework.security:spring-security-test](https://github.com/spring-projects/spring-security), [org.springframework.security:spring-security-core](https://github.com/spring-projects/spring-security), [org.springframework.security:spring-security-web](https://github.com/spring-projects/spring-security) and [org.springframework.security:spring-security-config](https://github.com/spring-projects/spring-security). Updates `org.springframework.security:spring-security-test` from 5.7.12 to 5.7.14 - [Release notes](https://github.com/spring-projects/spring-security/releases) - [Changelog](https://github.com/spring-projects/spring-security/blob/main/RELEASE.adoc) - [Commits](https://github.com/spring-projects/spring-security/compare/5.7.12...5.7.14) Updates `org.springframework.security:spring-security-core` from 5.7.12 to 5.7.14 - [Release notes](https://github.com/spring-projects/spring-security/releases) - [Changelog](https://github.com/spring-projects/spring-security/blob/main/RELEASE.adoc) - [Commits](https://github.com/spring-projects/spring-security/compare/5.7.12...5.7.14) Updates `org.springframework.security:spring-security-web` from 5.7.12 to 5.7.14 - [Release notes](https://github.com/spring-projects/spring-security/releases) - [Changelog](https://github.com/spring-projects/spring-security/blob/main/RELEASE.adoc) - [Commits](https://github.com/spring-projects/spring-security/compare/5.7.12...5.7.14) Updates `org.springframework.security:spring-security-config` from 5.7.12 to 5.7.14 - [Release notes](https://github.com/spring-projects/spring-security/releases) - [Changelog](https://github.com/spring-projects/spring-security/blob/main/RELEASE.adoc) - [Commits](https://github.com/spring-projects/spring-security/compare/5.7.12...5.7.14) Updates `org.springframework.security:spring-security-core` from 5.7.12 to 5.7.14 - [Release notes](https://github.com/spring-projects/spring-security/releases) - [Changelog](https://github.com/spring-projects/spring-security/blob/main/RELEASE.adoc) - [Commits](https://github.com/spring-projects/spring-security/compare/5.7.12...5.7.14) Updates `org.springframework.security:spring-security-web` from 5.7.12 to 5.7.14 - [Release notes](https://github.com/spring-projects/spring-security/releases) - [Changelog](https://github.com/spring-projects/spring-security/blob/main/RELEASE.adoc) - [Commits](https://github.com/spring-projects/spring-security/compare/5.7.12...5.7.14) Updates `org.springframework.security:spring-security-config` from 5.7.12 to 5.7.14 - [Release notes](https://github.com/spring-projects/spring-security/releases) - [Changelog](https://github.com/spring-projects/spring-security/blob/main/RELEASE.adoc) - [Commits](https://github.com/spring-projects/spring-security/compare/5.7.12...5.7.14) --- updated-dependencies: - dependency-name: org.springframework.security:spring-security-test dependency-type: direct:production update-type: version-update:semver-patch dependency-group: spring - dependency-name: org.springframework.security:spring-security-core dependency-type: direct:production update-type: version-update:semver-patch dependency-group: spring - dependency-name: org.springframework.security:spring-security-web dependency-type: direct:production update-type: version-update:semver-patch dependency-group: spring - dependency-name: org.springframework.security:spring-security-config dependency-type: direct:production update-type: version-update:semver-patch dependency-group: spring - dependency-name: org.springframework.security:spring-security-core dependency-type: direct:production update-type: version-update:semver-patch dependency-group: spring - dependency-name: org.springframework.security:spring-security-web dependency-type: direct:production update-type: version-update:semver-patch dependency-group: spring - dependency-name: org.springframework.security:spring-security-config dependency-type: direct:production update-type: version-update:semver-patch dependency-group: spring ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 523f50c3830..40870b0b777 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ 11 5.3.39 2.7.18 - 5.7.12 + 5.7.14 5.6.15.Final 6.2.5.Final 42.7.3 From bf1aa3da7aabff7825b57e74851649f7d12c5c24 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 22:43:50 +0000 Subject: [PATCH 405/939] Bump solr.client.version from 8.11.3 to 8.11.4 Bumps `solr.client.version` from 8.11.3 to 8.11.4. Updates `org.apache.solr:solr-solrj` from 8.11.3 to 8.11.4 Updates `org.apache.lucene:lucene-core` from 8.11.3 to 8.11.4 Updates `org.apache.lucene:lucene-analyzers-icu` from 8.11.3 to 8.11.4 Updates `org.apache.lucene:lucene-analyzers-smartcn` from 8.11.3 to 8.11.4 Updates `org.apache.lucene:lucene-analyzers-stempel` from 8.11.3 to 8.11.4 Updates `org.apache.solr:solr-core` from 8.11.3 to 8.11.4 --- updated-dependencies: - dependency-name: org.apache.solr:solr-solrj dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.lucene:lucene-core dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.lucene:lucene-analyzers-icu dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.lucene:lucene-analyzers-smartcn dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.lucene:lucene-analyzers-stempel dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.solr:solr-core dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 523f50c3830..57cd8066aa9 100644 --- a/pom.xml +++ b/pom.xml @@ -25,7 +25,7 @@ 5.6.15.Final 6.2.5.Final 42.7.3 - 8.11.3 + 8.11.4 3.10.8 2.31.0 From 8076745a09236ebd6636f1c6c83d906ee6cec0cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 22:43:53 +0000 Subject: [PATCH 406/939] Bump io.swagger:swagger-core from 1.6.2 to 1.6.15 Bumps [io.swagger:swagger-core](https://github.com/swagger-api/swagger-core) from 1.6.2 to 1.6.15. - [Release notes](https://github.com/swagger-api/swagger-core/releases) - [Commits](https://github.com/swagger-api/swagger-core/compare/v1.6.2...v1.6.15) --- updated-dependencies: - dependency-name: io.swagger:swagger-core dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index cf81408a265..2ad962d88a3 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -938,7 +938,7 @@ io.swagger swagger-core - 1.6.2 + 1.6.15 org.scala-lang From b3e113900603d8aacbad84ae84543f4e59ad245b Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 21 Jan 2025 13:41:10 -0600 Subject: [PATCH 407/939] Remove swagger-core as it is unused --- dspace-api/pom.xml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 2ad962d88a3..8071b24b084 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -935,11 +935,6 @@ validation-api 2.0.1.Final - - io.swagger - swagger-core - 1.6.15 - org.scala-lang scala-library From 7d4a5fc311cad860b18591fbd1d6381b340d5e5a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2025 23:28:06 +0000 Subject: [PATCH 408/939] Bump bouncycastle.version from 1.78.1 to 1.80 Bumps `bouncycastle.version` from 1.78.1 to 1.80. Updates `org.bouncycastle:bcpkix-jdk18on` from 1.78.1 to 1.80 - [Changelog](https://github.com/bcgit/bc-java/blob/main/docs/releasenotes.html) - [Commits](https://github.com/bcgit/bc-java/commits) Updates `org.bouncycastle:bcprov-jdk18on` from 1.78.1 to 1.80 - [Changelog](https://github.com/bcgit/bc-java/blob/main/docs/releasenotes.html) - [Commits](https://github.com/bcgit/bc-java/commits) Updates `org.bouncycastle:bcutil-jdk18on` from 1.78.1 to 1.80 - [Changelog](https://github.com/bcgit/bc-java/blob/main/docs/releasenotes.html) - [Commits](https://github.com/bcgit/bc-java/commits) --- updated-dependencies: - dependency-name: org.bouncycastle:bcpkix-jdk18on dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.bouncycastle:bcprov-jdk18on dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.bouncycastle:bcutil-jdk18on dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3597b20f331..6bdb1399fd7 100644 --- a/pom.xml +++ b/pom.xml @@ -44,7 +44,7 @@ 1.7.36 2.9.2 - 1.78.1 + 1.80 From 9705c30f8a811b62b317532f2078e955c81d2548 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2025 23:28:11 +0000 Subject: [PATCH 409/939] Bump net.bytebuddy:byte-buddy from 1.11.13 to 1.16.1 Bumps [net.bytebuddy:byte-buddy](https://github.com/raphw/byte-buddy) from 1.11.13 to 1.16.1. - [Release notes](https://github.com/raphw/byte-buddy/releases) - [Changelog](https://github.com/raphw/byte-buddy/blob/master/release-notes.md) - [Commits](https://github.com/raphw/byte-buddy/compare/byte-buddy-1.11.13...byte-buddy-1.16.1) --- updated-dependencies: - dependency-name: net.bytebuddy:byte-buddy dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3597b20f331..a7d025a57cf 100644 --- a/pom.xml +++ b/pom.xml @@ -1814,7 +1814,7 @@ net.bytebuddy byte-buddy - 1.11.13 + 1.16.1 From 39e2ef2b304e0ba0b09cba3e67a2c67922c7e5ba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2025 23:28:19 +0000 Subject: [PATCH 410/939] Bump pdfbox-version from 2.0.31 to 2.0.33 Bumps `pdfbox-version` from 2.0.31 to 2.0.33. Updates `org.apache.pdfbox:pdfbox` from 2.0.31 to 2.0.33 Updates `org.apache.pdfbox:fontbox` from 2.0.31 to 2.0.33 --- updated-dependencies: - dependency-name: org.apache.pdfbox:pdfbox dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.pdfbox:fontbox dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3597b20f331..04cd35050b4 100644 --- a/pom.xml +++ b/pom.xml @@ -39,7 +39,7 @@ 9.4.57.v20241219 2.24.3 - 2.0.31 + 2.0.33 1.19.0 1.7.36 2.9.2 From e3abd1c129ed22daea69764f8d451f3ee7ec941d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2025 23:28:34 +0000 Subject: [PATCH 411/939] Bump org.apache.velocity:velocity-engine-core from 2.3 to 2.4.1 Bumps org.apache.velocity:velocity-engine-core from 2.3 to 2.4.1. --- updated-dependencies: - dependency-name: org.apache.velocity:velocity-engine-core dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 38c490c4dbd..05a9b1ba853 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -912,7 +912,7 @@ org.apache.velocity velocity-engine-core - 2.3 + 2.4.1 org.xmlunit From 9bca0c928b5b1f8319bbfdb4786b881636eecdc7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2025 23:28:37 +0000 Subject: [PATCH 412/939] Bump net.cnri:cnri-servlet-container from 3.0.0 to 3.1.0 Bumps [net.cnri:cnri-servlet-container](https://gitlab.com/cnri/cnri-servlet-container) from 3.0.0 to 3.1.0. - [Commits](https://gitlab.com/cnri/cnri-servlet-container/compare/v3.0.0...v3.1.0) --- updated-dependencies: - dependency-name: net.cnri:cnri-servlet-container dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3597b20f331..a2de1a0f5fb 100644 --- a/pom.xml +++ b/pom.xml @@ -1389,7 +1389,7 @@ net.cnri cnri-servlet-container - 3.0.0 + 3.1.0 From 9d46a45191da6126bf2aa06dda8660ac63c604e3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2025 23:28:48 +0000 Subject: [PATCH 413/939] Bump org.apache.ant:ant from 1.10.14 to 1.10.15 Bumps org.apache.ant:ant from 1.10.14 to 1.10.15. --- updated-dependencies: - dependency-name: org.apache.ant:ant dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3597b20f331..ecdcf1e3923 100644 --- a/pom.xml +++ b/pom.xml @@ -1354,7 +1354,7 @@ org.apache.ant ant - 1.10.14 + 1.10.15 org.apache.jena From cd2fbbf51bbcb19c7e3eda091c00ebb03e7ecb6e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2025 23:28:54 +0000 Subject: [PATCH 414/939] Bump dnsjava:dnsjava from 3.6.0 to 3.6.2 Bumps [dnsjava:dnsjava](https://github.com/dnsjava/dnsjava) from 3.6.0 to 3.6.2. - [Release notes](https://github.com/dnsjava/dnsjava/releases) - [Changelog](https://github.com/dnsjava/dnsjava/blob/master/Changelog) - [Commits](https://github.com/dnsjava/dnsjava/compare/v3.6.0...v3.6.2) --- updated-dependencies: - dependency-name: dnsjava:dnsjava dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 38c490c4dbd..7c91f117254 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -646,7 +646,7 @@ dnsjava dnsjava - 3.6.0 + 3.6.2 From da3584759b3478d4c37e85e05359015ec03d6eb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eike=20Martin=20L=C3=B6hden?= Date: Wed, 15 Jan 2025 08:05:07 +0100 Subject: [PATCH 415/939] Add check to avoid adding empty array of values to an item. (cherry picked from commit 6e0124fcbb5e95bf7400deeedf8df1ccfca478f1) --- .../dspace/identifier/VersionedDOIIdentifierProvider.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/identifier/VersionedDOIIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/VersionedDOIIdentifierProvider.java index e5a90907c7b..b42c915163d 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/VersionedDOIIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/VersionedDOIIdentifierProvider.java @@ -354,7 +354,10 @@ void removePreviousVersionDOIsOutOfObject(Context c, Item item, String oldDoi) if (changed) { try { itemService.clearMetadata(c, item, MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, Item.ANY); - itemService.addMetadata(c, item, MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, null, newIdentifiers); + // Checks if Array newIdentifiers is empty to avoid adding null values to the metadata field. + if (!newIdentifiers.isEmpty()) { + itemService.addMetadata(c, item, MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, null, newIdentifiers); + } itemService.update(c, item); } catch (SQLException ex) { throw new RuntimeException("A problem with the database connection occured.", ex); From 0a55995b7c49b4e276c7d2f55d022d2d3e793273 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 17:08:10 +0000 Subject: [PATCH 416/939] Remove unused joad-time dependency Bumps [joda-time:joda-time](https://github.com/JodaOrg/joda-time) from 2.12.5 to 2.13.0. - [Release notes](https://github.com/JodaOrg/joda-time/releases) - [Changelog](https://github.com/JodaOrg/joda-time/blob/main/RELEASE-NOTES.txt) - [Commits](https://github.com/JodaOrg/joda-time/compare/v2.12.5...v2.13.0) --- updated-dependencies: - dependency-name: joda-time:joda-time dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 59564fafff4..730cfe2cc93 100644 --- a/pom.xml +++ b/pom.xml @@ -1536,7 +1536,7 @@ joda-time joda-time - 2.12.5 + 2.13.0 com.sun.mail From f451307e6f5c69301b491837f3f33cd689958b69 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 18:23:01 +0000 Subject: [PATCH 417/939] Bump com.amazonaws:aws-java-sdk-s3 from 1.12.261 to 1.12.780 Bumps [com.amazonaws:aws-java-sdk-s3](https://github.com/aws/aws-sdk-java) from 1.12.261 to 1.12.780. - [Changelog](https://github.com/aws/aws-sdk-java/blob/master/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-java/compare/1.12.261...1.12.780) --- updated-dependencies: - dependency-name: com.amazonaws:aws-java-sdk-s3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index ce6cd41a8f0..ff2a26cb61d 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -756,7 +756,7 @@ com.amazonaws aws-java-sdk-s3 - 1.12.261 + 1.12.780 From 3767093c158b0fdddf66f33283128a41b234558c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 18:23:11 +0000 Subject: [PATCH 418/939] Bump org.atteo:evo-inflector from 1.2.1 to 1.3 Bumps [org.atteo:evo-inflector](https://github.com/atteo/evo-inflector) from 1.2.1 to 1.3. - [Commits](https://github.com/atteo/evo-inflector/compare/1.2.1...1.3) --- updated-dependencies: - dependency-name: org.atteo:evo-inflector dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dspace-rest/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index 3f4bdaf1d0d..fa4e15e8b62 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -175,7 +175,7 @@ org.atteo evo-inflector - 1.2.1 + 1.3 org.apache.logging.log4j From 1b850aaf0b042ade92c1d7ef2ff56a9b78704d6d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 18:23:27 +0000 Subject: [PATCH 419/939] Bump net.minidev:json-smart from 2.5.0 to 2.5.1 Bumps [net.minidev:json-smart](https://github.com/netplex/json-smart-v2) from 2.5.0 to 2.5.1. - [Release notes](https://github.com/netplex/json-smart-v2/releases) - [Commits](https://github.com/netplex/json-smart-v2/compare/2.5.0...2.5.1) --- updated-dependencies: - dependency-name: net.minidev:json-smart dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- dspace-server-webapp/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index e078bfea625..7c4739dc79e 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -509,7 +509,7 @@ net.minidev json-smart - 2.5.0 + 2.5.1 From 0f3fc0eccb86a9e0d2a4df2f9d044b2ae55d43b7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 18:23:37 +0000 Subject: [PATCH 420/939] Bump org.postgresql:postgresql from 42.7.3 to 42.7.5 Bumps [org.postgresql:postgresql](https://github.com/pgjdbc/pgjdbc) from 42.7.3 to 42.7.5. - [Release notes](https://github.com/pgjdbc/pgjdbc/releases) - [Changelog](https://github.com/pgjdbc/pgjdbc/blob/master/CHANGELOG.md) - [Commits](https://github.com/pgjdbc/pgjdbc/compare/REL42.7.3...REL42.7.5) --- updated-dependencies: - dependency-name: org.postgresql:postgresql dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 59564fafff4..e41992328c6 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ 5.7.14 5.6.15.Final 6.2.5.Final - 42.7.3 + 42.7.5 8.11.4 3.10.8 From daa29e582a9c5d2c52e66c319cf981614073e503 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 18:23:43 +0000 Subject: [PATCH 421/939] Bump org.jboss.logging:jboss-logging from 3.4.3.Final to 3.6.1.Final Bumps [org.jboss.logging:jboss-logging](https://github.com/jboss-logging/jboss-logging) from 3.4.3.Final to 3.6.1.Final. - [Release notes](https://github.com/jboss-logging/jboss-logging/releases) - [Commits](https://github.com/jboss-logging/jboss-logging/compare/3.4.3.Final...3.6.1.Final) --- updated-dependencies: - dependency-name: org.jboss.logging:jboss-logging dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 59564fafff4..1c386f0f116 100644 --- a/pom.xml +++ b/pom.xml @@ -1148,7 +1148,7 @@ org.jboss.logging jboss-logging - 3.4.3.Final + 3.6.1.Final From 75d747e40ad3c63a50a57d17d4270d8930c07104 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 18:23:46 +0000 Subject: [PATCH 422/939] Bump org.apache.james:apache-mime4j-core from 0.8.10 to 0.8.12 Bumps org.apache.james:apache-mime4j-core from 0.8.10 to 0.8.12. --- updated-dependencies: - dependency-name: org.apache.james:apache-mime4j-core dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 59564fafff4..f7b20122e02 100644 --- a/pom.xml +++ b/pom.xml @@ -1320,7 +1320,7 @@ org.apache.james apache-mime4j-core - 0.8.10 + 0.8.12 From 0d069354f7dbba1afcdfe44a6642c4763e7eb478 Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Mon, 23 Dec 2024 15:17:32 +0100 Subject: [PATCH 423/939] 122350: Move failRunningProcesses during tomcat startup to RestRepository --- .../dspace/scripts/ProcessServiceImpl.java | 47 +++++++------------ .../scripts/service/ProcessService.java | 10 ++++ .../repository/ProcessRestRepository.java | 7 +++ 3 files changed, 35 insertions(+), 29 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java b/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java index ab5147221cf..b3893f74438 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java @@ -47,13 +47,12 @@ import org.dspace.eperson.Group; import org.dspace.scripts.service.ProcessService; import org.dspace.services.ConfigurationService; -import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; /** * The implementation for the {@link ProcessService} class */ -public class ProcessServiceImpl implements ProcessService, InitializingBean { +public class ProcessServiceImpl implements ProcessService { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(ProcessService.class); @@ -75,33 +74,6 @@ public class ProcessServiceImpl implements ProcessService, InitializingBean { @Autowired private ConfigurationService configurationService; - @Override - public void afterPropertiesSet() throws Exception { - try { - Context context = new Context(); - - // Processes that were running or scheduled when tomcat crashed, should be cleaned up during startup. - List processesToBeFailed = findByStatusAndCreationTimeOlderThan( - context, List.of(ProcessStatus.RUNNING, ProcessStatus.SCHEDULED), new Date()); - for (Process process : processesToBeFailed) { - context.setCurrentUser(process.getEPerson()); - // Fail the process. - log.info("Process with ID {} did not complete before tomcat shutdown, failing it now.", - process.getID()); - fail(context, process); - // But still attach its log to the process. - appendLog(process.getID(), process.getName(), - "Process did not complete before tomcat shutdown.", - ProcessLogLevel.ERROR); - createLogBitstream(context, process); - } - - context.complete(); - } catch (Exception e) { - log.error("Unable to clean up Processes: ", e); - } - } - @Override public Process create(Context context, EPerson ePerson, String scriptName, List parameters, @@ -359,6 +331,23 @@ public int countByUser(Context context, EPerson user) throws SQLException { return processDAO.countByUser(context, user); } + @Override + public void failRunningProcesses(Context context) throws SQLException, IOException, AuthorizeException { + List processesToBeFailed = findByStatusAndCreationTimeOlderThan( + context, List.of(ProcessStatus.RUNNING, ProcessStatus.SCHEDULED), new Date()); + for (Process process : processesToBeFailed) { + context.setCurrentUser(process.getEPerson()); + // Fail the process. + log.info("Process with ID {} did not complete before tomcat shutdown, failing it now.", process.getID()); + fail(context, process); + // But still attach its log to the process. + appendLog(process.getID(), process.getName(), + "Process did not complete before tomcat shutdown.", + ProcessLogLevel.ERROR); + createLogBitstream(context, process); + } + } + private String formatLogLine(int processId, String scriptName, String output, ProcessLogLevel processLogLevel) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); StringBuilder sb = new StringBuilder(); diff --git a/dspace-api/src/main/java/org/dspace/scripts/service/ProcessService.java b/dspace-api/src/main/java/org/dspace/scripts/service/ProcessService.java index c6fc2488815..5df2ca15aad 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/service/ProcessService.java +++ b/dspace-api/src/main/java/org/dspace/scripts/service/ProcessService.java @@ -277,4 +277,14 @@ List findByStatusAndCreationTimeOlderThan(Context context, List Date: Fri, 24 Jan 2025 00:21:38 +0000 Subject: [PATCH 424/939] Bump the google-apis group with 4 updates Bumps the google-apis group with 4 updates: [com.google.api-client:google-api-client](https://github.com/googleapis/google-api-java-client), [com.google.http-client:google-http-client](https://github.com/googleapis/google-http-java-client), [com.google.http-client:google-http-client-jackson2](https://github.com/googleapis/google-http-java-client) and [com.google.oauth-client:google-oauth-client](https://github.com/googleapis/google-oauth-java-client). Updates `com.google.api-client:google-api-client` from 1.23.0 to 1.35.2 - [Release notes](https://github.com/googleapis/google-api-java-client/releases) - [Changelog](https://github.com/googleapis/google-api-java-client/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/google-api-java-client/compare/1.23.0...v1.35.2) Updates `com.google.http-client:google-http-client` from 1.23.0 to 1.45.3 - [Release notes](https://github.com/googleapis/google-http-java-client/releases) - [Changelog](https://github.com/googleapis/google-http-java-client/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/google-http-java-client/compare/1.23.0...v1.45.3) Updates `com.google.http-client:google-http-client-jackson2` from 1.23.0 to 1.45.3 - [Release notes](https://github.com/googleapis/google-http-java-client/releases) - [Changelog](https://github.com/googleapis/google-http-java-client/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/google-http-java-client/compare/1.23.0...v1.45.3) Updates `com.google.oauth-client:google-oauth-client` from 1.33.3 to 1.37.0 - [Release notes](https://github.com/googleapis/google-oauth-java-client/releases) - [Changelog](https://github.com/googleapis/google-oauth-java-client/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/google-oauth-java-client/compare/v1.33.3...v1.37.0) --- updated-dependencies: - dependency-name: com.google.api-client:google-api-client dependency-type: direct:production update-type: version-update:semver-minor dependency-group: google-apis - dependency-name: com.google.http-client:google-http-client dependency-type: direct:production update-type: version-update:semver-minor dependency-group: google-apis - dependency-name: com.google.http-client:google-http-client-jackson2 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: google-apis - dependency-name: com.google.oauth-client:google-oauth-client dependency-type: direct:production update-type: version-update:semver-minor dependency-group: google-apis ... Signed-off-by: dependabot[bot] --- pom.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 1e396be2cd1..7be509838d0 100644 --- a/pom.xml +++ b/pom.xml @@ -1697,17 +1697,17 @@ com.google.api-client google-api-client - 1.23.0 + 1.35.2 com.google.http-client google-http-client - 1.23.0 + 1.45.3 com.google.http-client google-http-client-jackson2 - 1.23.0 + 1.45.3 jackson-core @@ -1722,7 +1722,7 @@ com.google.oauth-client google-oauth-client - 1.33.3 + 1.37.0 From 77efa5ce8872b4e14fb765534165355ad280cc9f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Jan 2025 00:21:41 +0000 Subject: [PATCH 425/939] Bump com.ibm.icu:icu4j from 62.1 to 62.2 Bumps [com.ibm.icu:icu4j](https://github.com/unicode-org/icu) from 62.1 to 62.2. - [Release notes](https://github.com/unicode-org/icu/releases) - [Commits](https://github.com/unicode-org/icu/commits) --- updated-dependencies: - dependency-name: com.ibm.icu:icu4j dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1e396be2cd1..a08f2748589 100644 --- a/pom.xml +++ b/pom.xml @@ -1606,7 +1606,7 @@ com.ibm.icu icu4j - 62.1 + 62.2 From 63081494d87d36acdb3f9026ac14b431881ce41c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Jan 2025 00:22:13 +0000 Subject: [PATCH 426/939] Bump com.opencsv:opencsv from 5.9 to 5.10 Bumps com.opencsv:opencsv from 5.9 to 5.10. --- updated-dependencies: - dependency-name: com.opencsv:opencsv dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index ff2a26cb61d..07d7f1d21c0 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -805,7 +805,7 @@ com.opencsv opencsv - 5.9 + 5.10 From 25be075b3c9679458cebc131cc692984da751121 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Jan 2025 00:22:28 +0000 Subject: [PATCH 427/939] Bump de.digitalcollections.iiif:iiif-apis from 0.3.10 to 0.3.11 Bumps [de.digitalcollections.iiif:iiif-apis](https://github.com/dbmdz/iiif-apis) from 0.3.10 to 0.3.11. - [Release notes](https://github.com/dbmdz/iiif-apis/releases) - [Commits](https://github.com/dbmdz/iiif-apis/compare/0.3.10...0.3.11) --- updated-dependencies: - dependency-name: de.digitalcollections.iiif:iiif-apis dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- dspace-iiif/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index 49ee02b5650..86702fe01b9 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -98,7 +98,7 @@ de.digitalcollections.iiif iiif-apis - 0.3.10 + 0.3.11 org.javassist From 10b3f06480ca43de24ce1ed81874a671a27e2963 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Chac=C3=B3n?= <167455436+oscar-escire@users.noreply.github.com> Date: Fri, 24 Jan 2025 10:08:22 -0600 Subject: [PATCH 428/939] added expunge request param and enabled workflowitem delete based on it (#9950) * added expunge request param and enabled workflowitem delete based on it * enabled expunge param only in workflowitem delete endpoint and IT * Remove invalid param from JavaDocs --------- Co-authored-by: Tim Donohue --- .../WorkflowItemRestRepository.java | 15 ++++- .../rest/WorkflowItemRestRepositoryIT.java | 65 +++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemRestRepository.java index de39ff69fb9..f34961727e2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemRestRepository.java @@ -71,6 +71,7 @@ public class WorkflowItemRestRepository extends DSpaceRestRepository { public static final String OPERATION_PATH_SECTIONS = "sections"; + public static final String REQUESTPARAMETER_EXPUNGE = "expunge"; private static final Logger log = LogManager.getLogger(); @@ -239,13 +240,25 @@ public void patch(Context context, HttpServletRequest request, String apiCategor * move the workflowitem back to the submitter workspace regardless to how the workflow is designed */ protected void delete(Context context, Integer id) { + String expungeParam = getRequestService() + .getCurrentRequest() + .getServletRequest() + .getParameter(REQUESTPARAMETER_EXPUNGE); + boolean expunge = false; + if (expungeParam != null) { + expunge = Boolean.parseBoolean(expungeParam); + } XmlWorkflowItem witem = null; try { witem = wis.find(context, id); if (witem == null) { throw new ResourceNotFoundException("WorkflowItem ID " + id + " not found"); } - wfs.abort(context, witem, context.getCurrentUser()); + if (expunge) { + wis.delete(context, witem); + } else { + wfs.abort(context, witem, context.getCurrentUser()); + } } catch (AuthorizeException e) { throw new RESTAuthorizationException(e); } catch (SQLException e) { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java index 72612fc5eb5..fe8e67bbf56 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java @@ -686,6 +686,71 @@ public void deleteOneTest() throws Exception { .andExpect(jsonPath("$.page.totalElements", is(1))); } + @Test + /** + * A delete request over a workflowitem with expunge param should result in delete item over detabase + * workspace + * + * @throws Exception + */ + public void deleteOneWithExpungeTest() throws Exception { + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community with one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1") + .withWorkflowGroup(1, admin).build(); + + //2. create a normal user to use as submitter + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("submitter@example.com") + .withPassword("dspace") + .build(); + + context.setCurrentUser(submitter); + + //3. a workflow item + XmlWorkflowItem witem = WorkflowItemBuilder.createWorkflowItem(context, col1) + .withTitle("Workflow Item 1") + .withIssueDate("2017-10-17") + .build(); + + Item item = witem.getItem(); + + //Add a bitstream to the item + String bitstreamContent = "ThisIsSomeDummyText"; + Bitstream bitstream = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, Charset.defaultCharset())) { + bitstream = BitstreamBuilder + .createBitstream(context, item, is) + .withName("Bitstream1") + .withMimeType("text/plain").build(); + } + + context.restoreAuthSystemState(); + + String token = getAuthToken(admin.getEmail(), password); + + // Delete the workflowitem + getClient(token).perform(delete("/api/workflow/workflowitems/" + witem.getID() + "?expunge=true")) + .andExpect(status().is(204)); + + // Trying to get deleted workflowitem should fail with 404 + getClient(token).perform(get("/api/workflow/workflowitems/" + witem.getID())) + .andExpect(status().is(404)); + + // the workflowitem's item should fail with 404 + getClient(token).perform(get("/api/core/items/" + item.getID())) + .andExpect(status().is(404)); + + // the workflowitem's bitstream should fail with 404 + getClient(token).perform(get("/api/core/bitstreams/" + bitstream.getID())) + .andExpect(status().is(404)); + } + @Test /** * Test the deposit of a workspaceitem POSTing to the resource workflowitems collection endpoint a workspaceitem (as From 96451f7391ec30548c2c61cc0288440a59dde527 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 24 Jan 2025 15:06:43 -0600 Subject: [PATCH 429/939] Dependency convergence fixes --- pom.xml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/pom.xml b/pom.xml index 7be509838d0..dfe2347fd64 100644 --- a/pom.xml +++ b/pom.xml @@ -1703,6 +1703,23 @@ com.google.http-client google-http-client 1.45.3 + + + com.google.errorprone + error_prone_annotations + + + com.google.j2objc + j2objc-annotations + + + + + + io.grpc + grpc-context + 1.69.0 com.google.http-client @@ -1724,6 +1741,13 @@ google-oauth-client 1.37.0 + + + com.google.http-client + google-http-client-gson + 1.43.3 + From 5169a55437f39756bf914336640619a2a6090e48 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 15 Nov 2024 13:41:37 -0600 Subject: [PATCH 430/939] Resolve dependency convergence issue --- dspace-server-webapp/pom.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 7c4739dc79e..63de9fe721c 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -270,6 +270,11 @@ com.fasterxml.jackson.datatype jackson-datatype-jdk8 + + + net.minidev + json-smart + @@ -553,6 +558,13 @@ com.jayway.jsonpath json-path test + + + + net.minidev + json-smart + + com.jayway.jsonpath From 1cc98e6c2d6a5f2ec6d713dfad326593362d8437 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 24 Jan 2025 16:31:05 -0600 Subject: [PATCH 431/939] Fix missing dependency errors for javax.el --- dspace-api/pom.xml | 5 +++++ pom.xml | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 38c490c4dbd..a3184d9adff 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -505,6 +505,11 @@ javax.servlet-api provided + + + javax.el + javax.el-api + javax.annotation javax.annotation-api diff --git a/pom.xml b/pom.xml index a2de1a0f5fb..1399e1c0549 100644 --- a/pom.xml +++ b/pom.xml @@ -1548,6 +1548,12 @@ javax.servlet-api 3.1.0 + + + javax.el + javax.el-api + 3.0.0 + From f7902bce00f7e480f034a151b6b180294963e573 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 17 Sep 2024 14:36:06 -0400 Subject: [PATCH 432/939] On error in bulk operations, continue instead of panic-stopping. (cherry picked from commit bfeba1aa7a8119d083fd6a9e15b58bf55988d006) --- .../dspace/identifier/doi/DOIOrganiser.java | 61 +++++++++++++------ 1 file changed, 41 insertions(+), 20 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java b/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java index 12f6d7c4fca..88738226755 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java +++ b/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java @@ -13,7 +13,6 @@ import java.sql.SQLException; import java.util.Arrays; import java.util.Date; -import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.UUID; @@ -227,8 +226,16 @@ public static void runCLI(Context context, DOIOrganiser organiser, String[] args } for (DOI doi : dois) { - organiser.reserve(doi); - context.uncacheEntity(doi); + doi = context.reloadEntity(doi); + try { + organiser.reserve(doi); + context.commit(); + } catch (RuntimeException e) { + System.err.format("DOI %s for object %s reservation failed, skipping: %s%n", + doi.getDSpaceObject().getID().toString(), + doi.getDoi(), e.getMessage()); + context.rollback(); + } } } catch (SQLException ex) { System.err.println("Error in database connection:" + ex.getMessage()); @@ -245,8 +252,16 @@ public static void runCLI(Context context, DOIOrganiser organiser, String[] args + "that could be registered."); } for (DOI doi : dois) { - organiser.register(doi); - context.uncacheEntity(doi); + doi = context.reloadEntity(doi); + try { + organiser.register(doi); + context.commit(); + } catch (SQLException e) { + System.err.format("DOI %s for object %s registration failed, skipping: %s%n", + doi.getDSpaceObject().getID().toString(), + doi.getDoi(), e.getMessage()); + context.rollback(); + } } } catch (SQLException ex) { System.err.println("Error in database connection:" + ex.getMessage()); @@ -268,8 +283,9 @@ public static void runCLI(Context context, DOIOrganiser organiser, String[] args } for (DOI doi : dois) { + doi = context.reloadEntity(doi); organiser.update(doi); - context.uncacheEntity(doi); + context.commit(); } } catch (SQLException ex) { System.err.println("Error in database connection:" + ex.getMessage()); @@ -286,12 +302,17 @@ public static void runCLI(Context context, DOIOrganiser organiser, String[] args + "that could be deleted."); } - Iterator iterator = dois.iterator(); - while (iterator.hasNext()) { - DOI doi = iterator.next(); - iterator.remove(); - organiser.delete(doi.getDoi()); - context.uncacheEntity(doi); + for (DOI doi : dois) { + doi = context.reloadEntity(doi); + try { + organiser.delete(doi.getDoi()); + context.commit(); + } catch (SQLException e) { + System.err.format("DOI %s for object %s deletion failed, skipping: %s%n", + doi.getDSpaceObject().getID().toString(), + doi.getDoi(), e.getMessage()); + context.rollback(); + } } } catch (SQLException ex) { System.err.println("Error in database connection:" + ex.getMessage()); @@ -473,10 +494,10 @@ public void register(DOI doiRow, Filter filter) throws SQLException, DOIIdentifi } /** - * Register DOI with the provider + * Register DOI with the provider. * @param doiRow - doi to register - * @throws SQLException - * @throws DOIIdentifierException + * @throws SQLException passed through + * @throws DOIIdentifierException passed through */ public void register(DOI doiRow) throws SQLException, DOIIdentifierException { register(doiRow, this.filter); @@ -485,18 +506,18 @@ public void register(DOI doiRow) throws SQLException, DOIIdentifierException { /** * Reserve DOI with the provider, * @param doiRow - doi to reserve - * @throws SQLException - * @throws DOIIdentifierException */ public void reserve(DOI doiRow) { reserve(doiRow, this.filter); } /** - * Reserve DOI with the provider + * Reserve DOI with the provider. * @param doiRow - doi to reserve - * @throws SQLException - * @throws DOIIdentifierException + * @param filter - Logical item filter to determine whether this + * identifier should be reserved online. + * @throws IllegalStateException on invalid DOI. + * @throws RuntimeException on database error. */ public void reserve(DOI doiRow, Filter filter) { DSpaceObject dso = doiRow.getDSpaceObject(); From cc3841d2122e1df9595d2dade28f7614f00a0a6b Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 17 Sep 2024 16:22:01 -0400 Subject: [PATCH 433/939] Reorganize some documentation. (cherry picked from commit 77425163f2d14567013fad1b8e4f73eee631bd63) --- .../dspace/identifier/doi/package-info.java | 19 +++++++---------- .../dspace/identifier/ezid/package-info.java | 21 +++++++++++++++++++ 2 files changed, 29 insertions(+), 11 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/identifier/ezid/package-info.java diff --git a/dspace-api/src/main/java/org/dspace/identifier/doi/package-info.java b/dspace-api/src/main/java/org/dspace/identifier/doi/package-info.java index e92170daf0c..30ee5c45dd3 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/doi/package-info.java +++ b/dspace-api/src/main/java/org/dspace/identifier/doi/package-info.java @@ -6,17 +6,14 @@ * http://www.dspace.org/license/ */ /** - * Make requests to the DOI registration angencies, f.e.to - * EZID DOI service, and analyze the responses. - * + * Make requests to the DOI registration agencies and analyze the responses. + * + *

    + * {@link DOIOrganiser} is a tool for managing DOI registrations. + * *

    - * Use {@link org.dspace.identifier.ezid.EZIDRequestFactory#getInstance} to - * configure an {@link org.dspace.identifier.ezid.EZIDRequest} - * with your authority number and credentials. {@code EZIDRequest} encapsulates - * EZID's operations (lookup, create/mint, modify, delete...). - * An operation returns an {@link org.dspace.identifier.ezid.EZIDResponse} which - * gives easy access to EZID's status code and value, status of the underlying - * HTTP request, and key/value pairs found in the response body (if any). - *

    + * Classes specific to the DataCite + * registrar are here. See {@link org.dspace.identifier.ezid} for the + * EZID registrar. */ package org.dspace.identifier.doi; diff --git a/dspace-api/src/main/java/org/dspace/identifier/ezid/package-info.java b/dspace-api/src/main/java/org/dspace/identifier/ezid/package-info.java new file mode 100644 index 00000000000..bff0bdea26e --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/identifier/ezid/package-info.java @@ -0,0 +1,21 @@ +/** + * 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/ + */ +/** + * DOI classes specific to the EZID registrar. + * + *

    + * Use {@link org.dspace.identifier.ezid.EZIDRequestFactory#getInstance} to + * configure an {@link org.dspace.identifier.ezid.EZIDRequest} + * with your authority number and credentials. {@code EZIDRequest} encapsulates + * EZID's operations (lookup, create/mint, modify, delete...). + * An operation returns an {@link org.dspace.identifier.ezid.EZIDResponse} which + * gives easy access to EZID's status code and value, status of the underlying + * HTTP request, and key/value pairs found in the response body (if any). + *

    + */ +package org.dspace.identifier.ezid; From 6d187a08fda16b5c24a281cc8e0e4d41dc335e66 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 21 Jan 2025 14:03:06 -0500 Subject: [PATCH 434/939] Document and handle the actual exceptions returned by register. (cherry picked from commit 2edea69ca3cbb1448557517bce25b96fbfc3bd8e) --- .../dspace/identifier/doi/DOIOrganiser.java | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java b/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java index 88738226755..507503ffaa1 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java +++ b/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java @@ -264,10 +264,10 @@ public static void runCLI(Context context, DOIOrganiser organiser, String[] args } } } catch (SQLException ex) { - System.err.println("Error in database connection:" + ex.getMessage()); + System.err.format("Error in database connection: %s%n", ex.getMessage()); ex.printStackTrace(System.err); - } catch (DOIIdentifierException ex) { - System.err.println("Error registering DOI identifier:" + ex.getMessage()); + } catch (RuntimeException ex) { + System.err.format("Error registering DOI identifier: %s%n", ex.getMessage()); } } @@ -422,12 +422,18 @@ public void list(String processName, PrintStream out, PrintStream err, Integer . /** * Register DOI with the provider - * @param doiRow - doi to register - * @param filter - logical item filter to override - * @throws SQLException - * @throws DOIIdentifierException + * @param doiRow DOI to register + * @param filter logical item filter to override + * @throws IllegalArgumentException + * if {@link doiRow} does not name an Item. + * @throws IllegalStateException + * on invalid DOI. + * @throws RuntimeException + * on database error. */ - public void register(DOI doiRow, Filter filter) throws SQLException, DOIIdentifierException { + public void register(DOI doiRow, Filter filter) + throws IllegalArgumentException, IllegalStateException, + RuntimeException { DSpaceObject dso = doiRow.getDSpaceObject(); if (Constants.ITEM != dso.getType()) { throw new IllegalArgumentException("Currenty DSpace supports DOIs for Items only."); @@ -495,11 +501,14 @@ public void register(DOI doiRow, Filter filter) throws SQLException, DOIIdentifi /** * Register DOI with the provider. - * @param doiRow - doi to register - * @throws SQLException passed through - * @throws DOIIdentifierException passed through + * @param doiRow DOI to register + * @throws IllegalArgumentException passed through. + * @throws IllegalStateException passed through. + * @throws RuntimeException passed through. */ - public void register(DOI doiRow) throws SQLException, DOIIdentifierException { + public void register(DOI doiRow) + throws IllegalStateException, IllegalArgumentException, + RuntimeException { register(doiRow, this.filter); } From 338e17d1973c4a910b2a3ef4e03068127070378b Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 24 Jan 2025 16:45:20 -0600 Subject: [PATCH 435/939] Remove unused configuration (cherry picked from commit 845c706cacdc13706acd120a63083764d3141194) --- dspace-api/src/test/data/dspaceFolder/config/local.cfg | 2 -- dspace/config/dspace.cfg | 2 -- 2 files changed, 4 deletions(-) diff --git a/dspace-api/src/test/data/dspaceFolder/config/local.cfg b/dspace-api/src/test/data/dspaceFolder/config/local.cfg index 35ed8a235b7..0b4fe8288cf 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/local.cfg +++ b/dspace-api/src/test/data/dspaceFolder/config/local.cfg @@ -156,8 +156,6 @@ useProxies = true proxies.trusted.ipranges = 7.7.7.7 proxies.trusted.include_ui_ip = true -csvexport.dir = dspace-server-webapp/src/test/data/dspaceFolder/exports - # For the tests we have to disable this health indicator because there isn't a mock server and the calculated status was DOWN management.health.solrOai.enabled = false diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index c21fb54655b..14d962f5683 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -19,8 +19,6 @@ # Windows note: Please remember to use forward slashes for all paths (e.g. C:/dspace) dspace.dir = /dspace -csvexport.dir = ${dspace.dir}/exports - # Public URL of DSpace backend ('server' webapp). May require a port number if not using standard ports (80 or 443) # DO NOT end it with '/'. # This is where REST API and all enabled server modules (OAI-PMH, SWORD, SWORDv2, RDF, etc) will respond. From 859dd4c69d6b91a311a0f8fccf742777c915748b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 23:07:18 +0000 Subject: [PATCH 436/939] Bump io.grpc:grpc-context from 1.69.0 to 1.70.0 Bumps [io.grpc:grpc-context](https://github.com/grpc/grpc-java) from 1.69.0 to 1.70.0. - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.69.0...v1.70.0) --- updated-dependencies: - dependency-name: io.grpc:grpc-context dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index aec280f63aa..21074f3def7 100644 --- a/pom.xml +++ b/pom.xml @@ -1725,7 +1725,7 @@ io.grpc grpc-context - 1.69.0 + 1.70.0 com.google.http-client From 3885966b4dd58cfa1ead8c6d24f53acf2fa3b3cc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 23:07:21 +0000 Subject: [PATCH 437/939] Bump dnsjava:dnsjava from 3.6.2 to 3.6.3 Bumps [dnsjava:dnsjava](https://github.com/dnsjava/dnsjava) from 3.6.2 to 3.6.3. - [Release notes](https://github.com/dnsjava/dnsjava/releases) - [Changelog](https://github.com/dnsjava/dnsjava/blob/master/Changelog) - [Commits](https://github.com/dnsjava/dnsjava/compare/v3.6.2...v3.6.3) --- updated-dependencies: - dependency-name: dnsjava:dnsjava dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 77adf3b9675..7bbcf26b6a1 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -651,7 +651,7 @@ dnsjava dnsjava - 3.6.2 + 3.6.3 From e8a54e698c9f92eb0f7e049d76049c9fbe77203e Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 29 Jan 2025 12:42:25 +0100 Subject: [PATCH 438/939] cli speed improvement: periodically uncache entities when processing many --- .../src/main/java/org/dspace/core/Context.java | 12 ++++++++++++ .../src/main/java/org/dspace/core/DBConnection.java | 12 ++++++++++++ .../java/org/dspace/core/HibernateDBConnection.java | 6 ++++++ .../java/org/dspace/discovery/SolrServiceImpl.java | 6 ++++++ 4 files changed, 36 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/core/Context.java b/dspace-api/src/main/java/org/dspace/core/Context.java index 82b39dd2dfc..391405c0ae6 100644 --- a/dspace-api/src/main/java/org/dspace/core/Context.java +++ b/dspace-api/src/main/java/org/dspace/core/Context.java @@ -951,4 +951,16 @@ public void setAuthenticationMethod(final String authenticationMethod) { public boolean isContextUserSwitched() { return currentUserPreviousState != null; } + + /** + * Remove all entities from the cache and reload the current user entity. This is useful when batch processing + * a large number of entities when the calling code requires the cache to be completely cleared before continuing. + * + * @throws SQLException if a database error occurs. + */ + public void uncacheEntities() throws SQLException { + dbConnection.uncacheEntities(); + reloadContextBoundEntities(); + } + } diff --git a/dspace-api/src/main/java/org/dspace/core/DBConnection.java b/dspace-api/src/main/java/org/dspace/core/DBConnection.java index cb5825eec1d..210998902c5 100644 --- a/dspace-api/src/main/java/org/dspace/core/DBConnection.java +++ b/dspace-api/src/main/java/org/dspace/core/DBConnection.java @@ -148,4 +148,16 @@ public interface DBConnection { * @throws java.sql.SQLException passed through. */ public void uncacheEntity(E entity) throws SQLException; + + /** + * Remove all entities from the session cache. + * + *

    Entities removed from cache are not saved in any way. Therefore, if you + * have modified any entities, you should be sure to {@link #commit()} changes + * before calling this method. + * + * @throws SQLException passed through. + */ + public void uncacheEntities() throws SQLException; + } diff --git a/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java b/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java index 3321e4d837e..11661ee64cc 100644 --- a/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java +++ b/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java @@ -337,4 +337,10 @@ public void uncacheEntity(E entity) throws SQLExcep } } } + + @Override + public void uncacheEntities() throws SQLException { + getSession().clear(); + } + } diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java index 0cf2aa50af6..8cb3c34db57 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java @@ -72,6 +72,7 @@ import org.dspace.discovery.indexobject.IndexableItem; import org.dspace.discovery.indexobject.factory.IndexFactory; import org.dspace.discovery.indexobject.factory.IndexObjectFactoryFactory; +import org.dspace.discovery.indexobject.factory.ItemIndexFactory; import org.dspace.eperson.Group; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.GroupService; @@ -341,6 +342,7 @@ public void updateIndex(Context context, boolean force, String type) { try { final List indexableObjectServices = indexObjectServiceFactory. getIndexFactories(); + int indexObject = 0; for (IndexFactory indexableObjectService : indexableObjectServices) { if (type == null || StringUtils.equals(indexableObjectService.getType(), type)) { final Iterator indexableObjects = indexableObjectService.findAll(context); @@ -348,6 +350,10 @@ public void updateIndex(Context context, boolean force, String type) { final IndexableObject indexableObject = indexableObjects.next(); indexContent(context, indexableObject, force); context.uncacheEntity(indexableObject.getIndexedObject()); + indexObject++; + if ((indexObject % 100) == 0 && indexableObjectService instanceof ItemIndexFactory) { + context.uncacheEntities(); + } } } } From 6b4c7a773d4e16d4eebfa73539b85e6b7f44575a Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Wed, 22 Jan 2025 13:33:58 +0100 Subject: [PATCH 439/939] 124579: If no ORIGINAL bundle exists, end the process gracefully instead of throwing NPE (cherry picked from commit 046c1b5d0561b6bca558c71265c2bbc7cb331230) --- .../src/main/java/org/dspace/ctask/general/ClamScan.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/ctask/general/ClamScan.java b/dspace-api/src/main/java/org/dspace/ctask/general/ClamScan.java index d1fa70565df..c939b67af56 100644 --- a/dspace-api/src/main/java/org/dspace/ctask/general/ClamScan.java +++ b/dspace-api/src/main/java/org/dspace/ctask/general/ClamScan.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.List; +import org.apache.commons.collections4.ListUtils; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; @@ -99,7 +100,12 @@ public int perform(DSpaceObject dso) throws IOException { } try { - Bundle bundle = itemService.getBundles(item, "ORIGINAL").get(0); + List bundles = itemService.getBundles(item, "ORIGINAL"); + if (ListUtils.emptyIfNull(bundles).isEmpty()) { + setResult("No ORIGINAL bundle found for item: " + getItemHandle(item)); + return Curator.CURATE_SKIP; + } + Bundle bundle = bundles.get(0); results = new ArrayList(); for (Bitstream bitstream : bundle.getBitstreams()) { InputStream inputstream = bitstreamService.retrieve(Curator.curationContext(), bitstream); From 094af861b305ff5d4dd3f0ffcdc0c3a62ea3fc91 Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Thu, 23 Jan 2025 12:02:15 +0100 Subject: [PATCH 440/939] 124579: catch any exceptions during process and end task gracefully (cherry picked from commit 11a1c9ab06703b90c87e14843def82571fc6c51f) --- .../main/java/org/dspace/ctask/general/ClamScan.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/ctask/general/ClamScan.java b/dspace-api/src/main/java/org/dspace/ctask/general/ClamScan.java index c939b67af56..6b84efe3196 100644 --- a/dspace-api/src/main/java/org/dspace/ctask/general/ClamScan.java +++ b/dspace-api/src/main/java/org/dspace/ctask/general/ClamScan.java @@ -15,12 +15,10 @@ import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketException; -import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import org.apache.commons.collections4.ListUtils; -import org.dspace.authorize.AuthorizeException; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.DSpaceObject; @@ -127,10 +125,11 @@ public int perform(DSpaceObject dso) throws IOException { } } - } catch (AuthorizeException authE) { - throw new IOException(authE.getMessage(), authE); - } catch (SQLException sqlE) { - throw new IOException(sqlE.getMessage(), sqlE); + } catch (Exception e) { + // Any exception which may occur during the performance of the task should be caught here + // And end the process gracefully + log.error("Error scanning item: " + getItemHandle(item), e); + status = Curator.CURATE_ERROR; } finally { closeSession(); } From 63fbffc963d5beb2cbd89afc807ec63c668e8af9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Feb 2025 23:19:27 +0000 Subject: [PATCH 441/939] Bump the apache-commons group with 2 updates Bumps the apache-commons group with 2 updates: [commons-codec:commons-codec](https://github.com/apache/commons-codec) and org.apache.commons:commons-pool2. Updates `commons-codec:commons-codec` from 1.17.2 to 1.18.0 - [Changelog](https://github.com/apache/commons-codec/blob/master/RELEASE-NOTES.txt) - [Commits](https://github.com/apache/commons-codec/compare/rel/commons-codec-1.17.2...rel/commons-codec-1.18.0) Updates `org.apache.commons:commons-pool2` from 2.12.0 to 2.12.1 --- updated-dependencies: - dependency-name: commons-codec:commons-codec dependency-type: direct:production update-type: version-update:semver-minor dependency-group: apache-commons - dependency-name: org.apache.commons:commons-pool2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: apache-commons ... Signed-off-by: dependabot[bot] --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index aec280f63aa..ef8b4e4c09f 100644 --- a/pom.xml +++ b/pom.xml @@ -1474,7 +1474,7 @@ commons-codec commons-codec - 1.17.2 + 1.18.0 org.apache.commons @@ -1521,7 +1521,7 @@ org.apache.commons commons-pool2 - 2.12.0 + 2.12.1 org.apache.commons From 723ad07ae9225d0d92ea542d74612059a7b908f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Feb 2025 23:20:40 +0000 Subject: [PATCH 442/939] Bump joda-time:joda-time from 2.13.0 to 2.13.1 Bumps [joda-time:joda-time](https://github.com/JodaOrg/joda-time) from 2.13.0 to 2.13.1. - [Release notes](https://github.com/JodaOrg/joda-time/releases) - [Changelog](https://github.com/JodaOrg/joda-time/blob/main/RELEASE-NOTES.txt) - [Commits](https://github.com/JodaOrg/joda-time/compare/v2.13.0...v2.13.1) --- updated-dependencies: - dependency-name: joda-time:joda-time dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index aec280f63aa..2acf9d49712 100644 --- a/pom.xml +++ b/pom.xml @@ -1536,7 +1536,7 @@ joda-time joda-time - 2.13.0 + 2.13.1 com.sun.mail From 94ad5744e21e097bab27ba99463ad6af18de31c2 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 31 Jul 2024 21:18:46 +0200 Subject: [PATCH 443/939] [DURACOM-288] Add DSpace property to specify addition rest url --- dspace/config/dspace.cfg | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index c21fb54655b..f6a8b9dcefe 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -28,6 +28,10 @@ csvexport.dir = ${dspace.dir}/exports # and is usually "synced" with the "rest" section in the DSpace User Interface's config.*.yml. # It corresponds to the URL that you would type into your browser to access the REST API. dspace.server.url = http://localhost:8080/server +# Additional URL of DSpace backend which could be used by DSpace frontend during SSR execution. +# May require a port number if not using standard ports (80 or 443) +# DO NOT end it with '/'. +dspace.server.ssr.url = # Public URL of DSpace frontend (Angular UI). May require a port number if not using standard ports (80 or 443) # DO NOT end it with '/'. From ea15a3b9650eb3693c50af3b04f56efce7631a68 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 31 Jul 2024 21:22:03 +0200 Subject: [PATCH 444/939] [DURACOM-288] Add check to allow base object uri containing dspace.server.ssr.uri --- .../java/org/dspace/app/rest/utils/Utils.java | 16 ++- .../rest/AuthorizationRestRepositoryIT.java | 127 ++++++++++++++++++ 2 files changed, 141 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java index ed6e26ed0fb..498247679d6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java @@ -989,21 +989,27 @@ public Object getDSpaceAPIObjectFromRest(Context context, BaseObjectRest restObj */ public BaseObjectRest getBaseObjectRestFromUri(Context context, String uri) throws SQLException { String dspaceUrl = configurationService.getProperty("dspace.server.url"); + String dspaceSSRUrl = configurationService.getProperty("dspace.server.ssr.url", ""); // Convert strings to URL objects. // Do this early to check that inputs are well-formed. URL dspaceUrlObject; + URL dspaceUrlSSRObject = null; URL requestUrlObject; try { dspaceUrlObject = new URL(dspaceUrl); requestUrlObject = new URL(uri); + if (StringUtils.isNoneBlank(dspaceSSRUrl)) { + dspaceUrlSSRObject = new URL(dspaceSSRUrl); + } } catch (MalformedURLException ex) { throw new IllegalArgumentException( String.format("Configuration '%s' or request '%s' is malformed", dspaceUrl, uri)); } // Check whether the URI could be valid. - if (!urlIsPrefixOf(dspaceUrl, uri)) { + if (!urlIsPrefixOf(dspaceUrl, uri) && (StringUtils.isBlank(dspaceSSRUrl) || + !urlIsPrefixOf(dspaceSSRUrl, uri))) { throw new IllegalArgumentException("the supplied uri is not ours: " + uri); } @@ -1013,10 +1019,16 @@ public BaseObjectRest getBaseObjectRestFromUri(Context context, String uri) thro String[] requestPath = StringUtils.split(requestUrlObject.getPath(), '/'); String[] uriParts = Arrays.copyOfRange(requestPath, dspacePathLength, requestPath.length); + String[] uriSSRParts = new String[0]; + if (StringUtils.isNoneBlank(dspaceSSRUrl) && !Objects.isNull(dspaceUrlSSRObject)) { + int dspaceSSRPathLength = StringUtils.split(dspaceUrlSSRObject.getPath(), '/').length; + uriSSRParts = Arrays.copyOfRange(requestPath, dspaceSSRPathLength, + requestPath.length); + } if ("api".equalsIgnoreCase(uriParts[0])) { uriParts = Arrays.copyOfRange(uriParts, 1, uriParts.length); } - if (uriParts.length != 3) { + if (uriParts.length != 3 && uriSSRParts.length != 3) { throw new IllegalArgumentException("the supplied uri lacks required path elements: " + uri); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java index c334d110b11..94d69590181 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java @@ -837,6 +837,133 @@ public void findByObjectBadRequestTest() throws Exception { .andExpect(status().isBadRequest()); } + @Test + /** + * Verify that the findByObject return the 400 Bad Request response for invalid or missing URI (required parameter) + * + * @throws Exception + */ + public void findByObjectBadRequestSSRTest() throws Exception { + Site site = siteService.findSite(context); + SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT); + String siteUri = "http://ssr.example.com/api/core/sites/" + siteRest.getId(); + String[] invalidUris = new String[] { + "invalid-uri", + "", + "http://localhost/api/wrongcategory/wrongmodel/1", + "http://localhost/api/core/sites/this-is-not-an-uuid" + }; + + // disarm the alwaysThrowExceptionFeature + configurationService.setProperty("org.dspace.app.rest.authorization.AlwaysThrowExceptionFeature.turnoff", true); + configurationService.setProperty("dspace.server.ssr.url", "http://ssr.example.com/api"); + + String adminToken = getAuthToken(admin.getEmail(), password); + String epersonToken = getAuthToken(eperson.getEmail(), password); + + // verify that it works for administrator users + getClient(adminToken).perform(get("/api/authz/authorizations/search/object") + .param("uri", siteUri)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.authorizations", Matchers.hasSize(greaterThanOrEqualTo(1)))) + .andExpect(jsonPath("$._embedded.authorizations", Matchers.everyItem( + Matchers.anyOf( + JsonPathMatchers.hasJsonPath("$.type", is("authorization")), + JsonPathMatchers.hasJsonPath("$._embedded.feature", + Matchers.allOf( + is(alwaysTrue.getName()) + )), + JsonPathMatchers.hasJsonPath("$._embedded.feature", + Matchers.not(Matchers.anyOf( + is(alwaysFalse.getName()), + is(alwaysException.getName()), + is(trueForTestUsers.getName()), + is(trueForAdmins.getName()) + ) + )), + JsonPathMatchers.hasJsonPath("$._embedded.feature.resourcetypes", + Matchers.hasItem(is("authorization"))), + JsonPathMatchers.hasJsonPath("$.id", + Matchers.anyOf( + Matchers.startsWith(eperson.getID().toString()), + Matchers.endsWith(siteRest.getUniqueType() + "_" + siteRest.getId())))) + ) + ) + ) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/authz/authorizations/search/object"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(1))); + + // verify that it works for authenticated users + getClient(epersonToken).perform(get("/api/authz/authorizations/search/object") + .param("uri", siteUri)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.authorizations", Matchers.hasSize(greaterThanOrEqualTo(1)))) + .andExpect(jsonPath("$._embedded.authorizations", Matchers.everyItem( + Matchers.anyOf( + JsonPathMatchers.hasJsonPath("$.type", is("authorization")), + JsonPathMatchers.hasJsonPath("$._embedded.feature", + Matchers.allOf( + is(alwaysTrue.getName()) + )), + JsonPathMatchers.hasJsonPath("$._embedded.feature", + Matchers.not(Matchers.anyOf( + is(alwaysFalse.getName()), + is(alwaysException.getName()), + is(trueForTestUsers.getName()), + is(trueForAdmins.getName()) + ) + )), + JsonPathMatchers.hasJsonPath("$._embedded.feature.resourcetypes", + Matchers.hasItem(is("authorization"))), + JsonPathMatchers.hasJsonPath("$.id", + Matchers.anyOf( + Matchers.startsWith(eperson.getID().toString()), + Matchers.endsWith(siteRest.getUniqueType() + "_" + siteRest.getId())))) + ) + ) + ) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/authz/authorizations/search/object"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(1))); + + // verify that it works for anonymous users + getClient(epersonToken).perform(get("/api/authz/authorizations/search/object") + .param("uri", siteUri)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.authorizations", Matchers.hasSize(greaterThanOrEqualTo(1)))) + .andExpect(jsonPath("$._embedded.authorizations", Matchers.everyItem( + Matchers.anyOf( + JsonPathMatchers.hasJsonPath("$.type", is("authorization")), + JsonPathMatchers.hasJsonPath("$._embedded.feature", + Matchers.allOf( + is(alwaysTrue.getName()) + )), + JsonPathMatchers.hasJsonPath("$._embedded.feature", + Matchers.not(Matchers.anyOf( + is(alwaysFalse.getName()), + is(alwaysException.getName()), + is(trueForTestUsers.getName()), + is(trueForAdmins.getName()) + ) + )), + JsonPathMatchers.hasJsonPath("$._embedded.feature.resourcetypes", + Matchers.hasItem(is("authorization"))), + JsonPathMatchers.hasJsonPath("$.id", + Matchers.anyOf( + Matchers.startsWith(eperson.getID().toString()), + Matchers.endsWith(siteRest.getUniqueType() + "_" + siteRest.getId())))) + ) + ) + ) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/authz/authorizations/search/object"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(1))); + } + @Test /** * Verify that the findByObject return the 401 Unauthorized response when an eperson is involved From 369bda130abf10a1a082351d262ccf17f7d76eb0 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 1 Aug 2024 15:12:20 +0200 Subject: [PATCH 445/939] [DURACOM-288] Add dspace.server.ssr.uri in the local.cfg.EXAMPLE --- dspace/config/local.cfg.EXAMPLE | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dspace/config/local.cfg.EXAMPLE b/dspace/config/local.cfg.EXAMPLE index 7176ed275a5..e2d0fae3e53 100644 --- a/dspace/config/local.cfg.EXAMPLE +++ b/dspace/config/local.cfg.EXAMPLE @@ -40,6 +40,11 @@ dspace.dir=/dspace # It corresponds to the URL that you would type into your browser to access the REST API. dspace.server.url = http://localhost:8080/server +# Additional URL of DSpace backend which could be used by DSpace frontend during SSR execution. +# May require a port number if not using standard ports (80 or 443) +# DO NOT end it with '/'. +dspace.server.ssr.url = + # Public URL of DSpace frontend (Angular UI). May require a port number if not using standard ports (80 or 443) # DO NOT end it with '/'. # This is used by the backend to provide links in emails, RSS feeds, Sitemaps, etc. From a2e518444c942c1a89d719c28d6bf96a633d4d4a Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 27 Sep 2024 12:11:40 +0200 Subject: [PATCH 446/939] [DURACOM-288] Fix test --- .../java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java index 94d69590181..dc5319ed219 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java @@ -843,6 +843,7 @@ public void findByObjectBadRequestTest() throws Exception { * * @throws Exception */ + @Test public void findByObjectBadRequestSSRTest() throws Exception { Site site = siteService.findSite(context); SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT); @@ -964,7 +965,6 @@ public void findByObjectBadRequestSSRTest() throws Exception { .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(1))); } - @Test /** * Verify that the findByObject return the 401 Unauthorized response when an eperson is involved * From c39042095a5d9bde4db2c93b1b8c0346af7711e6 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 8 Jan 2025 18:38:00 +0100 Subject: [PATCH 447/939] [DURACOM-288] Code refactoring and test improvements --- .../java/org/dspace/app/rest/utils/Utils.java | 16 +- .../rest/AuthorizationRestRepositoryIT.java | 187 +++++++++++++++++- 2 files changed, 185 insertions(+), 18 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java index 498247679d6..0e685e0619d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java @@ -989,7 +989,7 @@ public Object getDSpaceAPIObjectFromRest(Context context, BaseObjectRest restObj */ public BaseObjectRest getBaseObjectRestFromUri(Context context, String uri) throws SQLException { String dspaceUrl = configurationService.getProperty("dspace.server.url"); - String dspaceSSRUrl = configurationService.getProperty("dspace.server.ssr.url", ""); + String dspaceSSRUrl = configurationService.getProperty("dspace.server.ssr.url", dspaceUrl); // Convert strings to URL objects. // Do this early to check that inputs are well-formed. @@ -1008,8 +1008,7 @@ public BaseObjectRest getBaseObjectRestFromUri(Context context, String uri) thro } // Check whether the URI could be valid. - if (!urlIsPrefixOf(dspaceUrl, uri) && (StringUtils.isBlank(dspaceSSRUrl) || - !urlIsPrefixOf(dspaceSSRUrl, uri))) { + if (!urlIsPrefixOf(dspaceUrl, uri) && !urlIsPrefixOf(dspaceSSRUrl, uri)) { throw new IllegalArgumentException("the supplied uri is not ours: " + uri); } @@ -1019,12 +1018,11 @@ public BaseObjectRest getBaseObjectRestFromUri(Context context, String uri) thro String[] requestPath = StringUtils.split(requestUrlObject.getPath(), '/'); String[] uriParts = Arrays.copyOfRange(requestPath, dspacePathLength, requestPath.length); - String[] uriSSRParts = new String[0]; - if (StringUtils.isNoneBlank(dspaceSSRUrl) && !Objects.isNull(dspaceUrlSSRObject)) { - int dspaceSSRPathLength = StringUtils.split(dspaceUrlSSRObject.getPath(), '/').length; - uriSSRParts = Arrays.copyOfRange(requestPath, dspaceSSRPathLength, - requestPath.length); - } + + int dspaceSSRPathLength = StringUtils.split(dspaceUrlSSRObject.getPath(), '/').length; + String[] uriSSRParts = Arrays.copyOfRange(requestPath, dspaceSSRPathLength, + requestPath.length); + if ("api".equalsIgnoreCase(uriParts[0])) { uriParts = Arrays.copyOfRange(uriParts, 1, uriParts.length); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java index dc5319ed219..a35e62e1f85 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java @@ -839,21 +839,19 @@ public void findByObjectBadRequestTest() throws Exception { @Test /** - * Verify that the findByObject return the 400 Bad Request response for invalid or missing URI (required parameter) + * Verify that the search by object works properly in allowed scenarios, when SSR rest url is defined, : + * - for an administrator + * - for an administrator that want to inspect permission of the anonymous users or another user + * - for a logged-in "normal" user + * - for anonymous * * @throws Exception */ @Test - public void findByObjectBadRequestSSRTest() throws Exception { + public void findByObjectSSRTest() throws Exception { Site site = siteService.findSite(context); SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT); String siteUri = "http://ssr.example.com/api/core/sites/" + siteRest.getId(); - String[] invalidUris = new String[] { - "invalid-uri", - "", - "http://localhost/api/wrongcategory/wrongmodel/1", - "http://localhost/api/core/sites/this-is-not-an-uuid" - }; // disarm the alwaysThrowExceptionFeature configurationService.setProperty("org.dspace.app.rest.authorization.AlwaysThrowExceptionFeature.turnoff", true); @@ -931,7 +929,7 @@ public void findByObjectBadRequestSSRTest() throws Exception { .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(1))); // verify that it works for anonymous users - getClient(epersonToken).perform(get("/api/authz/authorizations/search/object") + getClient().perform(get("/api/authz/authorizations/search/object") .param("uri", siteUri)) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations", Matchers.hasSize(greaterThanOrEqualTo(1)))) @@ -965,6 +963,177 @@ public void findByObjectBadRequestSSRTest() throws Exception { .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(1))); } + /** + * Verify that the findByObject return the 400 Bad Request response for invalid or missing URI (required parameter) + * + * @throws Exception + */ + @Test + public void findByObjectBadRequestSSRTest() throws Exception { + // disarm the alwaysThrowExceptionFeature + configurationService.setProperty("org.dspace.app.rest.authorization.AlwaysThrowExceptionFeature.turnoff", true); + configurationService.setProperty("dspace.server.ssr.url", "http://ssr.example.com/api"); + String[] invalidUris = new String[] { + "invalid-uri", + "", + "http://localhost/api/wrongcategory/wrongmodel/1", + "http://localhost/api/core/sites/this-is-not-an-uuid" + }; + + String adminToken = getAuthToken(admin.getEmail(), password); + String epersonToken = getAuthToken(eperson.getEmail(), password); + for (String invalidUri : invalidUris) { + log.debug("findByObjectBadRequestTest - Testing the URI: " + invalidUri); + + // verify that it works for administrators with an invalid or missing uri - with eperson parameter + getClient(adminToken).perform(get("/api/authz/authorizations/search/object") + .param("uri", invalidUri) + .param("eperson", admin.getID().toString())) + .andExpect(status().isBadRequest()); + + // verify that it works for administrators with an invalid or missing uri - without eperson parameter + getClient(adminToken).perform(get("/api/authz/authorizations/search/object") + .param("uri", invalidUri)) + .andExpect(status().isBadRequest()); + + // verify that it works for normal loggedin users with an invalid or missing uri - with eperson parameter + getClient(epersonToken).perform(get("/api/authz/authorizations/search/object") + .param("uri", invalidUri) + .param("eperson", eperson.getID().toString())) + .andExpect(status().isBadRequest()); + + // verify that it works for normal loggedin users with an invalid or missing uri - without eperson parameter + getClient(epersonToken).perform(get("/api/authz/authorizations/search/object") + .param("uri", invalidUri)) + .andExpect(status().isBadRequest()); + + // verify that it works for administrators inspecting other users with an invalid or missing uri - by + // using the eperson parameter + getClient(adminToken).perform(get("/api/authz/authorizations/search/object") + .param("uri", invalidUri) + .param("eperson", eperson.getID().toString())) + .andExpect(status().isBadRequest()); + + // verify that it works for administrators inspecting other users with an invalid or missing uri - by + // assuming login + getClient(adminToken).perform(get("/api/authz/authorizations/search/object") + .param("uri", invalidUri) + .header("X-On-Behalf-Of", eperson.getID())) + .andExpect(status().isBadRequest()); + + // verify that it works for anonymous users with an invalid or missing uri + getClient().perform(get("/api/authz/authorizations/search/object") + .param("uri", invalidUri)) + .andExpect(status().isBadRequest()); + } + getClient(adminToken).perform(get("/api/authz/authorizations/search/object") + .param("eperson", admin.getID().toString())) + .andExpect(status().isBadRequest()); + getClient(epersonToken).perform(get("/api/authz/authorizations/search/object") + .param("eperson", eperson.getID().toString())) + .andExpect(status().isBadRequest()); + getClient(adminToken).perform(get("/api/authz/authorizations/search/object") + .param("eperson", eperson.getID().toString())) + .andExpect(status().isBadRequest()); + getClient().perform(get("/api/authz/authorizations/search/object")) + .andExpect(status().isBadRequest()); + getClient(adminToken).perform(get("/api/authz/authorizations/search/object")) + .andExpect(status().isBadRequest()); + } + + /** + * Verify that the findByObject return an empty page when the requested object doesn't exist but the uri is + * potentially valid (i.e. deleted object) + * + * @throws Exception + */ + @Test + public void findByNotExistingObjectSSSTest() throws Exception { + String wrongSiteUri = "http://localhost/api/core/sites/" + UUID.randomUUID(); + + // disarm the alwaysThrowExceptionFeature + configurationService.setProperty("org.dspace.app.rest.authorization.AlwaysThrowExceptionFeature.turnoff", true); + configurationService.setProperty("dspace.server.ssr.url", "http://ssr.example.com/api"); + + String adminToken = getAuthToken(admin.getEmail(), password); + + // verify that it works for administrators, no result - with eperson parameter + getClient(adminToken).perform(get("/api/authz/authorizations/search/object") + .param("uri", wrongSiteUri) + .param("eperson", admin.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", JsonPathMatchers.hasNoJsonPath("$._embedded.authorizations"))) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/authz/authorizations/search/object"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + // verify that it works for administrators, no result - without eperson parameter + getClient(adminToken).perform(get("/api/authz/authorizations/search/object") + .param("uri", wrongSiteUri)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", JsonPathMatchers.hasNoJsonPath("$._embedded.authorizations"))) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/authz/authorizations/search/object"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + String epersonToken = getAuthToken(eperson.getEmail(), password); + + // verify that it works for normal loggedin users - with eperson parameter + getClient(epersonToken).perform(get("/api/authz/authorizations/search/object") + .param("uri", wrongSiteUri) + .param("eperson", eperson.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", JsonPathMatchers.hasNoJsonPath("$._embedded.authorizations"))) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/authz/authorizations/search/object"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + // verify that it works for normal loggedin users - without eperson parameter + getClient(epersonToken).perform(get("/api/authz/authorizations/search/object") + .param("uri", wrongSiteUri)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", JsonPathMatchers.hasNoJsonPath("$._embedded.authorizations"))) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/authz/authorizations/search/object"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + // verify that it works for administrators inspecting other users - by using the eperson parameter + getClient(adminToken).perform(get("/api/authz/authorizations/search/object") + .param("uri", wrongSiteUri) + .param("eperson", eperson.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", JsonPathMatchers.hasNoJsonPath("$._embedded.authorizations"))) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/authz/authorizations/search/object"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + // verify that it works for administrators inspecting other users - by assuming login + getClient(adminToken).perform(get("/api/authz/authorizations/search/object") + .param("uri", wrongSiteUri) + .header("X-On-Behalf-Of", eperson.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", JsonPathMatchers.hasNoJsonPath("$._embedded.authorizations"))) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/authz/authorizations/search/object"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + // verify that it works for anonymous users + getClient().perform(get("/api/authz/authorizations/search/object") + .param("uri", wrongSiteUri)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", JsonPathMatchers.hasNoJsonPath("$._embedded.authorizations"))) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/authz/authorizations/search/object"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(0))); + } + /** * Verify that the findByObject return the 401 Unauthorized response when an eperson is involved * From 648d9f6972422a1ca47fe70184f1ff3d39158eeb Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 8 Jan 2025 18:38:38 +0100 Subject: [PATCH 448/939] [DURACOM-288] Set dspace.server.ssr.url as dspace.server.url by default --- dspace/config/dspace.cfg | 2 +- dspace/config/local.cfg.EXAMPLE | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index f6a8b9dcefe..c310c505ad4 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -31,7 +31,7 @@ dspace.server.url = http://localhost:8080/server # Additional URL of DSpace backend which could be used by DSpace frontend during SSR execution. # May require a port number if not using standard ports (80 or 443) # DO NOT end it with '/'. -dspace.server.ssr.url = +dspace.server.ssr.url = ${dspace.server.url} # Public URL of DSpace frontend (Angular UI). May require a port number if not using standard ports (80 or 443) # DO NOT end it with '/'. diff --git a/dspace/config/local.cfg.EXAMPLE b/dspace/config/local.cfg.EXAMPLE index e2d0fae3e53..7a565823e8b 100644 --- a/dspace/config/local.cfg.EXAMPLE +++ b/dspace/config/local.cfg.EXAMPLE @@ -43,7 +43,7 @@ dspace.server.url = http://localhost:8080/server # Additional URL of DSpace backend which could be used by DSpace frontend during SSR execution. # May require a port number if not using standard ports (80 or 443) # DO NOT end it with '/'. -dspace.server.ssr.url = +dspace.server.ssr.url = ${dspace.server.url} # Public URL of DSpace frontend (Angular UI). May require a port number if not using standard ports (80 or 443) # DO NOT end it with '/'. From e6f565e90f52c753e22025ee4599479090fef7e5 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 30 Jan 2025 15:27:51 +0100 Subject: [PATCH 449/939] [DURACOM-288] Build endpoint map according to request origin, in order to get internal urls if used --- .../app/rest/RootRestResourceController.java | 2 +- .../app/rest/converter/RootConverter.java | 14 ++++++- .../rest/repository/RootRestRepository.java | 5 ++- .../app/rest/converter/RootConverterTest.java | 38 ++++++++++++++++--- 4 files changed, 48 insertions(+), 11 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RootRestResourceController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RootRestResourceController.java index 7cb1854af88..ebe62ea8cda 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RootRestResourceController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RootRestResourceController.java @@ -44,6 +44,6 @@ public class RootRestResourceController { @RequestMapping(method = RequestMethod.GET) public RootResource listDefinedEndpoint(HttpServletRequest request) { - return converter.toResource(rootRestRepository.getRoot()); + return converter.toResource(rootRestRepository.getRoot(request)); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RootConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RootConverter.java index 61f18a5b3c9..db6e9f5f9a9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RootConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RootConverter.java @@ -9,6 +9,7 @@ import static org.dspace.app.util.Util.getSourceVersion; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.RootRest; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; @@ -23,12 +24,21 @@ public class RootConverter { @Autowired private ConfigurationService configurationService; - public RootRest convert() { + public RootRest convert(HttpServletRequest request) { RootRest rootRest = new RootRest(); rootRest.setDspaceName(configurationService.getProperty("dspace.name")); rootRest.setDspaceUI(configurationService.getProperty("dspace.ui.url")); - rootRest.setDspaceServer(configurationService.getProperty("dspace.server.url")); + String requestUrl = request.getRequestURL().toString(); + String dspaceUrl = configurationService.getProperty("dspace.server.url"); + String dspaceSSRUrl = configurationService.getProperty("dspace.server.ssr.url", dspaceUrl); + if (dspaceUrl.equals(dspaceSSRUrl) || requestUrl.startsWith(dspaceUrl)) { + rootRest.setDspaceServer(dspaceUrl); + } else { + rootRest.setDspaceServer(dspaceSSRUrl); + } rootRest.setDspaceVersion("DSpace " + getSourceVersion()); return rootRest; } + + } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RootRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RootRestRepository.java index 54c678c18e9..3600ed19e1b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RootRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RootRestRepository.java @@ -7,6 +7,7 @@ */ package org.dspace.app.rest.repository; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.app.rest.converter.RootConverter; import org.dspace.app.rest.model.RootRest; import org.springframework.beans.factory.annotation.Autowired; @@ -21,7 +22,7 @@ public class RootRestRepository { @Autowired RootConverter rootConverter; - public RootRest getRoot() { - return rootConverter.convert(); + public RootRest getRoot(HttpServletRequest request) { + return rootConverter.convert(request); } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/converter/RootConverterTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/converter/RootConverterTest.java index acb3b0c263d..951bedbf47c 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/converter/RootConverterTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/converter/RootConverterTest.java @@ -20,6 +20,7 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.mock.web.MockHttpServletRequest; /** * This class' purpose is to test the RootConvertor class. @@ -33,30 +34,55 @@ public class RootConverterTest { @Mock private ConfigurationService configurationService; + private MockHttpServletRequest request = new MockHttpServletRequest(); + + private String serverURL = "https://dspace-rest/server"; + private String serverSSRURL = "http://internal-rest:8080/server"; + @Before public void setUp() throws Exception { when(configurationService.getProperty("dspace.ui.url")).thenReturn("dspaceurl"); when(configurationService.getProperty("dspace.name")).thenReturn("dspacename"); - when(configurationService.getProperty("dspace.server.url")).thenReturn("rest"); + when(configurationService.getProperty("dspace.server.url")).thenReturn(serverURL); + when(configurationService.getProperty("dspace.server.ssr.url", serverURL)).thenReturn(serverSSRURL); + } @Test public void testReturnCorrectClass() throws Exception { - assertEquals(rootConverter.convert().getClass(), RootRest.class); + assertEquals(rootConverter.convert(request).getClass(), RootRest.class); } @Test public void testCorrectPropertiesSetFromConfigurationService() throws Exception { - String restUrl = "rest"; - RootRest rootRest = rootConverter.convert(); + String restUrl = "/server/api"; + request.setScheme("https"); + request.setServerName("dspace-rest"); + request.setServerPort(443); + request.setRequestURI(restUrl); + RootRest rootRest = rootConverter.convert(request); assertEquals("dspaceurl", rootRest.getDspaceUI()); assertEquals("dspacename", rootRest.getDspaceName()); - assertEquals(restUrl, rootRest.getDspaceServer()); + assertEquals(serverURL, rootRest.getDspaceServer()); assertEquals("DSpace " + Util.getSourceVersion(), rootRest.getDspaceVersion()); } @Test public void testReturnNotNull() throws Exception { - assertNotNull(rootConverter.convert()); + assertNotNull(rootConverter.convert(request)); + } + + @Test + public void testCorrectInternalUrlSetFromConfigurationService() throws Exception { + String restUrl = "/server/api"; + request.setScheme("http"); + request.setServerName("internal-rest"); + request.setServerPort(8080); + request.setRequestURI(restUrl); + RootRest rootRest = rootConverter.convert(request); + assertEquals("dspaceurl", rootRest.getDspaceUI()); + assertEquals("dspacename", rootRest.getDspaceName()); + assertEquals(serverSSRURL, rootRest.getDspaceServer()); + assertEquals("DSpace " + Util.getSourceVersion(), rootRest.getDspaceVersion()); } } From 81642ea1391577b6495fac1f5defc123b9df80f1 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Mon, 3 Feb 2025 09:31:04 +0100 Subject: [PATCH 450/939] [DURACOM-288] Change logic in order to use dspaceUrl as default fallback --- .../java/org/dspace/app/rest/converter/RootConverter.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RootConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RootConverter.java index db6e9f5f9a9..bfc125fd867 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RootConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RootConverter.java @@ -31,10 +31,10 @@ public RootRest convert(HttpServletRequest request) { String requestUrl = request.getRequestURL().toString(); String dspaceUrl = configurationService.getProperty("dspace.server.url"); String dspaceSSRUrl = configurationService.getProperty("dspace.server.ssr.url", dspaceUrl); - if (dspaceUrl.equals(dspaceSSRUrl) || requestUrl.startsWith(dspaceUrl)) { - rootRest.setDspaceServer(dspaceUrl); - } else { + if (!dspaceUrl.equals(dspaceSSRUrl) && requestUrl.startsWith(dspaceSSRUrl)) { rootRest.setDspaceServer(dspaceSSRUrl); + } else { + rootRest.setDspaceServer(dspaceUrl); } rootRest.setDspaceVersion("DSpace " + getSourceVersion()); return rootRest; From 888edc45973a77deafe2902f764ee88a4a031260 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 6 Feb 2025 18:03:23 +0100 Subject: [PATCH 451/939] [DURACOM-288] Fix wrong imports --- .../java/org/dspace/app/rest/converter/RootConverter.java | 3 ++- .../org/dspace/app/rest/repository/RootRestRepository.java | 4 +++- .../org/dspace/app/rest/AuthorizationRestRepositoryIT.java | 1 - 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RootConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RootConverter.java index bfc125fd867..4aa2245b051 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RootConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RootConverter.java @@ -9,7 +9,8 @@ import static org.dspace.app.util.Util.getSourceVersion; -import jakarta.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequest; + import org.dspace.app.rest.model.RootRest; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RootRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RootRestRepository.java index 3600ed19e1b..0384f5ef7f9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RootRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RootRestRepository.java @@ -7,7 +7,9 @@ */ package org.dspace.app.rest.repository; -import jakarta.servlet.http.HttpServletRequest; + +import javax.servlet.http.HttpServletRequest; + import org.dspace.app.rest.converter.RootConverter; import org.dspace.app.rest.model.RootRest; import org.springframework.beans.factory.annotation.Autowired; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java index a35e62e1f85..80d9cdb8069 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java @@ -837,7 +837,6 @@ public void findByObjectBadRequestTest() throws Exception { .andExpect(status().isBadRequest()); } - @Test /** * Verify that the search by object works properly in allowed scenarios, when SSR rest url is defined, : * - for an administrator From 7e4fc1daafb4cbec417539e4f6040996cf7becaa Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 7 Feb 2025 10:22:53 -0600 Subject: [PATCH 452/939] Update LICENSES_THIRD_PARTY for 7.6.3 --- LICENSES_THIRD_PARTY | 423 +++++++++++++++++++++++-------------------- pom.xml | 4 +- 2 files changed, 228 insertions(+), 199 deletions(-) diff --git a/LICENSES_THIRD_PARTY b/LICENSES_THIRD_PARTY index 791009e4c3a..e12c429121a 100644 --- a/LICENSES_THIRD_PARTY +++ b/LICENSES_THIRD_PARTY @@ -21,51 +21,54 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines Apache Software License, Version 2.0: * Ant-Contrib Tasks (ant-contrib:ant-contrib:1.0b3 - http://ant-contrib.sourceforge.net) - * AWS SDK for Java - Core (com.amazonaws:aws-java-sdk-core:1.12.261 - https://aws.amazon.com/sdkforjava) - * AWS Java SDK for AWS KMS (com.amazonaws:aws-java-sdk-kms:1.12.261 - https://aws.amazon.com/sdkforjava) - * AWS Java SDK for Amazon S3 (com.amazonaws:aws-java-sdk-s3:1.12.261 - https://aws.amazon.com/sdkforjava) - * JMES Path Query library (com.amazonaws:jmespath-java:1.12.261 - https://aws.amazon.com/sdkforjava) + * AWS SDK for Java - Core (com.amazonaws:aws-java-sdk-core:1.12.780 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK for AWS KMS (com.amazonaws:aws-java-sdk-kms:1.12.780 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK for Amazon S3 (com.amazonaws:aws-java-sdk-s3:1.12.780 - https://aws.amazon.com/sdkforjava) + * JMES Path Query library (com.amazonaws:jmespath-java:1.12.780 - https://aws.amazon.com/sdkforjava) * HPPC Collections (com.carrotsearch:hppc:0.8.1 - http://labs.carrotsearch.com/hppc.html/hppc) * com.drewnoakes:metadata-extractor (com.drewnoakes:metadata-extractor:2.19.0 - https://drewnoakes.com/code/exif/) * parso (com.epam:parso:2.0.14 - https://github.com/epam/parso) - * ClassMate (com.fasterxml:classmate:1.6.0 - https://github.com/FasterXML/java-classmate) - * Jackson-annotations (com.fasterxml.jackson.core:jackson-annotations:2.16.0 - https://github.com/FasterXML/jackson) - * Jackson-core (com.fasterxml.jackson.core:jackson-core:2.16.0 - https://github.com/FasterXML/jackson-core) - * jackson-databind (com.fasterxml.jackson.core:jackson-databind:2.16.0 - https://github.com/FasterXML/jackson) - * Jackson dataformat: CBOR (com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.12.6 - http://github.com/FasterXML/jackson-dataformats-binary) + * Internet Time Utility (com.ethlo.time:itu:1.7.0 - https://github.com/ethlo/itu) + * ClassMate (com.fasterxml:classmate:1.7.0 - https://github.com/FasterXML/java-classmate) + * Jackson-annotations (com.fasterxml.jackson.core:jackson-annotations:2.18.2 - https://github.com/FasterXML/jackson) + * Jackson-core (com.fasterxml.jackson.core:jackson-core:2.18.2 - https://github.com/FasterXML/jackson-core) + * jackson-databind (com.fasterxml.jackson.core:jackson-databind:2.18.2 - https://github.com/FasterXML/jackson) + * Jackson dataformat: CBOR (com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.17.2 - https://github.com/FasterXML/jackson-dataformats-binary) * Jackson dataformat: Smile (com.fasterxml.jackson.dataformat:jackson-dataformat-smile:2.15.2 - https://github.com/FasterXML/jackson-dataformats-binary) - * Jackson-dataformat-YAML (com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.11.1 - https://github.com/FasterXML/jackson-dataformats-text) + * Jackson-dataformat-YAML (com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.14.0 - https://github.com/FasterXML/jackson-dataformats-text) * Jackson datatype: jdk8 (com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.13.5 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8) - * Jackson datatype: JSR310 (com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.1 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310) * Jackson datatype: JSR310 (com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.5 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310) + * Jackson datatype: JSR310 (com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.14.0 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310) * Jackson-module-parameter-names (com.fasterxml.jackson.module:jackson-module-parameter-names:2.13.5 - https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names) - * Java UUID Generator (com.fasterxml.uuid:java-uuid-generator:4.0.1 - https://github.com/cowtowncoder/java-uuid-generator) + * Java UUID Generator (com.fasterxml.uuid:java-uuid-generator:4.1.0 - https://github.com/cowtowncoder/java-uuid-generator) * Woodstox (com.fasterxml.woodstox:woodstox-core:6.5.1 - https://github.com/FasterXML/woodstox) * zjsonpatch (com.flipkart.zjsonpatch:zjsonpatch:0.4.16 - https://github.com/flipkart-incubator/zjsonpatch/) * Caffeine cache (com.github.ben-manes.caffeine:caffeine:2.9.3 - https://github.com/ben-manes/caffeine) + * JSON.simple (com.github.cliftonlabs:json-simple:3.0.2 - https://cliftonlabs.github.io/json-simple/) * btf (com.github.java-json-tools:btf:1.3 - https://github.com/java-json-tools/btf) * jackson-coreutils (com.github.java-json-tools:jackson-coreutils:2.0 - https://github.com/java-json-tools/jackson-coreutils) * jackson-coreutils-equivalence (com.github.java-json-tools:jackson-coreutils-equivalence:1.0 - https://github.com/java-json-tools/jackson-coreutils) + * json-patch (com.github.java-json-tools:json-patch:1.13 - https://github.com/java-json-tools/json-patch) * json-schema-core (com.github.java-json-tools:json-schema-core:1.2.14 - https://github.com/java-json-tools/json-schema-core) * json-schema-validator (com.github.java-json-tools:json-schema-validator:2.2.14 - https://github.com/java-json-tools/json-schema-validator) * msg-simple (com.github.java-json-tools:msg-simple:1.2 - https://github.com/java-json-tools/msg-simple) * uri-template (com.github.java-json-tools:uri-template:0.10 - https://github.com/java-json-tools/uri-template) * JCIP Annotations under Apache License (com.github.stephenc.jcip:jcip-annotations:1.0-1 - http://stephenc.github.com/jcip-annotations) - * Google APIs Client Library for Java (com.google.api-client:google-api-client:1.23.0 - https://github.com/google/google-api-java-client/google-api-client) + * Google APIs Client Library for Java (com.google.api-client:google-api-client:1.35.2 - https://github.com/googleapis/google-api-java-client/google-api-client) * Google Analytics API v3-rev145-1.23.0 (com.google.apis:google-api-services-analytics:v3-rev145-1.23.0 - http://nexus.sonatype.org/oss-repository-hosting.html/google-api-services-analytics) * FindBugs-jsr305 (com.google.code.findbugs:jsr305:3.0.2 - http://findbugs.sourceforge.net/) - * Gson (com.google.code.gson:gson:2.9.0 - https://github.com/google/gson/gson) - * error-prone annotations (com.google.errorprone:error_prone_annotations:2.18.0 - https://errorprone.info/error_prone_annotations) + * Gson (com.google.code.gson:gson:2.10.1 - https://github.com/google/gson/gson) + * error-prone annotations (com.google.errorprone:error_prone_annotations:2.21.1 - https://errorprone.info/error_prone_annotations) * Guava InternalFutureFailureAccess and InternalFutures (com.google.guava:failureaccess:1.0.1 - https://github.com/google/guava/failureaccess) - * Guava: Google Core Libraries for Java (com.google.guava:guava:32.0.0-jre - https://github.com/google/guava) - * Guava: Google Core Libraries for Java (JDK5 Backport) (com.google.guava:guava-jdk5:17.0 - http://code.google.com/p/guava-libraries/guava-jdk5) + * Guava: Google Core Libraries for Java (com.google.guava:guava:32.1.3-jre - https://github.com/google/guava) * Guava ListenableFuture only (com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava - https://github.com/google/guava/listenablefuture) - * Google HTTP Client Library for Java (com.google.http-client:google-http-client:1.23.0 - https://github.com/google/google-http-java-client/google-http-client) - * GSON extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-gson:1.41.7 - https://github.com/googleapis/google-http-java-client/google-http-client-gson) - * Jackson 2 extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-jackson2:1.23.0 - https://github.com/google/google-http-java-client/google-http-client-jackson2) + * Google HTTP Client Library for Java (com.google.http-client:google-http-client:1.45.3 - https://github.com/googleapis/google-http-java-client/google-http-client) + * Apache HTTP transport v2 for the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-apache-v2:1.42.0 - https://github.com/googleapis/google-http-java-client/google-http-client-apache-v2) + * GSON extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-gson:1.43.3 - https://github.com/googleapis/google-http-java-client/google-http-client-gson) + * Jackson 2 extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-jackson2:1.45.3 - https://github.com/googleapis/google-http-java-client/google-http-client-jackson2) * J2ObjC Annotations (com.google.j2objc:j2objc-annotations:1.3 - https://github.com/google/j2objc/) * J2ObjC Annotations (com.google.j2objc:j2objc-annotations:2.8 - https://github.com/google/j2objc/) - * Google OAuth Client Library for Java (com.google.oauth-client:google-oauth-client:1.33.3 - https://github.com/googleapis/google-oauth-java-client/google-oauth-client) + * Google OAuth Client Library for Java (com.google.oauth-client:google-oauth-client:1.37.0 - https://github.com/googleapis/google-oauth-java-client/google-oauth-client) * ConcurrentLinkedHashMap (com.googlecode.concurrentlinkedhashmap:concurrentlinkedhashmap-lru:1.4.2 - http://code.google.com/p/concurrentlinkedhashmap) * libphonenumber (com.googlecode.libphonenumber:libphonenumber:8.11.1 - https://github.com/google/libphonenumber/) * Jackcess (com.healthmarketscience.jackcess:jackcess:4.0.5 - https://jackcess.sourceforge.io) @@ -75,8 +78,10 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Disruptor Framework (com.lmax:disruptor:3.4.2 - http://lmax-exchange.github.com/disruptor) * MaxMind DB Reader (com.maxmind.db:maxmind-db:2.1.0 - http://dev.maxmind.com/) * MaxMind GeoIP2 API (com.maxmind.geoip2:geoip2:2.17.0 - https://dev.maxmind.com/geoip?lang=en) + * JsonSchemaValidator (com.networknt:json-schema-validator:1.0.76 - https://github.com/networknt/json-schema-validator) * Nimbus JOSE+JWT (com.nimbusds:nimbus-jose-jwt:7.9 - https://bitbucket.org/connect2id/nimbus-jose-jwt) - * opencsv (com.opencsv:opencsv:5.9 - http://opencsv.sf.net) + * Nimbus JOSE+JWT (com.nimbusds:nimbus-jose-jwt:9.28 - https://bitbucket.org/connect2id/nimbus-jose-jwt) + * opencsv (com.opencsv:opencsv:5.10 - http://opencsv.sf.net) * java-libpst (com.pff:java-libpst:0.9.3 - https://github.com/rjohnsondev/java-libpst) * rome (com.rometools:rome:1.19.0 - http://rometools.com/rome) * rome-modules (com.rometools:rome-modules:1.19.0 - http://rometools.com/rome-modules) @@ -93,16 +98,16 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * scala-logging (com.typesafe.scala-logging:scala-logging_2.13:3.9.2 - https://github.com/lightbend/scala-logging) * JSON library from Android SDK (com.vaadin.external.google:android-json:0.0.20131108.vaadin1 - http://developer.android.com/sdk) * SparseBitSet (com.zaxxer:SparseBitSet:1.3 - https://github.com/brettwooldridge/SparseBitSet) - * Apache Commons BeanUtils (commons-beanutils:commons-beanutils:1.9.4 - https://commons.apache.org/proper/commons-beanutils/) - * Apache Commons CLI (commons-cli:commons-cli:1.6.0 - https://commons.apache.org/proper/commons-cli/) - * Apache Commons Codec (commons-codec:commons-codec:1.16.0 - https://commons.apache.org/proper/commons-codec/) + * Apache Commons BeanUtils (commons-beanutils:commons-beanutils:1.10.0 - https://commons.apache.org/proper/commons-beanutils) + * Apache Commons CLI (commons-cli:commons-cli:1.9.0 - https://commons.apache.org/proper/commons-cli/) + * Apache Commons Codec (commons-codec:commons-codec:1.17.2 - https://commons.apache.org/proper/commons-codec/) * Apache Commons Collections (commons-collections:commons-collections:3.2.2 - http://commons.apache.org/collections/) * Commons Digester (commons-digester:commons-digester:2.1 - http://commons.apache.org/digester/) * Apache Commons FileUpload (commons-fileupload:commons-fileupload:1.5 - https://commons.apache.org/proper/commons-fileupload/) - * Apache Commons IO (commons-io:commons-io:2.15.1 - https://commons.apache.org/proper/commons-io/) + * Apache Commons IO (commons-io:commons-io:2.18.0 - https://commons.apache.org/proper/commons-io/) * Commons Lang (commons-lang:commons-lang:2.6 - http://commons.apache.org/lang/) - * Apache Commons Logging (commons-logging:commons-logging:1.3.0 - https://commons.apache.org/proper/commons-logging/) - * Apache Commons Validator (commons-validator:commons-validator:1.7 - http://commons.apache.org/proper/commons-validator/) + * Apache Commons Logging (commons-logging:commons-logging:1.3.4 - https://commons.apache.org/proper/commons-logging/) + * Apache Commons Validator (commons-validator:commons-validator:1.9.0 - http://commons.apache.org/proper/commons-validator/) * GeoJson POJOs for Jackson (de.grundid.opendatalab:geojson-jackson:1.14 - https://github.com/opendatalab-de/geojson-jackson) * OpenAIRE Funders Model (eu.openaire:funders-model:2.0.0 - https://api.openaire.eu) * Metrics Core (io.dropwizard.metrics:metrics-core:4.1.5 - https://metrics.dropwizard.io/metrics-core) @@ -110,75 +115,89 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Metrics Integration for Jetty 9.3 and higher (io.dropwizard.metrics:metrics-jetty9:4.1.5 - https://metrics.dropwizard.io/metrics-jetty9) * Metrics Integration with JMX (io.dropwizard.metrics:metrics-jmx:4.1.5 - https://metrics.dropwizard.io/metrics-jmx) * JVM Integration for Metrics (io.dropwizard.metrics:metrics-jvm:4.1.5 - https://metrics.dropwizard.io/metrics-jvm) + * io.grpc:grpc-api (io.grpc:grpc-api:1.69.0 - https://github.com/grpc/grpc-java) + * io.grpc:grpc-context (io.grpc:grpc-context:1.69.0 - https://github.com/grpc/grpc-java) * micrometer-core (io.micrometer:micrometer-core:1.9.17 - https://github.com/micrometer-metrics/micrometer) - * Netty/Buffer (io.netty:netty-buffer:4.1.106.Final - https://netty.io/netty-buffer/) + * Netty/Buffer (io.netty:netty-buffer:4.1.117.Final - https://netty.io/netty-buffer/) * Netty/Buffer (io.netty:netty-buffer:4.1.99.Final - https://netty.io/netty-buffer/) - * Netty/Codec (io.netty:netty-codec:4.1.106.Final - https://netty.io/netty-codec/) + * Netty/Codec (io.netty:netty-codec:4.1.117.Final - https://netty.io/netty-codec/) * Netty/Codec (io.netty:netty-codec:4.1.99.Final - https://netty.io/netty-codec/) - * Netty/Codec/HTTP (io.netty:netty-codec-http:4.1.53.Final - https://netty.io/netty-codec-http/) - * Netty/Codec/Socks (io.netty:netty-codec-socks:4.1.53.Final - https://netty.io/netty-codec-socks/) - * Netty/Common (io.netty:netty-common:4.1.106.Final - https://netty.io/netty-common/) + * Netty/Codec/HTTP (io.netty:netty-codec-http:4.1.86.Final - https://netty.io/netty-codec-http/) + * Netty/Codec/HTTP2 (io.netty:netty-codec-http2:4.1.86.Final - https://netty.io/netty-codec-http2/) + * Netty/Codec/Socks (io.netty:netty-codec-socks:4.1.86.Final - https://netty.io/netty-codec-socks/) + * Netty/Common (io.netty:netty-common:4.1.117.Final - https://netty.io/netty-common/) * Netty/Common (io.netty:netty-common:4.1.99.Final - https://netty.io/netty-common/) - * Netty/Handler (io.netty:netty-handler:4.1.106.Final - https://netty.io/netty-handler/) + * Netty/Handler (io.netty:netty-handler:4.1.117.Final - https://netty.io/netty-handler/) * Netty/Handler (io.netty:netty-handler:4.1.99.Final - https://netty.io/netty-handler/) - * Netty/Handler/Proxy (io.netty:netty-handler-proxy:4.1.53.Final - https://netty.io/netty-handler-proxy/) + * Netty/Handler/Proxy (io.netty:netty-handler-proxy:4.1.86.Final - https://netty.io/netty-handler-proxy/) * Netty/Resolver (io.netty:netty-resolver:4.1.99.Final - https://netty.io/netty-resolver/) - * Netty/Transport (io.netty:netty-transport:4.1.106.Final - https://netty.io/netty-transport/) + * Netty/TomcatNative [BoringSSL - Static] (io.netty:netty-tcnative-boringssl-static:2.0.56.Final - https://github.com/netty/netty-tcnative/netty-tcnative-boringssl-static/) + * Netty/TomcatNative [OpenSSL - Classes] (io.netty:netty-tcnative-classes:2.0.56.Final - https://github.com/netty/netty-tcnative/netty-tcnative-classes/) + * Netty/Transport (io.netty:netty-transport:4.1.117.Final - https://netty.io/netty-transport/) * Netty/Transport (io.netty:netty-transport:4.1.99.Final - https://netty.io/netty-transport/) + * Netty/Transport/Classes/Epoll (io.netty:netty-transport-classes-epoll:4.1.99.Final - https://netty.io/netty-transport-classes-epoll/) * Netty/Transport/Native/Epoll (io.netty:netty-transport-native-epoll:4.1.99.Final - https://netty.io/netty-transport-native-epoll/) - * Netty/Transport/Native/Unix/Common (io.netty:netty-transport-native-unix-common:4.1.106.Final - https://netty.io/netty-transport-native-unix-common/) + * Netty/Transport/Native/Unix/Common (io.netty:netty-transport-native-unix-common:4.1.117.Final - https://netty.io/netty-transport-native-unix-common/) * Netty/Transport/Native/Unix/Common (io.netty:netty-transport-native-unix-common:4.1.99.Final - https://netty.io/netty-transport-native-unix-common/) + * OpenCensus (io.opencensus:opencensus-api:0.31.1 - https://github.com/census-instrumentation/opencensus-java) + * OpenCensus (io.opencensus:opencensus-contrib-http-util:0.31.1 - https://github.com/census-instrumentation/opencensus-java) * OpenTracing API (io.opentracing:opentracing-api:0.33.0 - https://github.com/opentracing/opentracing-java/opentracing-api) * OpenTracing-noop (io.opentracing:opentracing-noop:0.33.0 - https://github.com/opentracing/opentracing-java/opentracing-noop) * OpenTracing-util (io.opentracing:opentracing-util:0.33.0 - https://github.com/opentracing/opentracing-java/opentracing-util) + * Prometheus Java Simpleclient (io.prometheus:simpleclient:0.16.0 - http://github.com/prometheus/client_java/simpleclient) + * Prometheus Java Simpleclient Common (io.prometheus:simpleclient_common:0.16.0 - http://github.com/prometheus/client_java/simpleclient_common) + * Prometheus Java Simpleclient Httpserver (io.prometheus:simpleclient_httpserver:0.16.0 - http://github.com/prometheus/client_java/simpleclient_httpserver) + * Prometheus Java Span Context Supplier - Common (io.prometheus:simpleclient_tracer_common:0.16.0 - http://github.com/prometheus/client_java/simpleclient_tracer/simpleclient_tracer_common) + * Prometheus Java Span Context Supplier - OpenTelemetry (io.prometheus:simpleclient_tracer_otel:0.16.0 - http://github.com/prometheus/client_java/simpleclient_tracer/simpleclient_tracer_otel) + * Prometheus Java Span Context Supplier - OpenTelemetry Agent (io.prometheus:simpleclient_tracer_otel_agent:0.16.0 - http://github.com/prometheus/client_java/simpleclient_tracer/simpleclient_tracer_otel_agent) * Google S2 geometry library (io.sgr:s2-geometry-library-java:1.0.0 - https://github.com/sgr-io/s2-geometry-library-java) - * swagger-annotations (io.swagger:swagger-annotations:1.6.2 - https://github.com/swagger-api/swagger-core/modules/swagger-annotations) - * swagger-compat-spec-parser (io.swagger:swagger-compat-spec-parser:1.0.52 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-compat-spec-parser) - * swagger-core (io.swagger:swagger-core:1.6.2 - https://github.com/swagger-api/swagger-core/modules/swagger-core) - * swagger-models (io.swagger:swagger-models:1.6.2 - https://github.com/swagger-api/swagger-core/modules/swagger-models) - * swagger-parser (io.swagger:swagger-parser:1.0.52 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser) - * swagger-annotations (io.swagger.core.v3:swagger-annotations:2.1.5 - https://github.com/swagger-api/swagger-core/modules/swagger-annotations) - * swagger-core (io.swagger.core.v3:swagger-core:2.1.5 - https://github.com/swagger-api/swagger-core/modules/swagger-core) - * swagger-models (io.swagger.core.v3:swagger-models:2.1.5 - https://github.com/swagger-api/swagger-core/modules/swagger-models) - * swagger-parser (io.swagger.parser.v3:swagger-parser:2.0.23 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser) - * swagger-parser (io.swagger.parser.v3:swagger-parser-core:2.0.23 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser-core) - * swagger-parser-v2-converter (io.swagger.parser.v3:swagger-parser-v2-converter:2.0.23 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser-v2-converter) - * swagger-parser-v3 (io.swagger.parser.v3:swagger-parser-v3:2.0.23 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser-v3) + * swagger-annotations (io.swagger:swagger-annotations:1.6.9 - https://github.com/swagger-api/swagger-core/modules/swagger-annotations) + * swagger-compat-spec-parser (io.swagger:swagger-compat-spec-parser:1.0.64 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-compat-spec-parser) + * swagger-core (io.swagger:swagger-core:1.6.9 - https://github.com/swagger-api/swagger-core/modules/swagger-core) + * swagger-models (io.swagger:swagger-models:1.6.9 - https://github.com/swagger-api/swagger-core/modules/swagger-models) + * swagger-parser (io.swagger:swagger-parser:1.0.64 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser) + * swagger-annotations (io.swagger.core.v3:swagger-annotations:2.2.8 - https://github.com/swagger-api/swagger-core/modules/swagger-annotations) + * swagger-core (io.swagger.core.v3:swagger-core:2.2.8 - https://github.com/swagger-api/swagger-core/modules/swagger-core) + * swagger-models (io.swagger.core.v3:swagger-models:2.2.8 - https://github.com/swagger-api/swagger-core/modules/swagger-models) + * swagger-parser (io.swagger.parser.v3:swagger-parser:2.1.10 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser) + * swagger-parser (io.swagger.parser.v3:swagger-parser-core:2.1.10 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser-core) + * swagger-parser-v2-converter (io.swagger.parser.v3:swagger-parser-v2-converter:2.1.10 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser-v2-converter) + * swagger-parser-v3 (io.swagger.parser.v3:swagger-parser-v3:2.1.10 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser-v3) * Jakarta Bean Validation API (jakarta.validation:jakarta.validation-api:2.0.2 - https://beanvalidation.org) * JSR107 API and SPI (javax.cache:cache-api:1.1.1 - https://github.com/jsr107/jsr107spec) * javax.inject (javax.inject:javax.inject:1 - http://code.google.com/p/atinject/) - * Bean Validation API (javax.validation:validation-api:2.0.1.Final - http://beanvalidation.org) * jdbm (jdbm:jdbm:1.0 - no url defined) - * Joda-Time (joda-time:joda-time:2.12.5 - https://www.joda.org/joda-time/) - * Byte Buddy (without dependencies) (net.bytebuddy:byte-buddy:1.11.13 - https://bytebuddy.net/byte-buddy) + * Joda-Time (joda-time:joda-time:2.13.0 - https://www.joda.org/joda-time/) + * Byte Buddy (without dependencies) (net.bytebuddy:byte-buddy:1.16.1 - https://bytebuddy.net/byte-buddy) * Byte Buddy agent (net.bytebuddy:byte-buddy-agent:1.11.13 - https://bytebuddy.net/byte-buddy-agent) * eigenbase-properties (net.hydromatic:eigenbase-properties:1.1.5 - http://github.com/julianhyde/eigenbase-properties) - * json-unit-core (net.javacrumbs.json-unit:json-unit-core:2.19.0 - https://github.com/lukas-krecan/JsonUnit/json-unit-core) + * json-unit-core (net.javacrumbs.json-unit:json-unit-core:2.36.0 - https://github.com/lukas-krecan/JsonUnit/json-unit-core) * "Java Concurrency in Practice" book annotations (net.jcip:jcip-annotations:1.0 - http://jcip.net/) - * ASM based accessors helper used by json-smart (net.minidev:accessors-smart:1.2 - http://www.minidev.net/) * ASM based accessors helper used by json-smart (net.minidev:accessors-smart:2.5.0 - https://urielch.github.io/) - * JSON Small and Fast Parser (net.minidev:json-smart:2.3 - http://www.minidev.net/) + * ASM based accessors helper used by json-smart (net.minidev:accessors-smart:2.5.1 - https://urielch.github.io/) * JSON Small and Fast Parser (net.minidev:json-smart:2.5.0 - https://urielch.github.io/) + * JSON Small and Fast Parser (net.minidev:json-smart:2.5.1 - https://urielch.github.io/) * Abdera Core (org.apache.abdera:abdera-core:1.1.3 - http://abdera.apache.org/abdera-core) * I18N Libraries (org.apache.abdera:abdera-i18n:1.1.3 - http://abdera.apache.org) - * Apache Ant Core (org.apache.ant:ant:1.10.14 - https://ant.apache.org/) - * Apache Ant Launcher (org.apache.ant:ant-launcher:1.10.14 - https://ant.apache.org/) - * Apache Commons BCEL (org.apache.bcel:bcel:6.7.0 - https://commons.apache.org/proper/commons-bcel) + * Apache Ant Core (org.apache.ant:ant:1.10.15 - https://ant.apache.org/) + * Apache Ant Launcher (org.apache.ant:ant-launcher:1.10.15 - https://ant.apache.org/) + * Apache Commons BCEL (org.apache.bcel:bcel:6.10.0 - https://commons.apache.org/proper/commons-bcel) * Calcite Core (org.apache.calcite:calcite-core:1.35.0 - https://calcite.apache.org) * Calcite Linq4j (org.apache.calcite:calcite-linq4j:1.35.0 - https://calcite.apache.org) * Apache Calcite Avatica (org.apache.calcite.avatica:avatica-core:1.23.0 - https://calcite.apache.org/avatica) * Apache Calcite Avatica Metrics (org.apache.calcite.avatica:avatica-metrics:1.23.0 - https://calcite.apache.org/avatica) * Apache Commons Collections (org.apache.commons:commons-collections4:4.4 - https://commons.apache.org/proper/commons-collections/) - * Apache Commons Compress (org.apache.commons:commons-compress:1.26.0 - https://commons.apache.org/proper/commons-compress/) - * Apache Commons Configuration (org.apache.commons:commons-configuration2:2.10.1 - https://commons.apache.org/proper/commons-configuration/) + * Apache Commons Compress (org.apache.commons:commons-compress:1.27.1 - https://commons.apache.org/proper/commons-compress/) + * Apache Commons Configuration (org.apache.commons:commons-configuration2:2.11.0 - https://commons.apache.org/proper/commons-configuration/) * Apache Commons CSV (org.apache.commons:commons-csv:1.10.0 - https://commons.apache.org/proper/commons-csv/) - * Apache Commons DBCP (org.apache.commons:commons-dbcp2:2.11.0 - https://commons.apache.org/dbcp/) + * Apache Commons DBCP (org.apache.commons:commons-dbcp2:2.13.0 - https://commons.apache.org/proper/commons-dbcp/) + * Apache Commons Digester (org.apache.commons:commons-digester3:3.2 - http://commons.apache.org/digester/) * Apache Commons Exec (org.apache.commons:commons-exec:1.3 - http://commons.apache.org/proper/commons-exec/) * Apache Commons Exec (org.apache.commons:commons-exec:1.4.0 - https://commons.apache.org/proper/commons-exec/) - * Apache Commons Lang (org.apache.commons:commons-lang3:3.14.0 - https://commons.apache.org/proper/commons-lang/) + * Apache Commons Lang (org.apache.commons:commons-lang3:3.17.0 - https://commons.apache.org/proper/commons-lang/) * Apache Commons Math (org.apache.commons:commons-math3:3.6.1 - http://commons.apache.org/proper/commons-math/) * Apache Commons Pool (org.apache.commons:commons-pool2:2.12.0 - https://commons.apache.org/proper/commons-pool/) - * Apache Commons Text (org.apache.commons:commons-text:1.10.0 - https://commons.apache.org/proper/commons-text) + * Apache Commons Text (org.apache.commons:commons-text:1.13.0 - https://commons.apache.org/proper/commons-text) * Curator Client (org.apache.curator:curator-client:2.13.0 - http://curator.apache.org/curator-client) * Curator Framework (org.apache.curator:curator-framework:2.13.0 - http://curator.apache.org/curator-framework) * Curator Recipes (org.apache.curator:curator-recipes:2.13.0 - http://curator.apache.org/curator-recipes) @@ -194,7 +213,7 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Apache HttpClient (org.apache.httpcomponents.client5:httpclient5:5.1.3 - https://hc.apache.org/httpcomponents-client-5.0.x/5.1.3/httpclient5/) * Apache HttpComponents Core HTTP/1.1 (org.apache.httpcomponents.core5:httpcore5:5.1.3 - https://hc.apache.org/httpcomponents-core-5.1.x/5.1.3/httpcore5/) * Apache HttpComponents Core HTTP/2 (org.apache.httpcomponents.core5:httpcore5-h2:5.1.3 - https://hc.apache.org/httpcomponents-core-5.1.x/5.1.3/httpcore5-h2/) - * Apache James :: Mime4j :: Core (org.apache.james:apache-mime4j-core:0.8.10 - http://james.apache.org/mime4j/apache-mime4j-core) + * Apache James :: Mime4j :: Core (org.apache.james:apache-mime4j-core:0.8.12 - http://james.apache.org/mime4j/apache-mime4j-core) * Apache James :: Mime4j :: DOM (org.apache.james:apache-mime4j-dom:0.8.11 - http://james.apache.org/mime4j/apache-mime4j-dom) * Apache Jena - Libraries POM (org.apache.jena:apache-jena-libs:2.13.0 - http://jena.apache.org/apache-jena-libs/) * Apache Jena - ARQ (SPARQL 1.1 Query Engine) (org.apache.jena:jena-arq:2.13.0 - http://jena.apache.org/jena-arq/) @@ -205,48 +224,48 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Kerby-kerb Util (org.apache.kerby:kerb-util:1.0.1 - http://directory.apache.org/kerby/kerby-kerb/kerb-util) * Kerby ASN1 Project (org.apache.kerby:kerby-asn1:1.0.1 - http://directory.apache.org/kerby/kerby-common/kerby-asn1) * Kerby PKIX Project (org.apache.kerby:kerby-pkix:1.0.1 - http://directory.apache.org/kerby/kerby-pkix) - * Apache Log4j 1.x Compatibility API (org.apache.logging.log4j:log4j-1.2-api:2.23.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-1.2-api/) - * Apache Log4j API (org.apache.logging.log4j:log4j-api:2.23.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-api/) - * Apache Log4j Core (org.apache.logging.log4j:log4j-core:2.23.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-core/) - * Apache Log4j JUL Adapter (org.apache.logging.log4j:log4j-jul:2.23.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-jul/) + * Apache Log4j 1.x Compatibility API (org.apache.logging.log4j:log4j-1.2-api:2.24.3 - https://logging.apache.org/log4j/2.x/log4j/log4j-1.2-api/) + * Apache Log4j API (org.apache.logging.log4j:log4j-api:2.24.3 - https://logging.apache.org/log4j/2.x/log4j/log4j-api/) + * Apache Log4j Core (org.apache.logging.log4j:log4j-core:2.24.3 - https://logging.apache.org/log4j/2.x/log4j/log4j-core/) + * Apache Log4j JUL Adapter (org.apache.logging.log4j:log4j-jul:2.17.2 - https://logging.apache.org/log4j/2.x/log4j-jul/) * Apache Log4j Layout for JSON template (org.apache.logging.log4j:log4j-layout-template-json:2.17.2 - https://logging.apache.org/log4j/2.x/log4j-layout-template-json/) - * Apache Log4j SLF4J Binding (org.apache.logging.log4j:log4j-slf4j-impl:2.23.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-slf4j-impl/) - * Apache Log4j Web (org.apache.logging.log4j:log4j-web:2.23.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-web/) - * Lucene Common Analyzers (org.apache.lucene:lucene-analyzers-common:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-common) - * Lucene ICU Analysis Components (org.apache.lucene:lucene-analyzers-icu:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-icu) - * Lucene Kuromoji Japanese Morphological Analyzer (org.apache.lucene:lucene-analyzers-kuromoji:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-kuromoji) - * Lucene Nori Korean Morphological Analyzer (org.apache.lucene:lucene-analyzers-nori:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-nori) - * Lucene Phonetic Filters (org.apache.lucene:lucene-analyzers-phonetic:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-phonetic) - * Lucene Smart Chinese Analyzer (org.apache.lucene:lucene-analyzers-smartcn:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-smartcn) - * Lucene Stempel Analyzer (org.apache.lucene:lucene-analyzers-stempel:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-stempel) - * Lucene Memory (org.apache.lucene:lucene-backward-codecs:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-backward-codecs) - * Lucene Classification (org.apache.lucene:lucene-classification:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-classification) - * Lucene codecs (org.apache.lucene:lucene-codecs:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-codecs) - * Lucene Core (org.apache.lucene:lucene-core:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-core) - * Lucene Expressions (org.apache.lucene:lucene-expressions:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-expressions) - * Lucene Grouping (org.apache.lucene:lucene-grouping:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-grouping) - * Lucene Highlighter (org.apache.lucene:lucene-highlighter:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-highlighter) - * Lucene Join (org.apache.lucene:lucene-join:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-join) - * Lucene Memory (org.apache.lucene:lucene-memory:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-memory) - * Lucene Miscellaneous (org.apache.lucene:lucene-misc:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-misc) - * Lucene Queries (org.apache.lucene:lucene-queries:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-queries) - * Lucene QueryParsers (org.apache.lucene:lucene-queryparser:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-queryparser) - * Lucene Sandbox (org.apache.lucene:lucene-sandbox:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-sandbox) - * Lucene Spatial Extras (org.apache.lucene:lucene-spatial-extras:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-spatial-extras) - * Lucene Spatial 3D (org.apache.lucene:lucene-spatial3d:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-spatial3d) - * Lucene Suggest (org.apache.lucene:lucene-suggest:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-suggest) - * Apache FontBox (org.apache.pdfbox:fontbox:2.0.31 - http://pdfbox.apache.org/) + * Apache Log4j SLF4J Binding (org.apache.logging.log4j:log4j-slf4j-impl:2.17.2 - https://logging.apache.org/log4j/2.x/log4j-slf4j-impl/) + * Apache Log4j Web (org.apache.logging.log4j:log4j-web:2.17.2 - https://logging.apache.org/log4j/2.x/log4j-web/) + * Lucene Common Analyzers (org.apache.lucene:lucene-analyzers-common:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-analyzers-common) + * Lucene ICU Analysis Components (org.apache.lucene:lucene-analyzers-icu:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-analyzers-icu) + * Lucene Kuromoji Japanese Morphological Analyzer (org.apache.lucene:lucene-analyzers-kuromoji:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-analyzers-kuromoji) + * Lucene Nori Korean Morphological Analyzer (org.apache.lucene:lucene-analyzers-nori:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-analyzers-nori) + * Lucene Phonetic Filters (org.apache.lucene:lucene-analyzers-phonetic:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-analyzers-phonetic) + * Lucene Smart Chinese Analyzer (org.apache.lucene:lucene-analyzers-smartcn:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-analyzers-smartcn) + * Lucene Stempel Analyzer (org.apache.lucene:lucene-analyzers-stempel:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-analyzers-stempel) + * Lucene Memory (org.apache.lucene:lucene-backward-codecs:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-backward-codecs) + * Lucene Classification (org.apache.lucene:lucene-classification:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-classification) + * Lucene codecs (org.apache.lucene:lucene-codecs:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-codecs) + * Lucene Core (org.apache.lucene:lucene-core:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-core) + * Lucene Expressions (org.apache.lucene:lucene-expressions:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-expressions) + * Lucene Grouping (org.apache.lucene:lucene-grouping:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-grouping) + * Lucene Highlighter (org.apache.lucene:lucene-highlighter:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-highlighter) + * Lucene Join (org.apache.lucene:lucene-join:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-join) + * Lucene Memory (org.apache.lucene:lucene-memory:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-memory) + * Lucene Miscellaneous (org.apache.lucene:lucene-misc:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-misc) + * Lucene Queries (org.apache.lucene:lucene-queries:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-queries) + * Lucene QueryParsers (org.apache.lucene:lucene-queryparser:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-queryparser) + * Lucene Sandbox (org.apache.lucene:lucene-sandbox:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-sandbox) + * Lucene Spatial Extras (org.apache.lucene:lucene-spatial-extras:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-spatial-extras) + * Lucene Spatial 3D (org.apache.lucene:lucene-spatial3d:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-spatial3d) + * Lucene Suggest (org.apache.lucene:lucene-suggest:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-suggest) + * Apache FontBox (org.apache.pdfbox:fontbox:2.0.33 - http://pdfbox.apache.org/) * PDFBox JBIG2 ImageIO plugin (org.apache.pdfbox:jbig2-imageio:3.0.4 - https://www.apache.org/jbig2-imageio/) * Apache JempBox (org.apache.pdfbox:jempbox:1.8.17 - http://www.apache.org/pdfbox-parent/jempbox/) - * Apache PDFBox (org.apache.pdfbox:pdfbox:2.0.31 - https://www.apache.org/pdfbox-parent/pdfbox/) + * Apache PDFBox (org.apache.pdfbox:pdfbox:2.0.33 - https://www.apache.org/pdfbox-parent/pdfbox/) * Apache PDFBox tools (org.apache.pdfbox:pdfbox-tools:2.0.31 - https://www.apache.org/pdfbox-parent/pdfbox-tools/) * Apache XmpBox (org.apache.pdfbox:xmpbox:2.0.31 - https://www.apache.org/pdfbox-parent/xmpbox/) * Apache POI - Common (org.apache.poi:poi:5.2.5 - https://poi.apache.org/) * Apache POI - API based on OPC and OOXML schemas (org.apache.poi:poi-ooxml:5.2.5 - https://poi.apache.org/) * Apache POI (org.apache.poi:poi-ooxml-lite:5.2.5 - https://poi.apache.org/) * Apache POI (org.apache.poi:poi-scratchpad:5.2.5 - https://poi.apache.org/) - * Apache Solr Core (org.apache.solr:solr-core:8.11.3 - https://lucene.apache.org/solr-parent/solr-core) - * Apache Solr Solrj (org.apache.solr:solr-solrj:8.11.3 - https://lucene.apache.org/solr-parent/solr-solrj) + * Apache Solr Core (org.apache.solr:solr-core:8.11.4 - https://lucene.apache.org/solr-parent/solr-core) + * Apache Solr Solrj (org.apache.solr:solr-solrj:8.11.4 - https://lucene.apache.org/solr-parent/solr-solrj) * Apache Standard Taglib Implementation (org.apache.taglibs:taglibs-standard-impl:1.2.5 - http://tomcat.apache.org/taglibs/standard-1.2.5/taglibs-standard-impl) * Apache Standard Taglib Specification API (org.apache.taglibs:taglibs-standard-spec:1.2.5 - http://tomcat.apache.org/taglibs/standard-1.2.5/taglibs-standard-spec) * Apache Thrift (org.apache.thrift:libthrift:0.9.2 - http://thrift.apache.org) @@ -277,8 +296,9 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * tomcat-embed-core (org.apache.tomcat.embed:tomcat-embed-core:9.0.83 - https://tomcat.apache.org/) * tomcat-embed-el (org.apache.tomcat.embed:tomcat-embed-el:9.0.83 - https://tomcat.apache.org/) * tomcat-embed-websocket (org.apache.tomcat.embed:tomcat-embed-websocket:9.0.83 - https://tomcat.apache.org/) - * Apache Velocity - Engine (org.apache.velocity:velocity-engine-core:2.3 - http://velocity.apache.org/engine/devel/velocity-engine-core/) - * Apache Velocity - JSR 223 Scripting (org.apache.velocity:velocity-engine-scripting:2.2 - http://velocity.apache.org/engine/devel/velocity-engine-scripting/) + * Apache Velocity - Engine (org.apache.velocity:velocity-engine-core:2.4.1 - http://velocity.apache.org/engine/devel/velocity-engine-core/) + * Apache Velocity - JSR 223 Scripting (org.apache.velocity:velocity-engine-scripting:2.3 - http://velocity.apache.org/engine/devel/velocity-engine-scripting/) + * Apache Velocity Tools - Generic tools (org.apache.velocity.tools:velocity-tools-generic:3.1 - https://velocity.apache.org/tools/devel/velocity-tools-generic/) * Axiom API (org.apache.ws.commons.axiom:axiom-api:1.2.22 - http://ws.apache.org/axiom/) * Abdera Model (FOM) Implementation (org.apache.ws.commons.axiom:fom-impl:1.2.22 - http://ws.apache.org/axiom/implementations/fom-impl/) * XmlBeans (org.apache.xmlbeans:xmlbeans:5.2.0 - https://xmlbeans.apache.org/) @@ -295,50 +315,50 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Apache :: JSTL module (org.eclipse.jetty:apache-jstl:9.4.15.v20190215 - http://tomcat.apache.org/taglibs/standard/) * Jetty :: ALPN :: Client (org.eclipse.jetty:jetty-alpn-client:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-client) * Jetty :: ALPN :: JDK9 Client Implementation (org.eclipse.jetty:jetty-alpn-java-client:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-client) - * Jetty :: ALPN :: JDK9 Server Implementation (org.eclipse.jetty:jetty-alpn-java-server:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-server) + * Jetty :: ALPN :: JDK9 Server Implementation (org.eclipse.jetty:jetty-alpn-java-server:9.4.57.v20241219 - https://jetty.org/jetty-alpn-parent/jetty-alpn-java-server/) * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server) - * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server) + * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.57.v20241219 - https://jetty.org/jetty-alpn-parent/jetty-alpn-server/) * Jetty :: Servlet Annotations (org.eclipse.jetty:jetty-annotations:9.4.15.v20190215 - http://www.eclipse.org/jetty) * Jetty :: Asynchronous HTTP Client (org.eclipse.jetty:jetty-client:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-client) * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-continuation) - * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-continuation) - * Jetty :: Deployers (org.eclipse.jetty:jetty-deploy:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-deploy) - * Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-http) - * Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-io) + * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.57.v20241219 - https://jetty.org/jetty-continuation/) + * Jetty :: Deployers (org.eclipse.jetty:jetty-deploy:9.4.57.v20241219 - https://jetty.org/jetty-deploy/) + * Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.57.v20241219 - https://jetty.org/jetty-http/) + * Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.57.v20241219 - https://jetty.org/jetty-io/) * Jetty :: JMX Management (org.eclipse.jetty:jetty-jmx:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-jmx) * Jetty :: JNDI Naming (org.eclipse.jetty:jetty-jndi:9.4.15.v20190215 - http://www.eclipse.org/jetty) * Jetty :: Plus (org.eclipse.jetty:jetty-plus:9.4.15.v20190215 - http://www.eclipse.org/jetty) * Jetty :: Rewrite Handler (org.eclipse.jetty:jetty-rewrite:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-rewrite) * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-security) - * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-security) - * Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-server) - * Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-servlet) - * Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-servlets) - * Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-util) - * Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-util-ajax) - * Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-webapp) - * Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-xml) + * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.57.v20241219 - https://jetty.org/jetty-security/) + * Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.57.v20241219 - https://jetty.org/jetty-server/) + * Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.57.v20241219 - https://jetty.org/jetty-servlet/) + * Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.57.v20241219 - https://jetty.org/jetty-servlets/) + * Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.57.v20241219 - https://jetty.org/jetty-util/) + * Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.57.v20241219 - https://jetty.org/jetty-util-ajax/) + * Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.57.v20241219 - https://jetty.org/jetty-webapp/) + * Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.57.v20241219 - https://jetty.org/jetty-xml/) * Jetty :: HTTP2 :: Client (org.eclipse.jetty.http2:http2-client:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-client) - * Jetty :: HTTP2 :: Common (org.eclipse.jetty.http2:http2-common:9.4.54.v20240208 - https://eclipse.org/jetty/http2-parent/http2-common) + * Jetty :: HTTP2 :: Common (org.eclipse.jetty.http2:http2-common:9.4.57.v20241219 - https://jetty.org/http2-parent/http2-common/) * Jetty :: HTTP2 :: HPACK (org.eclipse.jetty.http2:http2-hpack:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-hpack) * Jetty :: HTTP2 :: HTTP Client Transport (org.eclipse.jetty.http2:http2-http-client-transport:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-http-client-transport) - * Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.54.v20240208 - https://eclipse.org/jetty/http2-parent/http2-server) + * Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.57.v20241219 - https://jetty.org/http2-parent/http2-server/) * Jetty :: Schemas (org.eclipse.jetty.toolchain:jetty-schemas:3.1.2 - https://eclipse.org/jetty/jetty-schemas) * Ehcache (org.ehcache:ehcache:3.10.8 - http://ehcache.org) * flyway-core (org.flywaydb:flyway-core:8.5.13 - https://flywaydb.org/flyway-core) * Ogg and Vorbis for Java, Core (org.gagravarr:vorbis-java-core:0.8 - https://github.com/Gagravarr/VorbisJava) * Apache Tika plugin for Ogg, Vorbis and FLAC (org.gagravarr:vorbis-java-tika:0.8 - https://github.com/Gagravarr/VorbisJava) - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * Hibernate Validator Engine (org.hibernate.validator:hibernate-validator:6.2.5.Final - http://hibernate.org/validator/hibernate-validator) * Hibernate Validator Portable Extension (org.hibernate.validator:hibernate-validator-cdi:6.2.5.Final - http://hibernate.org/validator/hibernate-validator-cdi) * org.immutables.value-annotations (org.immutables:value-annotations:2.9.2 - http://immutables.org/value-annotations) * leveldb (org.iq80.leveldb:leveldb:0.12 - http://github.com/dain/leveldb/leveldb) * leveldb-api (org.iq80.leveldb:leveldb-api:0.12 - http://github.com/dain/leveldb/leveldb-api) - * Javassist (org.javassist:javassist:3.29.0-GA - http://www.javassist.org/) + * Javassist (org.javassist:javassist:3.30.2-GA - https://www.javassist.org/) * Java Annotation Indexer (org.jboss:jandex:2.4.2.Final - http://www.jboss.org/jandex) - * JBoss Logging 3 (org.jboss.logging:jboss-logging:3.4.3.Final - http://www.jboss.org) + * JBoss Logging 3 (org.jboss.logging:jboss-logging:3.6.1.Final - http://www.jboss.org) * JDOM (org.jdom:jdom2:2.0.6.1 - http://www.jdom.org) * jtwig-core (org.jtwig:jtwig-core:5.87.0.RELEASE - http://jtwig.org) * jtwig-reflection (org.jtwig:jtwig-reflection:5.87.0.RELEASE - http://jtwig.org) @@ -347,10 +367,10 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * jtwig-web (org.jtwig:jtwig-web:5.87.0.RELEASE - http://jtwig.org) * Proj4J (org.locationtech.proj4j:proj4j:1.1.5 - https://github.com/locationtech/proj4j) * Spatial4J (org.locationtech.spatial4j:spatial4j:0.7 - https://projects.eclipse.org/projects/locationtech.spatial4j) - * MockServer Java Client (org.mock-server:mockserver-client-java:5.11.2 - http://www.mock-server.com) - * MockServer Core (org.mock-server:mockserver-core:5.11.2 - http://www.mock-server.com) - * MockServer JUnit 4 Integration (org.mock-server:mockserver-junit-rule:5.11.2 - http://www.mock-server.com) - * MockServer & Proxy Netty (org.mock-server:mockserver-netty:5.11.2 - http://www.mock-server.com) + * MockServer Java Client (org.mock-server:mockserver-client-java:5.15.0 - https://www.mock-server.com) + * MockServer Core (org.mock-server:mockserver-core:5.15.0 - https://www.mock-server.com) + * MockServer JUnit 4 Integration (org.mock-server:mockserver-junit-rule:5.15.0 - https://www.mock-server.com) + * MockServer & Proxy Netty (org.mock-server:mockserver-netty:5.15.0 - https://www.mock-server.com) * MortBay :: Apache EL :: API and Implementation (org.mortbay.jasper:apache-el:8.5.35.1 - https://github.com/jetty-project/jasper-jsp/apache-el) * MortBay :: Apache Jasper :: JSP Implementation (org.mortbay.jasper:apache-jsp:8.5.35.1 - https://github.com/jetty-project/jasper-jsp/apache-jsp) * Jetty Server (org.mortbay.jetty:jetty:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/modules/jetty) @@ -359,10 +379,10 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Servlet Specification API (org.mortbay.jetty:servlet-api:2.5-20081211 - http://jetty.mortbay.org/servlet-api) * jwarc (org.netpreserve:jwarc:0.29.0 - https://github.com/iipc/jwarc) * Objenesis (org.objenesis:objenesis:3.2 - http://objenesis.org/objenesis) - * parboiled-core (org.parboiled:parboiled-core:1.3.1 - http://parboiled.org) - * parboiled-java (org.parboiled:parboiled-java:1.3.1 - http://parboiled.org) + * parboiled-core (org.parboiled:parboiled-core:1.1.7 - http://parboiled.org) + * parboiled-java (org.parboiled:parboiled-java:1.1.7 - http://parboiled.org) * RRD4J (org.rrd4j:rrd4j:3.5 - https://github.com/rrd4j/rrd4j/) - * Scala Library (org.scala-lang:scala-library:2.13.11 - https://www.scala-lang.org/) + * Scala Library (org.scala-lang:scala-library:2.13.16 - https://www.scala-lang.org/) * Scala Compiler (org.scala-lang:scala-reflect:2.13.0 - https://www.scala-lang.org/) * scala-collection-compat (org.scala-lang.modules:scala-collection-compat_2.13:2.1.6 - http://www.scala-lang.org/) * scala-java8-compat (org.scala-lang.modules:scala-java8-compat_2.13:0.9.0 - http://www.scala-lang.org/) @@ -370,19 +390,19 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * scala-xml (org.scala-lang.modules:scala-xml_2.13:1.3.0 - http://www.scala-lang.org/) * JSONassert (org.skyscreamer:jsonassert:1.5.1 - https://github.com/skyscreamer/JSONassert) * JCL 1.2 implemented over SLF4J (org.slf4j:jcl-over-slf4j:1.7.36 - http://www.slf4j.org) - * Spring AOP (org.springframework:spring-aop:5.3.34 - https://github.com/spring-projects/spring-framework) - * Spring Beans (org.springframework:spring-beans:5.3.34 - https://github.com/spring-projects/spring-framework) - * Spring Context (org.springframework:spring-context:5.3.34 - https://github.com/spring-projects/spring-framework) - * Spring Context Support (org.springframework:spring-context-support:5.3.34 - https://github.com/spring-projects/spring-framework) - * Spring Core (org.springframework:spring-core:5.3.34 - https://github.com/spring-projects/spring-framework) - * Spring Expression Language (SpEL) (org.springframework:spring-expression:5.3.34 - https://github.com/spring-projects/spring-framework) - * Spring Commons Logging Bridge (org.springframework:spring-jcl:5.3.34 - https://github.com/spring-projects/spring-framework) - * Spring JDBC (org.springframework:spring-jdbc:5.3.34 - https://github.com/spring-projects/spring-framework) - * Spring Object/Relational Mapping (org.springframework:spring-orm:5.3.34 - https://github.com/spring-projects/spring-framework) - * Spring TestContext Framework (org.springframework:spring-test:5.3.34 - https://github.com/spring-projects/spring-framework) - * Spring Transaction (org.springframework:spring-tx:5.3.34 - https://github.com/spring-projects/spring-framework) - * Spring Web (org.springframework:spring-web:5.3.34 - https://github.com/spring-projects/spring-framework) - * Spring Web MVC (org.springframework:spring-webmvc:5.3.34 - https://github.com/spring-projects/spring-framework) + * Spring AOP (org.springframework:spring-aop:5.3.39 - https://github.com/spring-projects/spring-framework) + * Spring Beans (org.springframework:spring-beans:5.3.39 - https://github.com/spring-projects/spring-framework) + * Spring Context (org.springframework:spring-context:5.3.39 - https://github.com/spring-projects/spring-framework) + * Spring Context Support (org.springframework:spring-context-support:5.3.39 - https://github.com/spring-projects/spring-framework) + * Spring Core (org.springframework:spring-core:5.3.39 - https://github.com/spring-projects/spring-framework) + * Spring Expression Language (SpEL) (org.springframework:spring-expression:5.3.39 - https://github.com/spring-projects/spring-framework) + * Spring Commons Logging Bridge (org.springframework:spring-jcl:5.3.39 - https://github.com/spring-projects/spring-framework) + * Spring JDBC (org.springframework:spring-jdbc:5.3.39 - https://github.com/spring-projects/spring-framework) + * Spring Object/Relational Mapping (org.springframework:spring-orm:5.3.39 - https://github.com/spring-projects/spring-framework) + * Spring TestContext Framework (org.springframework:spring-test:5.3.39 - https://github.com/spring-projects/spring-framework) + * Spring Transaction (org.springframework:spring-tx:5.3.39 - https://github.com/spring-projects/spring-framework) + * Spring Web (org.springframework:spring-web:5.3.39 - https://github.com/spring-projects/spring-framework) + * Spring Web MVC (org.springframework:spring-webmvc:5.3.39 - https://github.com/spring-projects/spring-framework) * spring-boot (org.springframework.boot:spring-boot:2.7.18 - https://spring.io/projects/spring-boot) * spring-boot-actuator (org.springframework.boot:spring-boot-actuator:2.7.18 - https://spring.io/projects/spring-boot) * spring-boot-actuator-autoconfigure (org.springframework.boot:spring-boot-actuator-autoconfigure:2.7.18 - https://spring.io/projects/spring-boot) @@ -407,18 +427,21 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Spring HATEOAS (org.springframework.hateoas:spring-hateoas:1.5.6 - https://github.com/spring-projects/spring-hateoas) * Spring Plugin - Core (org.springframework.plugin:spring-plugin-core:2.0.0.RELEASE - https://github.com/spring-projects/spring-plugin/spring-plugin-core) * spring-security-config (org.springframework.security:spring-security-config:5.7.11 - https://spring.io/projects/spring-security) + * spring-security-config (org.springframework.security:spring-security-config:5.7.14 - https://spring.io/projects/spring-security) * spring-security-core (org.springframework.security:spring-security-core:5.7.11 - https://spring.io/projects/spring-security) + * spring-security-core (org.springframework.security:spring-security-core:5.7.14 - https://spring.io/projects/spring-security) * spring-security-crypto (org.springframework.security:spring-security-crypto:5.7.11 - https://spring.io/projects/spring-security) - * spring-security-test (org.springframework.security:spring-security-test:5.7.11 - https://spring.io/projects/spring-security) + * spring-security-crypto (org.springframework.security:spring-security-crypto:5.7.14 - https://spring.io/projects/spring-security) + * spring-security-test (org.springframework.security:spring-security-test:5.7.14 - https://spring.io/projects/spring-security) * spring-security-web (org.springframework.security:spring-security-web:5.7.11 - https://spring.io/projects/spring-security) + * spring-security-web (org.springframework.security:spring-security-web:5.7.14 - https://spring.io/projects/spring-security) * SWORD v2 :: Common Server Library (org.swordapp:sword2-server:1.0 - http://www.swordapp.org/) * snappy-java (org.xerial.snappy:snappy-java:1.1.10.1 - https://github.com/xerial/snappy-java) * xml-matchers (org.xmlmatchers:xml-matchers:0.10 - http://code.google.com/p/xml-matchers/) * org.xmlunit:xmlunit-core (org.xmlunit:xmlunit-core:2.10.0 - https://www.xmlunit.org/) * org.xmlunit:xmlunit-core (org.xmlunit:xmlunit-core:2.9.1 - https://www.xmlunit.org/) - * org.xmlunit:xmlunit-placeholders (org.xmlunit:xmlunit-placeholders:2.8.0 - https://www.xmlunit.org/xmlunit-placeholders/) + * org.xmlunit:xmlunit-placeholders (org.xmlunit:xmlunit-placeholders:2.9.1 - https://www.xmlunit.org/xmlunit-placeholders/) * SnakeYAML (org.yaml:snakeyaml:1.30 - https://bitbucket.org/snakeyaml/snakeyaml) - * software.amazon.ion:ion-java (software.amazon.ion:ion-java:1.0.2 - https://github.com/amznlabs/ion-java/) * Xerces2-j (xerces:xercesImpl:2.12.2 - https://xerces.apache.org/xerces2-j/) * XML Commons External Components XML APIs (xml-apis:xml-apis:1.4.01 - http://xml.apache.org/commons/components/external/) @@ -432,25 +455,26 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * curvesapi (com.github.virtuald:curvesapi:1.08 - https://github.com/virtuald/curvesapi) * Protocol Buffers [Core] (com.google.protobuf:protobuf-java:3.15.0 - https://developers.google.com/protocol-buffers/protobuf-java/) * JZlib (com.jcraft:jzlib:1.1.3 - http://www.jcraft.com/jzlib/) - * dnsjava (dnsjava:dnsjava:2.1.9 - http://www.dnsjava.org) + * jmustache (com.samskivert:jmustache:1.15 - http://github.com/samskivert/jmustache) + * dnsjava (dnsjava:dnsjava:3.6.2 - https://github.com/dnsjava/dnsjava) * jaxen (jaxen:jaxen:2.0.0 - http://www.cafeconleche.org/jaxen/jaxen) * ANTLR 4 Runtime (org.antlr:antlr4-runtime:4.5.1-1 - http://www.antlr.org/antlr4-runtime) * commons-compiler (org.codehaus.janino:commons-compiler:3.1.8 - http://janino-compiler.github.io/commons-compiler/) * janino (org.codehaus.janino:janino:3.1.8 - http://janino-compiler.github.io/janino/) * Stax2 API (org.codehaus.woodstox:stax2-api:4.2.1 - http://github.com/FasterXML/stax2-api) * Hamcrest Date (org.exparity:hamcrest-date:2.0.8 - https://github.com/exparity/hamcrest-date) - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * Hamcrest (org.hamcrest:hamcrest:2.2 - http://hamcrest.org/JavaHamcrest/) * Hamcrest Core (org.hamcrest:hamcrest-core:2.2 - http://hamcrest.org/JavaHamcrest/) * HdrHistogram (org.hdrhistogram:HdrHistogram:2.1.12 - http://hdrhistogram.github.io/HdrHistogram/) * JBibTeX (org.jbibtex:jbibtex:1.0.20 - http://www.jbibtex.org) * asm (org.ow2.asm:asm:8.0.1 - http://asm.ow2.io/) - * asm-analysis (org.ow2.asm:asm-analysis:7.1 - http://asm.ow2.org/) + * ASM Analysis (org.ow2.asm:asm-analysis:5.0.3 - http://asm.objectweb.org/asm-analysis/) * asm-commons (org.ow2.asm:asm-commons:9.3 - http://asm.ow2.io/) - * asm-tree (org.ow2.asm:asm-tree:7.1 - http://asm.ow2.org/) - * asm-util (org.ow2.asm:asm-util:7.1 - http://asm.ow2.org/) - * PostgreSQL JDBC Driver (org.postgresql:postgresql:42.7.3 - https://jdbc.postgresql.org) + * ASM Tree (org.ow2.asm:asm-tree:5.0.3 - http://asm.objectweb.org/asm-tree/) + * ASM Util (org.ow2.asm:asm-util:5.0.3 - http://asm.objectweb.org/asm-util/) + * PostgreSQL JDBC Driver (org.postgresql:postgresql:42.7.5 - https://jdbc.postgresql.org) * Reflections (org.reflections:reflections:0.9.12 - http://github.com/ronmamo/reflections) * JMatIO (org.tallison:jmatio:1.5 - https://github.com/tballison/jmatio) * XMLUnit for Java (xmlunit:xmlunit:1.3 - http://xmlunit.sourceforge.net/) @@ -471,6 +495,7 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * JavaBeans Activation Framework (JAF) (javax.activation:activation:1.1 - http://java.sun.com/products/javabeans/jaf/index.jsp) * JavaBeans Activation Framework API jar (javax.activation:javax.activation-api:1.2.0 - http://java.net/all/javax.activation-api/) * javax.annotation API (javax.annotation:javax.annotation-api:1.3.2 - http://jcp.org/en/jsr/detail?id=250) + * Expression Language 3.0 API (javax.el:javax.el-api:3.0.0 - http://uel-spec.java.net) * Java Servlet API (javax.servlet:javax.servlet-api:3.1.0 - http://servlet-spec.java.net) * javax.transaction API (javax.transaction:javax.transaction-api:1.3 - http://jta-spec.java.net) * jaxb-api (javax.xml.bind:jaxb-api:2.3.1 - https://github.com/javaee/jaxb-spec/jaxb-api) @@ -481,16 +506,19 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * OSGi resource locator (org.glassfish.hk2:osgi-resource-locator:1.0.3 - https://projects.eclipse.org/projects/ee4j/osgi-resource-locator) * aopalliance version 1.0 repackaged as a module (org.glassfish.hk2.external:aopalliance-repackaged:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/external/aopalliance-repackaged) * javax.inject:1 as OSGi bundle (org.glassfish.hk2.external:jakarta.inject:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/external/jakarta.inject) - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * Java Transaction API (org.jboss.spec.javax.transaction:jboss-transaction-api_1.2_spec:1.1.1.Final - http://www.jboss.org/jboss-transaction-api_1.2_spec) Cordra (Version 2) License Agreement: - * net.cnri:cnri-servlet-container (net.cnri:cnri-servlet-container:3.0.0 - https://gitlab.com/cnri/cnri-servlet-container) - * net.cnri:cnri-servlet-container-lib (net.cnri:cnri-servlet-container-lib:3.0.0 - https://gitlab.com/cnri/cnri-servlet-container) + * net.cnri:cnri-servlet-container-lib (net.cnri:cnri-servlet-container-lib:3.1.0 - https://gitlab.com/cnri/cnri-servlet-container) * net.cnri:cnriutil (net.cnri:cnriutil:2.0 - https://gitlab.com/cnri/cnriutil) + Cordra (Version 2.5.0) License Agreement: + + * net.cnri:cnri-servlet-container (net.cnri:cnri-servlet-container:3.1.0 - https://gitlab.com/cnri/cnri-servlet-container) + Eclipse Distribution License, Version 1.0: * Jakarta Activation (com.sun.activation:jakarta.activation:1.2.2 - https://github.com/eclipse-ee4j/jaf/jakarta.activation) @@ -500,8 +528,8 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * javax.persistence-api (javax.persistence:javax.persistence-api:2.2 - https://github.com/javaee/jpa-spec) * JAXB Runtime (org.glassfish.jaxb:jaxb-runtime:2.3.9 - https://eclipse-ee4j.github.io/jaxb-ri/) * TXW2 Runtime (org.glassfish.jaxb:txw2:2.3.9 - https://eclipse-ee4j.github.io/jaxb-ri/) - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * Java Persistence API, Version 2.1 (org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.2.Final - http://hibernate.org) * org.locationtech.jts:jts-core (org.locationtech.jts:jts-core:1.19.0 - https://www.locationtech.org/projects/technology.jts/jts-modules/jts-core) * org.locationtech.jts.io:jts-io-common (org.locationtech.jts.io:jts-io-common:1.19.0 - https://www.locationtech.org/projects/technology.jts/jts-modules/jts-io/jts-io-common) @@ -509,7 +537,7 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines Eclipse Public License: * System Rules (com.github.stefanbirkner:system-rules:1.19.0 - http://stefanbirkner.github.io/system-rules/) - * H2 Database Engine (com.h2database:h2:2.2.224 - https://h2database.com) + * H2 Database Engine (com.h2database:h2:2.3.232 - https://h2database.com) * Jakarta Annotations API (jakarta.annotation:jakarta.annotation-api:1.3.5 - https://projects.eclipse.org/projects/ee4j.ca) * javax.transaction API (jakarta.transaction:jakarta.transaction-api:1.3.3 - https://projects.eclipse.org/projects/ee4j.jta) * jakarta.ws.rs-api (jakarta.ws.rs:jakarta.ws.rs-api:2.1.6 - https://github.com/eclipse-ee4j/jaxrs-api) @@ -521,34 +549,34 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Apache :: JSTL module (org.eclipse.jetty:apache-jstl:9.4.15.v20190215 - http://tomcat.apache.org/taglibs/standard/) * Jetty :: ALPN :: Client (org.eclipse.jetty:jetty-alpn-client:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-client) * Jetty :: ALPN :: JDK9 Client Implementation (org.eclipse.jetty:jetty-alpn-java-client:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-client) - * Jetty :: ALPN :: JDK9 Server Implementation (org.eclipse.jetty:jetty-alpn-java-server:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-server) + * Jetty :: ALPN :: JDK9 Server Implementation (org.eclipse.jetty:jetty-alpn-java-server:9.4.57.v20241219 - https://jetty.org/jetty-alpn-parent/jetty-alpn-java-server/) * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server) - * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server) + * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.57.v20241219 - https://jetty.org/jetty-alpn-parent/jetty-alpn-server/) * Jetty :: Servlet Annotations (org.eclipse.jetty:jetty-annotations:9.4.15.v20190215 - http://www.eclipse.org/jetty) * Jetty :: Asynchronous HTTP Client (org.eclipse.jetty:jetty-client:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-client) * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-continuation) - * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-continuation) - * Jetty :: Deployers (org.eclipse.jetty:jetty-deploy:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-deploy) - * Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-http) - * Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-io) + * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.57.v20241219 - https://jetty.org/jetty-continuation/) + * Jetty :: Deployers (org.eclipse.jetty:jetty-deploy:9.4.57.v20241219 - https://jetty.org/jetty-deploy/) + * Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.57.v20241219 - https://jetty.org/jetty-http/) + * Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.57.v20241219 - https://jetty.org/jetty-io/) * Jetty :: JMX Management (org.eclipse.jetty:jetty-jmx:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-jmx) * Jetty :: JNDI Naming (org.eclipse.jetty:jetty-jndi:9.4.15.v20190215 - http://www.eclipse.org/jetty) * Jetty :: Plus (org.eclipse.jetty:jetty-plus:9.4.15.v20190215 - http://www.eclipse.org/jetty) * Jetty :: Rewrite Handler (org.eclipse.jetty:jetty-rewrite:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-rewrite) * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-security) - * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-security) - * Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-server) - * Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-servlet) - * Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-servlets) - * Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-util) - * Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-util-ajax) - * Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-webapp) - * Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-xml) + * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.57.v20241219 - https://jetty.org/jetty-security/) + * Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.57.v20241219 - https://jetty.org/jetty-server/) + * Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.57.v20241219 - https://jetty.org/jetty-servlet/) + * Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.57.v20241219 - https://jetty.org/jetty-servlets/) + * Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.57.v20241219 - https://jetty.org/jetty-util/) + * Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.57.v20241219 - https://jetty.org/jetty-util-ajax/) + * Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.57.v20241219 - https://jetty.org/jetty-webapp/) + * Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.57.v20241219 - https://jetty.org/jetty-xml/) * Jetty :: HTTP2 :: Client (org.eclipse.jetty.http2:http2-client:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-client) - * Jetty :: HTTP2 :: Common (org.eclipse.jetty.http2:http2-common:9.4.54.v20240208 - https://eclipse.org/jetty/http2-parent/http2-common) + * Jetty :: HTTP2 :: Common (org.eclipse.jetty.http2:http2-common:9.4.57.v20241219 - https://jetty.org/http2-parent/http2-common/) * Jetty :: HTTP2 :: HPACK (org.eclipse.jetty.http2:http2-hpack:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-hpack) * Jetty :: HTTP2 :: HTTP Client Transport (org.eclipse.jetty.http2:http2-http-client-transport:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-http-client-transport) - * Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.54.v20240208 - https://eclipse.org/jetty/http2-parent/http2-server) + * Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.57.v20241219 - https://jetty.org/http2-parent/http2-server/) * Jetty :: Schemas (org.eclipse.jetty.toolchain:jetty-schemas:3.1.2 - https://eclipse.org/jetty/jetty-schemas) * HK2 API module (org.glassfish.hk2:hk2-api:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-api) * ServiceLocator Default Implementation (org.glassfish.hk2:hk2-locator:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-locator) @@ -556,9 +584,9 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * OSGi resource locator (org.glassfish.hk2:osgi-resource-locator:1.0.3 - https://projects.eclipse.org/projects/ee4j/osgi-resource-locator) * aopalliance version 1.0 repackaged as a module (org.glassfish.hk2.external:aopalliance-repackaged:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/external/aopalliance-repackaged) * javax.inject:1 as OSGi bundle (org.glassfish.hk2.external:jakarta.inject:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/external/jakarta.inject) - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * Java Persistence API, Version 2.1 (org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.2.Final - http://hibernate.org) * org.locationtech.jts:jts-core (org.locationtech.jts:jts-core:1.19.0 - https://www.locationtech.org/projects/technology.jts/jts-modules/jts-core) * org.locationtech.jts.io:jts-io-common (org.locationtech.jts.io:jts-io-common:1.19.0 - https://www.locationtech.org/projects/technology.jts/jts-modules/jts-io/jts-io-common) @@ -579,6 +607,7 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * btf (com.github.java-json-tools:btf:1.3 - https://github.com/java-json-tools/btf) * jackson-coreutils (com.github.java-json-tools:jackson-coreutils:2.0 - https://github.com/java-json-tools/jackson-coreutils) * jackson-coreutils-equivalence (com.github.java-json-tools:jackson-coreutils-equivalence:1.0 - https://github.com/java-json-tools/jackson-coreutils) + * json-patch (com.github.java-json-tools:json-patch:1.13 - https://github.com/java-json-tools/json-patch) * json-schema-core (com.github.java-json-tools:json-schema-core:1.2.14 - https://github.com/java-json-tools/json-schema-core) * json-schema-validator (com.github.java-json-tools:json-schema-validator:2.2.14 - https://github.com/java-json-tools/json-schema-validator) * msg-simple (com.github.java-json-tools:msg-simple:1.2 - https://github.com/java-json-tools/msg-simple) @@ -590,7 +619,7 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Hibernate ORM - hibernate-jpamodelgen (org.hibernate:hibernate-jpamodelgen:5.6.15.Final - https://hibernate.org/orm) * Hibernate Commons Annotations (org.hibernate.common:hibernate-commons-annotations:5.1.2.Final - http://hibernate.org) * im4java (org.im4java:im4java:1.4.0 - http://sourceforge.net/projects/im4java/) - * Javassist (org.javassist:javassist:3.29.0-GA - http://www.javassist.org/) + * Javassist (org.javassist:javassist:3.30.2-GA - https://www.javassist.org/) * XOM (xom:xom:1.3.9 - https://xom.nu) Go License: @@ -610,24 +639,24 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * better-files (com.github.pathikrit:better-files_2.13:3.9.1 - https://github.com/pathikrit/better-files) * Java SemVer (com.github.zafarkhaja:java-semver:0.9.0 - https://github.com/zafarkhaja/jsemver) * dd-plist (com.googlecode.plist:dd-plist:1.28 - http://www.github.com/3breadt/dd-plist) - * DigitalCollections: IIIF API Library (de.digitalcollections.iiif:iiif-apis:0.3.10 - https://github.com/dbmdz/iiif-apis) + * DigitalCollections: IIIF API Library (de.digitalcollections.iiif:iiif-apis:0.3.11 - https://github.com/dbmdz/iiif-apis) * s3mock (io.findify:s3mock_2.13:0.2.6 - https://github.com/findify/s3mock) + * ClassGraph (io.github.classgraph:classgraph:4.8.154 - https://github.com/classgraph/classgraph) * JOpt Simple (net.sf.jopt-simple:jopt-simple:5.0.4 - http://jopt-simple.github.io/jopt-simple) * Bouncy Castle S/MIME API (org.bouncycastle:bcmail-jdk18on:1.77 - https://www.bouncycastle.org/java.html) - * Bouncy Castle PKIX, CMS, EAC, TSP, PKCS, OCSP, CMP, and CRMF APIs (org.bouncycastle:bcpkix-jdk18on:1.78.1 - https://www.bouncycastle.org/java.html) - * Bouncy Castle Provider (org.bouncycastle:bcprov-jdk18on:1.78.1 - https://www.bouncycastle.org/java.html) - * Bouncy Castle ASN.1 Extension and Utility APIs (org.bouncycastle:bcutil-jdk18on:1.78.1 - https://www.bouncycastle.org/java.html) + * Bouncy Castle PKIX, CMS, EAC, TSP, PKCS, OCSP, CMP, and CRMF APIs (org.bouncycastle:bcpkix-jdk18on:1.80 - https://www.bouncycastle.org/download/bouncy-castle-java/) + * Bouncy Castle Provider (org.bouncycastle:bcprov-jdk18on:1.80 - https://www.bouncycastle.org/download/bouncy-castle-java/) + * Bouncy Castle ASN.1 Extension and Utility APIs (org.bouncycastle:bcutil-jdk18on:1.80 - https://www.bouncycastle.org/download/bouncy-castle-java/) * org.brotli:dec (org.brotli:dec:0.1.2 - http://brotli.org/dec) * Checker Qual (org.checkerframework:checker-qual:3.23.0 - https://checkerframework.org) - * Checker Qual (org.checkerframework:checker-qual:3.42.0 - https://checkerframework.org/) - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * Checker Qual (org.checkerframework:checker-qual:3.48.3 - https://checkerframework.org/) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * mockito-core (org.mockito:mockito-core:3.12.4 - https://github.com/mockito/mockito) * mockito-inline (org.mockito:mockito-inline:3.12.4 - https://github.com/mockito/mockito) * ORCID - Model (org.orcid:orcid-model:3.0.2 - http://github.com/ORCID/orcid-model) * JUL to SLF4J bridge (org.slf4j:jul-to-slf4j:1.7.36 - http://www.slf4j.org) * SLF4J API Module (org.slf4j:slf4j-api:1.7.36 - http://www.slf4j.org) - * SLF4J Extensions Module (org.slf4j:slf4j-ext:1.7.28 - http://www.slf4j.org) * HAL Browser (org.webjars:hal-browser:ad9b865 - http://webjars.org) * toastr (org.webjars.bowergithub.codeseven:toastr:2.1.4 - http://webjars.org) * backbone (org.webjars.bowergithub.jashkenas:backbone:1.4.1 - https://www.webjars.org) @@ -635,22 +664,22 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * jquery (org.webjars.bowergithub.jquery:jquery-dist:3.7.1 - https://www.webjars.org) * urijs (org.webjars.bowergithub.medialize:uri.js:1.19.11 - https://www.webjars.org) * bootstrap (org.webjars.bowergithub.twbs:bootstrap:4.6.2 - https://www.webjars.org) - * core-js (org.webjars.npm:core-js:3.37.1 - https://www.webjars.org) - * @json-editor/json-editor (org.webjars.npm:json-editor__json-editor:2.6.1 - https://www.webjars.org) + * core-js (org.webjars.npm:core-js:3.40.0 - https://www.webjars.org) + * @json-editor/json-editor (org.webjars.npm:json-editor__json-editor:2.15.1 - https://www.webjars.org) Mozilla Public License: * juniversalchardet (com.github.albfernandez:juniversalchardet:2.4.0 - https://github.com/albfernandez/juniversalchardet) - * H2 Database Engine (com.h2database:h2:2.2.224 - https://h2database.com) + * H2 Database Engine (com.h2database:h2:2.3.232 - https://h2database.com) * Saxon-HE (net.sf.saxon:Saxon-HE:9.8.0-14 - http://www.saxonica.com/) - * Javassist (org.javassist:javassist:3.29.0-GA - http://www.javassist.org/) + * Javassist (org.javassist:javassist:3.30.2-GA - https://www.javassist.org/) * Mozilla Rhino (org.mozilla:rhino:1.7.7.2 - https://developer.mozilla.org/en/Rhino) Public Domain: - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * HdrHistogram (org.hdrhistogram:HdrHistogram:2.1.12 - http://hdrhistogram.github.io/HdrHistogram/) * JSON in Java (org.json:json:20231013 - https://github.com/douglascrockford/JSON-java) * LatencyUtils (org.latencyutils:LatencyUtils:2.0.3 - http://latencyutils.github.io/LatencyUtils/) @@ -663,14 +692,14 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines Unicode/ICU License: - * ICU4J (com.ibm.icu:icu4j:62.1 - http://icu-project.org/) + * ICU4J (com.ibm.icu:icu4j:62.2 - http://icu-project.org/) W3C license: - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) jQuery license: - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.39.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) diff --git a/pom.xml b/pom.xml index aec280f63aa..e8a2b39faf4 100644 --- a/pom.xml +++ b/pom.xml @@ -706,7 +706,7 @@ Apache Software License, Version 2.0|The SAX License|The W3C License Apache Software License, Version 2.0|Similar to Apache License but with the acknowledgment clause removed - BSD License|The BSD License|BSD licence|BSD license|BSD|BSD-style license|New BSD License|New BSD license|Revised BSD License|BSD 2-Clause license|3-Clause BSD License|BSD 2-Clause|BSD 3-clause New License|BSD Licence 3|BSD-2-Clause|BSD-3-Clause|Modified BSD|The New BSD License|The BSD 3-Clause License (BSD3)|BSD License 3|BSD License 2.0 + BSD License|The BSD License|BSD licence|BSD license|BSD|BSD-style license|New BSD License|New BSD license|Revised BSD License|BSD 2-Clause license|3-Clause BSD License|BSD 2-Clause|BSD 3-clause New License|BSD Licence 3|BSD-2-Clause|BSD-3-Clause|Modified BSD|The New BSD License|The BSD 3-Clause License (BSD3)|BSD License 3|BSD License 2.0|The (New) BSD License BSD License|DSpace BSD License|DSpace Sourcecode License @@ -735,7 +735,7 @@ GNU Lesser General Public License (LGPL)|The GNU Lesser General Public License, Version 2.1|GNU Lesser General Public License (LGPL), Version 2.1|GNU LESSER GENERAL PUBLIC LICENSE, Version 2.1|GNU Lesser General Public License, version 2.1|GNU LESSER GENERAL PUBLIC LICENSE|GNU Lesser General Public License|GNU Lesser Public License|GNU Lesser General Public License, Version 2.1|Lesser General Public License (LGPL) v 2.1|LGPL 2.1|LGPL 2.1 license|LGPL 3.0 license|LGPL, v2.1 or later|LGPL|LGPL, version 2.1|lgpl|Lesser General Public License, version 3 or greater GNU Lesser General Public License (LGPL)|GNU Library General Public License v2.1 or later - MIT License|The MIT License|MIT LICENSE|MIT|MIT license|MIT License (MIT) + MIT License|The MIT License|MIT LICENSE|MIT|MIT license|MIT License (MIT)|The MIT License (MIT) MIT License|Bouncy Castle Licence From 6181f2723ccc0cba6b8260e8241d6b20f5bcfcd9 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 7 Feb 2025 10:57:02 -0600 Subject: [PATCH 453/939] [maven-release-plugin] prepare release dspace-7.6.3 --- dspace-api/pom.xml | 2 +- dspace-iiif/pom.xml | 2 +- dspace-oai/pom.xml | 2 +- dspace-rdf/pom.xml | 2 +- dspace-rest/pom.xml | 4 ++-- dspace-server-webapp/pom.xml | 2 +- dspace-services/pom.xml | 2 +- dspace-sword/pom.xml | 2 +- dspace-swordv2/pom.xml | 2 +- dspace/modules/additions/pom.xml | 2 +- dspace/modules/pom.xml | 2 +- dspace/modules/rest/pom.xml | 2 +- dspace/modules/server/pom.xml | 2 +- dspace/pom.xml | 2 +- pom.xml | 32 ++++++++++++++++---------------- 15 files changed, 31 insertions(+), 31 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 77adf3b9675..fa46d10104d 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6.3-SNAPSHOT + 7.6.3 .. diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index 86702fe01b9..1dbdd4ab73e 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.3-SNAPSHOT + 7.6.3 .. diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index f7acdae9929..ac9805ee691 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - 7.6.3-SNAPSHOT + 7.6.3 .. diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index 64d88b63c78..d7550e4693f 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.3-SNAPSHOT + 7.6.3 .. diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index fa4e15e8b62..e67b1e39100 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -3,7 +3,7 @@ org.dspace dspace-rest war - 7.6.3-SNAPSHOT + 7.6.3 DSpace (Deprecated) REST Webapp DSpace RESTful Web Services API. NOTE: this REST API is DEPRECATED. Please consider using the REST API in the dspace-server-webapp instead! @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6.3-SNAPSHOT + 7.6.3 .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 63de9fe721c..79e5320ed97 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.3-SNAPSHOT + 7.6.3 .. diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index 6504ae2e4b2..5c8d1ac1b0e 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.3-SNAPSHOT + 7.6.3 diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index c4826645460..f3bc891ddc8 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.3-SNAPSHOT + 7.6.3 .. diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index 7cae0567247..944c8df23af 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - 7.6.3-SNAPSHOT + 7.6.3 .. diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index db934380401..e9139578336 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - 7.6.3-SNAPSHOT + 7.6.3 .. diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index 3f15bdd01f4..d48c14803b0 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - 7.6.3-SNAPSHOT + 7.6.3 ../../pom.xml diff --git a/dspace/modules/rest/pom.xml b/dspace/modules/rest/pom.xml index e2e0041cb29..39271f9599d 100644 --- a/dspace/modules/rest/pom.xml +++ b/dspace/modules/rest/pom.xml @@ -13,7 +13,7 @@ org.dspace modules - 7.6.3-SNAPSHOT + 7.6.3 .. diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index cdbec2f60c8..ea04eddd950 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -13,7 +13,7 @@ just adding new jar in the classloader modules org.dspace - 7.6.3-SNAPSHOT + 7.6.3 .. diff --git a/dspace/pom.xml b/dspace/pom.xml index b3b474bfbcb..7fd41160492 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - 7.6.3-SNAPSHOT + 7.6.3 ../pom.xml diff --git a/pom.xml b/pom.xml index e8a2b39faf4..afd5cef2e97 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - 7.6.3-SNAPSHOT + 7.6.3 DSpace Parent Project DSpace open source software is a turnkey institutional repository application. @@ -875,14 +875,14 @@ org.dspace dspace-rest - 7.6.3-SNAPSHOT + 7.6.3 jar classes org.dspace dspace-rest - 7.6.3-SNAPSHOT + 7.6.3 war @@ -1033,69 +1033,69 @@ org.dspace dspace-api - 7.6.3-SNAPSHOT + 7.6.3 org.dspace dspace-api test-jar - 7.6.3-SNAPSHOT + 7.6.3 test org.dspace.modules additions - 7.6.3-SNAPSHOT + 7.6.3 org.dspace dspace-sword - 7.6.3-SNAPSHOT + 7.6.3 org.dspace dspace-swordv2 - 7.6.3-SNAPSHOT + 7.6.3 org.dspace dspace-oai - 7.6.3-SNAPSHOT + 7.6.3 org.dspace dspace-services - 7.6.3-SNAPSHOT + 7.6.3 org.dspace dspace-server-webapp test-jar - 7.6.3-SNAPSHOT + 7.6.3 test org.dspace dspace-rdf - 7.6.3-SNAPSHOT + 7.6.3 org.dspace dspace-iiif - 7.6.3-SNAPSHOT + 7.6.3 org.dspace dspace-server-webapp - 7.6.3-SNAPSHOT + 7.6.3 jar classes org.dspace dspace-server-webapp - 7.6.3-SNAPSHOT + 7.6.3 war @@ -1948,7 +1948,7 @@ scm:git:git@github.com:DSpace/DSpace.git scm:git:git@github.com:DSpace/DSpace.git git@github.com:DSpace/DSpace.git - dspace-7_x + dspace-7.6.3 From e0aa56ca825b3f9d5e42ec8d86b583eb6371bcb3 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 7 Feb 2025 10:57:05 -0600 Subject: [PATCH 454/939] [maven-release-plugin] prepare for next development iteration --- dspace-api/pom.xml | 2 +- dspace-iiif/pom.xml | 2 +- dspace-oai/pom.xml | 2 +- dspace-rdf/pom.xml | 2 +- dspace-rest/pom.xml | 4 ++-- dspace-server-webapp/pom.xml | 2 +- dspace-services/pom.xml | 2 +- dspace-sword/pom.xml | 2 +- dspace-swordv2/pom.xml | 2 +- dspace/modules/additions/pom.xml | 2 +- dspace/modules/pom.xml | 2 +- dspace/modules/rest/pom.xml | 2 +- dspace/modules/server/pom.xml | 2 +- dspace/pom.xml | 2 +- pom.xml | 32 ++++++++++++++++---------------- 15 files changed, 31 insertions(+), 31 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index fa46d10104d..50f618fd5f4 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6.3 + 7.6.4-SNAPSHOT .. diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index 1dbdd4ab73e..24c3d5f1642 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.3 + 7.6.4-SNAPSHOT .. diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index ac9805ee691..0a7fcf3a1b2 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - 7.6.3 + 7.6.4-SNAPSHOT .. diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index d7550e4693f..ee06135cdce 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.3 + 7.6.4-SNAPSHOT .. diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index e67b1e39100..e7f2c20ec8e 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -3,7 +3,7 @@ org.dspace dspace-rest war - 7.6.3 + 7.6.4-SNAPSHOT DSpace (Deprecated) REST Webapp DSpace RESTful Web Services API. NOTE: this REST API is DEPRECATED. Please consider using the REST API in the dspace-server-webapp instead! @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6.3 + 7.6.4-SNAPSHOT .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 79e5320ed97..f3fd8613fbf 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.3 + 7.6.4-SNAPSHOT .. diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index 5c8d1ac1b0e..6d783349f16 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.3 + 7.6.4-SNAPSHOT diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index f3bc891ddc8..d0ff123d741 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.3 + 7.6.4-SNAPSHOT .. diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index 944c8df23af..755dc30d696 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - 7.6.3 + 7.6.4-SNAPSHOT .. diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index e9139578336..dd38555808a 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - 7.6.3 + 7.6.4-SNAPSHOT .. diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index d48c14803b0..bc902913e96 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - 7.6.3 + 7.6.4-SNAPSHOT ../../pom.xml diff --git a/dspace/modules/rest/pom.xml b/dspace/modules/rest/pom.xml index 39271f9599d..c424668c898 100644 --- a/dspace/modules/rest/pom.xml +++ b/dspace/modules/rest/pom.xml @@ -13,7 +13,7 @@ org.dspace modules - 7.6.3 + 7.6.4-SNAPSHOT .. diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index ea04eddd950..318c01edd21 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -13,7 +13,7 @@ just adding new jar in the classloader modules org.dspace - 7.6.3 + 7.6.4-SNAPSHOT .. diff --git a/dspace/pom.xml b/dspace/pom.xml index 7fd41160492..af6881303b9 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - 7.6.3 + 7.6.4-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index afd5cef2e97..9695ae68300 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - 7.6.3 + 7.6.4-SNAPSHOT DSpace Parent Project DSpace open source software is a turnkey institutional repository application. @@ -875,14 +875,14 @@ org.dspace dspace-rest - 7.6.3 + 7.6.4-SNAPSHOT jar classes org.dspace dspace-rest - 7.6.3 + 7.6.4-SNAPSHOT war @@ -1033,69 +1033,69 @@ org.dspace dspace-api - 7.6.3 + 7.6.4-SNAPSHOT org.dspace dspace-api test-jar - 7.6.3 + 7.6.4-SNAPSHOT test org.dspace.modules additions - 7.6.3 + 7.6.4-SNAPSHOT org.dspace dspace-sword - 7.6.3 + 7.6.4-SNAPSHOT org.dspace dspace-swordv2 - 7.6.3 + 7.6.4-SNAPSHOT org.dspace dspace-oai - 7.6.3 + 7.6.4-SNAPSHOT org.dspace dspace-services - 7.6.3 + 7.6.4-SNAPSHOT org.dspace dspace-server-webapp test-jar - 7.6.3 + 7.6.4-SNAPSHOT test org.dspace dspace-rdf - 7.6.3 + 7.6.4-SNAPSHOT org.dspace dspace-iiif - 7.6.3 + 7.6.4-SNAPSHOT org.dspace dspace-server-webapp - 7.6.3 + 7.6.4-SNAPSHOT jar classes org.dspace dspace-server-webapp - 7.6.3 + 7.6.4-SNAPSHOT war @@ -1948,7 +1948,7 @@ scm:git:git@github.com:DSpace/DSpace.git scm:git:git@github.com:DSpace/DSpace.git git@github.com:DSpace/DSpace.git - dspace-7.6.3 + dspace-7_x From a2cb8cc8383aa64beb51509a730fcaea0965df37 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 10 Feb 2025 10:14:36 -0600 Subject: [PATCH 455/939] Remove unused byte-buddy --- pom.xml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pom.xml b/pom.xml index 9695ae68300..28be939a992 100644 --- a/pom.xml +++ b/pom.xml @@ -1839,13 +1839,6 @@ javax.annotation-api ${javax-annotation.version} - - - - net.bytebuddy - byte-buddy - 1.16.1 - From 8e411ac70cab3d30de693b929fc4c54d46022d61 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Feb 2025 22:25:47 +0000 Subject: [PATCH 456/939] Bump com.github.spotbugs:spotbugs in the build-tools group Bumps the build-tools group with 1 update: [com.github.spotbugs:spotbugs](https://github.com/spotbugs/spotbugs). Updates `com.github.spotbugs:spotbugs` from 4.9.0 to 4.9.1 - [Release notes](https://github.com/spotbugs/spotbugs/releases) - [Changelog](https://github.com/spotbugs/spotbugs/blob/master/CHANGELOG.md) - [Commits](https://github.com/spotbugs/spotbugs/compare/4.9.0...4.9.1) --- updated-dependencies: - dependency-name: com.github.spotbugs:spotbugs dependency-type: direct:production update-type: version-update:semver-patch dependency-group: build-tools ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7710ec7eed4..6c937611805 100644 --- a/pom.xml +++ b/pom.xml @@ -305,7 +305,7 @@ com.github.spotbugs spotbugs - 4.9.0 + 4.9.1 From 06a5458205dd13de9580974cdf9cf76e823edfc8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Feb 2025 22:25:53 +0000 Subject: [PATCH 457/939] Bump the google-apis group across 1 directory with 3 updates Bumps the google-apis group with 3 updates in the / directory: [com.google.http-client:google-http-client](https://github.com/googleapis/google-http-java-client), [com.google.http-client:google-http-client-jackson2](https://github.com/googleapis/google-http-java-client) and [com.google.http-client:google-http-client-gson](https://github.com/googleapis/google-http-java-client). Updates `com.google.http-client:google-http-client` from 1.45.3 to 1.46.1 - [Release notes](https://github.com/googleapis/google-http-java-client/releases) - [Changelog](https://github.com/googleapis/google-http-java-client/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/google-http-java-client/compare/v1.45.3...v1.46.1) Updates `com.google.http-client:google-http-client-jackson2` from 1.45.3 to 1.46.1 - [Release notes](https://github.com/googleapis/google-http-java-client/releases) - [Changelog](https://github.com/googleapis/google-http-java-client/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/google-http-java-client/compare/v1.45.3...v1.46.1) Updates `com.google.http-client:google-http-client-gson` from 1.43.3 to 1.46.1 - [Release notes](https://github.com/googleapis/google-http-java-client/releases) - [Changelog](https://github.com/googleapis/google-http-java-client/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/google-http-java-client/compare/v1.43.3...v1.46.1) --- updated-dependencies: - dependency-name: com.google.http-client:google-http-client dependency-type: direct:production update-type: version-update:semver-minor dependency-group: google-apis - dependency-name: com.google.http-client:google-http-client-jackson2 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: google-apis - dependency-name: com.google.http-client:google-http-client-gson dependency-type: direct:production update-type: version-update:semver-minor dependency-group: google-apis ... Signed-off-by: dependabot[bot] --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 7710ec7eed4..da8f18e3b96 100644 --- a/pom.xml +++ b/pom.xml @@ -1708,7 +1708,7 @@ com.google.http-client google-http-client - 1.45.3 + 1.46.1 com.google.errorprone @@ -1730,7 +1730,7 @@ com.google.http-client google-http-client-jackson2 - 1.45.3 + 1.46.1 jackson-core @@ -1752,7 +1752,7 @@ com.google.http-client google-http-client-gson - 1.43.3 + 1.46.1 From ba318d971049ef01b1ec4a7e5daaeb06f4d53967 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Feb 2025 22:26:17 +0000 Subject: [PATCH 458/939] Bump the test-tools group with 6 updates Bumps the test-tools group with 6 updates: | Package | From | To | | --- | --- | --- | | [io.netty:netty-buffer](https://github.com/netty/netty) | `4.1.117.Final` | `4.1.118.Final` | | [io.netty:netty-transport](https://github.com/netty/netty) | `4.1.117.Final` | `4.1.118.Final` | | [io.netty:netty-transport-native-unix-common](https://github.com/netty/netty) | `4.1.117.Final` | `4.1.118.Final` | | [io.netty:netty-common](https://github.com/netty/netty) | `4.1.117.Final` | `4.1.118.Final` | | [io.netty:netty-handler](https://github.com/netty/netty) | `4.1.117.Final` | `4.1.118.Final` | | [io.netty:netty-codec](https://github.com/netty/netty) | `4.1.117.Final` | `4.1.118.Final` | Updates `io.netty:netty-buffer` from 4.1.117.Final to 4.1.118.Final - [Commits](https://github.com/netty/netty/compare/netty-4.1.117.Final...netty-4.1.118.Final) Updates `io.netty:netty-transport` from 4.1.117.Final to 4.1.118.Final - [Commits](https://github.com/netty/netty/compare/netty-4.1.117.Final...netty-4.1.118.Final) Updates `io.netty:netty-transport-native-unix-common` from 4.1.117.Final to 4.1.118.Final - [Commits](https://github.com/netty/netty/compare/netty-4.1.117.Final...netty-4.1.118.Final) Updates `io.netty:netty-common` from 4.1.117.Final to 4.1.118.Final - [Commits](https://github.com/netty/netty/compare/netty-4.1.117.Final...netty-4.1.118.Final) Updates `io.netty:netty-handler` from 4.1.117.Final to 4.1.118.Final - [Commits](https://github.com/netty/netty/compare/netty-4.1.117.Final...netty-4.1.118.Final) Updates `io.netty:netty-codec` from 4.1.117.Final to 4.1.118.Final - [Commits](https://github.com/netty/netty/compare/netty-4.1.117.Final...netty-4.1.118.Final) --- updated-dependencies: - dependency-name: io.netty:netty-buffer dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-transport dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-transport-native-unix-common dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-common dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-handler dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-codec dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index e90773d1def..29b9865a5a7 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -887,32 +887,32 @@ io.netty netty-buffer - 4.1.117.Final + 4.1.118.Final io.netty netty-transport - 4.1.117.Final + 4.1.118.Final io.netty netty-transport-native-unix-common - 4.1.117.Final + 4.1.118.Final io.netty netty-common - 4.1.117.Final + 4.1.118.Final io.netty netty-handler - 4.1.117.Final + 4.1.118.Final io.netty netty-codec - 4.1.117.Final + 4.1.118.Final org.apache.velocity From 090001b685c6595450210e5fa311f928afe2c1be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Feb 2025 22:26:31 +0000 Subject: [PATCH 459/939] Bump commons-logging:commons-logging in the apache-commons group Bumps the apache-commons group with 1 update: commons-logging:commons-logging. Updates `commons-logging:commons-logging` from 1.3.4 to 1.3.5 --- updated-dependencies: - dependency-name: commons-logging:commons-logging dependency-type: direct:production update-type: version-update:semver-patch dependency-group: apache-commons ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7710ec7eed4..505395fafec 100644 --- a/pom.xml +++ b/pom.xml @@ -1511,7 +1511,7 @@ commons-logging commons-logging - 1.3.4 + 1.3.5 org.apache.commons From 2c13ee40fef393dabf56d35b93e7e537608af9f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Feb 2025 22:27:33 +0000 Subject: [PATCH 460/939] Bump tika.version from 2.9.2 to 2.9.3 Bumps `tika.version` from 2.9.2 to 2.9.3. Updates `org.apache.tika:tika-core` from 2.9.2 to 2.9.3 - [Changelog](https://github.com/apache/tika/blob/2.9.3/CHANGES.txt) - [Commits](https://github.com/apache/tika/compare/2.9.2...2.9.3) Updates `org.apache.tika:tika-parsers-standard-package` from 2.9.2 to 2.9.3 --- updated-dependencies: - dependency-name: org.apache.tika:tika-core dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.tika:tika-parsers-standard-package dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7710ec7eed4..ce859ee7e78 100644 --- a/pom.xml +++ b/pom.xml @@ -42,7 +42,7 @@ 2.0.33 1.19.0 1.7.36 - 2.9.2 + 2.9.3 1.80 From 102c347455329181205a2be040def3f30e74b8ca Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 11 Feb 2025 10:39:16 -0600 Subject: [PATCH 461/939] Dependency convergence fix --- pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pom.xml b/pom.xml index ce859ee7e78..313a6bbfe9a 100644 --- a/pom.xml +++ b/pom.xml @@ -1316,6 +1316,12 @@ bcutil-jdk18on ${bouncycastle.version} + + + com.healthmarketscience.jackcess + jackcess + 4.0.8 + org.apache.james From ee762260cf423075ceb6129b00cb8cb981f725ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Wed, 12 Feb 2025 12:26:27 +0000 Subject: [PATCH 462/939] [Port dspace-7_x] Fixing Crossref document type issue with new metadata mapping processor (#9909) * new metadata mapping processor for crossref document type * licence and code style fixes * adjust crossref test to consider mapped dc.type to Article * correcting english * remove trailing space --- ...nValueMappingMetadataProcessorService.java | 85 +++++++++++++++++++ ...CrossRefImportMetadataSourceServiceIT.java | 6 +- ...sref-to-dspace-publication-type.properties | 29 +++++++ .../spring/api/crossref-integration.xml | 10 +++ 4 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/transform/StringJsonValueMappingMetadataProcessorService.java create mode 100644 dspace/config/crosswalks/mapConverter-crossref-to-dspace-publication-type.properties diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/transform/StringJsonValueMappingMetadataProcessorService.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/transform/StringJsonValueMappingMetadataProcessorService.java new file mode 100644 index 00000000000..a8f667eb979 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/transform/StringJsonValueMappingMetadataProcessorService.java @@ -0,0 +1,85 @@ +/** + * 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.importer.external.metadatamapping.transform; + +import static java.util.Optional.ofNullable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Optional; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.JsonNodeType; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.importer.external.metadatamapping.contributor.JsonPathMetadataProcessor; +import org.dspace.util.SimpleMapConverter; + +/** + * This class is a Metadata processor from a structured JSON Metadata result + * and uses a SimpleMapConverter, with a mapping properties file + * to map to a single string value based on mapped keys.
    + * Like:
    + * journal-article = Article + * + * @author paulo-graca + */ +public class StringJsonValueMappingMetadataProcessorService implements JsonPathMetadataProcessor { + + private final static Logger log = LogManager.getLogger(); + /** + * The value map converter. + * a list of values to map from + */ + private SimpleMapConverter valueMapConverter; + private String path; + + @Override + public Collection processMetadata(String json) { + JsonNode rootNode = convertStringJsonToJsonNode(json); + Optional abstractNode = Optional.of(rootNode.at(path)); + Collection values = new ArrayList<>(); + + if (abstractNode.isPresent() && abstractNode.get().getNodeType().equals(JsonNodeType.STRING)) { + + String stringValue = abstractNode.get().asText(); + values.add(ofNullable(stringValue) + .map(value -> valueMapConverter != null ? valueMapConverter.getValue(value) : value) + .orElse(valueMapConverter.getValue(null))); + } + return values; + } + + private JsonNode convertStringJsonToJsonNode(String json) { + ObjectMapper mapper = new ObjectMapper(); + JsonNode body = null; + try { + body = mapper.readTree(json); + } catch (JsonProcessingException e) { + log.error("Unable to process json response.", e); + } + return body; + } + + /* Getters and Setters */ + + public String convertType(String type) { + return valueMapConverter != null ? valueMapConverter.getValue(type) : type; + } + + public void setValueMapConverter(SimpleMapConverter valueMapConverter) { + this.valueMapConverter = valueMapConverter; + } + + public void setPath(String path) { + this.path = path; + } + +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CrossRefImportMetadataSourceServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CrossRefImportMetadataSourceServiceIT.java index f61a81140dd..a182e7d8907 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CrossRefImportMetadataSourceServiceIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CrossRefImportMetadataSourceServiceIT.java @@ -163,7 +163,8 @@ private ArrayList getRecords() { "State of Awareness of Freshers’ Groups Chortkiv State" + " Medical College of Prevention of Iodine Deficiency Diseases"); MetadatumDTO author = createMetadatumDTO("dc", "contributor", "author", "Senyuk, L.V."); - MetadatumDTO type = createMetadatumDTO("dc", "type", null, "journal-article"); + // is expected the dc.type to be mapped from journal-article to Article + MetadatumDTO type = createMetadatumDTO("dc", "type", null, "Article"); MetadatumDTO date = createMetadatumDTO("dc", "date", "issued", "2016-05-19"); MetadatumDTO ispartof = createMetadatumDTO("dc", "relation", "ispartof", "Ukraïnsʹkij žurnal medicini, bìologìï ta sportu"); @@ -192,7 +193,8 @@ private ArrayList getRecords() { MetadatumDTO title2 = createMetadatumDTO("dc", "title", null, "Ischemic Heart Disease and Role of Nurse of Cardiology Department"); MetadatumDTO author2 = createMetadatumDTO("dc", "contributor", "author", "Kozak, K. І."); - MetadatumDTO type2 = createMetadatumDTO("dc", "type", null, "journal-article"); + // is expected the dc.type to be mapped from journal-article to Article + MetadatumDTO type2 = createMetadatumDTO("dc", "type", null, "Article"); MetadatumDTO date2 = createMetadatumDTO("dc", "date", "issued", "2016-05-19"); MetadatumDTO ispartof2 = createMetadatumDTO("dc", "relation", "ispartof", "Ukraïnsʹkij žurnal medicini, bìologìï ta sportu"); diff --git a/dspace/config/crosswalks/mapConverter-crossref-to-dspace-publication-type.properties b/dspace/config/crosswalks/mapConverter-crossref-to-dspace-publication-type.properties new file mode 100644 index 00000000000..3bd4468148d --- /dev/null +++ b/dspace/config/crosswalks/mapConverter-crossref-to-dspace-publication-type.properties @@ -0,0 +1,29 @@ +# based on Crossref content type at https://crossref.gitlab.io/knowledge_base/docs/topics/content-types/#types-in-cayenne-rest-api +# Mapping between work type supported by Crossref and the DSpace common publication's types +journal-article = Article +journal-issue = Other +journal-volume = Other +journal = Other +proceedings-article = Other +proceedings = Other +dataset = Dataset +component = Other +report = Other +report-series = Other +standard = Other +standard-series = Other +edited-book = Other +monograph = Other +reference-book = Other +book = Book +book-series = Other +book-set = Other +book-chapter = Book chapter +book-section = Other +book-part = Other +book-track = Other +reference-entry = Other +dissertation = Other +posted-content = Other +peer-review = Other +other = Other \ No newline at end of file diff --git a/dspace/config/spring/api/crossref-integration.xml b/dspace/config/spring/api/crossref-integration.xml index 4786c44a786..6fe9723d796 100644 --- a/dspace/config/spring/api/crossref-integration.xml +++ b/dspace/config/spring/api/crossref-integration.xml @@ -49,9 +49,19 @@ + + + + + + + + + + From 268b5fc8b703a0fabfdb1ed8b3336e4bbca48ee2 Mon Sep 17 00:00:00 2001 From: Martin Walk Date: Thu, 13 Feb 2025 14:34:28 +0100 Subject: [PATCH 463/939] Fix #10405 bug in log4j-cli (cherry picked from commit 4c044adcf388a291dced77b53a9b816a6a11659c) --- dspace/config/log4j2-cli.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/dspace/config/log4j2-cli.xml b/dspace/config/log4j2-cli.xml index 73acab877f1..6cac529978f 100644 --- a/dspace/config/log4j2-cli.xml +++ b/dspace/config/log4j2-cli.xml @@ -25,6 +25,7 @@ From b63329b45a758212048f235c9966ade1ba314021 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Feb 2025 22:25:23 +0000 Subject: [PATCH 464/939] Bump com.github.spotbugs:spotbugs-maven-plugin in the build-tools group Bumps the build-tools group with 1 update: [com.github.spotbugs:spotbugs-maven-plugin](https://github.com/spotbugs/spotbugs-maven-plugin). Updates `com.github.spotbugs:spotbugs-maven-plugin` from 4.8.6.6 to 4.9.1.0 - [Release notes](https://github.com/spotbugs/spotbugs-maven-plugin/releases) - [Commits](https://github.com/spotbugs/spotbugs-maven-plugin/compare/spotbugs-maven-plugin-4.8.6.6...spotbugs-maven-plugin-4.9.1.0) --- updated-dependencies: - dependency-name: com.github.spotbugs:spotbugs-maven-plugin dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 84bba384734..0de103004d5 100644 --- a/pom.xml +++ b/pom.xml @@ -295,7 +295,7 @@ com.github.spotbugs spotbugs-maven-plugin - 4.8.6.6 + 4.9.1.0 Max Low From edbf9ef60598f40f0147f2563150d5a047623362 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Feb 2025 22:25:48 +0000 Subject: [PATCH 465/939] Bump commons-beanutils:commons-beanutils in the apache-commons group Bumps the apache-commons group with 1 update: commons-beanutils:commons-beanutils. Updates `commons-beanutils:commons-beanutils` from 1.10.0 to 1.10.1 --- updated-dependencies: - dependency-name: commons-beanutils:commons-beanutils dependency-type: direct:production update-type: version-update:semver-patch dependency-group: apache-commons ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 84bba384734..a6d2a608463 100644 --- a/pom.xml +++ b/pom.xml @@ -1470,7 +1470,7 @@ commons-beanutils commons-beanutils - 1.10.0 + 1.10.1 commons-cli From 51c766caa32d73aa3d3415cccee59d175499506c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Feb 2025 22:26:49 +0000 Subject: [PATCH 466/939] Bump net.minidev:json-smart from 2.5.1 to 2.5.2 Bumps [net.minidev:json-smart](https://github.com/netplex/json-smart-v2) from 2.5.1 to 2.5.2. - [Release notes](https://github.com/netplex/json-smart-v2/releases) - [Commits](https://github.com/netplex/json-smart-v2/compare/2.5.1...2.5.2) --- updated-dependencies: - dependency-name: net.minidev:json-smart dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- dspace-server-webapp/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index f3fd8613fbf..52105803889 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -514,7 +514,7 @@ net.minidev json-smart - 2.5.1 + 2.5.2 From 1d85653ed0e524d95765bfbf64b18e1fd8436fda Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Feb 2025 22:27:07 +0000 Subject: [PATCH 467/939] Bump com.amazonaws:aws-java-sdk-s3 from 1.12.780 to 1.12.781 Bumps [com.amazonaws:aws-java-sdk-s3](https://github.com/aws/aws-sdk-java) from 1.12.780 to 1.12.781. - [Changelog](https://github.com/aws/aws-sdk-java/blob/master/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-java/compare/1.12.780...1.12.781) --- updated-dependencies: - dependency-name: com.amazonaws:aws-java-sdk-s3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 29b9865a5a7..322e1820d32 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -761,7 +761,7 @@ com.amazonaws aws-java-sdk-s3 - 1.12.780 + 1.12.781 From 15dab1e41e3b0734e434c08e43db914750182a88 Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Fri, 28 Feb 2025 11:27:36 +0100 Subject: [PATCH 468/939] Use NestableJsonFacet to process browse entries count response (cherry picked from commit 7ba09b7a857f6f3545d88b26940c1089692a860a) --- .../java/org/dspace/discovery/SolrServiceImpl.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java index 34efe96ae7f..29b695b5e78 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java @@ -41,6 +41,8 @@ import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.client.solrj.response.FacetField; import org.apache.solr.client.solrj.response.QueryResponse; +import org.apache.solr.client.solrj.response.json.BucketBasedJsonFacet; +import org.apache.solr.client.solrj.response.json.NestableJsonFacet; import org.apache.solr.client.solrj.util.ClientUtils; import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrDocumentList; @@ -1105,13 +1107,11 @@ protected DiscoverResult retrieveResult(Context context, DiscoverQuery query) */ private void resolveEntriesCount(DiscoverResult result, QueryResponse solrQueryResponse) { - Object facetsObj = solrQueryResponse.getResponse().get("facets"); - if (facetsObj instanceof NamedList) { - NamedList facets = (NamedList) facetsObj; - Object bucketsInfoObj = facets.get("entries_count"); - if (bucketsInfoObj instanceof NamedList) { - NamedList bucketsInfo = (NamedList) bucketsInfoObj; - result.setTotalEntries((int) bucketsInfo.get("numBuckets")); + NestableJsonFacet response = solrQueryResponse.getJsonFacetingResponse(); + if (response != null) { + BucketBasedJsonFacet facet = response.getBucketBasedFacets("entries_count"); + if (facet != null) { + result.setTotalEntries(facet.getNumBucketsCount()); } } } From 238893ce6dc5376338bf3add87fef288209bcd44 Mon Sep 17 00:00:00 2001 From: Zahraa Chreim Date: Tue, 11 Mar 2025 16:02:03 +0200 Subject: [PATCH 469/939] Fix invalid cast in DOIOrganiser exception handling --- .../dspace/identifier/doi/DOIOrganiser.java | 70 +++++++++---------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java b/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java index 507503ffaa1..1a142802d41 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java +++ b/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java @@ -448,28 +448,28 @@ public void register(DOI doiRow, Filter filter) + " is successfully registered."); } } catch (IdentifierException ex) { + String message; if (!(ex instanceof DOIIdentifierException)) { - LOG.error("It wasn't possible to register this identifier: " - + DOI.SCHEME + doiRow.getDoi() - + " online. ", ex); + message = "It wasn't possible to register this identifier: " + + DOI.SCHEME + doiRow.getDoi() + + " online. "; + } else { + DOIIdentifierException doiIdentifierException = (DOIIdentifierException) ex; + message = "It wasn't possible to register this identifier : " + + DOI.SCHEME + doiRow.getDoi() + + " online. Exceptions code: " + + DOIIdentifierException.codeToString(doiIdentifierException.getCode()); } - DOIIdentifierException doiIdentifierException = (DOIIdentifierException) ex; - try { sendAlertMail("Register", dso, DOI.SCHEME + doiRow.getDoi(), - doiIdentifierException.codeToString(doiIdentifierException - .getCode())); + message); } catch (IOException ioe) { LOG.error("Couldn't send mail", ioe); } - LOG.error("It wasn't possible to register this identifier : " - + DOI.SCHEME + doiRow.getDoi() - + " online. Exceptions code: " - + doiIdentifierException - .codeToString(doiIdentifierException.getCode()), ex); + LOG.error(message, ex); if (!quiet) { System.err.println("It wasn't possible to register this identifier: " @@ -541,27 +541,27 @@ public void reserve(DOI doiRow, Filter filter) { System.out.println("This identifier : " + DOI.SCHEME + doiRow.getDoi() + " is successfully reserved."); } } catch (IdentifierException ex) { + String message; if (!(ex instanceof DOIIdentifierException)) { - LOG.error("It wasn't possible to register this identifier : " - + DOI.SCHEME + doiRow.getDoi() - + " online. ", ex); + message = "It wasn't possible to register this identifier : " + + DOI.SCHEME + doiRow.getDoi() + + " online. "; + } else { + DOIIdentifierException doiIdentifierException = (DOIIdentifierException) ex; + message = "It wasn't possible to reserve the identifier online. " + + " Exceptions code: " + + DOIIdentifierException.codeToString(doiIdentifierException.getCode()); } - DOIIdentifierException doiIdentifierException = (DOIIdentifierException) ex; - try { sendAlertMail("Reserve", dso, DOI.SCHEME + doiRow.getDoi(), - DOIIdentifierException.codeToString( - doiIdentifierException.getCode())); + message); } catch (IOException ioe) { LOG.error("Couldn't send mail", ioe); } - LOG.error("It wasn't possible to reserve the identifier online. " - + " Exceptions code: " - + DOIIdentifierException - .codeToString(doiIdentifierException.getCode()), ex); + LOG.error(message, ex); if (!quiet) { System.err.println("It wasn't possible to reserve this identifier: " + DOI.SCHEME + doiRow.getDoi()); @@ -606,27 +606,27 @@ public void update(DOI doiRow) { + doiRow.getDoi() + "."); } } catch (IdentifierException ex) { + String message; if (!(ex instanceof DOIIdentifierException)) { - LOG.error("Registering DOI {} for object {}: the registrar returned an error.", - doiRow.getDoi(), dso.getID(), ex); + message = String.format("Registering DOI %s for object %s: the registrar returned an error.", + doiRow.getDoi(), dso.getID()); + } else { + DOIIdentifierException doiIdentifierException = (DOIIdentifierException) ex; + message = "It wasn't possible to update this identifier: " + + DOI.SCHEME + doiRow.getDoi() + + " Exceptions code: " + + DOIIdentifierException.codeToString(doiIdentifierException.getCode()); } - DOIIdentifierException doiIdentifierException = (DOIIdentifierException) ex; - try { sendAlertMail("Update", dso, DOI.SCHEME + doiRow.getDoi(), - doiIdentifierException.codeToString(doiIdentifierException - .getCode())); + message); } catch (IOException ioe) { LOG.error("Couldn't send mail", ioe); } - LOG.error("It wasn't possible to update this identifier: " - + DOI.SCHEME + doiRow.getDoi() - + " Exceptions code: " - + doiIdentifierException - .codeToString(doiIdentifierException.getCode()), ex); + LOG.error(message, ex); if (!quiet) { System.err.println("It wasn't possible to update this identifier: " + DOI.SCHEME + doiRow.getDoi()); @@ -830,4 +830,4 @@ private void setQuiet() { this.quiet = true; } -} \ No newline at end of file +} From fbec7f2e56076de7575ea7404f9c36c47ef82539 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Fri, 21 Mar 2025 14:20:07 +0100 Subject: [PATCH 470/939] add missing whitespace in exception message (cherry picked from commit 9a904ab4c93775257f1e17d3bd3991e6c7257754) --- .../java/org/dspace/discovery/utils/DiscoverQueryBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/utils/DiscoverQueryBuilder.java b/dspace-api/src/main/java/org/dspace/discovery/utils/DiscoverQueryBuilder.java index 92a973dff88..b816e222539 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/utils/DiscoverQueryBuilder.java +++ b/dspace-api/src/main/java/org/dspace/discovery/utils/DiscoverQueryBuilder.java @@ -302,7 +302,7 @@ private void configureSorting(String sortProperty, String sortDirection, Discove if (StringUtils.isNotBlank(sortBy) && !isConfigured(sortBy, searchSortConfiguration)) { throw new SearchServiceException( - "The field: " + sortBy + "is not configured for the configuration!"); + "The field: " + sortBy + " is not configured for the configuration!"); } From 00da667ba51362f134a4938514e3949c9bd60277 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Fri, 21 Mar 2025 14:34:04 +0100 Subject: [PATCH 471/939] add missing whitespace (cherry picked from commit 4ea49580937e60978035daf76b22588e3b979215) --- .../src/main/java/org/dspace/app/util/DCInputsReader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/app/util/DCInputsReader.java b/dspace-api/src/main/java/org/dspace/app/util/DCInputsReader.java index 8dc8239ca50..c77c3bf10e0 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/DCInputsReader.java +++ b/dspace-api/src/main/java/org/dspace/app/util/DCInputsReader.java @@ -379,7 +379,7 @@ private void processRow(String formName, int rowIdx, Node n, List Date: Fri, 21 Mar 2025 19:01:09 +0000 Subject: [PATCH 472/939] Add bitstream null check to XOAI (cherry picked from commit 54602f47b1d35a7cb75b28acde08d43a93461e8a) --- dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 2d267957780..16c16b0115b 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 @@ -110,7 +110,7 @@ private List getFileFormats(Item item) { try { for (Bundle b : itemService.getBundles(item, "ORIGINAL")) { for (Bitstream bs : b.getBitstreams()) { - if (!formats.contains(bs.getFormat(context).getMIMEType())) { + if (bs != null && !formats.contains(bs.getFormat(context).getMIMEType())) { formats.add(bs.getFormat(context).getMIMEType()); } } From c797441b868d76f415e8df35191471ecc966720e Mon Sep 17 00:00:00 2001 From: DSpace Bot <68393067+dspace-bot@users.noreply.github.com> Date: Mon, 24 Mar 2025 14:04:03 -0500 Subject: [PATCH 473/939] [Port dspace-7_x] Add null check in SolrServiceFileInfoPlugin for index-discovery (#10517) * Add null check in SolrServiceFileInfoPlugin for index-discovery (cherry picked from commit d07f1e0caad7632231a7a98cf2f2ff119d6a3b89) * Fix starting curly brace. (cherry picked from commit e11994c0ee75baf7389446e67ca8252f5f16a8d2) * Update SolrServiceFileInfoPlugin.java (cherry picked from commit 18372ae07246c5b0829286f7bb20263017528518) --------- Co-authored-by: jameswsullivan <81947235+jameswsullivan@users.noreply.github.com> --- .../discovery/SolrServiceFileInfoPlugin.java | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceFileInfoPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceFileInfoPlugin.java index 7aece5acf31..6142dd0dba4 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceFileInfoPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceFileInfoPlugin.java @@ -52,21 +52,23 @@ public void additionalIndex(Context context, IndexableObject indexableObject, So List bitstreams = bundle.getBitstreams(); if (bitstreams != null) { for (Bitstream bitstream : bitstreams) { - document.addField(SOLR_FIELD_NAME_FOR_FILENAMES, bitstream.getName()); - // Add _keyword and _filter fields which are necessary to support filtering and faceting - // for the file names - document.addField(SOLR_FIELD_NAME_FOR_FILENAMES + "_keyword", bitstream.getName()); - document.addField(SOLR_FIELD_NAME_FOR_FILENAMES + "_filter", bitstream.getName()); + if (bitstream != null) { + document.addField(SOLR_FIELD_NAME_FOR_FILENAMES, bitstream.getName()); + // Add _keyword and _filter fields which are necessary to + // support filtering and faceting for the file names + document.addField(SOLR_FIELD_NAME_FOR_FILENAMES + "_keyword", bitstream.getName()); + document.addField(SOLR_FIELD_NAME_FOR_FILENAMES + "_filter", bitstream.getName()); - String description = bitstream.getDescription(); - if ((description != null) && !description.isEmpty()) { - document.addField(SOLR_FIELD_NAME_FOR_DESCRIPTIONS, description); - // Add _keyword and _filter fields which are necessary to support filtering and - // faceting for the descriptions - document.addField(SOLR_FIELD_NAME_FOR_DESCRIPTIONS + "_keyword", - description); - document.addField(SOLR_FIELD_NAME_FOR_DESCRIPTIONS + "_filter", - description); + String description = bitstream.getDescription(); + if ((description != null) && !description.isEmpty()) { + document.addField(SOLR_FIELD_NAME_FOR_DESCRIPTIONS, description); + // Add _keyword and _filter fields which are necessary to support filtering and + // faceting for the descriptions + document.addField(SOLR_FIELD_NAME_FOR_DESCRIPTIONS + "_keyword", + description); + document.addField(SOLR_FIELD_NAME_FOR_DESCRIPTIONS + "_filter", + description); + } } } } From 08e330c1c0a62ecfb83c2e7617472bd5a50f8c5a Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Wed, 26 Mar 2025 18:06:08 +0100 Subject: [PATCH 474/939] [DURACOM-243] Adds rotation handling inside JPEGFilter Conflicts: --- .../mediafilter/BrandedPreviewJPEGFilter.java | 23 +- .../dspace/app/mediafilter/JPEGFilter.java | 274 ++++++++++++++---- .../app/mediafilter/PDFBoxThumbnail.java | 1 + 3 files changed, 231 insertions(+), 67 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/BrandedPreviewJPEGFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/BrandedPreviewJPEGFilter.java index 7b082c6c21a..483e4f5f6ea 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/BrandedPreviewJPEGFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/BrandedPreviewJPEGFilter.java @@ -7,9 +7,7 @@ */ package org.dspace.app.mediafilter; -import java.awt.image.BufferedImage; import java.io.InputStream; -import javax.imageio.ImageIO; import org.dspace.content.Item; import org.dspace.services.ConfigurationService; @@ -63,27 +61,20 @@ public String getDescription() { @Override public InputStream getDestinationStream(Item currentItem, InputStream source, boolean verbose) throws Exception { - // read in bitstream's image - BufferedImage buf = ImageIO.read(source); - // get config params ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); - float xmax = (float) configurationService - .getIntProperty("webui.preview.maxwidth"); - float ymax = (float) configurationService - .getIntProperty("webui.preview.maxheight"); - boolean blurring = (boolean) configurationService - .getBooleanProperty("webui.preview.blurring"); - boolean hqscaling = (boolean) configurationService - .getBooleanProperty("webui.preview.hqscaling"); + int xmax = configurationService.getIntProperty("webui.preview.maxwidth"); + int ymax = configurationService.getIntProperty("webui.preview.maxheight"); + boolean blurring = configurationService.getBooleanProperty("webui.preview.blurring"); + boolean hqscaling = configurationService.getBooleanProperty("webui.preview.hqscaling"); int brandHeight = configurationService.getIntProperty("webui.preview.brand.height"); String brandFont = configurationService.getProperty("webui.preview.brand.font"); int brandFontPoint = configurationService.getIntProperty("webui.preview.brand.fontpoint"); JPEGFilter jpegFilter = new JPEGFilter(); - return jpegFilter - .getThumbDim(currentItem, buf, verbose, xmax, ymax, blurring, hqscaling, brandHeight, brandFontPoint, - brandFont); + return jpegFilter.getThumb( + currentItem, source, verbose, xmax, ymax, blurring, hqscaling, brandHeight, brandFontPoint, brandFont + ); } } diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/JPEGFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/JPEGFilter.java index 502f71eb5ca..181e3bcc4b5 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/JPEGFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/JPEGFilter.java @@ -8,22 +8,36 @@ package org.dspace.app.mediafilter; import java.awt.Color; +import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.Transparency; +import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.awt.image.BufferedImageOp; import java.awt.image.ConvolveOp; import java.awt.image.Kernel; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; import java.io.InputStream; import javax.imageio.ImageIO; +import com.drew.imaging.ImageMetadataReader; +import com.drew.imaging.ImageProcessingException; +import com.drew.metadata.Metadata; +import com.drew.metadata.MetadataException; +import com.drew.metadata.exif.ExifIFD0Directory; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.content.Item; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; +import org.w3c.dom.Node; /** * Filter image bitstreams, scaling the image to be within the bounds of @@ -33,6 +47,8 @@ * @author Jason Sherman jsherman@usao.edu */ public class JPEGFilter extends MediaFilter implements SelfRegisterInputFormats { + private static final Logger log = LogManager.getLogger(JPEGFilter.class); + @Override public String getFilteredName(String oldFilename) { return oldFilename + ".jpg"; @@ -62,6 +78,134 @@ public String getDescription() { return "Generated Thumbnail"; } + /** + * Gets the rotation angle from image's metadata using ImageReader. + * This method consumes the InputStream, so you need to be careful to don't reuse the same InputStream after + * computing the rotation angle. + * + * @param buf InputStream of the image file + * @return Rotation angle in degrees (0, 90, 180, or 270) + */ + public static int getImageRotationUsingImageReader(InputStream buf) { + try { + Metadata metadata = ImageMetadataReader.readMetadata(buf); + ExifIFD0Directory directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class); + if (directory != null && directory.containsTag(ExifIFD0Directory.TAG_ORIENTATION)) { + return convertRotationToDegrees(directory.getInt(ExifIFD0Directory.TAG_ORIENTATION)); + } + } catch (MetadataException | ImageProcessingException | IOException e) { + log.error("Error reading image metadata", e); + } + return 0; + } + + public static int convertRotationToDegrees(int valueNode) { + // Common orientation values: + // 1 = Normal (0°) + // 6 = Rotated 90° CW + // 3 = Rotated 180° + // 8 = Rotated 270° CW + switch (valueNode) { + case 6: + return 90; + case 3: + return 180; + case 8: + return 270; + default: + return 0; + } + } + + /** + * Helper method to find a node with given name in metadata tree + */ + private static Node findNode(Node node, String name) { + if (node.getNodeName().equalsIgnoreCase(name)) { + return node; + } + + Node child = node.getFirstChild(); + while (child != null) { + Node found = findNode(child, name); + if (found != null) { + return found; + } + child = child.getNextSibling(); + } + return null; + } + + /** + * Rotates an image by the specified angle + * + * @param image The original image + * @param angle The rotation angle in degrees + * @return Rotated image + */ + public static BufferedImage rotateImage(BufferedImage image, int angle) { + if (angle == 0) { + return image; + } + + double radians = Math.toRadians(angle); + double sin = Math.abs(Math.sin(radians)); + double cos = Math.abs(Math.cos(radians)); + + int newWidth = (int) Math.round(image.getWidth() * cos + image.getHeight() * sin); + int newHeight = (int) Math.round(image.getWidth() * sin + image.getHeight() * cos); + + BufferedImage rotated = new BufferedImage(newWidth, newHeight, image.getType()); + Graphics2D g2d = rotated.createGraphics(); + AffineTransform at = new AffineTransform(); + + at.translate(newWidth / 2, newHeight / 2); + at.rotate(radians); + at.translate(-image.getWidth() / 2, -image.getHeight() / 2); + + g2d.setTransform(at); + g2d.drawImage(image, 0, 0, null); + g2d.dispose(); + + return rotated; + } + + /** + * Calculates scaled dimension while maintaining aspect ratio + * + * @param imgSize Original image dimensions + * @param boundary Maximum allowed dimensions + * @return New dimensions that fit within boundary while preserving aspect ratio + */ + private Dimension getScaledDimension(Dimension imgSize, Dimension boundary) { + + int originalWidth = imgSize.width; + int originalHeight = imgSize.height; + int boundWidth = boundary.width; + int boundHeight = boundary.height; + int newWidth = originalWidth; + int newHeight = originalHeight; + + + // First check if we need to scale width + if (originalWidth > boundWidth) { + // Scale width to fit + newWidth = boundWidth; + // Scale height to maintain aspect ratio + newHeight = (newWidth * originalHeight) / originalWidth; + } + + // Then check if we need to scale even with the new height + if (newHeight > boundHeight) { + // Scale height to fit instead + newHeight = boundHeight; + newWidth = (newHeight * originalWidth) / originalHeight; + } + + return new Dimension(newWidth, newHeight); + } + + /** * @param currentItem item * @param source source input stream @@ -72,10 +216,59 @@ public String getDescription() { @Override public InputStream getDestinationStream(Item currentItem, InputStream source, boolean verbose) throws Exception { + return getThumb(currentItem, source, verbose); + } + + public InputStream getThumb(Item currentItem, InputStream source, boolean verbose) + throws Exception { + // get config params + final ConfigurationService configurationService + = DSpaceServicesFactory.getInstance().getConfigurationService(); + int xmax = configurationService + .getIntProperty("thumbnail.maxwidth"); + int ymax = configurationService + .getIntProperty("thumbnail.maxheight"); + boolean blurring = (boolean) configurationService + .getBooleanProperty("thumbnail.blurring"); + boolean hqscaling = (boolean) configurationService + .getBooleanProperty("thumbnail.hqscaling"); + + return getThumb(currentItem, source, verbose, xmax, ymax, blurring, hqscaling, 0, 0, null); + } + + protected InputStream getThumb( + Item currentItem, + InputStream source, + boolean verbose, + int xmax, + int ymax, + boolean blurring, + boolean hqscaling, + int brandHeight, + int brandFontPoint, + String brandFont + ) throws Exception { + + File tempFile = File.createTempFile("temp", ".tmp"); + tempFile.deleteOnExit(); + + // Write to temp file + try (FileOutputStream fos = new FileOutputStream(tempFile)) { + byte[] buffer = new byte[4096]; + int len; + while ((len = source.read(buffer)) != -1) { + fos.write(buffer, 0, len); + } + } + + int rotation = getImageRotationUsingImageReader(new FileInputStream(tempFile)); // read in bitstream's image - BufferedImage buf = ImageIO.read(source); + BufferedImage buf = ImageIO.read(new FileInputStream(tempFile)); - return getThumb(currentItem, buf, verbose); + return getThumbDim( + currentItem, buf, verbose, xmax, ymax, blurring, hqscaling, brandHeight, brandFontPoint, rotation, + brandFont + ); } public InputStream getThumb(Item currentItem, BufferedImage buf, boolean verbose) @@ -83,25 +276,28 @@ public InputStream getThumb(Item currentItem, BufferedImage buf, boolean verbose // get config params final ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); - float xmax = (float) configurationService + int xmax = configurationService .getIntProperty("thumbnail.maxwidth"); - float ymax = (float) configurationService + int ymax = configurationService .getIntProperty("thumbnail.maxheight"); boolean blurring = (boolean) configurationService .getBooleanProperty("thumbnail.blurring"); boolean hqscaling = (boolean) configurationService .getBooleanProperty("thumbnail.hqscaling"); - return getThumbDim(currentItem, buf, verbose, xmax, ymax, blurring, hqscaling, 0, 0, null); + return getThumbDim(currentItem, buf, verbose, xmax, ymax, blurring, hqscaling, 0, 0, 0, null); } - public InputStream getThumbDim(Item currentItem, BufferedImage buf, boolean verbose, float xmax, float ymax, + public InputStream getThumbDim(Item currentItem, BufferedImage buf, boolean verbose, int xmax, int ymax, boolean blurring, boolean hqscaling, int brandHeight, int brandFontPoint, - String brandFont) + int rotation, String brandFont) throws Exception { - // now get the image dimensions - float xsize = (float) buf.getWidth(null); - float ysize = (float) buf.getHeight(null); + + // Rotate the image if needed + BufferedImage correctedImage = rotateImage(buf, rotation); + + int xsize = correctedImage.getWidth(); + int ysize = correctedImage.getHeight(); // if verbose flag is set, print out dimensions // to STDOUT @@ -109,77 +305,53 @@ public InputStream getThumbDim(Item currentItem, BufferedImage buf, boolean verb System.out.println("original size: " + xsize + "," + ysize); } - // scale by x first if needed - if (xsize > xmax) { - // calculate scaling factor so that xsize * scale = new size (max) - float scale_factor = xmax / xsize; + // Calculate new dimensions while maintaining aspect ratio + Dimension newDimension = getScaledDimension( + new Dimension(xsize, ysize), + new Dimension(xmax, ymax) + ); - // if verbose flag is set, print out extracted text - // to STDOUT - if (verbose) { - System.out.println("x scale factor: " + scale_factor); - } - - // now reduce x size - // and y size - xsize = xsize * scale_factor; - ysize = ysize * scale_factor; - - // if verbose flag is set, print out extracted text - // to STDOUT - if (verbose) { - System.out.println("size after fitting to maximum width: " + xsize + "," + ysize); - } - } - - // scale by y if needed - if (ysize > ymax) { - float scale_factor = ymax / ysize; - - // now reduce x size - // and y size - xsize = xsize * scale_factor; - ysize = ysize * scale_factor; - } // if verbose flag is set, print details to STDOUT if (verbose) { - System.out.println("size after fitting to maximum height: " + xsize + ", " - + ysize); + System.out.println("size after fitting to maximum height: " + newDimension.width + ", " + + newDimension.height); } + xsize = newDimension.width; + ysize = newDimension.height; + // create an image buffer for the thumbnail with the new xsize, ysize - BufferedImage thumbnail = new BufferedImage((int) xsize, (int) ysize, - BufferedImage.TYPE_INT_RGB); + BufferedImage thumbnail = new BufferedImage(xsize, ysize, BufferedImage.TYPE_INT_RGB); // Use blurring if selected in config. // a little blur before scaling does wonders for keeping moire in check. if (blurring) { // send the buffered image off to get blurred. - buf = getBlurredInstance((BufferedImage) buf); + correctedImage = getBlurredInstance(correctedImage); } // Use high quality scaling method if selected in config. // this has a definite performance penalty. if (hqscaling) { // send the buffered image off to get an HQ downscale. - buf = getScaledInstance((BufferedImage) buf, (int) xsize, (int) ysize, - (Object) RenderingHints.VALUE_INTERPOLATION_BICUBIC, (boolean) true); + correctedImage = getScaledInstance(correctedImage, xsize, ysize, + RenderingHints.VALUE_INTERPOLATION_BICUBIC, true); } // now render the image into the thumbnail buffer Graphics2D g2d = thumbnail.createGraphics(); - g2d.drawImage(buf, 0, 0, (int) xsize, (int) ysize, null); + g2d.drawImage(correctedImage, 0, 0, xsize, ysize, null); if (brandHeight != 0) { ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); - Brand brand = new Brand((int) xsize, brandHeight, new Font(brandFont, Font.PLAIN, brandFontPoint), 5); + Brand brand = new Brand(xsize, brandHeight, new Font(brandFont, Font.PLAIN, brandFontPoint), 5); BufferedImage brandImage = brand.create(configurationService.getProperty("webui.preview.brand"), configurationService.getProperty("webui.preview.brand.abbrev"), currentItem == null ? "" : "hdl:" + currentItem.getHandle()); - g2d.drawImage(brandImage, (int) 0, (int) ysize, (int) xsize, (int) 20, null); + g2d.drawImage(brandImage, 0, ysize, xsize, 20, null); } // now create an input stream for the thumbnail buffer and return it diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/PDFBoxThumbnail.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/PDFBoxThumbnail.java index 3acb6900dbd..577f1dec4a1 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/PDFBoxThumbnail.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/PDFBoxThumbnail.java @@ -81,6 +81,7 @@ public InputStream getDestinationStream(Item currentItem, InputStream source, bo // Generate thumbnail derivative and return as IO stream. JPEGFilter jpegFilter = new JPEGFilter(); + return jpegFilter.getThumb(currentItem, buf, verbose); } } From 82d04061c084486b92c9f643b96f59ab81fd4a87 Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Thu, 27 Mar 2025 12:38:59 +0100 Subject: [PATCH 475/939] [DURACOM-243] Adds Test for JPEGFilter --- .../dspace/app/mediafilter/JPEGFilter.java | 53 ++-- .../app/mediafilter/JPEGFilterTest.java | 270 ++++++++++++++++++ .../dspace/app/mediafilter/cat-rotated-90.jpg | Bin 0 -> 36813 bytes .../org/dspace/app/mediafilter/cat.jpg | Bin 0 -> 36813 bytes 4 files changed, 290 insertions(+), 33 deletions(-) create mode 100644 dspace-api/src/test/java/org/dspace/app/mediafilter/JPEGFilterTest.java create mode 100644 dspace-api/src/test/resources/org/dspace/app/mediafilter/cat-rotated-90.jpg create mode 100644 dspace-api/src/test/resources/org/dspace/app/mediafilter/cat.jpg diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/JPEGFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/JPEGFilter.java index 181e3bcc4b5..2ccc2afbb2d 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/JPEGFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/JPEGFilter.java @@ -37,7 +37,6 @@ import org.dspace.content.Item; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; -import org.w3c.dom.Node; /** * Filter image bitstreams, scaling the image to be within the bounds of @@ -117,25 +116,6 @@ public static int convertRotationToDegrees(int valueNode) { } } - /** - * Helper method to find a node with given name in metadata tree - */ - private static Node findNode(Node node, String name) { - if (node.getNodeName().equalsIgnoreCase(name)) { - return node; - } - - Node child = node.getFirstChild(); - while (child != null) { - Node found = findNode(child, name); - if (found != null) { - return found; - } - child = child.getNextSibling(); - } - return null; - } - /** * Rotates an image by the specified angle * @@ -261,14 +241,20 @@ protected InputStream getThumb( } } - int rotation = getImageRotationUsingImageReader(new FileInputStream(tempFile)); - // read in bitstream's image - BufferedImage buf = ImageIO.read(new FileInputStream(tempFile)); + int rotation = 0; + try (FileInputStream fis = new FileInputStream(tempFile)) { + rotation = getImageRotationUsingImageReader(fis); + } - return getThumbDim( - currentItem, buf, verbose, xmax, ymax, blurring, hqscaling, brandHeight, brandFontPoint, rotation, - brandFont - ); + try (FileInputStream fis = new FileInputStream(tempFile)) { + // read in bitstream's image + BufferedImage buf = ImageIO.read(fis); + + return getThumbDim( + currentItem, buf, verbose, xmax, ymax, blurring, hqscaling, brandHeight, brandFontPoint, rotation, + brandFont + ); + } } public InputStream getThumb(Item currentItem, BufferedImage buf, boolean verbose) @@ -354,13 +340,14 @@ public InputStream getThumbDim(Item currentItem, BufferedImage buf, boolean verb g2d.drawImage(brandImage, 0, ysize, xsize, 20, null); } - // now create an input stream for the thumbnail buffer and return it - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - ImageIO.write(thumbnail, "jpeg", baos); - // now get the array - ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + ByteArrayInputStream bais; + // now create an input stream for the thumbnail buffer and return it + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + ImageIO.write(thumbnail, "jpeg", baos); + // now get the array + bais = new ByteArrayInputStream(baos.toByteArray()); + } return bais; // hope this gets written out before its garbage collected! } diff --git a/dspace-api/src/test/java/org/dspace/app/mediafilter/JPEGFilterTest.java b/dspace-api/src/test/java/org/dspace/app/mediafilter/JPEGFilterTest.java new file mode 100644 index 00000000000..1181dc7a60f --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/mediafilter/JPEGFilterTest.java @@ -0,0 +1,270 @@ +/** + * 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.app.mediafilter; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; + +import java.awt.RenderingHints; +import java.awt.image.BufferedImage; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +import org.dspace.AbstractUnitTest; +import org.dspace.content.Item; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.junit.Test; +import org.mockito.Mock; + +public class JPEGFilterTest extends AbstractUnitTest { + + @Mock + private ConfigurationService mockConfigurationService; + + @Mock + private DSpaceServicesFactory mockDSpaceServicesFactory; + + @Mock + private InputStream mockInputStream; + + @Mock + private Item mockItem; + + /** + * Tests that the convertRotationToDegrees method returns 0 for an input value + * that doesn't match any of the defined rotation cases. + */ + @Test + public void testConvertRotationToDegrees_UnknownValue_ReturnsZero() { + int result = JPEGFilter.convertRotationToDegrees(5); + assertEquals(0, result); + } + + /** + * Test getNormalizedInstance method with a null input. + * This tests the edge case of passing a null BufferedImage to the method. + * The method should throw a NullPointerException when given a null input. + */ + @Test(expected = NullPointerException.class) + public void testGetNormalizedInstanceWithNullInput() { + JPEGFilter filter = new JPEGFilter(); + filter.getNormalizedInstance(null); + } + + /** + * Test getThumbDim method with a null BufferedImage input. + * This tests the edge case where the input image is null, which should result in an exception. + */ + @Test(expected = NullPointerException.class) + public void testGetThumbDimWithNullBufferedImage() throws Exception { + JPEGFilter filter = new JPEGFilter(); + Item currentItem = null; + BufferedImage buf = null; + boolean verbose = false; + int xmax = 100; + int ymax = 100; + boolean blurring = false; + boolean hqscaling = false; + int brandHeight = 0; + int brandFontPoint = 0; + int rotation = 0; + String brandFont = null; + + filter.getThumbDim( + currentItem, buf, verbose, xmax, ymax, blurring, hqscaling, + brandHeight, brandFontPoint, rotation, brandFont + ); + } + + /** + * Tests that the rotateImage method returns the original image when the rotation angle is 0. + * This is an edge case explicitly handled in the method implementation. + */ + @Test + public void testRotateImageWithZeroAngle() { + BufferedImage originalImage = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB); + BufferedImage rotatedImage = JPEGFilter.rotateImage(originalImage, 0); + assertSame( + "When rotation angle is 0, the original image should be returned", + originalImage, rotatedImage + ); + } + + /** + * Test case for convertRotationToDegrees method when input is 6. + * Expected to return 90 degrees for the rotation value of 6. + */ + @Test + public void test_convertRotationToDegrees_whenInputIs6_returns90() { + int input = 6; + int expected = 90; + int result = JPEGFilter.convertRotationToDegrees(input); + assertEquals(expected, result); + } + + /** + * Tests that getBlurredInstance method applies a blur effect to the input image. + * It verifies that the returned image is not null, has the same dimensions as the input, + * and is different from the original image (indicating that blurring has occurred). + */ + @Test + public void test_getBlurredInstance_appliesBlurEffect() { + JPEGFilter filter = new JPEGFilter(); + BufferedImage original = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB); + + BufferedImage blurred = filter.getBlurredInstance(original); + + assertNotNull("Blurred image should not be null", blurred); + assertEquals("Width should be the same", original.getWidth(), blurred.getWidth()); + assertEquals("Height should be the same", original.getHeight(), blurred.getHeight()); + assertNotEquals("Blurred image should be different from original", original, blurred); + } + + /** + * Test case for getBundleName method of JPEGFilter class. + * This test verifies that the getBundleName method returns the expected string "THUMBNAIL". + */ + @Test + public void test_getBundleName_returnsExpectedString() { + JPEGFilter filter = new JPEGFilter(); + String result = filter.getBundleName(); + assertEquals("THUMBNAIL", result); + } + + /** + * Tests that the getDescription method returns the expected string "Generated Thumbnail". + * This verifies that the method correctly provides the description for the JPEG filter. + */ + @Test + public void test_getDescription_1() { + JPEGFilter filter = new JPEGFilter(); + String description = filter.getDescription(); + assertEquals("Generated Thumbnail", description); + } + + /** + * Tests that getFilteredName method appends ".jpg" to the input filename. + */ + @Test + public void test_getFilteredName_appendsJpgExtension() { + JPEGFilter filter = new JPEGFilter(); + String oldFilename = "testimage"; + String expectedResult = "testimage.jpg"; + String actualResult = filter.getFilteredName(oldFilename); + assertEquals(expectedResult, actualResult); + } + + /** + * Test case for getFormatString method of JPEGFilter class. + * Verifies that the method returns the expected string "JPEG". + */ + @Test + public void test_getFormatString_returnsJPEG() { + JPEGFilter filter = new JPEGFilter(); + String result = filter.getFormatString(); + assertEquals("JPEG", result); + } + + /** + * Tests the behavior of getImageRotationUsingImageReader when an ImageProcessingException occurs. + * This test verifies that the method handles an ImageProcessingException by logging the error + * and returning 0 degrees rotation. + */ + @Test + public void test_getImageRotationUsingImageReader_imageProcessingException() { + InputStream errorStream = new InputStream() { + @Override + public int read() throws IOException { + throw new IOException("Simulated image processing error"); + } + }; + int result = JPEGFilter.getImageRotationUsingImageReader(errorStream); + assertEquals(0, result); + } + + /** + * Testcase for getImageRotationUsingImageReader when the image doesn't contain orientation metadata. + * This test verifies that the method returns 0 when there's no ExifIFD0Directory + * or when it doesn't contain the TAG_ORIENTATION. + */ + @Test + public void test_getImageRotationUsingImageReader_noOrientationMetadata() throws IOException { + URL resource = this.getClass().getResource("cat.jpg"); + int rotationAngle = -1; + try (InputStream inputStream = new FileInputStream(resource.getFile())) { + // Call the method under test + rotationAngle = JPEGFilter.getImageRotationUsingImageReader(inputStream); + } + assertEquals(0, rotationAngle); + } + + /** + * Tests the getImageRotationUsingImageReader method when the image contains + * valid EXIF orientation metadata. + * + * This test verifies that the method correctly reads the orientation tag + * from the EXIF metadata and returns the appropriate rotation angle in degrees. + */ + @Test + public void test_getImageRotationUsingImageReader_withValidExifOrientation() throws Exception { + // Create a mock InputStream with EXIF metadata containing orientation information + URL resource = this.getClass().getResource("cat-rotated-90.jpg"); + int rotationAngle = -1; + try (InputStream inputStream = new FileInputStream(resource.getFile())) { + // Call the method under test + rotationAngle = JPEGFilter.getImageRotationUsingImageReader(inputStream); + } + + // Assert the expected rotation angle + // Note: The expected value should be adjusted based on the mock data + assertEquals(90, rotationAngle); + } + + /** + * Tests the getScaledInstance method of JPEGFilter class with higher quality scaling. + * This test verifies that the method correctly scales down an image in multiple passes + * when higherQuality is true and the image dimensions are larger than the target dimensions. + */ + @Test + public void test_getScaledInstance() { + JPEGFilter filter = new JPEGFilter(); + BufferedImage originalImage = new BufferedImage(400, 300, BufferedImage.TYPE_INT_RGB); + int targetWidth = 100; + int targetHeight = 75; + Object hint = RenderingHints.VALUE_INTERPOLATION_BILINEAR; + boolean higherQuality = true; + + BufferedImage result = filter.getScaledInstance(originalImage, targetWidth, targetHeight, hint, higherQuality); + + assertNotNull(result); + assertEquals(targetWidth, result.getWidth()); + assertEquals(targetHeight, result.getHeight()); + } + + /** + * Tests the rotateImage method with a non-zero angle. + * This test verifies that the image is rotated correctly when given a non-zero angle. + */ + @Test + public void test_rotateImage_nonZeroAngle() { + BufferedImage originalImage = new BufferedImage(100, 50, BufferedImage.TYPE_INT_RGB); + int angle = 90; + + BufferedImage rotatedImage = JPEGFilter.rotateImage(originalImage, angle); + + assertNotNull(rotatedImage); + assertEquals(50, rotatedImage.getWidth()); + assertEquals(100, rotatedImage.getHeight()); + } + +} diff --git a/dspace-api/src/test/resources/org/dspace/app/mediafilter/cat-rotated-90.jpg b/dspace-api/src/test/resources/org/dspace/app/mediafilter/cat-rotated-90.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5c0f91c4eda737b23e82a092905cacbf9dc56f29 GIT binary patch literal 36813 zcmeFZXIKAj+Ii}DVLV}iR=yZK2Iq_M6V$U&5x78g%*e>h!n})xWfvz43kxUd#=^Ok zh3$VLfcOkxqXQ5?IweF1pkRYgvO$QwU?Ulbbr3DEmedgP1y-1JQ34PODry>9I(i01 z$lr+&fP!*|xC?+#K`1GxD5z;^=%^`~*N!f7RB@;6(;rX?K&|SJ?MQ7hX0K2ImdMu4P7) zUaxBzTF5GU)jGUr?;UV4DZBi2+sKl#k%LcQpIb`G(Z{$!j)VsE2w zPg|v^<>8jo_iXtZ1DP2M0@489>WZA{i!yVETWfMYgz=WpHigE2&5C;!=fZ!c+P~MJ zsEvc~aJUfb?I=uX!rfTqUox2$&e^9cw`gOG73yVHBJ!Sg6 z-_NWEQp)YufBNFX)f~@~keRmknpZtu?e7eJMjLkf9?dhO;uQzA&bapQc4;DTR{IU} zQGK&m2&{X(x5_>Jp^C~Gh3O}oF^io~^^V+hx_O>{dCfNzwdg!LKdwfw4MB(h&>G&1 z#iRTZA+7uOOX`#zr4fzF7KFe>^fzsxFdx05b9_B*Y_;)RuA_=^eV;NmYkOh~kU+Bs zU>V*JIsUlo1j}bNORdQ#4uxw55%uM1QJX_X&Jt9mOKqCQ9dzNu&!ZtXD%jNzQ_eU^ z^0yYXmZryxMkK66t9*Ma*{gZy^*dNdeQOf;!U+#H!L;h@!zz?L{pS`xo|#p3l@EfP zqfJZ{%~*scO*K8ki-+Ff)nU(cZa>NK68b^4(elV*MP~br3pnb~Uekafdv5`4wV3kw z*_&yf?AGI*us8c1Lq_$~WaU^a4%1c$h%FQdRJK*&Q28sGDXBNsc^5{frv$DCW7_yr zdzh+bBaRgC6!6;B&}dCfq*25V!EaKlzr0 z)a-Ly=kdO2AoTVptsFIkYu%wm(@Xv>DO%^Q(tKZ)o!7d%X!y1J_}vSK(~ci6(Ud9d z`7Ay-_Tp2?>&JDz*$QpPr?`@)csaECZt@*s@k18(g)9?+w+DI8$Di4Ipr(0M!_-Ji zdVLtsbt5*8R=$mXvuUI4bZkoCKvByiVH9;L@-1yC>qpzuudaW}#tRv(ZXDQaoNYoG zs$3ZgoBAPbJ&t{^s@>eN`xbsl#rd-itq!g3Q#Be5-mYF_-{zsLxcAuJme;sgEWwKU z4p*0_8mu7rJ4YSgBxoLIU&GV@XPkf4UxWCvb zs%ZEQ0bgeG>z!jpbTw~=G`$-qG#SJ~@0_5Phz%$$Wh^{J1Tq$CkMQyB-cuejkoAcW z@$#!SI;|7kayqNUf3CP-F{L65ulN>x3yTcj;H*O;nMzHjWX&t_!f0q9lSMA zkpEdl0n0~o|N$Yc%~bjMr<1DM|656 zdVLo&0(E&UyQhlBU1du3jK$S@eA}vn{oYaEwMU!GcK%d=VU1gKPTldi$SLGS z1!O>z*p|pn zZ{Ky)@`=$SLpQ2=zM2G#H-79HW|$!;m0p#UVYIksSbeZ#Bs5U+dA+FbcQ9hFv+; z@ut|UGE|ypL2d)hRoX*4lQ4}=ZQ1ZlpK!V|AeniJ=}p(!06kUo9)#P(8nqqA!ksbc zTJd+`1y!E!$2T0kdY8=RCp@lDp4jA_Td@2NPP$fSjs3pWyi3{bIw660dApsxZ8iMy z>nKd4v*MBE+7|DLJ*DTqe^tJ9WyW;u8)jxh&B3UpGA!wsLZ$1=0~d@n`Rr-$LS21& zf>l4JDP&11FkB{7xhAJMNz}sAGTd)!wa4bQvLN2Jrxg_3b*BjRf z1^#^Edm|}x7Ee6glkMEx#o6;vGr#+&#|WLeVojbJUDNXwIMl#sdZE>B==e-h{>^e{~Pv*S)K>Uy6D< zXi45hTdZ%?Vx+kChuHVSmKW?3d!M}_SV{zzKcOyX=lwiG@wFl(Vxc{nvS*~%U8w3M zeaDYYLYfgSToI>uW_dMU?ah~~W%m?2T{{*gpy8%rMBvGK`>KKWowgz6rbi1G!q{gr zq04$Z(r(`8V((^bYfn0<;UD;Uy^Gp3S#K2`X=&GUd0gdMKdYyIvj2hA>(x*_ zogucIrRzQFr)=DV-K9oC;V<6oe)9VG{+y~H=U$cgOtWkH$on@kUQz&iuU9swL$D46 z`!B^2l|56A9a1t!0$y(%Ku}KFh2C;Ct9d7KQ6coDVdUxFGWiwPoK-c>xw{!`kMbK; zL+Ov5N}AZuNSWt8qN0@Z+>g2{!=-rv+TGo`nQ)>cQAa-KHntnS%S#9qjEv(@i0!)9 z_5(hvYrsteh%NKwH7B%}0d+QSCz0o0mIuf3&Ybz8)8>^HU8WaktKG&@yK-Yx8GGj9 zo5Rz5d|kn^T3YLg0o4gbEb8FQYr0}=?})#YsMv0!)oP7!qtQ}TsxM9Wq$Sd2{^%m| zKppdIS71naKCr5*zlR89pM`6F@phxGGknzTcJI@@LMKNBm9wRe15zo*-&)P5dUU)W z@i{AsxvkBN|POJjHB1I~ALVWpq&DApzTuq1f2?AUHxV55EWSb6T&>nX&ihA3a;OSK!zgtyaHC zfvl;_wT-y-HKh3%&9Ll@R`vrFG%97^LQlg+?dXPrw`#H!=b1(?bWi7bU~GJ2W}R6| z($2hVx~!9FQMPAa{`q$h8KBpugm-z>KJ;nro8ue8d1_PP(EgfaHO&`p$IrGtoa)c$ zUN6di^4`M~k^Abxlfu?Q{E_rE!*&KcV_EiGWY@5)rsl^d%(JV&*7e^*9lj)v}zo zmFv8ts3AOo0-g)&5rNa!y`yQqnC-4IKfZo!Y#|IM;PZtDWW}$#-6?w1yfC>5O<0?o zH$Tzd@Dg|4#D9OyC}eOx4R@XK;WYE&;rq$FUOcy^#^;`N-#C{2e7(PEMwSSq2(@c| z%IXtRA_6dGlncL&K=!Efa=W40QZ+5bCghgdgQX48e7$s^F+>CH6S1t z>$Ob$uV|ahm*n<7U-jQh1jOcBL!sZM(=3$ZG$S_zLeROGD(f#xH$qTXgnU*cA$Du7 zj2a82N9JhC&k<&q4>4DDR^Z3^F_pCvZR^C@r5d*+DF2D+hxADYakYo-MG#&;+6Hp`q2_`-DkXTZhm7LcG9z^2Mh2u$i zNaQV0vw5lqsbycIz5?N&)ms$OB-q=5fqZ(?N^K0e`~R& zA0CcD<3Rdu5~rRY9=+2Du#ukFp9yOpocYg$9u}?lD}l!UN|<>faX%>*KKOv22?G>T z|7QZ!<1f*=P=wF6BDS;ySR3i;fwiF|Rms{*P|yoQ@YKe9AtlL|5f=RqI9=pF^66r| zZ0*o^Lt$$qsc(N?x=1g01S%9VD&xl9;6nKD{19ph1;$SpQLI7W-#w9F@G!87W8lavtn(a$Su4T7OL$;l0d$8L9rM=2VWT2xuAu@(LOus zV<8c&!8++;@OTUgi9!2pXJOi*kgD(-o_Pmv4fpZ=oxr$50PFUXpEOn|r?%>_?em1} zJjvroluTpUE);xdodLG&jhT!id3^cFoO}M7rkp3I>B)ES1|jDHy}_i5VSA8(XmSk} z$yj4R+S?nXAi+E6iv};qHs^i z|Fj&41em13A>mLM&K_w-vVF*3%AqFHKr}n}M@vqa=>Jw0tq&GM@cRpj27`tBz<*9_ z1~w$=mSh|#gV}?C#~8uTFsuh2<^>`E+hD()S(#yrnS>!xOi(_8;N$~^z<`G*=&%$7!ympNj0gX4WC(Q70v!K=LlDpdLnTTifn?7RYdQ!5G~6hRAIaHI z@{p1WB3-oL-xdtscf7mXI0z|CV*2eO^X}lOc7T8NBxNK?`1AKi2Y+nf|4keCee$>l zCVoY1SZqD8J}^8O?j?orNU;)f^vfKN*ZWJ%gu%iC4-bGHBY2SF8|2U@DWtg_H6hPX zq_F4jX;NtP_cR#HMeT$SfMZB}@HUY&oiTVK30NHz7!$&iqNyNe=VFDSJYilYdU{|C z*&B|8?L-+FNC`s>76pb}w!+^GhCeZ+%tx&ZjKBvI8e{H($HT}0f}PA@1RUdoh2d~u zlXh@SAz;+N3v3_~jt~Az+v%N26ce;J29!q%PSHbsJdKPj@v@r#$N0bL{k_FJcL{*7#e1;bXq(b@k-w}yHBM*Vwk8$8$< z+jLT|{#LjN%EJd{<$?G8OT38F4hJJsba1~4+EE=7sj2_Af!tsjTsp`=k6@gRmzNF_ zuY<>9;hqFMxl>6sVE&2G1EZ1KRU=t1lO7U-gXw^wEf1fceL`n|hGM+Haj?w@@&W85 ztFrM30I~nJE({d_jDQ`BB598K7pu7kbf>deeqrq_OdNO6zscR+HASM3ZDVUqi#Zqxg!_Rb z8Pv?re&E^C_rKHz{dVgi+fUBz=Z0A>+7SV4i`)^tcRT$L zqCt^tJ68Qu_dAUyQOv;g5C-6q95jx!v}H$8yNobq;AFJZ1;6IGUF2Rg_wX~rfEEO1 z*)Ev0osDUSMv91T<3TjQYJ!F$30@>=D3THF6wkhmcZOlXNf8SMw11LlOwe8+D@n-q ze8y-7#=8yBq~UE~$sDVNR0&jSj>;tWuJ_+ULS zl)NL0ly8R!hKJ$c?2Gq6ZWlzF=~%aOl7hkD;Q5`zWCOlDV7RT>|F?EC+v+>Oz3{%q zu;12^kc^EM9OZwT0BFFj(6h1G(LZS>G6Ne=YSz!*+0h%fISlXN1+K5zYWu&P2S^Jd z7{S>{kAMSLD41C9$e;yr2973%J82p)?PzYbQ$EL5(gs7oLdk={&e~*$#z>yItuWvm z^7}iJWv5nup^*BB)d35~!_47mILZULT?l!OA>%=WEyf1!1%rBE|1#`oK~P#fdawR94MSZAo!#p|61BwvqB5L48d)95P&$qCgh99`>9Gxqj6Fmq&<^TPz*{s z$iq)sR!T-1P}d3q%>V(z3wpvptJ4sgsj3wcgnMZSIVhUTnEUC$eBs6+SeQ+Sg)KA$ z0X^a+q@^jS9;6zC@;55P5H-AlNoRJq#8m zs3;{b36+tR6;x7^l2uStQj(Pr1h;F-Ny~u$l_bG^n~F-Ra>|0+mk?MQ*2`PfTHkQH zEHI_9)2P6}K&e1^DGb&}TK341BP1Dea*`m2BrX`V+8{|ZPIyOxJ`4xN!pZB_1W6LX zHCY6_h7efOttO!Sev194y4qp%g8t<51J`np3-^Lb!;moYkOuWCOKLt@sNc^1)Sp@Y zBKjwp+!;HWK-HR?|MLtx{f0vQECPo&2mqV$?|K;+ZzCV!P`2!W%|2PS>e z59T8z__GgG$%7dz8LXcC@AzMzxEew@aEYOyiGiMy>=7j;NjWK5a(&IsRZY-1yayTz zGtt)&0vjs@hkL2Y8!G6@=&R`IACc2FP>_|?QPNc~R5ma)P?1&ESJKnn$*Yfn5=geQ zlh=!s_sIV&@77W$59EI(gEW#!#-?fv2j@g=@b*}u>4Hkfdz#TR`=nj^_V?J%O(TRj>bQ4%;esYYFFnIfs$# zf!x^N-tRjo@S}kQ{`^|e-1(~b^ZcWMKN|R>fj=7fqk%sf_W za2*Io1rSJkV<^o)_+kJY26~eyTR>O@NkG9tn6&qa69w}C9n$1o6C!vR)E9(hKs*!H z)>;pQ)j>xTlh1Fk=Wj3`47-550LckY-nJol5PC=ubl9i}8p8sSV1!H33UoqwV7)*W z5$Neg2Lr%Xn@PC<_J8=%O-66s{w?u0zJCXj`gV&xe~jE`5bpQCaKCc?!eKz)n+mwk zjPlAaoaZe7sE7ano~~awk#x}KcL4y(KmFDoZj!xh`)f!JEI14*MN;U`>pwF5Fane( zQb7vjXTO=z+k;C>0Kh?Dbj$8R7^IW$f4Nb_gGmY;oV;@k(6hD`gc7g;TP#$h2Q|1m zkrm(s_JAkc2?GZKNk9%z2Gjs;zyLT3SOT_y6W|I!!Nn^maQ|H(a0)mJTm)i(E5LQ& zHjoBn0=YmTPy$o{)xdM$HSiAj4738>z!zW`m;`2lC14Fahm97(4B>$6fe1hjKn_FX zASw_|hylb5VhwSExI=s(Xb1sv3UVG214)G3hTMhZLW&_zAkQIhA&rnu$N*#<@*T2D zK}o?x!AY@?LX<+9LWM$y!j!_6!iBS5|78X6jI8ZjDW8eOshd_ zN$Ww2r@csfi}nF+Eo~F+FzpXIMmhmHIXXi+XF4?9Il3geJh~dXCc06&HTqrj2k2Gl zE$O}JgX!bxGwCboKhY1-b^V2WePVS2{Y$u!T*%q+^R&FsvKXO3geWq!%r$NYnZ zgGGwPgvE;`oF$p1oTZ6nnw6gQ0ILqGD{BaA66+(@Ppsc|(eD!7rMJst*Xdo!yPoW7 z-?hlb!6wUQ$%bOP%$CRYmTiokmR*$HfZdDz0(%DgOZGtyN)BNTJq{?xd5%nuR~*Bf zG@N3bCY&cZV>k;qKXT4;v2!VK*>eSPC38LH`oc}gEy``ejpUB!F5zzDUfsQKx6W?Z z?n}E1cQ@@`=HcVf=7I4<^Az#4@~rL=++(l@u_s|q#hzYXDqeBkW4wX9sl2avzwvSM zsqsPiqWMbry7p4+72j*UH*{~t-uHVK_wnyD+UK|L*1nhfzVUPOYxAGvzs6s~KQ6!_ zpeXdMbQ87^m(MZt>(J?U|F%z*6v3#+fgUkoD z4*DOwcd+FU%^{UTh(jrdK8sU`D~kJyCyReNOmSH0F#K@J;YJB+i6atdiF*?5l1!31 zk^z$Wk^@rQQf5-;q$;GQrA4Hjr4yvF;Rp?OUP&8MJRD7Yhp`@&YRVq{(R~AurSH7d%uEMEerE*#2 z%@OJ&x<|r~R3BMURZ_*PmZ;9CNva{#^3=xE#nfTynd*ZYLK+?#cQw9f3TnD*rfYuD z+OOrIbx-T7wy?IBcDDAY&LN$XIuCTFb!Bw1x@Ee{da8P1de8JJ^^NqS^*J+ooU5#Lci~ zmF5)YN6oLA_gEaTKwDH;0+uG0S1o(3#H{?Ss*cedvpSZ1Y{XjD`jmCO4Tp`JO`gq? zt*&jnZMWS)JA&N{dsh1s_PO@U4*Cv>4qqIl9m5>oIq^EdogO>WI@>#EJ1-tLIDY;3 z@Cn5e7f-agh`9v0ymjSuMY=w9V|DXzd*n{-?%A--gefG(MSjav5QX$UzX14#*-D z9m*Z`1kH{{pzARFm>^7}-(kOteqa1m{jdAaVokBxI0()K_ZZI!eoVb593(^#1_Cq# zk^_DO+6O)gVhchCy$==-z7#wfVi=MYN)-wXeQ|34sk5gB!gRv!o`#(EJpCeEIQ&BR z@EM~s_s=q%MV$R`PUc+VxrOr%=PNJlyKv^hV1!Xb{zaCH*o*CvYLRJCR8jD#kC)^x z-MX|H?G@b+BNLMpvwj(Rxgl0I_GTg=+53dm+ma5c%?L@ zYNr;a?MsVETTO?jcic6)TYgXUUQz~4MnJ|$rhR68mP%G`Hg9%J_C^jm=gWQT`*pd> zxw(0J^WyU<@(KCl4^BMzT%cd@xKOh2ZV`7;%tPQI;o(HFdvRNdX~~O6Dvt_F50u_1 z<0y+Rhm;4G&sO+We0l8jxbex+Cod}1D$A;*s&cCLS0_K^dKzEDP;;S%SQ}Ei_{{Iw zx92|32Vc0o=zi(=vZd}=-N#p^uim^ieEqUsr@r=$#+&N5s&Ah(s5Df(Q+ikaUh#d| z2gMI%AC$K}PHx6x7ZklfP5K&;X{@2N_q;G+(lU-@3 zsHmu^XsD@a=xM+|MmicAIz}c221W)3rd>?rYx}X4`1>6KzT=pgn0a>X;`!g4?D_^G zK?wnmLj85Jt0=J!0JlR^P*6gC`_dtuk_w@q1?a%Db4e$@k{YsoLCD9t?gA*l16?V> zCV}U-zqsc8=WL?Fwf%vWw!c z;S276Zo9A1Kks;g+-~U zBrkTE(OSmoL91VCe~9~#NJ}0M&DYGn^u97keoVAqzNyUMD#9%JyuWd086=s3l8L$W z9`InfWnO9Z(AE8^Ei{>mRrjextxcw~E{SX9dMb{7%e$l&eW{}6^=7l31*?kQ%h(^G z;lg)cMTs%g5_COxLCi znZYwn@8Yw{g&jC@>D%MKvj!Ka_%zkz4=Cmt)ZJ+Cvs9&d;5fn07l*%_8KNLLI&q|} zN51Gj5h!QK9Y1BeH=t+DtzijvvA0nIp8y8m+i9MN&mzzj7={;*L5Xy92)SgphLfKTX;OCGOh)Zongn3^hP-CzrHp7dhruMuH9?H$$9$fLf6wQqexct<{>A+A-+23T zc=53YvI5+x{U~8%b4*8QF2y{JVA+y1%AtwfX&1scx#Riu8>Gyiw@;>by-TD* zDHImJ;^`GBe16P`-6{4Am(nFg+vK%Z8>JTm>M>S>CUVoXZ`qA+)E;6x8lG1|1khSZ z&5ixDCHT0Crpnhm@5gV+j(EzJd}8Z~coMS|-JY72x@Ymjje{ShmS>v3Wm)$Wcl-B+ z+sU8AKmC}>hUrbe6{i3jJWcW7l8-Li+r4xsjf>eaj7(3HzY!!fDxLFoN#AkyM*)78 z%^@Ks2XUsamsF>@X`kkX#TM#4yf&ro7&n>wRISm`5})c*hpJf*oq1id)}IhhY|8R1 zsPt8=o3z)^M@Fkny6(Y8#vij!dS89$LODZ_f$;GVc6Qsq=&{9;@rC}3;>%&QT;YJ0 z9iso;5p~h(Y!};>%RV0x6jYm@iOb$h)y9>XpMi2fx;x-Z)*G1dZgDcT)8F^x<2l-7Su2KRP+7ZX)dW-8($}S_YCeMgb#eshQi1>o6kP@VSRJ1+=Quj>F`kUE=61f%0>^-(SwICTO_`OU=NU z01kv6`f@f|knUL>@V)=WC;s1Tm2)fkmxnX^OCK zOBSMSeMNTDL#JjYifmVJw64TTJKTBIe4gV{<2_H=gkTJBa1mxLalN0VYd$P>vj1J{ zV#txf0ge?F>Xxi%f9|peq12_hvC@Yfmfp3l@Fy=CDjk;RztQ62-VlKVkBNMHu$IP1 z_y;1~?a^f5?8x$J4R{ov|AK8q>xYP?ve>G~sUkz8>kpnQwou;fqODmNlH~PdNP}w` za5ffPm2f_vUY@dWsY>2VD%A1xBO7>zvzR>adU-DNahzR_qDoy*0g4EmKmGLeb&B^h zl!-aoXzeA}jISxEOA1$$fOxt@i#~^$oKx5M8p7q#wJe)!sNQ3d@xc+f+#ej!yCrf!pIw2|m#C zP!XoRlsNE{uWU5;G!H>yMM>0Nf|A1a$2B(pPJbO=_93L|c&PuB&>ijwiD;`E?s?Sj zWPt#G1#1cQ4|N&6tg@aG@G-dN2`eu7qxT)hpPuVk8!O-Y-DP#ulqzFHci+)-$!*Ih z?ZLY5xoeg~n-xEFS6{Y=Q8v4X32O-jeV6m}j@(V7H1{r1YifKJJod0RJRzLR=uz*H z+s&=xlT~q)nroNFk|MC3gb*6BMe zSs<+@;0YZPdHmV@qwnCA)ZRh=B1}wm!^)c6Ag*K;ez(2TKrF6FO-In0D(h@##Xh5` zv#YsZ_C&wFjCyGrSZ1+jPpa&p)7mlu2Dw*aBu*bc_#+_CtxMX*SMthR(Ggn1?aN;{D|KiG$f3H@P8jz5I%7t9LgT=H{fkUDRdA7imq-NHK{YIk+Z{Ic*{c zZF=Tr>$AT&o8eF3Gag% zg|c-bzUOFM3lmZew;K9TYI$Aa6-#pOosKn=VN>{9L7u0U@`1PGbcNCL^vc%EE$5eC z0n3&g1hdDjg#n1K%7U_0vSKs5@^81lN@E>UE7m=wqt3VnN#X0Qc2`!+bV56$ z=Jr$CU6%@ffBnI*_z2qc)}FxmABGt{p$GFvGHkKb_ ziVr^U_|`-B`6Gy7hD8mBO|eaN0VENqA_6%F5+W(H@*UrsoiaxAmnSFH(=IhzKQO~0HfjB{LAwaGkcA)&p*@FM#_V_Vw@ydj%k zQFPJz^2sR9+eHtbJ2Ki{K1pAG{wUoZ6=sIX>l)Ni>o_zuJ zRegW;)3OiKw+Nl9y+QTj6HS9}GR0>F0mWKPyAQ25C!&|!EMg8c8I~H#;;+UHFWK8v zyPVVK#;_*}x$sp}=CydQC)7AQ_==n5@OZ`{z%dox-pvl?;alxRjti4F_!Ihi#q!pMalka*D5UH1+*{Uo<^ zND4a71E1$uh)%Y2RCys;m@~k#I8H*=BXp8W8t77w$VNsx_-hav_(guE>%&^pd9*J^jS$t5)9`ojNxB{!{on!a!$P8v*quFEdW76<4E^UU^ z>UrH*eB>3&G*wq>uby(+=&J*l;FZgq) zGs}N7dhBUEwe7PY*-q58mT{yd*2?>s(bva%X^Fd zK=EX|{dYuCMLWFaDY!gPhIfG6UZux*&W2YHC>MvdaX%IrD=pY$5uNpa`Is*M@fbzV z`K(g0$T0THsvWQc4!W;b-}IC=>jq1Q*=F`uzIeuN&-D2`;o7bX$0LgB+Nv9pg|u2+ z5O)b?^2t)_k-4w)3f~M^`+j&)H6_q;9NCOO6|9sUiE>R~T1iOYc?1NU#SzMrmS%G& zO2IFQd#bKpVVIo3$n~lkGs9p#Y9)JS#6Dzj#mf^nKH-Rf^K$rCA}~0dhPpGGmmDio_X2w@OzWO;rLj=^ZPzVnZ_2r zoIJMH5{`&cnQJg&Kt4kEG{#JE1JsBB^uTgHBX5H`ef16V{5Mb5(tA=r3i{>t*vnl~ zk3VE>ex{4Lw4~?zpsBve;C`2e99`Xeai{sNuEE7c>avzzEIU3HFPH|8sO81odnl0_ z8QYSj>0y2L`fbUoMhaFs3&!fSOZiUU-o)n#w}erCPvP>v$H)Vxy4)0$6DaheG3$qP zX}Rf;WA48Tn1? zl+SSGADILXo%ZRkSRW2JIKY>%+kh9+qrf$36l3vxxSs#E8B@sjQ{O3&XMIA08jlTuqUwOU8dIGry=j!dMXRT{9nYhv_BD za6tP@M5sxIPSKlhQk>&{Nvq>mx`#UJU%g)cu_j)cM*N(=Xnvpqw%PDCuJD?~n%1$~ z>-j-%(|OODMbv3~iKRfaF0c$Owc@_3;?wj*r|)t_GK^hDlz!zX3%ho?ZBq5NUmk8> zGi(XilShe}CP|FvrQsu>VzPLG8GL z$&k$;jq!#~+(c$Q6=pY1^+%L?~e0czJOirfiJX;1qLU~bgw#cT2*q{i?;2{={jla!=;{C>5>K1bA;kW zPK$!y_)XbZ9uc{uTMu%*3~1IB2N%mEX{}9!uOcyJyUpLFKeZK?e;F&PK9CSNCHCQ3 z2PInMI}xz4o44_lPk*&io%RlR_w)r??3I82@ncVhdul5jX?doCl@&jBEHa#dqfSHX zKpQPke_VU=8~y;p?m+ja8%src{2^-chdvD2csxs}e}O(&8Y3jHnt#gdncL)p@!O7^ z2L=86EFb!c$U*{d8Z7#6oVf1`L)7OB9y1lEEZ8tqbTYg26DJnCrZB5)yWW5Cj z?Rwuo%$=#Tf+$~B?p`f&4m-s`Y|JCjCyXF^_qrK!(`;OPoE6X~o~w2AHhhTSlE{&x z=rR4n9Hn--_TvRrZ;OoxB~!!&b)M54tGVN02-z=Q&kyu6#HvIq)Rc=p{Cwj$bEx== zrNyl=!0@E(K(`AM;^Cli_A~!)xlF?&&o9=X^)5sZyw$75KNj1hAj6>JV{>LNHQ8h4 zxcrQ32|sMWpEb3giU!z;Ku`Zs#@f-w7s+zDBI9?pUcR>_OpmU>HTH!w?lxJ}GMG5Z z!i}`)IYH%T{`AgCAM17NYNvS)-Jn)Rd1^}Q`iT2=HK#VGz#osrszp{BS^`k_uSe)L z`#ByrmM2=C3D0{R*7p`|gvn|3>i#||@I5H_gg2iwhO5B8NR4;&^(yC&wU9y%$6(pA zHLGQr;i_Sed7F`7?!pbNH}L8YXQ~)`Vp5w6?7R}{Z+~o{^aq2;0vRT_&o z%n$6&8B{t-(cF|Z*dBM&=R=HxG9H|yos+u!av^RUIY zwylZe1nYq*{O zv1z1p!`f5-;#c|X%5P|oD%*{L@9oKm7Ek^xh5Nb%nY<|mH(5@GhBIsMcuGAuR%cz9 z>8jC{&-RAjcJswcD}v*3n;NZa=O`nVqC*^geegd9oer44!b|e3PBr9(N*F92O&#Me z9p9&E1%)2wmraqJ8J;Qd`Sz%`AfLubkmk0I?RA-?6a}ZSllG`T}z3z?h8G% zh1%mV&V@t`ftFyCgew0?A>l8H@)eT;)A@t1%;)zxWQAj&>cuwt)XBeSGsX;@8>4iEoBJIG$wfo9v0lrqKdLt1t+7lY|BeYR!FCS3`7}q zY@~R1yJM&(<6y|u2ZM`29&V#w8X~$5(LOIv*EG>}U+gY1r(+S&5v1xWWCO69= z&d+3qEU1(DTJY_GYHU`^l1Q9O3ci?s?nMssbS>BF@oOUT0QD2TCY6yVy`mI+!h(>Y zvqNt%*_GXoB97Qa!NV-R-%#OMj-c;Xs1a(o%?=qTaQHz4Bxb5Kc~>#5dp4}5QgqKg z%X+pK85(RCs^yBkwZ@(8Gns;*d&cK=Gnh*JQSu{Mk$n||<$bER#&4v!X-=Z4He3(! zHpNS=avehiE;%TlsLV2kQeUAlbf9T}JZx2fZfKm|e{4vLQP`zwCHdGn8`<~vyI3-> zD4jsJ-yAp=({fQH)i&Y5g{pmqx3j7iG7&`JuIhu14Yz%5+EJr*N7z2acOG92yVc?` zy*Q)TyGx|_L5yd?SE|s%9rf{N6_nj+SqQ?^X8PR-y9Sx+4)+srs{xlK{JTbvVrihd z6;#*_f}zK;K>nqtc_HQ|(66s;>Lk2ui-hu2>P@y%Dj%*O_9ddc~k z^xXT;JW>8Ld9`mX2lDp2ah$qWUmb_{ zrD}M1G$VGCqqie8-$Lq9zJpovhiCM=iamk3o(}AE!K#n==%pt0_rA|N818pG`%#24 zsYMy-+N&1j6wlAPu^yl_9JHxF_CA2^C?Uf8$)?fLXyM>T8`c9NQR|{sdIvWMMSb=T z5$~G!9aweKuCEJZz2-E0JrYsD7vN}12q?tGC`q991|Cm7X&ZnmJ9>FoTkhd)K_qkR z)XU!2*CW-Bq!khYd3H^8BV14*szvg3mSv|Azxd3{RL|jTranVY9gcwpKSE%5En5 zf6+vpR9gyqGrqVw|G~t^ec)*2y0$;}(h1(t9=tnG)2DNGftVEw}UJyLf zDfjz>Q)Lsrm-ZeJzOrkxjW59OTW+!6H@`QrBNL5p?<%*jbz{11Y)e7{ye~DjB;>_U#$h;~_-<0uh+w7Xy72m5Jrq8~rGRu-Sh_Joiv(vO9#8l5l)B|GHwlw6#%ru0orVd;D{;=A$pzk#D|LnZF&F&VopP5Rh7X zGCTcr?a;Uuv1MfDG8OVnWV|{dFK+jFY0=Uy+GA2$`FCvlc&v-Ie4s* z1)<7p&^}G)s}SYKTnx0MdQ5Pvs*<-6fqo`f>0X)odKEH|^k`wN}w)s8JG zx-qE~+2`-sqAZ@?OMkOinA==w&p7I}uiM~m%t*o~Dt;mmwO;zAPFer$t&l?#DUM3( z?GnXo6)$n6kU2T$TiGEq*6V3zPWQs4u<5T0Pz@|Mil^^2B~Zq0JooK8Cz$SCWIjR{{@5^d*#pn0FZe+(<!eo1Ff{soBq$& zNZWZ46?KKyb%CUNQ$B`fb6cBhn?7d~vNctz!s#Bz6DYlWI&}m;m>Y2p<0LO>y zyWL2s8vDN5(lf0eM*3){%fnObBkEdkt3mNG{Ww(BKxJ|p-YTH1r00RAiyP?o_kDFb zPb_4U*azEDK%Pg-_&;4Qu*d(^_2d#*E!Dmd)U<|+%~^Cl0yQn! z!(Q8V(yE4ym4jb(t_k2f#Y_zuk=2R|72=oFAH1i9nx0`0Iddh;0#ZMTd5Y8Psn(4o z4C=vwE2@#D_XFWHNpMgmR1#ga+_xUyVy~K-k@Uw>iyn0n5KNjc5O3ecw#HZGd&cVu z#YW-*(!*b-^)>~M8!8oy65_;O`j57v!&1FLk(25kvx$bI%Ed`8#k3+cH+OFPd?JXf z#mr=4zmp>(w0NopC_fUY76<66#HhZg>oaC$&hi3?tYeKh5Uap@4M*j2cvQ@}88Q)Y zQ9c9NZXo+PRs1E?eG%#flO7(KGPdZ``BLkugY#dQzaP6(MlLTJ$rD7tGB_ZEz=KK> zmDhbc6fR{*!GP$&wYSrHc&XC2E)*#?#ba;#WSunmZ_B!(#*(tQM*470DZzmpM}0}@ z?w``;bG-+FZ@3%$J=C!DSuo>MxPtMW0Xh-%)4;=GO0p`3cZ}E-RsarQ=|L=g8`D`b zGL!{_U*Z6e4{o%qx8YLbr21ph@Z%MhXvdgsaba8fy&sy#+wc5GQR!=JRR9YM0bZQ= zdDrCc0s3;IPZ|-%{{W@v5rsOF-cQwPdR8^zbKCnVDioOA{)(top*A{j)|y3vT`$}Z z)kYD7b+JE$R(%tHIe#(TYgAx=|JLG2R6T+48?=1NH$6G4c2|+FRP-{k#L-9t#OriF z6_@`2gZ;DuTUHqgja9T=bR(9uu-PD}+B+_n=SvS;MKL<1v={BM_I%W=61GBLBXMiF zxgCeJYfGbp%5=TRwT-=+YAQ@Hs2OJ2v9(s?w5j9tRm=EJ0_vV&Rv@dJt8(G>Rd0eT zHarfJsuCpdl_TN>d#dMGO9^!ee78%I0|Cd5yl+?Q=Tp^Z9zz&O7Tc8gNxviARWFqF zJT4eHtt@fM9aDj~%}&O0t~e6Qmk=jg0l28tdv8%i&u}GI8_prjRf>v$CjCd_=ndRac#5o>g8w z3p)m1%g-A|<5h8}V0nvCQ!0Pc{{WiC%1I6^l0|WI8WsSJH*prJ!06c7auI0-)c0De z5Z@vgvLj%4o+8y!61SJ>{5NyC0qhpx>^+pev0rM&H$%i%-Nm>vo_w{Xc!&3?n6a^= zj0o3a5vb?%(UKS0;nVTld2z1LbR_GiWf*^B*dCh2EV}3ptDzr7ALc*Nap~BRAXQZW z4FLkT=ayY`v0^QNvEf@t%c`|tZSogF4wbtjC8--yc+eULQOAu2^P7We0a9iL+9E*r53+(3I{RK>s7~)NirtKg$wZI45bTm*D!V(+>D92JR zHiK0(sZ6P7bq^ARxU(@JSg&W^tL9INn^DdD)mL{1VcZvp97(yYzC0l05y>EFtyXs=GP|3k)9dg3o8b-0_wfhgZ zm2EoMBE!-8R!>Pp&c=nO$|C#7-LM^PwD9*-_0^$d>HTZe8F{c7pko%_<~Gu!Z)oqV z{EUly8Cb*00vVXE!*OCgwRn`K9FZ9b3hBH508sc(vWx>}WJMMCkgNs4ULQpm5l=Um zOQVJ#dei7>-Z)bv$4;A3uAP-os>{>Z z=x@iew&pXnJ%@L7QN*4Vw=tcoRx@pkC?>+%8r23PHvsn&NNdCbZ9vwC8s_xVtQY_k zDpKyYZrx}-1=jxnL8eX8Gh^LqR*|&E48Gt#y3W#L@`H8z_|)k?|IpylIc5rqcPTcv zXD~af%aQ5pc;`^Tf$%NE^>9_Amej5u0tv5@+Zc=H3_ zMDiM$1C6VwOFc$BM?kYkZZZo>8sdNvBWBV%iuKYMZTPzr`e-pYHs0<@}2t#ETX(%><1WG#C8e zP`23{{{WW&KhoX)f0@4JUbC?&)4Hohh|jVNM;F7vQK*jg^B?z{;J=Pm@q9HMaw1nD zu_w0ZR0ZPX_=z3$o+?l>q`*vh5kONVuA=7TaN|#ydf%n>mXE_YF|oK0d#?=*RfMqO z^^_{4O?xewK^ph_MNSe=8>s?8X`0LNY5=!=JO&%o5X4)^{{YBs50*){xYD*mlDwTt zY^_zwF#iCy;9OGCf2p2!CbN{RW8!to<=86|ZC1Yi@Z*Rb<3*VbATYWMyf|`s#Gxq$is(w%GX&_CF}^ zttW(hkAv%Mr^*ns#_Yp+f;~uI-r-Mtor`}@>v&lHPRgyvQy{@lrXR*Zrmop=PgsCFni`a@# zFwE|ONwkB+yL>13PXkQei+-*K#XVf&BMuh(WHEuYAP;6vj z+>M&AXMHPG9*YE@5^(^Hd+B|T5_plkWCv10@dsW%v{L&~kjDQ2C;DKrk##4HA5+$2 z>oUgSw%M=4k@p&SksqneJ8lZ0Nwkr$xb8ix{4`oIBZ)l0Y`U$!0(X5KqN*mHff4jW zZhl$ef5LfHVQNzb83Dg8e63YEO{-1=8vsuMTXPxIyg5@D;92z>D?4_zlNp0G`dBFpi<{*uNqEa2Hg@6WFdJAW3O)vD?wi%R zlpthvIp>_a10E8wypu8TFNcEC=HHrqN%&f^dj9}W#m0(O1I%ah8DR}9Xv1D#H+U7V z+rvMvmFLrc+ygl^W%rr%XQdX*hx_)Zz#mGs!l#7pbQ#=Ex zvVccB)yPPYgYLaKK>L@< z-}4O`=K6lF>W|5Trr$K(q9;pSj=(|tYJic+DyakqMz{fV*N_8=rDmHFZdk&p-dOWn zYuUJWbEgWACN@{)WAf9>iL{Q##LP~n%zhI~@(JW3-Z)kFECiWk;ylLCkfT;(ZLzQ6 z{p#DFD5YGh#t~e`ft-XkJ;j*(&fhWr04-Lh4fMFcK*CZ5_Msqf;>S+qsq5ua;jU)g z$fE2N0^h~m?Ee6&jncrLCEF&(Cc{>>h`S5ki9opL$(-s~vs?mn3N$yTYH@1BG0hOL z)L0(vD>`P^8*R#s2Oi2mWk-)`3NO!2R340X?cwoh?w~dhu^OI#Md;AkdRTkidk?;W zrW2zAbkLD+Xs3fu!PAUtHL(?PCeyINw~ekoZ`)N8lf;!(u167L`WmT9q@u_w2Hh>f zt5bmWexa1TJ;pd~$4NHm!B0CHud!mH*;zs-jz0#?T-+))?ko6Q^Z9Bo!$^OobuOB? zEhkW86j8R{E)#9-_nQ9z4XJZvo<-E}PV2bQV~BCk#}cS@^Me)u>P&~3*OgTEXTg0E zG4;hsRpOEzj-z~=f})`3V`gT*1*i8fs#m1-E%K9>I!})w-2{r4TX1WG;u?HZzW)H7 zJHlXfX*xbsdDxLWhj0>kYyyCO6T{)B*LmeLo$*~Gr*#Z|tB?7JmA*~Hj}6G+8-rV& zZ_l!IJTU8=DUyXH_LKJ2R2G)ybp17@p|lAw`zk0K^7CJRy59Pb|I_)hXL9TtQ^?mZ zG2cgqNTiHAAXi(E$L|h0_taTs8gB+-*8xI}L0(ooKRr`$QXG3723DG?rs*Ue3d60;F$r?C6!1QD}RWw=Og!g zKMhw?;JLm@I2Id{&Ikz|_!4d|YT|FgTce^k<^c(+HMa5KS3%2-XWz=BNt81&J_aP) zQLenrM5gkxd2E5Vn+5<2XbsPczp|*F2sZ`VrHLRfZoEkxh3L{u^gNi5x{)3eb4G*B)vdU66?qHSwcy1s!$M zl|QtlmK^Axl!gb#iaAe^NCO}SQ+^`i$Avkc1$8YJFISNn@FhuO1bJ|l*y^W>l5{rj z_?DcOd=}5j>FEBraiDBGwEVbMvm40BIuMQQvJVK8_DHl`o``?zwQ*Aa@Q`Dld^VOgtVN{HN{V!kpx2 zaow6k04wQoEvK;dQ=m+3-c*gdd||ALKo{*Dzq?DXkqzOL0n=bx8=yM)`2}7Vxzx2P z4$Zx=)JGFAh4!O4V5UF_@Tt|#;!QHtWRb@dW;R>xC5tw`A;-cr)9%yLAuKW~w8fYc z>i)r86N)>7nPY?rh0s!WA@bKG}I!59161M z`M^d`j9TE3Z~hb&O|~dt3Tk_K)Q7y8A_Blzeebr74TJb>PNdjS1{ncv4-v$6Yigh( zOBr7Y)LT>EOq$CgY1*elMu)ujAb79c`zh&Qk-gh!B(dS!dNdl72Oz5NWnISG*wLc| zYzZ;qEh{XG*r#7@Keh=OhZ$lpgmZ{opDAs*P4!~d=Tmz8szi~WCIhlfk~DrLvD$VY zyMGR}{?xIwC36ep#0t$2xEBk&0r3?YZvOyvR*W^#8>h(fN>s#L1|`Ro*0*Ka{6Wy$^wyY3-~k(?nC{B*d_qD5ZQXypS@r#y ztmc_w1%X@hQ@Yr`es`7QS=CBzByCGD-EDO@@Slpav&&5^(-)1J_a#VegmE3Z zbD~1Dcx8z~vW4IQdtX7u!v6s3qJwcfOnfV|b&9bTA5f{LmFeCF=d{@0;a$Cg+U=9@`pN9-9Ur1ECJYi&~xnQuO?_x6h_i~~JCC^aNH<|#JJ0aleNjpFz#eJffO-F?gf)kPAX1wn9x|JGiBY`dcnsU-j z^h{xm{L;lSr~aHI_(3N{7vWk@{{RtPhaHP!#Le*(QhlUXWN}d`ho}!wAOF_*p{?OO zE^Ne`*k7`_NMi5%G+q_`4iz{#q=*y$br8!!YqY11a^*mwXF6i1L!`x z7n>roJO_fPvtP7Puu9vfdMB*Tt7T=79uks8D`CZ!FGyo!Lm10;X~w5+fc+IVnrOlTkgK{=Pq8w|;q=Bma+6=x9PE6R@IeBc0)0tQ< zGU?*Bja3ql^rusN?1$+=O0F|+Eo}=P&vg{j$k`F($Bc2;WViB=>JGjy^3iC)vGH*~ z<}x1%NF~5Le5-&o)0OIN@_644X!54Wg`$tlhD*DG8)*l`5r@%FMlEYGNY*&iIF2*s zsp7g^Ui@m1f2T3>)m)gsAR!=Gu0K^@De2XESqG=QW06$-^1U+x&F?&X?hn@i*K0|BK_-L!}ZeG5A@6osn3#$z4f~=Hy!n# zUn+^V^zTq1NQkp2w(bf~?uv@2N?%X)U(5p}8=1}bST(-Iry$g-`fo8d4ZDMCcM?hc zJEO)MIe>75@U7;O48X{U!y{v^?*Pd`ah6@vKfVrdvF(6LZ2 zo-Ul!fI;_CF+bwERIlZXa8y{_>-N$1EET#(MU0YRKpNZlYid5!VY5CKG-`2SP(JV! zS}?4JT!}3k-PqlQNxhHMmm4Lb$kS&5Psl$HRfNGL#N7O#QGlySHdJ@t$A@U5g7ISo zn`F|LBY@*-kjygmM$8KlQmfm>s2a+fGC2W~AkX+s0pT_WQ-s5of+D9Aa+qE(DV`=a1$k42Q1JALv%0gW+}>FP z#@qp?XvMFmV&SGhY}ZpPDnPdZZbFurvJ4l>Y34f~ViqV{iA!;{G9Ugk5?+^*T`v>F z!BBnSs3M%91JSXbEQFDm#gQoT%Ui;G%^;h>X_5gP&XVN+05cvXwXJ`ifP11Wcvun* zyU^brVBEuT+9^Y&;OS7|=Sw~$Eear^8O0obznF0AFTL#4re~4rTz(cumm8)X@G^C1H4a@=!o6Gd4P$Ij*gy= ziJqQ`jgf(ojf<6uiIt0ogM*8MgNKcYd~H9rGJn5Am>C(FS(taRuuc zim?4p7!aQWY;*tu$e@G>0TgTyN;U|w7i=U0u@<5ON=XeNU!cIGixPlPP*Ky+($O<8 zf?fYtBm|(K+(GUFAXE@a3MvX}S{gcPN+tym$wo=FpIwex?}#UdP{0{^8qUN!5A}sb zxNKi|DJWvkHX6{1CfUsn%t3KV+?gfk#0;<4dmFvHi&x&=k!7N#p;jV` zuwRay3X~u~h=W@GOd=;qf)}=pvkD?!1K6`k26kLfoZ_4)4M_l6F{PJHpb&R=dmA|@ z<6&Qaitw)`5dS3tu?t|LB-hCX=l~;AeEumLwvGylQra|*ZSEEHHFJo)^e5salX@Hd zdfF<*tPi!E%CzTe3}R+13`_@jt1EJ+FUZavYJHabA)L2_wka&(Yj*tW_~ZPis{?wC zirYB&4n+vD-ipSQCf z4;xy0$B=Uo_`o)Wkgf@$MV z>tU*zjXZiNX+qauux+rics?S92o&DKGAuIhdAQ4jzI99;^P_ou>9JFbTzae+bxT!y z@#;r(S21SkP#L^p-DATgSm6p(Cy1aNcK-W^!l0&2*Y9_Xq@KLaK!^0!Uf|yJ-z*VL zc#?~NP>9jW>V&Nv)(Lexh1j@}Hu!d7W@YBHq?FLRE>;=ECm9h(@fzzbhn_tPcP~By zTdEf=+od?TQm-&BqjbvnOC28-S}NdcL{QM8P0G=@A+{%R=N^_A-wk@&zg(-e^5alP zRP30R^<ruioZ=Vm2c+Dz zlAe8m>pa>w4TRnLq?4a`QTO7LC7lAH8$_Q2NoMCEBt@ zJ)b28$6kIase4@Om!sHrbc!o^ikCyD?*`vN7JuZ!zR+bN@b&=jxrEbuMV~dVYMGnp z$gB?o`tHQW(aN_mZ#M08U5-o%h!(d@5=K!cqu$b%vVOEb_4?YU9K4Xp>W1iE(;PF( zFqO(M*whah+i~oBb=~HU-8b<|s;-~)X!U6IpK8!(@pko^`ZW(_$G^w+w$$O`uml_G zj}ImR$>zoDnO+YUdsFNr^In%bT0F_(SEkk!xUa<_x0$MTjgP*uNJD&6rltF-kct1D zJs6|r%B5zrhocB*ksIkRLfqTYfjB9^i6b^h4f7nL7#n`U7GO=GtSP~<*w7|FwdtcJn<>o>E2?e zn3C~31bmszzjuxq(bc>e+VpOm&}0+`y?u;YGA^*Rl(Fa}5xBchqsGU#drx`jK=vm> z%c<;^fVqc-i>Vdi7(f5n?mQxpbvJt%HZ+k7lY+NCA9#qQ4!D+)m@YbR)A(HNJ0($hC>B$t1Xf0Eqw2~ z@MiHm5sU3%ff_e1DWxLjVG^{_E)D%>aU zLN!XnUzF#~`OD+scF$G%4V6lVk3c06bvd-L?rU)x9Z&fm!({@x8f7)F^DXMNI{9d! zqFiT=lnZ>^Me|~#)rx(lFqb+r9n<;Q9+m`9~& zGkM$98Lo%(z_~ZpbU&XDog|zZzZ^mY+;1h^Egq}ol10P3pOoBa?Lv>31wAGb*eA}v%_CLOf`W|F+8m*e~`rT z*?UQD?yyK#Y-_tis@_O9?Y)VPt`0ZfE~>SlaDnV^F2~I$Hjled^eC%0#CFZrsmgr| z<~v?-@>Kk%$wR#Y^0(%?snEf|1QpvG2-Gg46XYQ;dUw$8GxYlE!WfEbB4ZnP{ z;t!2Y2W5VU~fK=8=rZ-(@0u9vBdl2puYt#-L3%AE) zY9!u86jph?AK!5H?p?B&pYXg)d2Ew+Zo&FHIO*D)G4=oU>|N?^w+Ttat6S~tZL1NF z>!LA@u1adlH7!09drHrK|EhBH@{IY|H_Xh2hLcH4Wq9%t#Y(qVqUTMu`5bBQK;3+M zLexK|D`rb6GF&25xuvALNY=p9?|R(OX^+cqWkI}cPc_vVw9K!POGUWVD<6HI{z0eB z?k?Lk?zrr(aa!c#Q>&LQ*c_jZOZ3r(SqH}Fz35tM-@Lo*l)a%`R;wq-uGyZ7ce{R# zP#C}`u{Vk`ck$TMJvpw;U7S4+vBU`?`0P_DfSw z2QMic*A?#@wHkR?^F#dmA?x#wNxjeC5UeGG%AZh|v-5tQq4-)68oAIOL)kOZ>mgM2 zioWB=CL!Gf7omhxI=#G_pz-F*m9k8wPPdMQ3220QI1zZV-o9$&bGvOwrRmYa`Ed4` zEaVc2~OHR13956`Y;Cd~&Uj*ya_rQjy39b9?n(u8K7I;BHdlvarS|bf{cBER7UN~L@ zCEdA^Z6~~&YiT#{3-Na|_BAJ5H1PNRz28M|nyt5rjkL5I9DiKpRzGWCc%uKlP2Fmk zf!+{X?$Wg$&69Q>As*5rVepr4c0Z{*x<9un*tJ(RAbA@bh!yRRsKz1J$6Gay(e zg5#Hmk(E7D&K=USYJqhdq6o@Khp?M&7SG;^Tu=;qWgK;?w@hKhEq7IebMDSvwnqhx z>S6RpP9{(6XQa&c7*SQuec?}Cb@zDl0<^okb2IT+Ns^vI?k#LLe3!QnDg+tNp%~Yd z+4ciIt8c_j1c)v3<UNrDo;&s0#M< zg*S($`S`j*)oZsCuTEkz6BcrLz(q94tUum(_HC!--f|=cOmraVGml*BVWGk6Q`omOjGA(FLv9 z0cIECLeBj_Z#Hq~T3sk_qpdI#n3Q;TQ0lXy$(;QINhM9kDIWaHr}&y%-a%UU^_(#uF?G-q3TBp@N zN+5eGYi%QbeGO?jMl&ooqmy$V1&vPKx6sqDQ8T)s=%bz@&3U@f8{N}+4j3EXm|16* zmU6J{nl9^PT9oVAS8(nfL>B0^E8$&Ubqsr2^XBM=aK6S=1hoHIiiY+}_oHW8A58V% z?Ord=dGg-V9Fh0>{F9>ABD`A0nsGa8@2WNtc)wimqg|m1%5P8z*N)3FrCK|&p{Rm+ zT5}`%7L!}rNOJX=`m-kr9z^%vagvjO&cVYkEkB8qzVi6}DIzc`9LjZSB6;dUIa4(z^T&^Y<{o{ov26Mt2#{^Izv*oJ$m0WQ>{V~@5ku;hSRHi zyOYuBp5FB%;RhFT1QyCSgQg?eK+aMYR!0}AO!DdlQi%XV^pVhyX=X2SBK)!U^EY%> z_qn$^a-DgANPS1_1c*~&6TkdM3evKGs=k>el_=Sfv5S2F?MB9_lL5ovorEWOasYtQ z9}7q0tqFL40v^ng99@774sYj$!63;LW@tPNh9;m$2vVB#G(ciWc~){+J9sdOLLZJN zl_8{4p5nv;|us<`lzBtRD83QcZ;8zBX|CO=uLgId6tbFl-KQl%s zq~XsDsK;Nd^`QvgZAENp39vQMHvpxfB#C6}Ehy-XA$aLxzL2uy%LI%52bezcAL;b5 z-u4b?ys@w?lGL|9FMXu9;6H=g;*fYU+$I>QYx|R&ArJ<|W3Yyvcu!Ib$U@rq+TeDq zf|Mo^gHMv!fCbEFi_PDvwFmv1|EyX&C~^yKgN5q4kT{U9T~I8>-^mXKb}ne4aJ27^ z`dCN^TTmu_3?7d`Au(v*?IKJ&7?KFT!I^j9ws2p+-w}*E2vD}4^rW#uIkhFjw$Br? z^CXWWF*1&2yHfC>a~jyPH)b-7Jzs4?rCLl>u3~6Z4 zd3oYszfCdJfCGR7=MF5016%EdA)vi+e;Zg(yc~!l^$$R@#-Cjd=Kp3gfc`JjdPAbO z{b@N6Nia);L&BjjoFmeLWc!f6)I&|ifnavIvVOUQ*%o_v%_96Z|vogaLF$qG#n4x?H!N>n~`zZ-n-HA1qwqV?P0-qGn}ctrNzz+L$?F|3o*SP1{HGd*CeMrt#n(j+_D=v;fC{pb!KMz)*=Yi6PlD#F_yn0UB-;#-HTu zCwWLo6_GAl@NX*(-gmsa+b{?zPa^v5BGc}`sdf_o>Pad{;_&D1j}HFW!2g>z@cZO( z70mpK*s$1pVtrwFFx*QD;gMn`IPf|#8J8D9n zp-5rR-}9u<=gTRX*y%@LK3igC@?03Cq+|1$j-$ILwUix%?u2{ z7_tu>3EPP>GLSOH7%U15xom~M8H|5INQDpE7@2?%CN##<6OV_H0|YyT!3a3U7YoDT zz$Wd$m_os*fj8JdBpe^|m$uWpk}zgy9}LKk6r7@m`gx+!FyvNeLFSp)h=0_?X=;rtug1Pg|(e#5i>4Q~tc{tf&0(sp>T zGq&-hUj40dGnA(<%*GS%_m_APrvnZ~#^~XGRkWizCQ?)XZ3DT%I=J+ZL7pKvJ#TM4 zBwi1X$HKh`cygzbBw+ptF#w~H+oF-Im&pK$!NK&v(3Yp~&px3uLPIg$;5gVO1nB^F zl2zGx27=IkD+>ci%VL1>5BY0DNJ8u}B>02zLQpp(UZjwyKFkl~__O;-MBJu$6mkb| zfI<0#HiyRV1Tbl~C8F4V^8AMgV*@C1CpuN(5x0Dz4o z6{%KmlzYMdVFc`06iIW;zep`Tp*x+$@(b!V04JlJF8DRi?IQQ0rKi6I2DBis z$ack~?QBdtI8sD(8xDd2Rx>mdN$@6dLy?SVr+W5nxGM|`PKsDCp#2j?V}|wyNl8q$ z=QBnNFy3v1CJhJNHg8h*Gj8>p^G-0BR3G~h0tQbSOg~54Prht+{+>`67H5R=$A=hz zq2wK2q;fk*Fgy$gXJ5Q0a=RkZOvk!ik`xRE2hZ;)COh!u0mE(0{=c=G+1}6z?v3{| zh5fdUgk)^A;3)sw1V96Jg@K*jj{ZqAkp-hX*YPGjcXF-pSK|d1p(To%%VpvUV5(7D^rr4z^}HI7af!ZG!>l zkl){#EIU&D1w-m1RwpbR53_`$;V4h!b|vIFh71P*_82?3Hw@~D{mZbU1yxNl%AXn~ z{W4IZ!yoAn-{+{>(4Tbv;93rH<=#*k7!pPv(x6`DNX;iR_21c_`m@Mi zSpP(mJ7cF1s9Hawz1q?X{z-Y4WANw@d%{)KdV2KbM}-cWTP@>i*r5IFjLVKOKD zVZK6wKl?zPJea|nLGcuRhyVJ-)e^#iOAH0gj0}|J)RdK_*J|o1t-dPc#%} zW~e0uHdY!A_f}UhRy2?`R5dVElh-#=l#|m_)>kxEF)}t%l~XZPHqhTGYlwjoNVc<6 z)|*sT?SEEwYpIhb^1tFi8p$MMQ#XZ!b0Rimd#u^P0=82~xFFfS)IFi3H=LFbl+@=i zZ=r24_@4U5R{uxIw^#;&D-{2&A#9Q1Fh2MoPb^H&7u3*yvNM@~B{$A9@IOec_n-9r zucRj1q^#VIP5u{BZ!44Z+r<|KPQ5Zh|BH4Q{jb`gB>VqQJD?aeX^GjdKJoX&f*-ng z@I9v`YhKiXFikJ-+U2kgI84frJuey9C3&EFaRtWEI0WBs44=Rc10e``Je z2U?dQz2de8vdrJd_*ULi={!{}1d5d?l!R|5tO^R=rzGIRDEzjARew z#{Twx-vNOi4W#7HuNCc`uZlmfj=7fqk%sf_@jaUhcxiZ-vL8|U)n*SH-q?< z$r=2K=Wb(bZER*?L=H1EnSwqx3WmNK;w6x>1l51(vMe=HblfY^Za3f`dS0!*I= z)5st^DM}5Q1=E~f2vVAYY>Cko~XI;6?FCPeTss2`Y?1>sCs zds_oAtqD4!n0$Xrd;ONigJBnt79cs{$=fyr4?qtJf({#1K~q=|5{z(3*?>+cPpmiS zA_6_#=nw$dYBQ-6!2SvSCyAgV zXl0=L8tiV+jZN|_2*S~SajX2s;lC{Q8wXNy%dWu$N7g35=`IcI`osxPPQ3@HI2ZxS zV`(4;^0VJ8=pDhOB>>d}e?|->bB!F289Gtv!3^1^@7lab9fm5O5MW16%-Nfy=-( z;1-Y$WC3|V5l{kD0M)Qd60*YCy*DAw~$6iCu9IJ4*3pQ zrJ$r>qTr<1MJ-%_s#{d|sLH8oshX(Lca|=hE}!lhT@&3X-5UKadQp0H zdTV-b`VjgA`YifN`cL%3^lJ=k4B`x04E78s7|t-`W3&229782u$%zxlGTQI+^B~nVH3yb(vk6@yzkedCaev`Q zaIi?Tn6Y@XM6jf=l(RIkOtaFnin8jly0M0`CbK?b{lxlh7yT}=T?V^6cb(dmvg^sN z_Fao?9BguI)@&%YOKkaUZ`sD!Y1zftjo7`}&$HiUf5kq?LCGP^VZZ_9ILDF2@tR|p zlZI2A(~R>3XDnwS=SR+2E_N9b9 zJ`Fx7UkqOfU)NrWy%KwE_lE7gyZ8Ox#eMwyO!oQjySeYxzHj{8{JQ)n_^T`6`b9_cfaX=!u~t^Kki=>5*Kn1IxAEx)GN#&tSamy zd{y|R@T`b{h^5F$kpht}QF>8TQ9se^qV=N7V&Y;>Vo_ohVq@Yw;%4HZ;sxS82bd4& z90)j&d7$MW%|X?Jh=ZvIKTA+ZC`tH9q)2=^L~%&@5d2W;p+-q+Ni|8dWTs@h6qA&m zRG?IW)POX%w1xCp=?dv-84(#*nM9d-nN3+GS)^>1Y_}Y{++n%1a+Px5L}(m-hBYm8}%Yr-_MGzYbWv^=%$XnoNZ)b`NM(Eg&c zU&m7?Q|GI$u&%dmj_#=5LA?`t_w}arW%aT8W%|no>IUHk&kZRJO$=iUKN_(cIT+nC z>N6HH_BAdro;6W22{U8R(zf1?!_M6<-)_lX z-#)><+u?u%!QrJNtK%`pJjZ1xL#HICFU~T~;m+?|cwOKwk6me99bI!=7mpepy>@i? zn9{Ke$J&mI9}hnM){WN<>Gsr})!ozmkq5PhlSjVCx~H{grstxUnb&QvS*QW@I&{)o z*ZZpXI7|zc1RM3y@=5X;^VRab;ydoAFx1)n=%dLsSAGQt{>iy$JMki{rE zln3ewnjMWm*JAiF!I(z>L;e^1zXYfUTnm`RnqzZt5ZrOxV>~DLG4-BsfDlO-2-FTt z3H%Y{81yKZEf^jAK13qqV#sKyacFiJRTwnv<;ndg&zu|x*9*UM3UbQp)XNCri1QJ{ zr%g`ZJHv1WapuEW*|SM!7tT4It31E&{OR+9ktUG^7g#P}FSJK#M5RYlMZ=>%UR1bv z^WtWVcT7X9Y;1Du`X%V4hB&#n8*#*VpZJdn$_Z(gX)dELw*s4<;UZJZvj5FM0V$^-)o&XzA@T zj1IeD44J z+Y8?pgD>4*cE56d)lz$;_Ty{w*Kg{K>t5CC)z`eydQ<&Y{q2(m)rN|9%J0hGE4?rK zp!A{aqcV67lgg*ZpVdBBHflCLYtm_Y(QMdU*J9T4uGOlwvCW~aqy1R>mk#fau});? zTvuS%TKDN5>YkY1UA@VDd;2o`5B3*-QU3CDz;NLGSG%v>gV4drA>7dV@c9wuk>pW< z(Y!JFvFdT-@y`=SCx$2eC)d7Rm|~l{J$+!hbVhgP{jBrs@EmrI`2EuSp84Db#f6uP zHj8~rsHL^#m>+w7+*?sysb6(k9bF4rr(M6Xad4w@(|ogshytVazfN`~eG6=z>`FsL zMMX_TLrqOXPXqoj($Ub+F)}eQFfuSO?P4Nd+mEfx-|rCc9mmYX%(H73&;RCR*EbMJ zN(guq>aUYs#fY^4xE-2;f)euEmk#NaR0stvKnI?kOFHqD)R659LO#}Y7eE0X=t>DT z2|Ujg00FU2&GXYD~H{9lN7Uiar z{J3RCTUnR;t^R5Kp&mmbE%`h&U$go$`pO^$u`&Jy=CXsU2#b_+0j8a0kQ4?=Cg##i z;Qn&Uyz=V7EBn)0XtI>5?oo-^noVV2l+elZQX2i1e^DdmV#Tw%&1MHHR#k&naX-Q& z>@OkcRpRI#Qe3n>p-kmG$1Px73MjBC2Ha(?w?;W~DP1TROf#ZwHM9A$+lH0-RG0Cd zNMKd_ks+0v;NH9eUy3vlp9@+Ukph{mx-pk6N2ucpE44QzFekFo=Ci&pAFbaoUz;*! z2G2CTgU>D(cH+pRZ%_En8d9k0+w`npKq=p-_IiWAwK~mx=Lvqkc>JBLP(`WH3AMH! zh2ncepqwFZ{G|Qfz@9a;r|@qp(A|fE@%LMH-_{dst{uag7hQcjba^hLN%bQg|726a zIobzVRE|_`m$Z<~!l9?+m+aHaKNvECh+=K5T0n1Iz&qvNb6zS=as;W?OOr~dgcDy3|Yu~`jIHqzxU1>shSkRTj3rZz0 zcFPs6%p)1*g6s#LRUKo6D`iO_>ln^-*mF#`U9_gx+w0eTtG3rZ=y;)%jrNaIPm?#* zjfstS@pDKD#KpE^!n~ges4-X*oAHHGb4OkVPnBjLPp$-BisnWqTtQmn(b-SAP5s@o zLSsC=6UIkqD#k7Axiux#<^W}>xe<r-6ifcm-0m=`;@iU8>JTl>oGQiX7batZ`n<+*BoR!9Fbo_1kgIk z&5ixDCHVM?rph{=_v1I^M!e)oKC$&gK8an5X-~^e+q3xL`hkzq%QMa2vTb`Fb_euD zI4GRJKmC}-hUv|?8LtQ%JVkN;qOU&N+r4xstqVD^j7(2cz7Zs~DqZt;$=r4gKmq>N z&7q-Y2XN+fOX}0yv`_QGrYG|Hf4Ji zR{AN`PC9BCB4acr-S*(45{@_~zpp-czMLW0Ncdxgr1^ z2SoonHBGVVoa6Q_mwZ1YDylax`}(CO#OQMk%gK2T3Qo4RH@=h;t?tPqRp;%?`a<^ir2cmI6FMk z-mY`-@sI~$u+#CJ=|JQmu0x0z;}@Q~6wsrH>J#=zrqS$`*dh1G@jMMGc&F45SHfDOsC5B*lLy9qLN$dSAUGw2-ll|{n z7emzs2RK$#savvR0=UcWhf$a2#mO9UT6)*I!k@Betb9m;|9Z=DkA_GjcueHm12r@z z!aooZ?vEyeW=ED+pMgj51uWP{wtk3QDvPU%nkqInxpx1BQVZpsF4|`cLsGn64C!zk zBhJRcE0V6~GRjjIE>ng4Q)Gg12J&t$CRZ^`DE<_Q5bElrxU88tE zLz$GTi`HFoyZbd2by4w3GLS%*WYy<1lY8Q}H=HyQ0t}jXiZ{u z<*@B#kIXbhj<@So;EB{kfY+XN^ao)`0?FG7Gw%s_EVwrAiHHST87{TkWFl9H2^)%W z^7W)RJ8>lduS4M7^lF!>xD>Qm^Jq$8es8}@&bwxxWB2#)%T`{n4HDFeMwG8_N{l?x zthcE8TBx12xv^St=jB6!Y$}4ywQ*(CyH4ZeBSVSCqvZB`GQFe_Uk?=nT;FV;@4QkB0?J3Ek$7l#H>t?vYRZ zP7VkRP_&iQ{7`$hmsQS756_qdfO6NoQ(&7CiQ_HzF~D%j8k7 z+O6i+@yV)qN^SRJi>wY;?k`?+5?02+m`vkER=gsmyyHwDn;5Q;qT2bEdv*^?%B@RY8sfKlG%SjkgI5Bvzsckhz1^OL&#R&0dU_-I1l z)ic3ZLwWUQr;R5xg5<{}vM7l_!>gK454(qu!*k!d(fdk0+KDaq8kD~XPI0~7N3dEH zSZpdE1Wj|S{=&VKgoy(=95=WjaD#%19GiF78Rq6>x{qthjW5!got9>jP&=?DfjMO+ z2yJ@qc6AKs+J`W;yy|uic2~KA!PTNZZr;A{f^fLy_pm>^}ZR zL%CuVp`G*IX`dUq{^M~=@wyLh#^+x@QsFj^65M6Ra-serrTg{ScIK7(0?iNW61^u5 zBx#vMCR~E}=O#AS#2Ll39zMB#C^r82A|ILBL;v6hL z#vC6a`uOGpk9jr3FvFsj)28^QrT~%%R1tw((Zndq>;mWa7AGx|s$#KBkSVd!i$0cf zNrkTst~A^bzfmHGNN0|P|IjV7R82@zw$=U0g{peC#&wxz;E) zg7PmulxQ@Biug-wpK%ps8;VdMtn(G3g6*S-t$D+z<)HgsK86-&E41(qLwb+S&(;?w z(P;|ZPZ8VDp7tG&VX5)(~>Z?Yt21p%cRZHEu7Hzs11+^u3on~Y11a^?_u8y? zy-Zo_<(1oI$~_vnLDy&Y;+xoR$Fe{0;y|(|j3uG>Eu29+Zl0|SI zXS^b#2M`y`IG5d!v_Z{J`-oMl=Ny_zw>kZt)0=`z=G=B7Zg9Q@74>rnn&p$Nxsv&3 zTD;R)WrPI;wSU~cePp3MM?vvKPc_&&frlbb_ zlH#w*6i=#F-hS_L5v};DUHp8!fJKuhqgU>(>GdC+Qk(fpk4Ht)Yfp|kp=uiRLPG;6 zt)SiD5x2m@#D=c0(u~Fp-TXJBrLwD$YA40+mXzqR0H0G>!y`?O(*u(CySi)N;ijMD zl@3Wm=Xv1s91Afi*3POgrHXO~m=|#weyGcD?A<2lMZ)rjdAftX>+nx@wMLvDB(~*` zG8*tqgL6*!L9O6(H_5*_Sx|# z$}t+_8TV%nn&+6-@M_<+Hhd9%V%-8VF%eXEC zaA-0sd^36Mf4JLRg@51IXu>lwh0>cma}<5H7f-uM8Wm+0q#rD`%8L@QVUDplWtuE& zT*Y^`!n0!SMCH(vv0S^?s^KaE2kJ{r**>Q-_xLZfF0^b|n|hgOK8}Ci9@(mY^y~5W zR{cQ|DGvK@i=>Hlc+XRCd7_MO19`p5kMmuPuZXHV3~%FpEHYMFxXB_m8}RBeUBTlq zik@@XrQ%WH?3dI#V4_a?b*pcBN}KgVWWw#UdMjT(XLn@!e2#E+*ZHH7#kFnK4Jkr8 zEyode2o?${(wb3ub@@ea25kL4ysVlMXgP{(MxY8;%G9FW5}8&K6L}s1foE`p^5mu2 zyoplqOCnR<%{v^EI~cWI^~}OJ#DH4akr}ZM8B+1;*!531BH+3l@s$V+jxSuLcZp*D z!tzqJy?Ukj$9&ZEXO4x>{?CE$dA2HrLea5tJd}&A*5_MKguD@9ef2W)!fX}WoeFlX z>O&e9SSk;GWuSZ6@}Lpx2c{HMeT1)qLskNlL#aP4HOAv zMa_hRww9;6m`h7~z7Luknhowh-jJ)WpBaCO@5&lnLZmi(>E*KXV~N6P@Q7MoT;>DG zw5Yh2Y;8~5GuLiORW(wu(pfQ9r(Y~^`SvCuU$`Zl@_Q;*Kqey(oa$0jaBh&$%f{>< zGEwWT#rz-c0t+MJcF*5*>~@Yhooj3P;wcf}nz8O2K3V0ITM3z@<<_qn=@Go)x68zT zTCaSDt3YiMJapQ(zhZqj@W23H;%*~eNRJ}dq)DvRi{X0yTh0gDyH32&EV>U(YD_{Q zx+BZ7k>?ZAWrRf{WM_Hfou;yv8qW{MS$=qYPn)xN(K*jDwA70Gu8vPP5SzZk6~!=i2~ql$qb&UDrM5}+TmJdD zea*17(ri!4#Ik4sOWC#qg|8wl^Dj8?l`JY9t}j*}H6PcKOIIw>pb$EMTwWLaaV&gT zDXQX$(9@HB#Zm7!oMf#p4@sCXJ{0!)W@A?KT>8+53Pf`{-TQm%HpaOwF2?<@!wPH0 z70rh125C$;^x`M7>Zvdzf_src{QMsdx$NmG2{q4!>6;JbCXc+|J)Jj)s_51~(Jhf6 z1+}Hw48NLZB)k@VV?!eg8V&6#(Td6Vx>|BpE31s_q(rdoMRnn!N3KSJOp|#n?3Qac zQo_3$%FYM$x%hOP^Y#NAYDB-#HWnHkv(Ufd%xP1};DSXy^GnvHm6cKxwRyf_lM8i|6i>_s4HJ za~=>3=(B#{CxL%knR1PHjJCPZo`=!v2~A%E9_s$CQm)jN14MU zR;;aVjseCe@M)%eGUcB#m4==j*2#Vc+0 z*f}nL(;C7LJMd>s&8Ol4b|TQzf0(gmwDDz%e4fbo9i3P2?FrMPD{!rS5sbUd7Ilm! z4zqA0?Rt(;`CC4{eZtpv-L~3go-)_ z^xFL#j~mOAtWQVeKMwDEi#EaJwt9De9~Jl>9CFNuPX@zP7*MRiJ6gBO`C~1#h{HKV zu58U_S$4Q;*mK@)B!s(YL+1^=`orle#-7-;=0XSW#QIww8)$u&rt{>!B-~H4>CLQrw=X{f2h)0 zylyGFJ9khKY1}Fqd?suHgXZE2Or@KEh8>Fm1r_=gSd#?6qQmfU2;)B-K_f@KV~> z6SG~F(+B_$U6iuCct@Mj^-J3BB{n;nSQ>3TORN5iwCKuPxFngCixCP#-ImEjV5&93 zK!DgZ(z#*lWq9GMLQds3v}cw5#=!UX6hw;`f41U1{lYBXRHGX#C&D6_wRpUw?;okP zEy{A!>MCG+!*9R&@|6w2`KaA9ovUXlBbQ=Ao&9|AKL%YyEnnlMcvhzx^1~#J77wS5 z@t2P8)3$*^5An;TO3e(<6#9O9R8v?$`jCuW|K)MF%qcqs4R+(YmGDL?NP>*{)78Zw_#J+1OvDJTR zfVR?j9L~9rq$SW2VwPAH5G5r1B}t)TQee7Z@U`XqKBw#m>{EldM&DY6x3aP&uSC+> zr)kfzMi&zLVTm6$vF*y&GxvUOg56Ysz|3P)mf+zb?JVjjM|E&AO3$&rm~4Y|3A~Fa zqmGM`=x%oo(`FnDy>fqWG1$|6^h-lz*FoAB@fckbmxFF7tE^*Xq%$A_@TY6TT+Zkte-k6nw&h zkfAd}Z!kHP-H#&G9HQajR^P9y@+?Qv_bWaVYPiJ?87Or6K?EdcsNm%)r@CuTqNz6A z4)Qi7NUw4oK?E&1sT`}!Hic4OrZIM+X@5LyQ;2S8oZf$ANQY7Qc-2bEk+XJk?;Urs zWL;K1hHk$xa3r?nf=HTu;{Eeg`;2d8S1n{Ah`=57`yCtZ``UD)M{CvCJ|%P>T@An4 z;yJxIqtv@g+BT}g|81X7=O4F^R6>&L&;S(efVzqMr>HXrcS5* zbz<`{N4<#1jc-sKs+usEp)*x1j%Yn_rC{IN0hKCoVYFASg^RaOg1?GBp1IaNaiesz z?$gNe>cg+SkJ3<0pmKSGGCD@hAGqrCoD{(cv;a7 z7-d$2GSPQbFV1~9KkLpaN@+Z3SAXPvAlqR=q|cL0lcmw3!H;&Vq9W1jVm1Z`HVDOi zj!u#9n)ivWy6e{02C-gs8NL>UsNf59wkHG@;bN5~QG0`qrkt=3#FZVsG^{KC;Fchg zId1AzZ{4*hO||qQBA~#ot!aV_4nnm^)n!|En(#}^yh`&L&SB~^_Tu5HHg$HZzesyM z93Q0J2fdzmVb*tiL(5y$2@VT?!Trr>(}8mM{_0crs~>G|sH)v`VH;hf6|%Rv@jz}f zDd3AX>V(Ep@SE|))%g!*z8(XIE7x@cxR;Lcj`rX^c$z+)bqK;lBYcJqJZNI`(f5Yn znNE7#8=NYe@VmHIP5APz%{IP3|8IE@{lEFYi5r<{e0xWwg{>RYZD(H+8t8Meu_f8i z!=aS7zx>gUf_3f4g+^S{=dy3lxgHN8`WJ}69KSf|tEfyW&)MilA%xXG-7+}(?Alo+ zNxpv1g~qcFlqb7GSt$ueXA7<=CCJ#Cl;kwN;W-d`7Pe;{VK5p$fy`iZz)SiDfiTi@SxA;p`&7v$3$UAY$GKGW3 z7Figk!UpZrcD({oDL5UR2Y#GqG=H9#lkUc#slyWF^e$eCjCrgrqAW72(p1-T&{Y-3e$JvNQj94V zs7`mCRbz8?8MJ$Lc*rc|o1S36!-s99gD)lCjbHw5{FW!NjB46jxI5e3V({KVr)ITt zOR9cs8b!{zOna2o)69%Fi$!_Om5z*~-ut?Z?!=BHexl+h0@3TGUusnh-`)&8IFahC zyxuPPkgehst`ssS?|L&QbjEf)-NGd^LK>SZsUbt-vN2R=<+w0 zw{t+h+|m)2S9YNUu}}YB0D~HQ<^KSXc|6l9-PGBT5%Ama_fUUt7zbA9@C;UxOf->r z4+v!+y?U>*aZR2ad}4fvQVACOffjB&yK644w2&1t6XL$>aIq%c-L(~P&fNp8w9uRW z&(}!Xc@gB}uyR9<2(|snd)v0A#Y&!w z&4G24FayGXToLUx7nRDfdU(f?AORE*ct7mDWz$IdC)27^q+=T`#c5|JCIfeN4qEM*jfu++N&3zb|JsVO~UfwVdTqa)!~c z@b0k#@b|C#YJVn-4``0jpf@YazSMqAtYv{F;g^k%eFpX95?C$Oz7W*3hKtQvbUp$# zE!o3f+ji2bhK-ejUv;ht;5)@k4H=QuiVGFum((A;r-hoHVGucUCCdU*KZ$vY)9b0$ zjU)`}!GSBPk)`(o;WSBbP$pCoUA5e|9^PWFnwpXH$5M+PbrKLvnlBJ<-^RAaSLJ)g z>k7q2;sMgbU#9go1&K?O+hNH^GNiM~-A~ZL5Zu@*9 zh^)oTWMaRQBOIIV?9+@(>=+pU9>#BqEUzooiyHiFkFB-`cM8GmQAcMez zN)naVeLEB`Wk|t*=)twO(|UNR(zh-YDK^DpZ~J7OH2H7Kx}wICvbaY2a84<~fgDGD zN$KvN(&lr$2Z3+68~i=gu=H6l<5ReT@tpxW5%kl*!(vLZDu#EA*cDa)4q)j)EPWf( zSu!$|1%qGW0FV!Ew5+$`QsbohW76>B6_#knm~C-kTl&2pn#bGk{6|sgYiv~j3kv~W zocMXyG2LraV1NJC;z(3If$$r&e9AXHIjeS8k*`$rGP1zzJe4MI)^~A;ik)iX80>GQDocrqjiZn8D^YEsSFgdfbs8W1Hy8u;Jon@X? zUOfvt24Bn18%E<*aj0NA{eqGV0fM))lw3-m+AaBbGZTR7UAqYl)kZFYQ{H1#8=(LxH6u6wWWB6_o!)QHe`DAln#C--=nku)A4MPLKhbgN*pVPr zRR9eE0=MUuU39TxEr7A%TS&{QwP0=X7eWq|yCWs38&Y`C8V6CwjRy0ZgK7a;Z@b@B z2o^ovwM5z?K=%)_f)qOdZP`&_V_%!`Uf1{15C7NU)5<)>kP@yePTg($YsimO+${3# zXl>?cxdxi{i}vDSvfR7sVzE%k!Dt z54V+VI@lt^(fU?TNkq=Zg{R6Q`^ep}9c{Gm_f+-Op=9a(Yt$Keuo<9Z7T@MJ(xY!^ z@2vcci+mYa!^#2~n6JZeVm-BZl%^b!83_vMyZ-=C_)oHo17>7J759*=1;Jh)MHmrJ zH<(MKh97#@A5|doC>rh;9KoPAN$0)SYXEydKSeAv&}6XyvXS06QzXYun^CTvl~Ah7 z)7a>5$FjEOGqpX3cXd(3o)xz-ovT(eZHy=;!rB_u1|v5B_Y+8K!~$(V)`uGA^wX>u z02C@x?zV2-Xgvki{{TUzP0}-C-D*~mw8jj+;6A#}(qr<2b^G|#=|BI_;LFapsP{D!lK7jE6_8V(&_R_qK8f>Xtfhy`W40RsnAFhlzT5YkA`jB|@ z1K&jQ8kqx)tEl8TTeymN3`ECp+zpB4uD$gZ959YH9poLScI`MCn-#5ESj!dMx3SOz zb>=ELG3chtTsg)#EXsp{B?I!>QwEYEX_0N*lxM>tePs(E27!+!ot!2N(B%Pn`{B-5~iyp*_7BbBQjTST) z{NGTv*&F`=mjFM~-Tr@>zU5xCu_@EKt44^=vJ6KT!@*Igj`s5(_nY9qj#lw}H63yy zS0S+{w&+v^;^g>=9rd0nP%@;zOnDJNQzfpV=HzhWPnde&rS+DN!#FXqxDR`;4GmR< zu;TTUDx^(&Etx?Y_xnXo5>Fec0zqk-%kgRew|zVY8`KcQTgd+a$ZZdnNw>JtwnLJ< zol0!2Rmw2`0Jh*dx*NAZ8} zP!D1>#DWNaWz_u!i3Y6wF)S6YW7unMc}G=b#BpKI?X6^GY03KPbl{{Xn=!W7`409! zDDSN&gnW;K>ujgW5VOYY!+C-|NMGLJPkfz=e^2XpSvF8iUJH*i!>?sqI+HJss2{hN8 zswoTR5Ada-|IzvUvTnYiTTV8y?F84Gk4d|;r5!ot%-q{v5ZB_ zvf@Rn*j7sn77mfbv{8BfGs?!CD{N1s^ zxgiAEO^GJfF63y;2L2jsQ7BUROTi+R%^@zQZ?CB8-c{Ozh!6CG@3?){{S^^ID=4Z zWMSNmny+VlD^(tg1fLRd0F8U;eUB1&k-TIFQbO?uUO%)_`%#d_{{ScYV6u^QCygId z)?@23#^JWvufvh|8hDW(sm(iX3ZY4~k+8V#J*)gQS}`MuJi%b=ec~xO*QwA9Uzb<^ORXI(oP6HbNPXSwV8PvQvQNHGUSBtO z6|dXFKd+VN(|_E29JOe(UGBCbc0z=1*372gRX%!^9dTmeht#@$YVO6zNxPJbk9AW# z1E{iqM?2NXNRZ=yzLhr3k^nOI(h?LHlZek;y8l1P4aA0d&`p1Bs<(n-XqV!l~X^ z^IL1#xOa1>3Xdi>SLI{!)60ppj>p8zPNvL$6HD?50JnEcM)G5-K9R;La0xWGWdQU&&*AaLTxPUWfV=Xju#og@x0IH4Bz@8=BCdMYiR<($`3*L!9xaY~7>R7W}0(1&AH>YZGYQ!=uiu)(*Du0C(uRT7iLl~t}s5o7uqsY;}x$SMZi zEyAl)fc1W%l)XL1IBmyCHt4}mI~uRCVxrkuLMM(t2F+aDDmLyb_+0b(YA?e`f2MUV znz$_|P-7HPw%;xjZSD7({{RiCb7Y=H)bCE~xY1*XanQ#SsCDy$769r@hnd%vRQ6}V zeG)PC#Yt7-k{ynte4B!zpyy*|X1@ie_b;kfr1dTGlb1SAk0IRzikDk(YlGq%d{n;w z0G&I+V0CFaK2&+wkvxZR5_xO_fPNFh;iuPmzpZ)g(dcr_SIAtmgRK)HKn1n2{8LAC>!$gUw^vZ`jG$A`Lbtn>>E?a z*Do>OM}|nGj5{D#Tad@^4m$VLS!Eh;24dF%LXAOQRy;uLsO~CPoo_C}ZP;4tuWiQ~ zM$2vT!;%4AJky1ga-tljW^u7#5Rb+7+ z_@gBmf!%9rZ^+()5+pJsW$PG;d@{%wc>WdIx4rIe7OH%eCmi6J0njCtNEa)Ah_UA* z_k2GMS5x4*zDYP18%A-k?Gci5} zB->H0yv;kAQRxQ z7h~P~#+3_1Yh)Y<<9bw#EI`{$O{`5L88Nq99W~eWngE9w9c`yR`%nx=6pLb4!+)yO z>?me|+APA$03^;d+G_kmx0i}mwqed`t8(lrBX=uhIWu69# zP#s9n9w7Hxu2QO(P3gJaM=pFwWd2kei_It^J@@db=dX1>hbt#b=&|BJqibZ#7$!#8 z0A?HxylwLS8qZXh$IqEs(vrkou7hx}9}5w{e6)Sf>S8468E_bm0)AbPi4ryOqj3ct zb<>qUw566D=%18^2giyzPmo9hAO=%@BI3t|IiCe}Efz0Vks0tMNn-?gaF*EWr;3tv zHt+bBoR)kR&&uiO{4$8`LfCe;B-D6d$@7uT(0IK0JCmy z2^Q?s@dk<_Ljgd(^ko2PBHvNpZ7V*G4>gr9q>=8qaMmDq4pk~Iku*#^9vl3p?cu_l z2fWnu=Z1+Ol{s&jk|ndtcpMv?H#|nORtd);gkW>U|SoYI{5hoUKhF4 zwJHwHy|C0r6EKDLqd8!vKnU=u)z0EgGSp;|#}sBZTkR!_HohUp!Zg$F)6*d=GAgvi zm=ojJ2gTlRSIwUZu_@6Xb1Zgd5-TP4js#lf#@Z3vQ{3_;@X;79=$%Lfwz0T2*Ow1< zMO+lH4hbG-k_eG52ZW1h-`h$RY7$Qn19JiZ=63M#+m$*-;q&9L3_Q>{k}uu`xKg2Q z=yUnNMo)}d;E-?r6ctUjC}0X|dwJA{yqO{bz*v3nwv7#g_-sz3*iZ%;0d5Zw#CB_H zpdw2dUkTJ(Q{POQ%OYvor$a`Ey!Rk@uig78>0pt)+h`=Q;oEvN8j}Ygs_$i8#@pD@ zqXcXTG2$&NEQ{EuUu{3O2^ohOVlaerh+Lm3ZMjYLV%FzVdi<(Hk)I|5vQ3gSekHNm zb|1Tc4z&K%v9u*~3*^KK%@DX33%mjG6&h~;0CiT3HPIWV$nr{5#9Rg?2>^RR(^{Xj z(PcjdNb_XH$i`;?0T4~0*-oXw2ZgxPozZzY&?Fd;x=Xe^eXNpeZB6Zd{{VeY7Vq?Z zNSIuwSumauGEz1PGB=CDHCHF*UzNWLR+jiiI_@||f;S1Xa)YE=-%JO_dLIiEyf4y1t z{h6%hnPLTjTk})8-%k-y6%o%Xxh6&`tI0*1+1*uvm|{>vviL#V9FT3;JIxk@pZOBV z0w`8s0w0);XK2&->Su^)d`t#5^Fa=vd2xkPe`p>Rr3I~etH!wKv9;uFHx{+P7Pz+r z9i!r=NSA`e8Ycdbu-jml?Z0KuI(w#4NOG2Na(0;5knB#k=WA=-S2OYQky|EAUZE6` z?N!svLb?lEjr+gL^3$GTZ;F0*mE&2}N^T@=OEBGSbvN*zinFuJO)S$Fjhgo*NNt31 z9lCR(LbP~gi9)i4-~oGILB_)W0P3QHaXd_XE3m>mjwDk}iEk}f0mL5L8dx5i1|S2W4#bOEo&r+z{I7~MFMc*0X#ZXpSWsKypwhCQQ?c&e0)!352Q`Ef~jB0Hv8IIk}aO!95A z@!`rw81fgwVxdnT=Gx=~*tJ?%XQRi3g@PoWa=v1;Y+s0R@Ysr(PYo>CDy=3wm&bjg zWdiI)u1fcEq6Q_;P|-J<0G2x;;Oa>`KqJL{qL@ubg%E-hk>X~&?)JKs8f+thE&iHv z(oOVCVU7IK#WAP;oFw=`Cq)hq*r8dQ7MP04^SWf*7>2W z;XE#E#GBY(vbp1~yrN53i#^D{vWte4z}!`VyI$Ab;{9Jxth)J8`IQh_M;q;+UkZ!% zsJ^u|Q6DOj#!HZZXaE2ZIqO8AXtAYu!&_i&Y@^MgqRj zq>u-O=iy(&M5-a3fM$X~ArY)A;cM?;M`^GXeB7zNNn;#|FcoBja;Q6<&ZUyzp59`# z>&Hmv;KxbndHHxtjT}zoJ(TJ+;;qWtXzKYB;bO(~FH*>Xzcj)ug*OCtj(oMO_9z4B zKD!s2BC|mfF!IZ@|w zLc&sPqWlS?L5`7;14v$N7-U&G zSS~W@;TU9P-wtT{6&bn!O7gCFFnG+)Y>t4h4(%295%nYf|l8L?byD&E$ z^`BoViMI6bP$5W&vnaOi3Qz8eil|CoPxW8S10)-n&G%R}zQv~?)T;V#F*XgmgKBpY zN&P#i9H%7o#G)WfDo-WkKCMkG8kFgsA0ilR`ZH;#hfe+^(WOs6NmCVq`0Zk87%R}R zP%oY?oYa6p_fj!G;<{9?<&1DtSlsLO(e^AAx<^Hfl3_p^+xTm0KGb2eJ{B};abZwC z@Dy4wtcF~PEgRj~-GxcLkJOhNC8EgFX8}*hKMz%e!6d}o{Gd^Qt4TIgci+c{XrhAg zV+EUJ(w8HE<7$x1GWABx3lUPQ+s3FG%9}Dd0g@ok_)P)fHV0FL!~r&4rkHwH6} z>28?CnOn?=8-~BtMvOe&KMxK)`#_KG+fz&Az!`FwUN0%0CN>3mQ-x6Q^og^&v#8wO zSp>%10jFriucu<+ra)}hQ!FY#w*hWKmYA{(7s_eoJ04;dC|ik3akVlZ{xcF@my%sC z6U4z#ec`AgoS_5Jv7Ri1k(kAiDDul&!h6jio55+40UXYfq)9?8Vd;b9Gui0Ik{5bE)ie&Wx>I47T8ucyd literal 0 HcmV?d00001 From d68c5ac8eb5ed5aaf21dca245ca77548c29d71cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Mar 2025 22:52:59 +0000 Subject: [PATCH 476/939] Bump joda-time:joda-time from 2.13.1 to 2.14.0 Bumps [joda-time:joda-time](https://github.com/JodaOrg/joda-time) from 2.13.1 to 2.14.0. - [Release notes](https://github.com/JodaOrg/joda-time/releases) - [Changelog](https://github.com/JodaOrg/joda-time/blob/main/RELEASE-NOTES.txt) - [Commits](https://github.com/JodaOrg/joda-time/compare/v2.13.1...v2.14.0) --- updated-dependencies: - dependency-name: joda-time:joda-time dependency-version: 2.14.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 886aa3fb36c..6fdf0815736 100644 --- a/pom.xml +++ b/pom.xml @@ -1542,7 +1542,7 @@ joda-time joda-time - 2.13.1 + 2.14.0 com.sun.mail From 3fa57d7c0e4da46e745afd995578c5ae9922565b Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Mon, 7 Oct 2024 11:42:35 +0200 Subject: [PATCH 477/939] More robust ORCID accessToken init, REST con. usage * Ensure that http client / IO exceptions don't cause a total DSpace startup failure because of unhandled exceptions in Spring service init methods. * Centralise access token retrieval method in factory utils. * Check for NULL rest connector since that can now happen and handle gracefully, with error logging (cherry picked from commit b72344ecfbdd85ce1cb98bfe0ee0d76ebd9439e6) --- .../orcid/Orcidv3SolrAuthorityImpl.java | 93 +++++++++---------- .../impl/OrcidV3AuthorDataProvider.java | 83 +++++++++-------- .../model/factory/OrcidFactoryUtils.java | 55 +++++++++++ .../org/dspace/authority/orcid/MockOrcid.java | 17 +++- 4 files changed, 157 insertions(+), 91 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/authority/orcid/Orcidv3SolrAuthorityImpl.java b/dspace-api/src/main/java/org/dspace/authority/orcid/Orcidv3SolrAuthorityImpl.java index 6753a5d113b..494daa97734 100644 --- a/dspace-api/src/main/java/org/dspace/authority/orcid/Orcidv3SolrAuthorityImpl.java +++ b/dspace-api/src/main/java/org/dspace/authority/orcid/Orcidv3SolrAuthorityImpl.java @@ -7,27 +7,22 @@ */ package org.dspace.authority.orcid; -import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.net.URLEncoder; import java.util.ArrayList; +import java.util.Collections; import java.util.LinkedList; import java.util.List; import org.apache.commons.lang.StringUtils; -import org.apache.http.HttpResponse; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.impl.client.HttpClientBuilder; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.authority.AuthorityValue; import org.dspace.authority.SolrAuthorityInterface; import org.dspace.external.OrcidRestConnector; import org.dspace.external.provider.orcid.xml.XMLtoBio; -import org.json.JSONObject; +import org.dspace.orcid.model.factory.OrcidFactoryUtils; import org.orcid.jaxb.model.v3.release.common.OrcidIdentifier; import org.orcid.jaxb.model.v3.release.record.Person; import org.orcid.jaxb.model.v3.release.search.Result; @@ -50,6 +45,11 @@ public class Orcidv3SolrAuthorityImpl implements SolrAuthorityInterface { private String accessToken; + /** + * Maximum retries to allow for the access token retrieval + */ + private int maxClientRetries = 3; + public void setOAUTHUrl(String oAUTHUrl) { OAUTHUrl = oAUTHUrl; } @@ -62,46 +62,32 @@ public void setClientSecret(String clientSecret) { this.clientSecret = clientSecret; } + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + /** * Initialize the accessToken that is required for all subsequent calls to ORCID */ public void init() { - if (StringUtils.isBlank(accessToken) - && StringUtils.isNotBlank(clientSecret) - && StringUtils.isNotBlank(clientId) - && StringUtils.isNotBlank(OAUTHUrl)) { - String authenticationParameters = "?client_id=" + clientId + - "&client_secret=" + clientSecret + - "&scope=/read-public&grant_type=client_credentials"; - try { - HttpPost httpPost = new HttpPost(OAUTHUrl + authenticationParameters); - httpPost.addHeader("Accept", "application/json"); - httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded"); - - HttpClient httpClient = HttpClientBuilder.create().build(); - HttpResponse getResponse = httpClient.execute(httpPost); - - JSONObject responseObject = null; - try (InputStream is = getResponse.getEntity().getContent(); - BufferedReader streamReader = new BufferedReader(new InputStreamReader(is, "UTF-8"))) { - String inputStr; - while ((inputStr = streamReader.readLine()) != null && responseObject == null) { - if (inputStr.startsWith("{") && inputStr.endsWith("}") && inputStr.contains("access_token")) { - try { - responseObject = new JSONObject(inputStr); - } catch (Exception e) { - //Not as valid as I'd hoped, move along - responseObject = null; - } - } - } - } - if (responseObject != null && responseObject.has("access_token")) { - accessToken = (String) responseObject.get("access_token"); - } - } catch (Exception e) { - throw new RuntimeException("Error during initialization of the Orcid connector", e); - } + // Initialize access token at spring instantiation. If it fails, the access token will be null rather + // than causing a fatal Spring startup error + initializeAccessToken(); + } + + public void initializeAccessToken() { + // If we have reaches max retries or the access token is already set, return immediately + if (maxClientRetries <= 0 || org.apache.commons.lang3.StringUtils.isNotBlank(accessToken)) { + return; + } + try { + accessToken = OrcidFactoryUtils.retrieveAccessToken(clientId, clientSecret, OAUTHUrl).orElse(null); + } catch (IOException e) { + log.error("Error retrieving ORCID access token, {} retries left", --maxClientRetries); } } @@ -116,7 +102,7 @@ public void setOrcidRestConnector(OrcidRestConnector orcidRestConnector) { */ @Override public List queryAuthorities(String text, int max) { - init(); + initializeAccessToken(); List bios = queryBio(text, max); List result = new ArrayList<>(); for (Person person : bios) { @@ -135,7 +121,7 @@ public List queryAuthorities(String text, int max) { */ @Override public AuthorityValue queryAuthorityID(String id) { - init(); + initializeAccessToken(); Person person = getBio(id); AuthorityValue valueFromPerson = Orcidv3AuthorityValue.create(person); return valueFromPerson; @@ -151,11 +137,14 @@ public Person getBio(String id) { if (!isValid(id)) { return null; } - init(); + if (orcidRestConnector == null) { + log.error("ORCID REST connector is null, returning null Person"); + return null; + } + initializeAccessToken(); InputStream bioDocument = orcidRestConnector.get(id + ((id.endsWith("/person")) ? "" : "/person"), accessToken); XMLtoBio converter = new XMLtoBio(); - Person person = converter.convertSinglePerson(bioDocument); - return person; + return converter.convertSinglePerson(bioDocument); } @@ -167,10 +156,16 @@ public Person getBio(String id) { * @return List */ public List queryBio(String text, int start, int rows) { - init(); if (rows > 100) { throw new IllegalArgumentException("The maximum number of results to retrieve cannot exceed 100."); } + // Check REST connector is initialized + if (orcidRestConnector == null) { + log.error("ORCID REST connector is not initialized, returning empty list"); + return Collections.emptyList(); + } + // Check / init access token + initializeAccessToken(); String searchPath = "search?q=" + URLEncoder.encode(text) + "&start=" + start + "&rows=" + rows; log.debug("queryBio searchPath=" + searchPath + " accessToken=" + accessToken); diff --git a/dspace-api/src/main/java/org/dspace/external/provider/impl/OrcidV3AuthorDataProvider.java b/dspace-api/src/main/java/org/dspace/external/provider/impl/OrcidV3AuthorDataProvider.java index 125da8f7c67..c7e41171a5b 100644 --- a/dspace-api/src/main/java/org/dspace/external/provider/impl/OrcidV3AuthorDataProvider.java +++ b/dspace-api/src/main/java/org/dspace/external/provider/impl/OrcidV3AuthorDataProvider.java @@ -7,24 +7,17 @@ */ package org.dspace.external.provider.impl; -import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.LinkedList; import java.util.List; -import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; -import org.apache.http.HttpResponse; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.impl.client.HttpClientBuilder; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.content.dto.MetadataValueDTO; @@ -32,7 +25,7 @@ import org.dspace.external.model.ExternalDataObject; import org.dspace.external.provider.AbstractExternalDataProvider; import org.dspace.external.provider.orcid.xml.XMLtoBio; -import org.json.JSONObject; +import org.dspace.orcid.model.factory.OrcidFactoryUtils; import org.orcid.jaxb.model.v3.release.common.OrcidIdentifier; import org.orcid.jaxb.model.v3.release.record.Person; import org.orcid.jaxb.model.v3.release.search.Result; @@ -60,6 +53,11 @@ public class OrcidV3AuthorDataProvider extends AbstractExternalDataProvider { private XMLtoBio converter; + /** + * Maximum retries to allow for the access token retrieval + */ + private int maxClientRetries = 3; + public static final String ORCID_ID_SYNTAX = "\\d{4}-\\d{4}-\\d{4}-(\\d{3}X|\\d{4})"; private static final int MAX_INDEX = 10000; @@ -78,47 +76,37 @@ public OrcidV3AuthorDataProvider() { * @throws java.io.IOException passed through from HTTPclient. */ public void init() throws IOException { - if (StringUtils.isNotBlank(clientSecret) && StringUtils.isNotBlank(clientId) - && StringUtils.isNotBlank(OAUTHUrl)) { - String authenticationParameters = "?client_id=" + clientId + - "&client_secret=" + clientSecret + - "&scope=/read-public&grant_type=client_credentials"; - HttpPost httpPost = new HttpPost(OAUTHUrl + authenticationParameters); - httpPost.addHeader("Accept", "application/json"); - httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded"); - - HttpClient httpClient = HttpClientBuilder.create().build(); - HttpResponse getResponse = httpClient.execute(httpPost); - - JSONObject responseObject = null; - try (InputStream is = getResponse.getEntity().getContent(); - BufferedReader streamReader = new BufferedReader(new InputStreamReader(is, "UTF-8"))) { - String inputStr; - while ((inputStr = streamReader.readLine()) != null && responseObject == null) { - if (inputStr.startsWith("{") && inputStr.endsWith("}") && inputStr.contains("access_token")) { - try { - responseObject = new JSONObject(inputStr); - } catch (Exception e) { - //Not as valid as I'd hoped, move along - responseObject = null; - } - } - } - } - if (responseObject != null && responseObject.has("access_token")) { - accessToken = (String) responseObject.get("access_token"); - } + // Initialize access token at spring instantiation. If it fails, the access token will be null rather + // than causing a fatal Spring startup error + initializeAccessToken(); + } + + /** + * Initialize access token, logging an error and decrementing remaining retries if an IOException is thrown. + * If the optional access token result is empty, set to null instead. + */ + public void initializeAccessToken() { + // If we have reaches max retries or the access token is already set, return immediately + if (maxClientRetries <= 0 || StringUtils.isNotBlank(accessToken)) { + return; + } + try { + accessToken = OrcidFactoryUtils.retrieveAccessToken(clientId, clientSecret, OAUTHUrl).orElse(null); + } catch (IOException e) { + log.error("Error retrieving ORCID access token, {} retries left", --maxClientRetries); } } @Override public Optional getExternalDataObject(String id) { + initializeAccessToken(); Person person = getBio(id); ExternalDataObject externalDataObject = convertToExternalDataObject(person); return Optional.of(externalDataObject); } protected ExternalDataObject convertToExternalDataObject(Person person) { + initializeAccessToken(); ExternalDataObject externalDataObject = new ExternalDataObject(sourceIdentifier); if (person.getName() != null) { String lastName = ""; @@ -167,6 +155,11 @@ public Person getBio(String id) { if (!isValid(id)) { return null; } + if (orcidRestConnector == null) { + log.error("ORCID REST connector is null, returning null ORCID Person Bio"); + return null; + } + initializeAccessToken(); InputStream bioDocument = orcidRestConnector.get(id + ((id.endsWith("/person")) ? "" : "/person"), accessToken); Person person = converter.convertSinglePerson(bioDocument); try { @@ -188,12 +181,18 @@ private boolean isValid(String text) { @Override public List searchExternalDataObjects(String query, int start, int limit) { + initializeAccessToken(); if (limit > 100) { throw new IllegalArgumentException("The maximum number of results to retrieve cannot exceed 100."); } if (start > MAX_INDEX) { throw new IllegalArgumentException("The starting number of results to retrieve cannot exceed 10000."); } + // Check REST connector is initialized + if (orcidRestConnector == null) { + log.error("ORCID REST connector is not initialized, returning empty list"); + return Collections.emptyList(); + } String searchPath = "search?q=" + URLEncoder.encode(query, StandardCharsets.UTF_8) + "&start=" + start @@ -218,9 +217,6 @@ public List searchExternalDataObjects(String query, int star } catch (IOException e) { log.error(e.getMessage(), e); } - if (Objects.isNull(bios)) { - return Collections.emptyList(); - } return bios.stream().map(bio -> convertToExternalDataObject(bio)).collect(Collectors.toList()); } @@ -231,6 +227,11 @@ public boolean supports(String source) { @Override public int getNumberOfResults(String query) { + if (orcidRestConnector == null) { + log.error("ORCID REST connector is null, returning 0"); + return 0; + } + initializeAccessToken(); String searchPath = "search?q=" + URLEncoder.encode(query, StandardCharsets.UTF_8) + "&start=" + 0 + "&rows=" + 0; diff --git a/dspace-api/src/main/java/org/dspace/orcid/model/factory/OrcidFactoryUtils.java b/dspace-api/src/main/java/org/dspace/orcid/model/factory/OrcidFactoryUtils.java index 4b8c1178efe..38aa611ff3a 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/model/factory/OrcidFactoryUtils.java +++ b/dspace-api/src/main/java/org/dspace/orcid/model/factory/OrcidFactoryUtils.java @@ -7,10 +7,21 @@ */ package org.dspace.orcid.model.factory; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.json.JSONObject; /** * Utility class for Orcid factory classes. This is used to parse the @@ -65,4 +76,48 @@ private static String[] parseConfiguration(String configuration) { return configurations; } + /** + * Retrieve access token from ORCID, given a client ID, client secret and OAuth URL + * + * @param clientId ORCID client ID + * @param clientSecret ORCID client secret + * @param oauthUrl ORCID oauth redirect URL + * @return response object as Optional string + * @throws IOException if any errors are encountered making the connection or reading a response + */ + public static Optional retrieveAccessToken(String clientId, String clientSecret, String oauthUrl) + throws IOException { + if (StringUtils.isNotBlank(clientSecret) && StringUtils.isNotBlank(clientId) + && StringUtils.isNotBlank(oauthUrl)) { + String authenticationParameters = "?client_id=" + clientId + + "&client_secret=" + clientSecret + + "&scope=/read-public&grant_type=client_credentials"; + HttpPost httpPost = new HttpPost(oauthUrl + authenticationParameters); + httpPost.addHeader("Accept", "application/json"); + httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded"); + + HttpResponse response; + try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { + response = httpClient.execute(httpPost); + } + JSONObject responseObject = null; + if (response != null && response.getStatusLine().getStatusCode() == 200) { + try (InputStream is = response.getEntity().getContent(); + BufferedReader streamReader = new BufferedReader(new InputStreamReader(is, + StandardCharsets.UTF_8))) { + String inputStr; + while ((inputStr = streamReader.readLine()) != null && responseObject == null) { + if (inputStr.startsWith("{") && inputStr.endsWith("}") && inputStr.contains("access_token")) { + responseObject = new JSONObject(inputStr); + } + } + } + } + if (responseObject != null && responseObject.has("access_token")) { + return Optional.of((String) responseObject.get("access_token")); + } + } + // Return empty by default + return Optional.empty(); + } } diff --git a/dspace-api/src/test/java/org/dspace/authority/orcid/MockOrcid.java b/dspace-api/src/test/java/org/dspace/authority/orcid/MockOrcid.java index 562aa86a585..82dc3fa5cc1 100644 --- a/dspace-api/src/test/java/org/dspace/authority/orcid/MockOrcid.java +++ b/dspace-api/src/test/java/org/dspace/authority/orcid/MockOrcid.java @@ -26,8 +26,24 @@ */ public class MockOrcid extends Orcidv3SolrAuthorityImpl { + public MockOrcid() { + setupMockConnector(); + setAccessToken("mock-access-token"); + } + @Override public void init() { + // Empty implementation as setup is now done in constructor + } + + @Override + public void initializeAccessToken() { + if (getAccessToken() == null) { + setAccessToken("mock-access-token"); + } + } + + private void setupMockConnector() { OrcidRestConnector orcidRestConnector = Mockito.mock(OrcidRestConnector.class); when(orcidRestConnector.get(ArgumentMatchers.startsWith("search?"), ArgumentMatchers.any())) .thenAnswer(new Answer() { @@ -53,5 +69,4 @@ public InputStream answer(InvocationOnMock invocation) { setOrcidRestConnector(orcidRestConnector); } - } From 1fe697a19ec24b7223346e00c658df1cfec0788b Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Sun, 30 Mar 2025 22:30:25 +0200 Subject: [PATCH 478/939] Fix some ORCID mock / test usage (cherry picked from commit 038ddeee9795c75b876ce1ea73db2b908a8c939c) --- .../spring/api/orcid-authority-services.xml | 2 +- .../org/dspace/authority/orcid/MockOrcid.java | 57 ++++++++++++------- .../app/rest/VocabularyRestRepositoryIT.java | 14 +++++ 3 files changed, 50 insertions(+), 23 deletions(-) diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/orcid-authority-services.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/orcid-authority-services.xml index 4a73b215cd4..3e38055b678 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/orcid-authority-services.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/orcid-authority-services.xml @@ -16,7 +16,7 @@ - + diff --git a/dspace-api/src/test/java/org/dspace/authority/orcid/MockOrcid.java b/dspace-api/src/test/java/org/dspace/authority/orcid/MockOrcid.java index 82dc3fa5cc1..511df79f1e5 100644 --- a/dspace-api/src/test/java/org/dspace/authority/orcid/MockOrcid.java +++ b/dspace-api/src/test/java/org/dspace/authority/orcid/MockOrcid.java @@ -26,41 +26,47 @@ */ public class MockOrcid extends Orcidv3SolrAuthorityImpl { - public MockOrcid() { - setupMockConnector(); - setAccessToken("mock-access-token"); - } + OrcidRestConnector orcidRestConnector; @Override public void init() { - // Empty implementation as setup is now done in constructor + initializeAccessToken(); + orcidRestConnector = Mockito.mock(OrcidRestConnector.class); } - @Override - public void initializeAccessToken() { - if (getAccessToken() == null) { - setAccessToken("mock-access-token"); - } - } - - private void setupMockConnector() { - OrcidRestConnector orcidRestConnector = Mockito.mock(OrcidRestConnector.class); + /** + * Call this to set up mocking for any test classes that need it. We don't set it in init() + * or other AbstractIntegrationTest implementations will complain of unnecessary Mockito stubbing + */ + public void setupNoResultsSearch() { when(orcidRestConnector.get(ArgumentMatchers.startsWith("search?"), ArgumentMatchers.any())) - .thenAnswer(new Answer() { - @Override - public InputStream answer(InvocationOnMock invocation) { - return this.getClass().getResourceAsStream("orcid-search-noresults.xml"); - } - }); + .thenAnswer(new Answer() { + @Override + public InputStream answer(InvocationOnMock invocation) { + return this.getClass().getResourceAsStream("orcid-search-noresults.xml"); + } + }); + } + /** + * Call this to set up mocking for any test classes that need it. We don't set it in init() + * or other AbstractIntegrationTest implementations will complain of unnecessary Mockito stubbing + */ + public void setupSingleSearch() { when(orcidRestConnector.get(ArgumentMatchers.startsWith("search?q=Bollini"), ArgumentMatchers.any())) - .thenAnswer(new Answer() { + .thenAnswer(new Answer() { @Override public InputStream answer(InvocationOnMock invocation) { return this.getClass().getResourceAsStream("orcid-search.xml"); } }); + } + /** + * Call this to set up mocking for any test classes that need it. We don't set it in init() + * or other AbstractIntegrationTest implementations will complain of unnecessary Mockito stubbing + */ + public void setupSearchWithResults() { when(orcidRestConnector.get(ArgumentMatchers.endsWith("/person"), ArgumentMatchers.any())) - .thenAnswer(new Answer() { + .thenAnswer(new Answer() { @Override public InputStream answer(InvocationOnMock invocation) { return this.getClass().getResourceAsStream("orcid-person-record.xml"); @@ -69,4 +75,11 @@ public InputStream answer(InvocationOnMock invocation) { setOrcidRestConnector(orcidRestConnector); } + + @Override + public void initializeAccessToken() { + if (getAccessToken() == null) { + setAccessToken("mock-access-token"); + } + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java index 30890d7ef83..7a3bc738eb6 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java @@ -22,6 +22,7 @@ import org.dspace.authority.AuthorityValueServiceImpl; import org.dspace.authority.PersonAuthorityValue; import org.dspace.authority.factory.AuthorityServiceFactory; +import org.dspace.authority.orcid.MockOrcid; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.content.Collection; @@ -29,11 +30,13 @@ import org.dspace.content.authority.service.ChoiceAuthorityService; import org.dspace.core.service.PluginService; import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; import org.hamcrest.Matchers; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; /** * This class handles all Authority related IT. It alters some config to run the tests, but it gets cleared again @@ -56,6 +59,17 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes @Before public void setup() throws Exception { super.setUp(); + + // Explicitly set stubbing for the MockOrcid class. We don't do it in the init() or constructor + // of the MockOrcid class itself or Mockito will complain of unnecessary stubbing in certain other + // AbstractIntegrationTest implementations (depending on how config is (re)loaded) + ApplicationContext applicationContext = DSpaceServicesFactory.getInstance() + .getServiceManager().getApplicationContext(); + MockOrcid mockOrcid = applicationContext.getBean(MockOrcid.class); + mockOrcid.setupNoResultsSearch(); + mockOrcid.setupSingleSearch(); + mockOrcid.setupSearchWithResults(); + configurationService.setProperty("plugin.named.org.dspace.content.authority.ChoiceAuthority", new String[] { "org.dspace.content.authority.SolrAuthority = SolrAuthorAuthority", From 4c73e4b01f5a11b48e6e9e4f2e07bb6b1ef0670e Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 7 Apr 2025 10:54:33 -0500 Subject: [PATCH 479/939] Update reusable-docker-build to use Ubuntu ARM64 runner for those images --- .github/workflows/reusable-docker-build.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml index 3b74f250b53..aec03603cef 100644 --- a/.github/workflows/reusable-docker-build.yml +++ b/.github/workflows/reusable-docker-build.yml @@ -87,17 +87,16 @@ jobs: matrix: # Architectures / Platforms for which we will build Docker images arch: [ 'linux/amd64', 'linux/arm64' ] - os: [ ubuntu-latest ] isPr: - ${{ github.event_name == 'pull_request' }} # If this is a PR, we ONLY build for AMD64. For PRs we only do a sanity check test to ensure Docker builds work. # The below exclude therefore ensures we do NOT build ARM64 for PRs. exclude: - isPr: true - os: ubuntu-latest arch: linux/arm64 - runs-on: ${{ matrix.os }} + # If ARM64, then use the Ubuntu ARM64 runner. Otherwise, use the Ubuntu AMD64 runner + runs-on: ${{ matrix.arch == 'linux/arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }} steps: # This step converts the slashes in the "arch" matrix values above into dashes & saves to env.ARCH_NAME @@ -123,10 +122,6 @@ jobs: username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - # https://github.com/docker/setup-qemu-action - - name: Set up QEMU emulation to build for multiple architectures - uses: docker/setup-qemu-action@v3 - # https://github.com/docker/setup-buildx-action - name: Setup Docker Buildx uses: docker/setup-buildx-action@v3 From bfe7e7871e443aefe783976672c5e36ef349dd9b Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 7 Apr 2025 14:57:31 -0500 Subject: [PATCH 480/939] Remove unused SWORD v1 client code. This is "dead code" which is unmaintained and obsolete --- .../java/org/purl/sword/client/Client.java | 463 ---------- .../purl/sword/client/ClientConstants.java | 40 - .../org/purl/sword/client/ClientFactory.java | 116 --- .../org/purl/sword/client/ClientOptions.java | 605 ------------- .../org/purl/sword/client/ClientType.java | 23 - .../java/org/purl/sword/client/CmdClient.java | 445 --------- .../purl/sword/client/DebugOutputStream.java | 44 - .../purl/sword/client/MessageOutputPanel.java | 138 --- .../purl/sword/client/PostDestination.java | 140 --- .../org/purl/sword/client/PostDialog.java | 599 ------------- .../org/purl/sword/client/PostMessage.java | 344 ------- .../purl/sword/client/PropertiesDialog.java | 240 ----- .../org/purl/sword/client/SWORDClient.java | 85 -- .../sword/client/SWORDClientException.java | 42 - .../org/purl/sword/client/SWORDComboBox.java | 129 --- .../org/purl/sword/client/SWORDFormPanel.java | 144 --- .../org/purl/sword/client/ServiceDialog.java | 227 ----- .../org/purl/sword/client/ServicePanel.java | 843 ------------------ .../sword/client/ServiceSelectedListener.java | 23 - .../org/purl/sword/client/ServletClient.java | 455 ---------- .../java/org/purl/sword/client/Status.java | 61 -- 21 files changed, 5206 deletions(-) delete mode 100644 dspace-sword/src/main/java/org/purl/sword/client/Client.java delete mode 100644 dspace-sword/src/main/java/org/purl/sword/client/ClientConstants.java delete mode 100644 dspace-sword/src/main/java/org/purl/sword/client/ClientFactory.java delete mode 100644 dspace-sword/src/main/java/org/purl/sword/client/ClientOptions.java delete mode 100644 dspace-sword/src/main/java/org/purl/sword/client/ClientType.java delete mode 100644 dspace-sword/src/main/java/org/purl/sword/client/CmdClient.java delete mode 100644 dspace-sword/src/main/java/org/purl/sword/client/DebugOutputStream.java delete mode 100644 dspace-sword/src/main/java/org/purl/sword/client/MessageOutputPanel.java delete mode 100644 dspace-sword/src/main/java/org/purl/sword/client/PostDestination.java delete mode 100644 dspace-sword/src/main/java/org/purl/sword/client/PostDialog.java delete mode 100644 dspace-sword/src/main/java/org/purl/sword/client/PostMessage.java delete mode 100644 dspace-sword/src/main/java/org/purl/sword/client/PropertiesDialog.java delete mode 100644 dspace-sword/src/main/java/org/purl/sword/client/SWORDClient.java delete mode 100644 dspace-sword/src/main/java/org/purl/sword/client/SWORDClientException.java delete mode 100644 dspace-sword/src/main/java/org/purl/sword/client/SWORDComboBox.java delete mode 100644 dspace-sword/src/main/java/org/purl/sword/client/SWORDFormPanel.java delete mode 100644 dspace-sword/src/main/java/org/purl/sword/client/ServiceDialog.java delete mode 100644 dspace-sword/src/main/java/org/purl/sword/client/ServicePanel.java delete mode 100644 dspace-sword/src/main/java/org/purl/sword/client/ServiceSelectedListener.java delete mode 100644 dspace-sword/src/main/java/org/purl/sword/client/ServletClient.java delete mode 100644 dspace-sword/src/main/java/org/purl/sword/client/Status.java diff --git a/dspace-sword/src/main/java/org/purl/sword/client/Client.java b/dspace-sword/src/main/java/org/purl/sword/client/Client.java deleted file mode 100644 index c6d335d8dd1..00000000000 --- a/dspace-sword/src/main/java/org/purl/sword/client/Client.java +++ /dev/null @@ -1,463 +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.purl.sword.client; - -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.UnsupportedEncodingException; -import java.net.MalformedURLException; -import java.net.URL; -import java.security.NoSuchAlgorithmException; -import java.util.Properties; - -import org.apache.http.HttpHost; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; -import org.apache.http.StatusLine; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.conn.params.ConnRoutePNames; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.FileEntity; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.params.HttpParams; -import org.apache.logging.log4j.Logger; -import org.purl.sword.base.ChecksumUtils; -import org.purl.sword.base.DepositResponse; -import org.purl.sword.base.HttpHeaders; -import org.purl.sword.base.ServiceDocument; -import org.purl.sword.base.SwordValidationInfo; -import org.purl.sword.base.UnmarshallException; - -/** - * This is an example Client implementation to demonstrate how to connect to a - * SWORD server. The client supports BASIC HTTP Authentication. This can be - * initialised by setting a username and password. - * - * @author Neil Taylor - */ -public class Client implements SWORDClient { - /** - * The status field for the response code from the recent network access. - */ - private Status status; - - /** - * The name of the server to contact. - */ - private String server; - - /** - * The port number for the server. - */ - private int port; - - /** - * Specifies if the network access should use HTTP authentication. - */ - private boolean doAuthentication; - - /** - * The username to use for Basic Authentication. - */ - private String username; - - /** - * User password that is to be used. - */ - private String password; - - /** - * The userAgent to identify this application. - */ - private String userAgent; - - /** - * The client that is used to send data to the specified server. - */ - private final DefaultHttpClient client; - - /** - * The default connection timeout. This can be modified by using the - * setSocketTimeout method. - */ - public static final int DEFAULT_TIMEOUT = 20000; - - /** - * Logger. - */ - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(Client.class); - - /** - * Create a new Client. The client will not use authentication by default. - */ - public Client() { - client = new DefaultHttpClient(); - HttpParams params = client.getParams(); - params.setParameter("http.socket.timeout", - Integer.valueOf(DEFAULT_TIMEOUT)); - HttpHost proxyHost = (HttpHost) params - .getParameter(ConnRoutePNames.DEFAULT_PROXY); // XXX does this really work? - log.debug("proxy host: " + proxyHost.getHostName()); - log.debug("proxy port: " + proxyHost.getPort()); - doAuthentication = false; - } - - /** - * Initialise the server that will be used to send the network access. - * - * @param server server address/hostname - * @param port server port - */ - public void setServer(String server, int port) { - this.server = server; - this.port = port; - } - - /** - * Set the user credentials that will be used when making the access to the - * server. - * - * @param username The username. - * @param password The password. - */ - public void setCredentials(String username, String password) { - this.username = username; - this.password = password; - doAuthentication = true; - } - - /** - * Set the basic credentials. You must have previously set the server and - * port using setServer. - * - * @param username The username. - * @param password The password. - */ - private void setBasicCredentials(String username, String password) { - log.debug("server: " + server + " port: " + port + " u: '" + username - + "' p '" + password + "'"); - client.getCredentialsProvider().setCredentials(new AuthScope(server, port), - new UsernamePasswordCredentials(username, password)); - } - - /** - * Set a proxy that should be used by the client when trying to access the - * server. If this is not set, the client will attempt to make a direct - * direct connection to the server. The port is set to 80. - * - * @param host The hostname. - */ - public void setProxy(String host) { - setProxy(host, 80); - } - - /** - * Set a proxy that should be used by the client when trying to access the - * server. If this is not set, the client will attempt to make a direct - * direct connection to the server. - * - * @param host The name of the host. - * @param port The port. - */ - public void setProxy(String host, int port) { - client.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, - new HttpHost(host, port)); // XXX does this really work? - } - - /** - * Clear the proxy setting. - */ - public void clearProxy() { - client.getParams().removeParameter(ConnRoutePNames.DEFAULT_PROXY); // XXX does this really work? - } - - /** - * Clear any user credentials that have been set for this client. - */ - public void clearCredentials() { - client.getCredentialsProvider().clear(); - doAuthentication = false; - } - - public void setUserAgent(String userAgent) { - this.userAgent = userAgent; - } - - /** - * Set the connection timeout for the socket. - * - * @param milliseconds The time, expressed as a number of milliseconds. - */ - public void setSocketTimeout(int milliseconds) { - client.getParams().setParameter("http.socket.timeout", - Integer.valueOf(milliseconds)); - } - - /** - * Retrieve the service document. The service document is located at the - * specified URL. This calls getServiceDocument(url,onBehalfOf). - * - * @param url The location of the service document. - * @return The ServiceDocument, or null if there was a - * problem accessing the document. e.g. invalid access. - * @throws SWORDClientException If there is an error accessing the resource. - */ - public ServiceDocument getServiceDocument(String url) - throws SWORDClientException { - return getServiceDocument(url, null); - } - - /** - * Retrieve the service document. The service document is located at the - * specified URL. This calls getServiceDocument(url,onBehalfOf). - * - * @param url The location of the service document. - * @return The ServiceDocument, or null if there was a - * problem accessing the document. e.g. invalid access. - * @throws SWORDClientException If there is an error accessing the resource. - */ - public ServiceDocument getServiceDocument(String url, String onBehalfOf) - throws SWORDClientException { - URL serviceDocURL = null; - try { - serviceDocURL = new URL(url); - } catch (MalformedURLException e) { - // Try relative URL - URL baseURL = null; - try { - baseURL = new URL("http", server, Integer.valueOf(port), "/"); - serviceDocURL = new URL(baseURL, (url == null) ? "" : url); - } catch (MalformedURLException e1) { - // No dice, can't even form base URL... - throw new SWORDClientException(url + " is not a valid URL (" - + e1.getMessage() - + "), and could not form a relative one from: " - + baseURL + " / " + url, e1); - } - } - - HttpGet httpget = new HttpGet(serviceDocURL.toExternalForm()); - if (doAuthentication) { - // this does not perform any check on the username password. It - // relies on the server to determine if the values are correct. - setBasicCredentials(username, password); - } - - Properties properties = new Properties(); - - if (containsValue(onBehalfOf)) { - log.debug("Setting on-behalf-of: " + onBehalfOf); - httpget.addHeader(url, url); - httpget.addHeader(HttpHeaders.X_ON_BEHALF_OF, onBehalfOf); - properties.put(HttpHeaders.X_ON_BEHALF_OF, onBehalfOf); - } - - if (containsValue(userAgent)) { - log.debug("Setting userAgent: " + userAgent); - httpget.addHeader(HttpHeaders.USER_AGENT, userAgent); - properties.put(HttpHeaders.USER_AGENT, userAgent); - } - - ServiceDocument doc = null; - - try { - HttpResponse response = client.execute(httpget); - StatusLine statusLine = response.getStatusLine(); - int statusCode = statusLine.getStatusCode(); - // store the status code - status = new Status(statusCode, statusLine.getReasonPhrase()); - - if (status.getCode() == HttpStatus.SC_OK) { - String message = readResponse(response.getEntity().getContent()); - log.debug("returned message is: " + message); - doc = new ServiceDocument(); - lastUnmarshallInfo = doc.unmarshall(message, properties); - } else { - throw new SWORDClientException( - "Received error from service document request: " - + status); - } - } catch (IOException ioex) { - throw new SWORDClientException(ioex.getMessage(), ioex); - } catch (UnmarshallException uex) { - throw new SWORDClientException(uex.getMessage(), uex); - } finally { - httpget.releaseConnection(); - } - - return doc; - } - - private SwordValidationInfo lastUnmarshallInfo; - - /** - * @return SWORD validation info - */ - public SwordValidationInfo getLastUnmarshallInfo() { - return lastUnmarshallInfo; - } - - /** - * Post a file to the server. The different elements of the post are encoded - * in the specified message. - * - * @param message The message that contains the post information. - * @throws SWORDClientException if there is an error during the post operation. - */ - public DepositResponse postFile(PostMessage message) - throws SWORDClientException { - if (message == null) { - throw new SWORDClientException("Message cannot be null."); - } - - HttpPost httppost = new HttpPost(message.getDestination()); - - if (doAuthentication) { - setBasicCredentials(username, password); - } - - DepositResponse response = null; - - String messageBody = ""; - - try { - if (message.isUseMD5()) { - String md5 = ChecksumUtils.generateMD5(message.getFilepath()); - if (message.getChecksumError()) { - md5 = "1234567890"; - } - log.debug("checksum error is: " + md5); - if (md5 != null) { - httppost.addHeader(HttpHeaders.CONTENT_MD5, md5); - } - } - - String filename = message.getFilename(); - if (!"".equals(filename)) { - httppost.addHeader(HttpHeaders.CONTENT_DISPOSITION, - " filename=" + filename); - } - - if (containsValue(message.getSlug())) { - httppost.addHeader(HttpHeaders.SLUG, message.getSlug()); - } - - if (message.getCorruptRequest()) { - // insert a header with an invalid boolean value - httppost.addHeader(HttpHeaders.X_NO_OP, "Wibble"); - } else { - httppost.addHeader(HttpHeaders.X_NO_OP, Boolean - .toString(message.isNoOp())); - } - httppost.addHeader(HttpHeaders.X_VERBOSE, Boolean - .toString(message.isVerbose())); - - String packaging = message.getPackaging(); - if (packaging != null && packaging.length() > 0) { - httppost.addHeader(HttpHeaders.X_PACKAGING, packaging); - } - - String onBehalfOf = message.getOnBehalfOf(); - if (containsValue(onBehalfOf)) { - httppost.addHeader(HttpHeaders.X_ON_BEHALF_OF, onBehalfOf); - } - - String userAgent = message.getUserAgent(); - if (containsValue(userAgent)) { - httppost.addHeader(HttpHeaders.USER_AGENT, userAgent); - } - - - FileEntity requestEntity = new FileEntity( - new File(message.getFilepath()), - ContentType.create(message.getFiletype())); - httppost.setEntity(requestEntity); - - HttpResponse httpResponse = client.execute(httppost); - StatusLine statusLine = httpResponse.getStatusLine(); - int statusCode = statusLine.getStatusCode(); - status = new Status(statusCode, statusLine.getReasonPhrase()); - - log.info("Checking the status code: " + status.getCode()); - - if (status.getCode() == HttpStatus.SC_ACCEPTED - || status.getCode() == HttpStatus.SC_CREATED) { - messageBody = readResponse(httpResponse.getEntity().getContent()); - response = new DepositResponse(status.getCode()); - response.setLocation(httpResponse.getFirstHeader("Location").getValue()); - // added call for the status code. - lastUnmarshallInfo = response.unmarshall(messageBody, new Properties()); - } else { - messageBody = readResponse(httpResponse.getEntity().getContent()); - response = new DepositResponse(status.getCode()); - response.unmarshallErrorDocument(messageBody); - } - return response; - - } catch (NoSuchAlgorithmException nex) { - throw new SWORDClientException("Unable to use MD5. " - + nex.getMessage(), nex); - } catch (IOException ioex) { - throw new SWORDClientException(ioex.getMessage(), ioex); - } catch (UnmarshallException uex) { - throw new SWORDClientException(uex.getMessage() + "(
    " + messageBody + "
    )", uex); - } finally { - httppost.releaseConnection(); - } - } - - /** - * Read a response from the stream and return it as a string. - * - * @param stream The stream that contains the response. - * @return The string extracted from the screen. - * @throws UnsupportedEncodingException - * @throws IOException A general class of exceptions produced by failed or interrupted I/O - * operations. - */ - private String readResponse(InputStream stream) - throws UnsupportedEncodingException, IOException { - BufferedReader reader = new BufferedReader(new InputStreamReader( - stream, "UTF-8")); - String line = null; - StringBuffer buffer = new StringBuffer(); - while ((line = reader.readLine()) != null) { - buffer.append(line); - buffer.append("\n"); - } - return buffer.toString(); - } - - /** - * Return the status information that was returned from the most recent - * request sent to the server. - * - * @return The status code returned from the most recent access. - */ - public Status getStatus() { - return status; - } - - /** - * Check to see if the specified item contains a non-empty string. - * - * @param item The string to check. - * @return True if the string is not null and has a length greater than 0 - * after any whitespace is trimmed from the start and end. - * Otherwise, false. - */ - private boolean containsValue(String item) { - return ((item != null) && (item.trim().length() > 0)); - } -} diff --git a/dspace-sword/src/main/java/org/purl/sword/client/ClientConstants.java b/dspace-sword/src/main/java/org/purl/sword/client/ClientConstants.java deleted file mode 100644 index 124ed38d33d..00000000000 --- a/dspace-sword/src/main/java/org/purl/sword/client/ClientConstants.java +++ /dev/null @@ -1,40 +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.purl.sword.client; - -/** - * Hold general constants for the client. - * - * @author Neil Taylor - */ -public class ClientConstants { - /** - * Current software version. - */ - public static final String CLIENT_VERSION = "1.1"; - - /** - * the name of this application - */ - public static final String SERVICE_NAME = "CASIS Test Client"; - - /** - * the name of this application - */ - public static final String NOT_DEFINED_TEXT = "Not defined"; - - /** - * The logging property file. - */ - public static final String LOGGING_PROPERTY_FILE = "log4j.properties"; - - /** - * Default constructor - */ - private ClientConstants() { } -} diff --git a/dspace-sword/src/main/java/org/purl/sword/client/ClientFactory.java b/dspace-sword/src/main/java/org/purl/sword/client/ClientFactory.java deleted file mode 100644 index 00bd9c17a54..00000000000 --- a/dspace-sword/src/main/java/org/purl/sword/client/ClientFactory.java +++ /dev/null @@ -1,116 +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.purl.sword.client; - -/** - * Entry point for the SWORD Demonstration Client. This will parse the list of - * command line options and load either a Command Line client or a GUI client. - * - * @author Neil Taylor - */ -public class ClientFactory { - /** - * Generate a string that specifies the command line options for this - * program. - * - * @return A list of the options for this program. - */ - public static String usage() { - StringBuilder buffer = new StringBuilder(); - buffer.append("swordclient: version "); - buffer.append(ClientConstants.CLIENT_VERSION); - buffer.append("\n"); - - buffer.append("GUI Mode: "); - buffer.append("swordclient [-gui] [-nocapture]"); - buffer.append("\n\n"); - - buffer.append("Command Mode: Service - Request a Service Document\n"); - buffer.append("swordclient -cmd -t service [user-options] [proxy-options] -href url [-onBehalfOf name] "); - buffer.append("\n\n"); - - buffer.append("Command Mode: Post - Post a file to a remote service.\n"); - buffer.append("swordclient -cmd -t post [user-options] [proxy-options] [post-options] \n"); - buffer.append(" [-file file] [-filetype type] [-onBehalfOf name]"); - buffer.append("\n\n"); - - buffer.append("Command Mode: MultiPost - Post a file to multiple remote services.\n"); - buffer.append("swordclient -cmd -t multipost [user-options] [proxy-options] [post-options] \n"); - buffer.append(" [-dest dest]"); - - buffer.append("\n\n"); - buffer.append("User options: \n"); - buffer.append(" -u username Specify a username to access the remote service.\n"); - buffer.append(" -p password Specify a password to access the remote service.\n"); - buffer.append(" Required if -u option is used."); - - buffer.append("\n\n"); - buffer.append("Proxy options: \n"); - buffer.append(" -host host Hostname of a proxy, wwwproxy.aber.ac.uk.\n"); - buffer.append(" -port port Proxy port number, e.g. 8080.\n"); - - buffer.append("\n\n"); - buffer.append("Post options: \n"); - buffer.append(" -noOp Specified to indicate that the post is a test operation.\n"); - buffer.append(" -md5 Use an MD5 checksum in the message header.\n"); - buffer.append(" -checksumError Mis-calculate the file checksum for server test purposes.\n"); - buffer.append(" -formatNamespace ns The format namespace value.\n"); - buffer.append(" -slug name The slug value.\n"); - buffer.append(" -verbose Request a verbose response from the server.\n"); - - buffer.append("\n\n"); - buffer.append("Other options: \n"); - buffer.append(" -help Show this message.\n"); - buffer.append(" -t type The type of operation: service, post or multipost.\n"); - buffer.append(" -href url The URL for the service or post document.\n"); - buffer.append(" Required for service. The post and multipost operations \n"); - buffer.append(" will prompt you if the value is not provided.\n"); - buffer.append(" -filetype type The filetype, e.g. application/zip. The post and multipost\n"); - buffer.append(" will prompt you for the value if it is not provided.\n"); - buffer.append(" -onBehalfOf name Specify this parameter to set the On Behalf Of value.\n"); - buffer.append(" -dest dest Specify the destination for a deposit. This can be repeated\n"); - buffer.append(" multiple times. The format is: \n"); - buffer.append(" []:@\n"); - buffer.append(" e.g. sword[nst]:swordpass@http://sword.aber.ac.uk/post/\n"); - buffer.append(" nst:pass@http://sword.aber.ac.uk/post\n"); - buffer.append(" -nocapture Do not capture System.out and System.err to a debug panel\n"); - buffer.append(" in the GUI panel."); - - return buffer.toString(); - } - - /** - * Create a client. If GUI mode is set, a GUI client is created. Otherwise, - * a command line client is created. - * - * @param options The list of options extracted from the command line. - * @return A new client. - */ - public ClientType createClient(ClientOptions options) { - return new CmdClient(); - } - - /** - * Start the application and determine which client should be loaded. The - * application currently has two modes: GUI and client. The GUI mode is the - * default option. - * - * @param args the command line arguments given - */ - public static void main(String[] args) { - ClientFactory factory = new ClientFactory(); - - ClientOptions options = new ClientOptions(); - if (options.parseOptions(args)) { - ClientType client = factory.createClient(options); - client.run(options); - } else { - System.out.println(usage()); - } - } -} diff --git a/dspace-sword/src/main/java/org/purl/sword/client/ClientOptions.java b/dspace-sword/src/main/java/org/purl/sword/client/ClientOptions.java deleted file mode 100644 index 1ffbfca022b..00000000000 --- a/dspace-sword/src/main/java/org/purl/sword/client/ClientOptions.java +++ /dev/null @@ -1,605 +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.purl.sword.client; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.DefaultParser; -import org.apache.commons.cli.Option; -import org.apache.commons.cli.Options; -import org.apache.commons.cli.ParseException; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -/** - * List of options that are parsed from the command line. - * - * @author Neil Taylor - */ -public class ClientOptions { - /** - * Label for the service operation. - */ - public static final String TYPE_SERVICE = "service"; - - /** - * Label for the post operation. - */ - public static final String TYPE_POST = "post"; - - /** - * Label for the multipost operation. - */ - public static final String TYPE_MULTI_POST = "multipost"; - - /** - * The access type. - */ - private String accessType = null; - - /** - * Proxy host name. - */ - private String proxyHost = null; - - /** - * Proxy host port. - */ - private int proxyPort = 8080; - - /** - * Username to access the service/post server. - */ - private String username = null; - - /** - * Password to access the service/post server. - */ - private String password = null; - - /** - * HREF of the server to access. - */ - private String href = null; - - /** - * Filename to post. - */ - private String filename = null; - - /** - * File type. - */ - private String filetype = null; - - /** - * Specifies that the output streams are not to be captured by the GUI client. - */ - private boolean noCapture = false; - - - /** - * SLUG Header field. - */ - private String slug = null; - - /** - * NoOp, used to indicate an operation on the server that does not - * require the file to be stored. - */ - private boolean noOp = false; - - /** - * Request verbose output from the server. - */ - private boolean verbose = false; - - /** - * OnBehalfOf user id. - */ - private String onBehalfOf = null; - - /** - * Format namespace to be used for the posted file. - */ - private String formatNamespace = null; - - /** - * Introduce a checksum error. This is used to simulate an error with the - * MD5 value. - */ - private boolean checksumError = false; - - /** - * Logger. - */ - private static final Logger log = LogManager.getLogger(); - - /** - * List of multiple destination items. Used if the mode is set to multipost. - */ - private final List multiPost = new ArrayList<>(); - - /** - * Pattern string to extract the data from a destination parameter in multipost mode. - */ - private static final Pattern MULTI_PATTERN - = Pattern.compile("(.*?)(\\[(.*?)\\]) {0,1}(:(.*)) {0,1}@(http://.*)"); - - /** - * Flag that indicates if the GUI mode has been set. This is - * true by default. - */ - private boolean guiMode = true; - - /** - * Flat that indicates if the MD5 option has been selected. This - * is true by default. - */ - private boolean md5 = false; - - /** - * Parse the list of options contained in the specified array. - * - * @param args The array of options. - * @return True if the options were parsed successfully. - */ - public boolean parseOptions(String[] args) { - Options options = new Options(); - options.addOption(Option.builder().longOpt("md5").build()) - .addOption(Option.builder().longOpt("noOp").build()) - .addOption(Option.builder().longOpt("verbose").build()) - .addOption(Option.builder().longOpt("cmd").build()) - .addOption(Option.builder().longOpt("gui").build()) - .addOption(Option.builder().longOpt("help").build()) - .addOption(Option.builder().longOpt("nocapture").build()); - - Option option; - - option = Option.builder().longOpt("host").hasArg().build(); - options.addOption(option); - - option = Option.builder().longOpt("port").hasArg().build(); - options.addOption(option); - - option = Option.builder("u").hasArg().build(); - options.addOption(option); - - option = Option.builder("p").hasArg().build(); - options.addOption(option); - - option = Option.builder().longOpt("href").hasArg().build(); - options.addOption(option); - - option = Option.builder("t").hasArg().build(); - options.addOption(option); - - option = Option.builder().longOpt("file").hasArg().build(); - options.addOption(option); - - option = Option.builder().longOpt("filetype").hasArg().build(); - options.addOption(option); - - option = Option.builder().longOpt("slug").hasArg().build(); - options.addOption(option); - - option = Option.builder().longOpt("onBehalfOf").hasArg().build(); - options.addOption(option); - - option = Option.builder().longOpt("formatNamespace").hasArg().build(); - options.addOption(option); - - option = Option.builder().longOpt("checksumError").build(); - options.addOption(option); - - option = Option.builder().longOpt("dest").hasArg().build(); - options.addOption(option); - - DefaultParser parser = new DefaultParser(); - CommandLine command; - try { - command = parser.parse(options, args); - } catch (ParseException ex) { - log.error(ex.getMessage()); - return false; - } - - if (command.hasOption("help")) { - return false; // force the calling code to display the usage information. - } - md5 = command.hasOption("md5"); - noOp = command.hasOption("noOp"); - verbose = command.hasOption("verbose"); - if (command.hasOption("cmd")) { - guiMode = false; - } - if (command.hasOption("gui")) { - guiMode = true; - } - proxyHost = command.getOptionValue("host"); - if (command.hasOption("port")) { - proxyPort = Integer.parseInt(command.getOptionValue("port")); - } - username = command.getOptionValue("u"); - password = command.getOptionValue("p"); - href = command.getOptionValue("href"); - accessType = command.getOptionValue("t"); - filename = command.getOptionValue("file"); - filetype = command.getOptionValue("filetype"); - slug = command.getOptionValue("slug"); - onBehalfOf = command.getOptionValue("onBehalfOf"); - formatNamespace = command.getOptionValue("formatNamespace"); - checksumError = command.hasOption("checksumError"); - noCapture = command.hasOption("nocapture"); - if (command.hasOption("dest")) { - String dest = command.getOptionValue("dest"); - Matcher m = MULTI_PATTERN.matcher(dest); - if (!m.matches()) { - log.debug("Error with dest parameter. Ignoring value: {}", dest); - } else { - int numGroups = m.groupCount(); - for (int g = 0; g <= numGroups; g++) { - log.debug("Group ({}) is: {}", g, m.group(g)); - } - - String group_username = m.group(1); - String group_onBehalfOf = m.group(3); - String group_password = m.group(5); - String group_url = m.group(6); - PostDestination destination = new PostDestination(group_url, - group_username, group_password, group_onBehalfOf); - - multiPost.add(destination); - } - } - - try { - // apply any settings - if (href == null && "service".equals(accessType)) { - log.error("No href specified."); - return false; - } - - if (multiPost.isEmpty() && "multipost".equals(accessType)) { - log.error("No destinations specified"); - return false; - } - - if (accessType == null && !guiMode) { - log.error("No access type specified"); - return false; - } - - if ((username == null && password != null) || (username != null && password == null)) { - log.error( - "The username and/or password are not specified. If one is specified, the other must also be " + - "specified."); - return false; - } - } catch (ArrayIndexOutOfBoundsException ex) { - log.error("Error with parameters."); - return false; - } - - return true; - } - - /** - * Get the access type. - * - * @return The value, or null if the value is not set. - */ - public String getAccessType() { - return accessType; - } - - /** - * Set the access type. - * - * @param accessType The value, or null to clear the value. - */ - public void setAccessType(String accessType) { - this.accessType = accessType; - } - - /** - * Get the proxy host. - * - * @return The value, or null if the value is not set. - */ - public String getProxyHost() { - return proxyHost; - } - - /** - * Set the proxy host. - * - * @param proxyHost The value, or null to clear the value. - */ - public void setProxyHost(String proxyHost) { - this.proxyHost = proxyHost; - } - - /** - * Get the proxy port. - * - * @return The proxy port. Default value is 80. - */ - public int getProxyPort() { - return proxyPort; - } - - /** - * Set the proxy port. - * - * @param proxyPort The proxy port. - */ - public void setProxyPort(int proxyPort) { - this.proxyPort = proxyPort; - } - - /** - * Get the username. - * - * @return The value, or null if the value is not set. - */ - public String getUsername() { - return username; - } - - /** - * Set the username. - * - * @param username The value, or null to clear the value. - */ - public void setUsername(String username) { - this.username = username; - } - - /** - * Get the password. - * - * @return The value, or null if the value is not set. - */ - public String getPassword() { - return password; - } - - /** - * Set the password. - * - * @param password The value, or null to clear the value. - */ - public void setPassword(String password) { - this.password = password; - } - - /** - * Get the HREF of the service to access. - * - * @return The value, or null if the value is not set. - */ - public String getHref() { - return href; - } - - /** - * Set the HREF of the service to access. - * - * @param href The value, or null to clear the value. - */ - public void setHref(String href) { - this.href = href; - } - - /** - * Get the name of the file to post. - * - * @return The value, or null if the value is not set. - */ - public String getFilename() { - return filename; - } - - /** - * Set the name of the file to post. - * - * @param filename The value, or null to clear the value. - */ - public void setFilename(String filename) { - this.filename = filename; - } - - /** - * Get the type of the file to post. - * - * @return The filetype, or null if the value is not set. - */ - public String getFiletype() { - return filetype; - } - - /** - * Set the type of the file to post. - * - * @param filetype The value, or null to clear the value. - */ - public void setFiletype(String filetype) { - this.filetype = filetype; - } - - /** - * Determine if the tool is to be run in GUI mode. - * - * @return True if the tool is set for GUI mode. - */ - public boolean isGuiMode() { - return guiMode; - } - - /** - * Set the tool to run in GUI mode. - * - * @param guiMode True if the tool is to run in gui mode. - */ - public void setGuiMode(boolean guiMode) { - this.guiMode = guiMode; - } - - /** - * Get the MD5 setting. True if the tool is to use MD5 for post operations. - * - * @return The MD5 setting. - */ - public boolean isMd5() { - return md5; - } - - /** - * Set the MD5 setting. - * - * @param md5 True if the tool should use MD5 for post operations. - */ - public void setMd5(boolean md5) { - this.md5 = md5; - } - - /** - * Determine if the NoOp header should be sent. - * - * @return True if the header should be sent. - */ - public boolean isNoOp() { - return noOp; - } - - /** - * Set the NoOp setting. - * - * @param noOp True if the NoOp header should be used. - */ - public void setNoOp(boolean noOp) { - this.noOp = noOp; - } - - /** - * Determine if the verbose option is set. - * - * @return True if verbose option is set. - */ - public boolean isVerbose() { - return verbose; - } - - /** - * Set the verbose option. - * - * @param verbose True if verbose should be set. - */ - public void setVerbose(boolean verbose) { - this.verbose = verbose; - } - - /** - * Get the onBehalfOf value. - * - * @return The value, or null to clear the value. - */ - public String getOnBehalfOf() { - return onBehalfOf; - } - - /** - * Set the onBehalf of Value. - * - * @param onBehalfOf The value, or null to clear the value. - */ - public void setOnBehalfOf(String onBehalfOf) { - this.onBehalfOf = onBehalfOf; - } - - /** - * Get the format namespace value. - * - * @return The value, or null if the value is not set. - */ - public String getFormatNamespace() { - return formatNamespace; - } - - /** - * Set the format namespace value. - * - * @param formatNamespace The value, or null to clear the value. - */ - public void setFormatNamespace(String formatNamespace) { - this.formatNamespace = formatNamespace; - } - - /** - * Get the checksum error value. - * - * @return True if an error should be introduced into the checksum. - */ - public boolean getChecksumError() { - return checksumError; - } - - /** - * Set the checksum error value. - * - * @param checksumError True if the error should be introduced. - */ - public void setChecksumError(boolean checksumError) { - this.checksumError = checksumError; - } - - /** - * Get the current slug header. - * - * @return The slug value, or null if the value is not set. - */ - public String getSlug() { - return this.slug; - } - - /** - * Set the text that is to be used for the slug header. - * - * @param slug The value, or null to clear the value. - */ - public void setSlug(String slug) { - this.slug = slug; - } - - /** - * Get the list of post destinations. - * - * @return An iterator over the list of PostDestination objects. - */ - public Iterator getMultiPost() { - return multiPost.iterator(); - } - - - /** - * Determine if the noCapture option is set. This indicates that the code - * should not attempt to redirect stdout and stderr to a different output - * destination. Intended for use in a GUI client. - * - * @return The noCapture setting. True if set. - */ - public boolean isNoCapture() { - return noCapture; - } -} diff --git a/dspace-sword/src/main/java/org/purl/sword/client/ClientType.java b/dspace-sword/src/main/java/org/purl/sword/client/ClientType.java deleted file mode 100644 index 11474782c1c..00000000000 --- a/dspace-sword/src/main/java/org/purl/sword/client/ClientType.java +++ /dev/null @@ -1,23 +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.purl.sword.client; - -/** - * Interface for a client. This contains a single method that allows the factory - * to pass a set of command line options to the client. - * - * @author Neil Taylor - */ -public interface ClientType { - /** - * Run the client, processing the specified options. - * - * @param options The options extracted from the command line. - */ - public void run(ClientOptions options); -} diff --git a/dspace-sword/src/main/java/org/purl/sword/client/CmdClient.java b/dspace-sword/src/main/java/org/purl/sword/client/CmdClient.java deleted file mode 100644 index 842e9d483dc..00000000000 --- a/dspace-sword/src/main/java/org/purl/sword/client/CmdClient.java +++ /dev/null @@ -1,445 +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.purl.sword.client; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Iterator; -import java.util.List; - -import org.apache.logging.log4j.Logger; -import org.purl.sword.atom.Author; -import org.purl.sword.atom.Content; -import org.purl.sword.atom.Contributor; -import org.purl.sword.atom.Generator; -import org.purl.sword.atom.Link; -import org.purl.sword.atom.Rights; -import org.purl.sword.atom.Summary; -import org.purl.sword.atom.Title; -import org.purl.sword.base.Collection; -import org.purl.sword.base.DepositResponse; -import org.purl.sword.base.SWORDEntry; -import org.purl.sword.base.ServiceDocument; -import org.purl.sword.base.SwordAcceptPackaging; -import org.purl.sword.base.Workspace; - -/** - * Example implementation of a command line client. This can send out service - * document requests and print out the results and process posting a file to - * either a single or multiple destinations. The command line options are - * initialised prior to calling the class. The options are passed into the - * run(ClientOptions) method. - * - * @author Neil Taylor - */ -public class CmdClient implements ClientType { - /** - * The client that is used to process the service and post requests. - */ - private SWORDClient client; - - /** - * List of the options that can be specified on the command line. - */ - private ClientOptions options; - - /** - * The logger. - */ - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(CmdClient.class); - - /** - * Create a new instance of the class and create an instance of the - * client. - */ - public CmdClient() { - client = new Client(); - } - - /** - * Process the options that have been initialised from the command line. - * This will call one of service(), post() or multiPost(). - */ - public void process() { - if (options.getProxyHost() != null) { - client.setProxy(options.getProxyHost(), options.getProxyPort()); - } - - try { - String accessType = options.getAccessType(); - if (ClientOptions.TYPE_SERVICE.equals(accessType)) { - service(); - } else if (ClientOptions.TYPE_POST.equals(accessType)) { - post(); - } else if (ClientOptions.TYPE_MULTI_POST.equals(accessType)) { - System.out.println("checking multi-post"); - multiPost(); - } else { - System.out.println("Access type not recognised."); - } - - } catch (MalformedURLException mex) { - System.out - .println("The specified href was not valid: " + options.getHref() + " message: " + mex.getMessage()); - } catch (SWORDClientException ex) { - System.out.println("Exception: " + ex.getMessage()); - log.error("Unable to process request", ex); - } - } - - /** - * Process the service operation. Output the results of the service request. - * - * @throws SWORDClientException if there is an error processing the service request. - * @throws MalformedURLException if there is an error with the URL for the service request. - */ - private void service() - throws SWORDClientException, MalformedURLException { - String href = options.getHref(); - initialiseServer(href, options.getUsername(), options.getPassword()); - - ServiceDocument document = client.getServiceDocument(href, options.getOnBehalfOf()); - Status status = client.getStatus(); - System.out.println("The status is: " + status); - - if (status.getCode() == 200) { - log.debug("message is: " + document.marshall()); - - System.out.println("\nThe following Details were retrieved: "); - System.out.println("SWORD Version: " - + document.getService().getVersion()); - System.out.println("Supports NoOp? " + document.getService().isNoOp()); - System.out.println("Supports Verbose? " - + document.getService().isVerbose()); - System.out.println("Max Upload File Size " - + document.getService().getMaxUploadSize() + " kB"); - - Iterator workspaces = document.getService().getWorkspaces(); - for (; workspaces.hasNext(); ) { - Workspace workspace = workspaces.next(); - System.out.println("\nWorkspace Title: '" - + workspace.getTitle() + "'"); - - System.out.println("\n+ Collections ---"); - // process the collections - Iterator collections = workspace - .collectionIterator(); - for (; collections.hasNext(); ) { - Collection collection = collections.next(); - System.out.println("\nCollection location: " - + collection.getLocation()); - System.out.println("Collection title: " - + collection.getTitle()); - System.out - .println("Abstract: " + collection.getAbstract()); - System.out.println("Collection Policy: " - + collection.getCollectionPolicy()); - System.out.println("Treatment: " - + collection.getTreatment()); - System.out.println("Mediation: " - + collection.getMediation()); - - String[] accepts = collection.getAccepts(); - if (accepts != null && accepts.length == 0) { - System.out.println("Accepts: none specified"); - } else { - for (String s : accepts) { - System.out.println("Accepts: " + s); - } - } - List acceptsPackaging = collection.getAcceptPackaging(); - - StringBuilder acceptPackagingList = new StringBuilder(); - for (Iterator i = acceptsPackaging.iterator(); i.hasNext(); ) { - SwordAcceptPackaging accept = (SwordAcceptPackaging) i.next(); - acceptPackagingList.append(accept.getContent()).append(" (").append(accept.getQualityValue()) - .append("), "); - } - - System.out.println("Accepts Packaging: " + acceptPackagingList.toString()); - } - System.out.println("+ End of Collections ---"); - } - } - } - - /** - * Perform a post. If any of the destination URL, the filename and the - * filetype are missing, the user will be prompted to enter the values. - * - * @throws SWORDClientException if there is an error processing the post for a requested - * destination. - * @throws MalformedURLException if there is an error with the URL for the post. - */ - private void post() - throws SWORDClientException, MalformedURLException { - String url = options.getHref(); - if (url == null) { - url = readLine("Please enter the URL for the deposit: "); - } - - initialiseServer(url, options.getUsername(), options.getPassword()); - String file = options.getFilename(); - if (file == null) { - file = readLine("Please enter the filename to deposit: "); - } - String type = options.getFiletype(); - if (type == null) { - type = readLine("Please enter the file type, e.g. application/zip: "); - } - - PostMessage message = new PostMessage(); - message.setFilepath(file); - message.setDestination(url); - message.setFiletype(type); - message.setUseMD5(options.isMd5()); - message.setVerbose(options.isVerbose()); - message.setNoOp(options.isNoOp()); - message.setFormatNamespace(options.getFormatNamespace()); - message.setOnBehalfOf(options.getOnBehalfOf()); - message.setChecksumError(options.getChecksumError()); - message.setUserAgent(ClientConstants.SERVICE_NAME); - - processPost(message); - - } - - /** - * Perform a multi-post. Iterate over the list of -dest arguments in the command line - * options. For each -dest argument, attempt to post the file to the server. - * - * @throws SWORDClientException if there is an error processing the post for a requested - * destination. - * @throws MalformedURLException if there is an error with the URL for the post. - */ - private void multiPost() - throws SWORDClientException, MalformedURLException { - // request the common information - String file = options.getFilename(); - if (file == null) { - file = readLine("Please enter the filename to deposit: "); - } - String type = options.getFiletype(); - if (type == null) { - type = readLine("Please enter the file type, e.g. application/zip: "); - } - - // process this information for each of the specified destinations - PostDestination destination; - String url = null; - - Iterator iterator = options.getMultiPost(); - while (iterator.hasNext()) { - destination = iterator.next(); - url = destination.getUrl(); - initialiseServer(url, destination.getUsername(), destination.getPassword()); - - String onBehalfOf = destination.getOnBehalfOf(); - if (onBehalfOf == null) { - onBehalfOf = ""; - } else { - onBehalfOf = " on behalf of: " + onBehalfOf; - } - - System.out.println("Sending file to: " + url + " for: " + destination.getUsername() + - onBehalfOf); - PostMessage message = new PostMessage(); - message.setFilepath(file); - message.setDestination(url); - message.setFiletype(type); - message.setUseMD5(options.isMd5()); - message.setVerbose(options.isVerbose()); - message.setNoOp(options.isNoOp()); - message.setFormatNamespace(options.getFormatNamespace()); - message.setOnBehalfOf(destination.getOnBehalfOf()); - message.setChecksumError(options.getChecksumError()); - message.setUserAgent(ClientConstants.SERVICE_NAME); - - processPost(message); - } - - } - - /** - * Process the post response. The message contains the list of arguments - * for the post. The method will then print out the details of the - * response. - * - * @param message The post options. - * @throws SWORDClientException if there is an error accessing the - * post response. - */ - protected void processPost(PostMessage message) - throws SWORDClientException { - DepositResponse response = client.postFile(message); - - System.out.println("The status is: " + client.getStatus()); - - if (response != null) { - log.debug("message is: " + response.marshall()); - - // iterate over the data and output it - SWORDEntry entry = response.getEntry(); - - - System.out.println("Id: " + entry.getId()); - Title title = entry.getTitle(); - if (title != null) { - System.out.print("Title: " + title.getContent() + " type: "); - if (title.getType() != null) { - System.out.println(title.getType().toString()); - } else { - System.out.println("Not specified."); - } - } - - // process the authors - Iterator authors = entry.getAuthors(); - while (authors.hasNext()) { - Author author = authors.next(); - System.out.println("Author - " + author.toString()); - } - - Iterator categories = entry.getCategories(); - while (categories.hasNext()) { - System.out.println("Category: " + categories.next()); - } - - Iterator contributors = entry.getContributors(); - while (contributors.hasNext()) { - Contributor contributor = contributors.next(); - System.out.println("Contributor - " + contributor.toString()); - } - - Iterator links = entry.getLinks(); - while (links.hasNext()) { - Link link = links.next(); - System.out.println(link.toString()); - } - - Generator generator = entry.getGenerator(); - if (generator != null) { - System.out.println("Generator - " + generator.toString()); - } else { - System.out.println("There is no generator"); - } - - System.out.println("Published: " + entry.getPublished()); - - Content content = entry.getContent(); - if (content != null) { - System.out.println(content.toString()); - } else { - System.out.println("There is no content element."); - } - - Rights right = entry.getRights(); - if (right != null) { - System.out.println(right.toString()); - } else { - System.out.println("There is no right element."); - } - - Summary summary = entry.getSummary(); - if (summary != null) { - - System.out.println(summary.toString()); - } else { - System.out.println("There is no summary element."); - } - - System.out.println("Update: " + entry.getUpdated()); - System.out.println("Published: " + entry.getPublished()); - System.out.println("Verbose Description: " + entry.getVerboseDescription()); - System.out.println("Treatment: " + entry.getTreatment()); - System.out.println("Packaging: " + entry.getPackaging()); - - if (entry.isNoOpSet()) { - System.out.println("NoOp: " + entry.isNoOp()); - } - } else { - System.out.println("No valid Entry document was received from the server"); - } - } - - /** - * Initialise the server. Set the server that will be connected to and - * initialise any username and password. If the username and password are - * either null or contain empty strings, the user credentials will be cleared. - * - * @param location The location to connect to. This is a URL, of the format, - * http://a.host.com:port/. The host name and port number will - * be extracted. If the port is not specified, a default port of - * 80 will be used. - * @param username The username. If this is null or an empty string, the basic - * credentials will be cleared. - * @param password The password. If this is null or an empty string, the basic - * credentials will be cleared. - * @throws MalformedURLException if there is an error processing the URL. - */ - private void initialiseServer(String location, String username, String password) - throws MalformedURLException { - URL url = new URL(location); - int port = url.getPort(); - if (port == -1) { - port = 80; - } - - client.setServer(url.getHost(), port); - - if (username != null && username.length() > 0 && - password != null && password.length() > 0) { - log.info("Setting the username/password: " + username + " " - + password); - client.setCredentials(username, password); - } else { - client.clearCredentials(); - } - } - - /** - * Read a line of text from System.in. If there is an error reading - * from the input, the prompt will be redisplayed and the user asked - * to try again. - * - * @param prompt The prompt to display before the prompt. - * @return The string that is read from the line. - */ - private String readLine(String prompt) { - BufferedReader reader = new BufferedReader(new InputStreamReader( - System.in)); - String result = null; - - boolean ok = false; - while (!ok) { - try { - System.out.print(prompt); - System.out.flush(); - result = reader.readLine(); - ok = true; - } catch (IOException ex) { - System.out.println("There was an error with your input. Please try again."); - } - } - - return result; - } - - /** - * Run the client and process the specified options. - * - * @param options The command line options. - */ - public void run(ClientOptions options) { - this.options = options; - process(); - } -} diff --git a/dspace-sword/src/main/java/org/purl/sword/client/DebugOutputStream.java b/dspace-sword/src/main/java/org/purl/sword/client/DebugOutputStream.java deleted file mode 100644 index dfd8b6c60f2..00000000000 --- a/dspace-sword/src/main/java/org/purl/sword/client/DebugOutputStream.java +++ /dev/null @@ -1,44 +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.purl.sword.client; - -import java.io.IOException; -import java.io.OutputStream; - -/** - * A stream that will write any output to the specified panel. - * - * @author Neil Taylor - */ -public class DebugOutputStream extends OutputStream { - /** - * Panel that will display the messages. - */ - private MessageOutputPanel panel; - - /** - * Create a new instance and specify the panel that will receive the output. - * - * @param panel The panel. - */ - public DebugOutputStream(MessageOutputPanel panel) { - this.panel = panel; - } - - /** - * Override the write method from OutputStream. Capture the char and - * send it to the panel. - * - * @param arg0 The output character, expressed as an integer. - * @see java.io.OutputStream#write(int) - */ - public void write(int arg0) throws IOException { - panel.addCharacter(Character.valueOf((char) arg0)); - } - -} diff --git a/dspace-sword/src/main/java/org/purl/sword/client/MessageOutputPanel.java b/dspace-sword/src/main/java/org/purl/sword/client/MessageOutputPanel.java deleted file mode 100644 index e703f79cccc..00000000000 --- a/dspace-sword/src/main/java/org/purl/sword/client/MessageOutputPanel.java +++ /dev/null @@ -1,138 +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/ - */ - -/** - * Copyright (c) 2007, Aberystwyth University - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * - Redistributions of source code must retain the above - * copyright notice, this list of conditions and the - * following disclaimer. - * - * - Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * - Neither the name of the Centre for Advanced Software and - * Intelligent Systems (CASIS) nor the names of its - * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR - * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF - * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ -package org.purl.sword.client; - -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import javax.swing.JButton; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JTextArea; - -/** - * Panel to display output messages. Text or characters can be sent to the - * panel for display. The panel also includes a button to clear any - * text that is currently displayed. - * - * @author Neil Taylor - */ -public class MessageOutputPanel extends JPanel - implements ActionListener { - - /** - * The text area that displays the messages. - */ - private JTextArea messages = null; - - /** - * Create a new instance and initialise the panel. - */ - public MessageOutputPanel() { - super(); - - setLayout(new GridBagLayout()); - - messages = new JTextArea(); - - JScrollPane detailsPane = new JScrollPane(messages, - JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, - JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); - JButton clearButton = new JButton("Clear"); - clearButton.addActionListener(this); - - //add components and set constraints - //dpc = details pane constraint - GridBagConstraints dpc = new GridBagConstraints(); - dpc.gridx = 0; - dpc.gridy = 0; - dpc.fill = GridBagConstraints.BOTH; - dpc.weightx = 0.75; - dpc.weighty = 0.45; - dpc.gridwidth = 2; - dpc.insets = new Insets(5, 5, 5, 5); - add(detailsPane, dpc); - - //cbc = clear button constraint - GridBagConstraints cbc = new GridBagConstraints(); - cbc.gridx = 1; - cbc.gridy = 1; - cbc.insets = new Insets(0, 0, 5, 5); - cbc.anchor = GridBagConstraints.LINE_END; - add(clearButton, cbc); - - } - - /** - * Add a message to the text area. The message will be added with a carriage return. - * - * @param message The message. - */ - public void addMessage(String message) { - messages.insert(message + "\n", messages.getDocument().getLength()); - } - - /** - * Add a single character to the text area. - * - * @param character The character. - */ - public void addCharacter(Character character) { - messages.insert(character.toString(), messages.getDocument().getLength()); - } - - /** - * Clear the text from the display. - * - * @param arg0 The action event. - * - * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) - */ - public void actionPerformed(ActionEvent arg0) { - messages.setText(""); - } -} diff --git a/dspace-sword/src/main/java/org/purl/sword/client/PostDestination.java b/dspace-sword/src/main/java/org/purl/sword/client/PostDestination.java deleted file mode 100644 index 3124f02b45d..00000000000 --- a/dspace-sword/src/main/java/org/purl/sword/client/PostDestination.java +++ /dev/null @@ -1,140 +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.purl.sword.client; - -/** - * Details for a destination. This is used to represent a destination. If - * expressed as a string, the destination looks like: - *
    - * <user>[<onBehalfOf>]:<password>@<url>
    - * 
    - * - * @author Neil Taylor - */ -public class PostDestination { - /** - * URL for the post destination. - */ - private String url; - - /** - * The username. - */ - private String username; - - /** - * The password. - */ - private String password; - - /** - * The onBehalfOf ID. - */ - private String onBehalfOf; - - /** - * Create a new instance. - */ - public PostDestination() { - // No-Op - } - - /** - * Create a new instance. - * - * @param url The url. - * @param username The username. - * @param password The password. - * @param onBehalfOf The onBehalfOf id. - */ - public PostDestination(String url, String username, String password, String onBehalfOf) { - this.url = url; - this.username = username; - this.password = password; - this.onBehalfOf = onBehalfOf; - } - - /** - * @return the url - */ - public String getUrl() { - return url; - } - - /** - * @param url the url to set - */ - public void setUrl(String url) { - this.url = url; - } - - /** - * @return the username - */ - public String getUsername() { - return username; - } - - /** - * @param username the username to set - */ - public void setUsername(String username) { - this.username = username; - } - - /** - * @return the password - */ - public String getPassword() { - return password; - } - - /** - * @param password the password to set - */ - public void setPassword(String password) { - this.password = password; - } - - /** - * @return the onBehalfOf - */ - public String getOnBehalfOf() { - return onBehalfOf; - } - - /** - * @param onBehalfOf the onBehalfOf to set - */ - public void setOnBehalfOf(String onBehalfOf) { - this.onBehalfOf = onBehalfOf; - } - - /** - * Create a string representation of this object. - * - * @return The string. - */ - public String toString() { - StringBuffer buffer = new StringBuffer(); - buffer.append(username); - if (onBehalfOf != null) { - buffer.append("["); - buffer.append(onBehalfOf); - buffer.append("]"); - } - - if (password != null) { - buffer.append(":******"); - } - buffer.append("@"); - buffer.append(url); - - return buffer.toString(); - } -} diff --git a/dspace-sword/src/main/java/org/purl/sword/client/PostDialog.java b/dspace-sword/src/main/java/org/purl/sword/client/PostDialog.java deleted file mode 100644 index 84815b327e3..00000000000 --- a/dspace-sword/src/main/java/org/purl/sword/client/PostDialog.java +++ /dev/null @@ -1,599 +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/ - */ - -/** - * Copyright (c) 2007, Aberystwyth University - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * - Redistributions of source code must retain the above - * copyright notice, this list of conditions and the - * following disclaimer. - * - * - Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * - Neither the name of the Centre for Advanced Software and - * Intelligent Systems (CASIS) nor the names of its - * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR - * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF - * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ -package org.purl.sword.client; - -import java.awt.BorderLayout; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.io.File; -import javax.swing.DefaultListModel; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JFileChooser; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JList; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JPasswordField; -import javax.swing.JScrollPane; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; - -/** - * Dialog for users to enter details of post destinations. - * - * @author Neil Taylor - */ -public class PostDialog - implements ActionListener, ChangeListener { - /** - * label for the browse command. - */ - protected static final String BROWSE = "browse"; - - /** - * label for the add command. - */ - protected static final String ADD = "add"; - - /** - * label for the edit command. - */ - protected static final String EDIT = "edit"; - - /** - * label for the delete command. - */ - protected static final String DELETE = "delete"; - - /** - * label for the clear command. - */ - protected static final String CLEAR = "clear"; - - /** - * Username combo box. - */ - private SWORDComboBox username; - - /** - * Post Location combo box. - */ - private SWORDComboBox postLocation; - - /** - * Password field. - */ - private JPasswordField password; - - /** - * The file combo box. - */ - private SWORDComboBox file; - - /** - * The filetype combo box. - */ - private SWORDComboBox fileType; - - /** - * The onBehalfOf combo box. - */ - private SWORDComboBox onBehalfOf; - - /** - * The md5 checkbox. - */ - private JCheckBox useMD5; - - /** - * The corruptMD5 checkbox. - */ - private JCheckBox corruptMD5; - - /** - * The corruptRequest checkbox. - */ - private JCheckBox corruptRequest; - - /** - * The useNoOp checkbox. - */ - private JCheckBox useNoOp; - - /** - * The verbose checkbox. - */ - private JCheckBox useVerbose; - - /** - * The format namespace combo box. - */ - private SWORDComboBox formatNamespace; - - /** - * The list of post destinations. - */ - private JList list; - - /** - * The parent frame for the dialog that is displayed. - */ - private JFrame parentFrame = null; - - /** - * Array that lists the labels for the buttons on the panel. - */ - private static Object[] options = {"Post File", "Cancel"}; - - /** - * The panel that holds the controls to show. - */ - private JPanel controls = null; - - /** - * - * @param parentFrame the parent of this dialog. - */ - public PostDialog(JFrame parentFrame) { - this.parentFrame = parentFrame; - controls = createControls(); - } - - /** - * Show the dialog with ok and cancel options. - * @return The return value from displaying JOptionPane. Either - * JOptionPane.OK_OPTION or JOptionPane.CANCEL_OPTION. - */ - public int show() { - int result = JOptionPane.showOptionDialog(parentFrame, - controls, - "Post Document", - JOptionPane.OK_CANCEL_OPTION, - JOptionPane.PLAIN_MESSAGE, - null, - options, - null); - - if (result == JOptionPane.OK_OPTION) { - // update the combo boxes with the values - username.updateList(); - file.updateList(); - fileType.updateList(); - onBehalfOf.updateList(); - formatNamespace.updateList(); - } - - return result; - } - - /** - * Create the controls for the main panel. - * - * @return The panel. - */ - protected final JPanel createControls() { - file = new SWORDComboBox(); - JPanel filePanel = new JPanel(new BorderLayout()); - filePanel.add(file, BorderLayout.CENTER); - JButton browse = new JButton("Browse..."); - browse.setActionCommand(BROWSE); - browse.addActionListener(this); - - filePanel.add(browse, BorderLayout.SOUTH); - - fileType = new SWORDComboBox(); - String type = "application/zip"; - fileType.addItem(type); - fileType.setSelectedItem(type); - - // controls that will be used in the second dialog - postLocation = new SWORDComboBox(); - username = new SWORDComboBox(); - password = new JPasswordField(); - onBehalfOf = new SWORDComboBox(); - - - useMD5 = new JCheckBox(); - useMD5.addChangeListener(this); - corruptMD5 = new JCheckBox(); - corruptRequest = new JCheckBox(); - useNoOp = new JCheckBox(); - useVerbose = new JCheckBox(); - formatNamespace = new SWORDComboBox(); - - JLabel fileLabel = new JLabel("File:", JLabel.TRAILING); - JLabel fileTypeLabel = new JLabel("File Type:", JLabel.TRAILING); - JLabel useMD5Label = new JLabel("Use MD5:", JLabel.TRAILING); - JLabel corruptMD5Label = new JLabel("Corrupt MD5:", JLabel.TRAILING); - JLabel corruptRequestLabel = new JLabel("Corrupt Request:", JLabel.TRAILING); - //JLabel corruptMD5Label = new JLabel("Corrupt MD5:", JLabel.TRAILING); - JLabel useNoOpLabel = new JLabel("Use noOp:", JLabel.TRAILING); - JLabel useVerboseLabel = new JLabel("Use verbose:", JLabel.TRAILING); - JLabel formatNamespaceLabel = new JLabel("X-Packaging:", JLabel.TRAILING); - JLabel userAgentLabel = new JLabel("User Agent:", JLabel.TRAILING); - JLabel userAgentNameLabel = new JLabel(ClientConstants.SERVICE_NAME, JLabel.LEADING); - - SWORDFormPanel panel = new SWORDFormPanel(); - panel.addFirstRow(new JLabel("Please enter the details for the post operation")); - - JPanel destinations = createDestinationsPanel(); - - panel.addRow(new JLabel("Destinations:"), destinations); - panel.addRow(fileLabel, filePanel); - panel.addRow(fileTypeLabel, fileType); - panel.addRow(useMD5Label, useMD5); - panel.addRow(corruptMD5Label, corruptMD5); - panel.addRow(corruptRequestLabel, corruptRequest); - panel.addRow(useNoOpLabel, useNoOp); - panel.addRow(useVerboseLabel, useVerbose); - panel.addRow(formatNamespaceLabel, formatNamespace); - panel.addRow(userAgentLabel, userAgentNameLabel); - - return panel; - } - - /** - * Create the destinations panel. This contains a list and four buttons - * to operate on values in the list. - * - * @return The panel containing the controls. - */ - protected JPanel createDestinationsPanel() { - DefaultListModel model = new DefaultListModel(); - list = new JList(model); - JScrollPane jsp = new JScrollPane(list); - - JPanel destinations = new JPanel(new BorderLayout()); - destinations.add(jsp, BorderLayout.CENTER); - JPanel destinationButtons = new JPanel(); - - JButton addButton = new JButton("Add"); - addButton.setActionCommand(ADD); - addButton.addActionListener(this); - - JButton editButton = new JButton("Edit"); - editButton.setActionCommand(EDIT); - editButton.addActionListener(this); - - JButton deleteButton = new JButton("Delete"); - deleteButton.setActionCommand(DELETE); - deleteButton.addActionListener(this); - - JButton clearButton = new JButton("Clear"); - clearButton.setActionCommand(CLEAR); - clearButton.addActionListener(this); - - destinationButtons.add(addButton); - destinationButtons.add(editButton); - destinationButtons.add(deleteButton); - destinationButtons.add(clearButton); - - destinations.add(destinationButtons, BorderLayout.SOUTH); - - return destinations; - } - - /** - * Handle the button click to select a file to upload. - */ - public void actionPerformed(ActionEvent evt) { - String cmd = evt.getActionCommand(); - - if (BROWSE.equals(cmd)) { - JFileChooser chooser = new JFileChooser(); - chooser.setCurrentDirectory(new File(System.getProperty("user.dir"))); - int returnVal = chooser.showOpenDialog(parentFrame); - if (returnVal == JFileChooser.APPROVE_OPTION) { - file.setSelectedItem(chooser.getSelectedFile().getAbsolutePath()); - } - } else if (ADD.equals(cmd)) { - PostDestination dest = showDestinationDialog(null); - if (dest != null) { - ((DefaultListModel) list.getModel()).addElement(dest); - } - } else if (EDIT.equals(cmd)) { - PostDestination dest = (PostDestination) list.getSelectedValue(); - if (dest != null) { - showDestinationDialog(dest); - list.repaint(); - } - } else if (DELETE.equals(cmd)) { - if (list.getSelectedIndex() != -1) { - ((DefaultListModel) list.getModel()).removeElementAt(list.getSelectedIndex()); - } - } else if (CLEAR.equals(cmd)) { - ((DefaultListModel) list.getModel()).clear(); - } - } - - /** - * Show the destination dialog. This is used to enter the URL, - * username, password and onBehalfOf name for a destination. - * - * @param destination The post destination. If this is not null, the values - * in the object are used to set the current values - * in the dialog controls. - * @return The post destination value. - */ - public PostDestination showDestinationDialog(PostDestination destination) { - SWORDFormPanel panel = new SWORDFormPanel(); - panel.addFirstRow(new JLabel("Please enter the details for the post operation")); - - JLabel postLabel = new JLabel("Post Location:", JLabel.TRAILING); - JLabel userLabel = new JLabel("Username:", JLabel.TRAILING); - JLabel passwordLabel = new JLabel("Password:", JLabel.TRAILING); - JLabel onBehalfOfLabel = new JLabel("On Behalf Of:", JLabel.TRAILING); - - panel.addRow(postLabel, postLocation); - panel.addRow(userLabel, username); - panel.addRow(passwordLabel, password); - panel.addRow(onBehalfOfLabel, onBehalfOf); - - if (destination != null) { - postLocation.insertItem(destination.getUrl()); - username.insertItem(destination.getUsername()); - password.setText(destination.getPassword()); - onBehalfOf.insertItem(destination.getOnBehalfOf()); - } else { - String s = ""; - postLocation.insertItem(s); - //postLocation.setSelectedItem(s); - username.insertItem(s); - username.setSelectedItem(s); - password.setText(s); - onBehalfOf.insertItem(s); - onBehalfOf.setSelectedItem(s); - } - - int result = JOptionPane.showOptionDialog(null, - panel, - "Destination", - JOptionPane.OK_CANCEL_OPTION, - JOptionPane.PLAIN_MESSAGE, - null, - new String[] {"OK", "Cancel"}, - null); - - if (result == JOptionPane.OK_OPTION) { - postLocation.updateList(); - username.updateList(); - onBehalfOf.updateList(); - - if (destination == null) { - destination = new PostDestination(); - } - - destination.setUrl(postLocation.getText()); - destination.setUsername(username.getText()); - String pass = new String(password.getPassword()); - if (pass.length() > 0) { - destination.setPassword(pass); - } else { - destination.setPassword(null); - } - - String obo = onBehalfOf.getText(); - if (obo.length() > 0) { - destination.setOnBehalfOf(onBehalfOf.getText()); - } else { - destination.setOnBehalfOf(null); - } - - } - - return destination; - } - - /** - * Get the list of Post Destinations. - * @return The destinations. - */ - public PostDestination[] getDestinations() { - DefaultListModel model = (DefaultListModel) list.getModel(); - PostDestination[] destinations = new PostDestination[model.size()]; - for (int i = 0; i < model.size(); i++) { - destinations[i] = (PostDestination) model.get(i); - } - return destinations; - } - - /** - * Get the file details. - * @return The value. - */ - public String getFile() { - return file.getText(); - } - - /** - * Get the filetype value. - * @return The value. - */ - public String getFileType() { - return fileType.getText(); - } - - /** - * Get the onBehalfOf value. - * @return The value. - */ - public String getOnBehalfOf() { - return onBehalfOf.getText(); - } - - /** - * Get the format namespace value. - * @return The value. - */ - public String getFormatNamespace() { - return formatNamespace.getText(); - } - - /** - * Determine if the MD5 checkbox is selected. - * - * @return True if the MD5 checkbox is selected. - */ - public boolean useMd5() { - return useMD5.isSelected(); - } - - /** - * Determine if the noOp checkbox is selected. - * - * @return True if the checkbox is selected. - */ - public boolean useNoOp() { - return useNoOp.isSelected(); - } - - /** - * Determine if the verbose checkbox is selected. - * - * @return True if the checkbox is selected. - */ - public boolean useVerbose() { - return useVerbose.isSelected(); - } - - /** - * Get the post location. - * @return The post location. - */ - public String getPostLocation() { - return postLocation.getText(); - } - - /** - * Determine if the MD5 hash should be corrupted. - * @return True if the corrupt MD5 checkbox is selected. The MD5 checkbox - * must also be selected. - */ - public boolean corruptMD5() { - return (corruptMD5.isEnabled() && corruptMD5.isSelected()); - } - - /** - * Determine if the POST request should be corrupted. - * @return True if the corrupt request checkbox is selected. - */ - public boolean corruptRequest() { - return (corruptRequest.isSelected()); - } - - /** - * Detect a state change event for the checkbox. - * - * @param evt The event. - */ - public void stateChanged(ChangeEvent evt) { - corruptMD5.setEnabled(useMD5.isSelected()); - } - - /** - * Add a list of user ids. - * - * @param users The user ids. - */ - public void addUserIds(String[] users) { - username.insertItems(users); - } - - /** - * Add a list of deposit URLs. - * - * @param deposits The URLs. - */ - public void addDepositUrls(String[] deposits) { - postLocation.insertItems(deposits); - } - - /** - * Add a list of onBehalfOf names. - * - * @param users The names. - */ - public void addOnBehalfOf(String[] users) { - onBehalfOf.insertItems(users); - } - - /** - * Add the list of formatNamespace strings. - * - * @param namespaces list of strings. - */ - public void addFormatNamespaces(String[] namespaces) { - formatNamespace.insertItems(namespaces); - } - - /** - * Add a list of file types. - * - * @param types The file types. - */ - public void addFileTypes(String[] types) { - fileType.insertItems(types); - } - - /** - * Add a list of file names. - * @param files The list of files. - */ - public void addFiles(String[] files) { - file.insertItems(files); - } - - /** - * Set the deposit location. - * - * @param location The location. - */ - public void setDepositLocation(String location) { - postLocation.insertItem(location); - postLocation.setSelectedItem(location); - } -} diff --git a/dspace-sword/src/main/java/org/purl/sword/client/PostMessage.java b/dspace-sword/src/main/java/org/purl/sword/client/PostMessage.java deleted file mode 100644 index a44f21ce2b8..00000000000 --- a/dspace-sword/src/main/java/org/purl/sword/client/PostMessage.java +++ /dev/null @@ -1,344 +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/ - */ - -/** - * Copyright (c) 2007, Aberystwyth University - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * - Redistributions of source code must retain the above - * copyright notice, this list of conditions and the - * following disclaimer. - * - * - Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * - Neither the name of the Centre for Advanced Software and - * Intelligent Systems (CASIS) nor the names of its - * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR - * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF - * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ -package org.purl.sword.client; - -import java.io.File; - -/** - * Represents the details of a post to a server. The message holds all of the possible values - * that are to be sent from the client to the server. Not all elements of the message - * must be filled in. Any required fields are defined in the current SWORD specification. - * - * @author Neil Taylor - */ -public class PostMessage { - /** - * The local filepath for the file to upload/deposit. - */ - private String filepath; - - /** - * The URL of the destination server. - */ - private String destination; - - /** - * The filetype of the package that is to be uploaded. - */ - private String filetype; - - /** - * The string with the username if the deposit is on behalf of another user. - */ - private String onBehalfOf; - - /** - * True if an MD5 checksum should be sent with the deposit. - */ - private boolean useMD5; - - /** - * True if the deposit is a test and should not result in an actual deposit. - */ - private boolean noOp; - - /** - * True if the verbose operation is requested. - */ - private boolean verbose; - - /** - * The packaging format for the deposit. - */ - private String packaging; - - /** - * True if the deposit should simulate a checksum error. The client should check this - * field to determine if a correct MD5 checksum should be sent or whether the checksum should - * be modified so that it generates an error at the server. - */ - private boolean checksumError; - - /** - * True if the deposit should corrupt the POST header. The client should check this - * field to determine if a correct header should be sent or whether the header should - * be modified so that it generates an error at the server. - */ - private boolean corruptRequest; - - /** - * The Slug header value. - */ - private String slug; - - /** - * The user agent name - */ - private String userAgent; - - /** - * Get the filepath. - * - * @return The filepath. - */ - public String getFilepath() { - return filepath; - } - - /** - * Get the filename. This is the last element of the filepath - * that has been set in this class. - * - * @return filename - */ - public String getFilename() { - File file = new File(filepath); - return file.getName(); - } - - /** - * Set the filepath. - * - * @param filepath The filepath. - */ - public void setFilepath(String filepath) { - this.filepath = filepath; - } - - /** - * Get the destination collection. - * - * @return The collection. - */ - public String getDestination() { - return destination; - } - - /** - * Set the destination collection. - * - * @param destination The destination. - */ - public void setDestination(String destination) { - this.destination = destination; - } - - /** - * Get the filetype. - * @return The filetype. - */ - public String getFiletype() { - return filetype; - } - - /** - * Set the filetype. - * - * @param filetype The filetype. - */ - public void setFiletype(String filetype) { - this.filetype = filetype; - } - - /** - * Get the onBehalfOf value. - * - * @return The value. - */ - public String getOnBehalfOf() { - return onBehalfOf; - } - - /** - * Set the onBehalfOf value. - * - * @param onBehalfOf The value. - */ - public void setOnBehalfOf(String onBehalfOf) { - this.onBehalfOf = onBehalfOf; - } - - /** - * Get the MD5 status. - * @return The value. - */ - public boolean isUseMD5() { - return useMD5; - } - - /** - * Set the md5 state. - * - * @param useMD5 True if the message should use an MD5 checksum. - */ - public void setUseMD5(boolean useMD5) { - this.useMD5 = useMD5; - } - - /** - * Get the no-op state. - * - * @return The value. - */ - public boolean isNoOp() { - return noOp; - } - - /** - * Set the no-op state. - * - * @param noOp The no-op. - */ - public void setNoOp(boolean noOp) { - this.noOp = noOp; - } - - /** - * Get the verbose value. - * - * @return The value. - */ - public boolean isVerbose() { - return verbose; - } - - /** - * Set the verbose state. - * - * @param verbose True if the post message should send a - * verbose header. - */ - public void setVerbose(boolean verbose) { - this.verbose = verbose; - } - - /** - * Get the packaging format. - * - * @return The value. - */ - public String getPackaging() { - return packaging; - } - - /** - * Set the packaging format. - * - * @param packaging The packaging format. - */ - public void setFormatNamespace(String packaging) { - this.packaging = packaging; - } - - /** - * Get the status of the checksum error. - * - * @return True if the client should simulate a checksum error. - */ - public boolean getChecksumError() { - return checksumError; - } - - /** - * Set the state of the checksum error. - * - * @param checksumError True if the item should include a checksum error. - */ - public void setChecksumError(boolean checksumError) { - this.checksumError = checksumError; - } - - /** - * Get the status of the corrupt request flag. - * - * @return True if the client should corrupt the POST header. - */ - public boolean getCorruptRequest() { - return corruptRequest; - } - - /** - * Set the state of the corrupt request flag. - * - * @param corruptRequest True if the item should corrupt the POST header. - */ - public void setCorruptRequest(boolean corruptRequest) { - this.corruptRequest = corruptRequest; - } - - /** - * Set the Slug value. - * - * @param slug The value. - */ - public void setSlug(String slug) { - this.slug = slug; - } - - /** - * Get the Slug value. - * - * @return The Slug. - */ - public String getSlug() { - return this.slug; - } - - /** - * @return the userAgent - */ - public String getUserAgent() { - return userAgent; - } - - /** - * Set the user agent - * - * @param userAgent the userAgent to set - */ - public void setUserAgent(String userAgent) { - this.userAgent = userAgent; - } -} diff --git a/dspace-sword/src/main/java/org/purl/sword/client/PropertiesDialog.java b/dspace-sword/src/main/java/org/purl/sword/client/PropertiesDialog.java deleted file mode 100644 index 6d43cb68ef1..00000000000 --- a/dspace-sword/src/main/java/org/purl/sword/client/PropertiesDialog.java +++ /dev/null @@ -1,240 +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.purl.sword.client; - -import java.awt.BorderLayout; -import java.util.Enumeration; -import java.util.Properties; -import javax.swing.DefaultCellEditor; -import javax.swing.JFrame; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JTable; -import javax.swing.table.AbstractTableModel; -import javax.swing.table.TableCellEditor; - -/** - * Dialog that is used to edit the collection of properties. - * - * @author Neil Taylor, Suzana Barreto - */ -public class PropertiesDialog { - /** - * The parent frame for the dialog that is displayed. - */ - private JFrame parentFrame = null; - - /** - * Array that lists the labels for the buttons on the panel. - */ - private static Object[] options = {"OK", "Cancel"}; - - /** - * The panel that holds the controls to show. - */ - private JPanel controls = null; - - /** - * The configuration properties - */ - private Properties properties = null; - - /** - * Table that is used to display the list of properties. - */ - private JTable propertiesTable; - - /** - * Create a new instance. - * - * @param parentFrame The parent frame for the dialog. - * @param props The properties lisst to display - */ - public PropertiesDialog(JFrame parentFrame, Properties props) { - this.parentFrame = parentFrame; - properties = props; - controls = createControls(); - } - - /** - * Create the controls that are to be displayed in the system. - * - * @return A panel that contains the controls. - */ - protected final JPanel createControls() { - JPanel panel = new JPanel(new BorderLayout()); - propertiesTable = new JTable(new PropertiesModel()); - ((DefaultCellEditor) propertiesTable.getDefaultEditor(String.class)).setClickCountToStart(1); - JScrollPane scrollpane = new JScrollPane(propertiesTable, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, - JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); - panel.add(scrollpane, BorderLayout.CENTER); - return panel; - } - - - /** - * Show the dialog and return the status code. - * - * @return The status code returned from the dialog. - */ - public int show() { - int result = JOptionPane.showOptionDialog(parentFrame, - controls, - "Edit Properties", - JOptionPane.OK_CANCEL_OPTION, - JOptionPane.PLAIN_MESSAGE, - null, - options, - null); - - // cancel any edit in the table. If there is a cell editing, the getEditingColumn will - // return a non-negative column number. This can be used to retreive the cell editor. - // The code then gets the default editor and calls the stopCellEditing. If custom - // editors are used, an additional check must be made to get the cell editor - // for a specific cell. - int column = propertiesTable.getEditingColumn(); - - if (column > -1) { - TableCellEditor editor = propertiesTable.getDefaultEditor(propertiesTable.getColumnClass(column)); - if (editor != null) { - editor.stopCellEditing(); - } - } - - return result; - } - - - /** - * A table model that is used to show the properties. The model links directly - * to the underlying properties object. As changes are made in the table, the - * corresponding changes are made in the properties object. The user can only - * edit the value column in the table. - */ - public class PropertiesModel extends AbstractTableModel { - /** - * Column names. - */ - private String columns[] = {"Property Name", "Value"}; - - /** - * Create a new instance of the model. If no properties object exists, - * a default model is created. Note, this will allow the table to - * continue editing, although this value will not be passed back to - * the calling window. - */ - public PropertiesModel() { - super(); - if (properties == null) { - properties = new Properties(); - } - } - - /** - * Get the number of columns. - * - * @return The number of columns. - */ - public int getColumnCount() { - return columns.length; - } - - /** - * Get the number of rows. - * - * @return The number of rows. - */ - public int getRowCount() { - return properties.size(); - } - - /** - * Get the value that is at the specified cell. - * - * @param row The row for the cell. - * @param col The column for the cell. - * @return The data value from the properties. - */ - public Object getValueAt(int row, int col) { - if (col == 0) { - return getKeyValue(row); - } else { - String key = getKeyValue(row); - return properties.get(key); - } - } - - /** - * Retrieve the column name for the specified column. - * - * @param col The column number. - * @return The column name. - */ - public String getColumnName(int col) { - return columns[col]; - } - - /** - * Retrieve the column class. - * - * @param col The column number. - * @return The class for the object found at the column position. - */ - public Class getColumnClass(int col) { - return getValueAt(0, col).getClass(); - } - - /** - * Determine if the cell can be edited. This model will only - * allow the second column to be edited. - * - * @param row The cell row. - * @param col The cell column. - * @return True if the cell can be edited. Otherwise, false. - */ - public boolean isCellEditable(int row, int col) { - if (col == 1) { - return true; - } - return false; - } - - /** - * Set the value for the specified cell. - * - * @param value The value to set. - * @param row The row for the cell. - * @param col The column. - */ - public void setValueAt(Object value, int row, int col) { - String key = getKeyValue(row); - properties.setProperty(key, ((String) value)); - fireTableCellUpdated(row, col); - } - - /** - * Get the Key value for the specified row. - * - * @param row The row. - * @return A string that shows the key value. - */ - public String getKeyValue(int row) { - int count = 0; - Enumeration k = properties.keys(); - while (k.hasMoreElements()) { - String key = (String) k.nextElement(); - if (count == row) { - return key; - } - count++; - } - return null; - } - } -} diff --git a/dspace-sword/src/main/java/org/purl/sword/client/SWORDClient.java b/dspace-sword/src/main/java/org/purl/sword/client/SWORDClient.java deleted file mode 100644 index 0df7fd53104..00000000000 --- a/dspace-sword/src/main/java/org/purl/sword/client/SWORDClient.java +++ /dev/null @@ -1,85 +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.purl.sword.client; - -import org.purl.sword.base.DepositResponse; -import org.purl.sword.base.ServiceDocument; - -/** - * Interface for any SWORD client implementation. - */ -public interface SWORDClient { - /** - * Set the server that is to be contacted on the next access. - * - * @param server The name of the server, e.g. www.aber.ac.uk - * @param port The port number, e.g. 80. - */ - public void setServer(String server, int port); - - /** - * Set the user credentials that are to be used for subsequent accesses. - * - * @param username The username. - * @param password The password. - */ - public void setCredentials(String username, String password); - - /** - * Clear the credentials settings on the client. - */ - public void clearCredentials(); - - /** - * Set the proxy that is to be used for subsequent accesses. - * - * @param host The host name, e.g. cache.host.com. - * @param port The port, e.g. 8080. - */ - public void setProxy(String host, int port); - - /** - * Get the status result returned from the most recent network test. - * - * @return The status code and message. - */ - public Status getStatus(); - - /** - * Get a service document, specified in the URL. - * - * @param url The URL to connect to. - * @return A ServiceDocument that contains the Service details that were - * obained from the specified URL. - * @throws SWORDClientException If there is an error accessing the - * URL. - */ - public ServiceDocument getServiceDocument(String url) throws SWORDClientException; - - /** - * Get a service document, specified in the URL. The document is accessed on - * behalf of the specified user. - * - * @param url The URL to connect to. - * @param onBehalfOf The username for the onBehalfOf access. - * @return A ServiceDocument that contains the Service details that were - * obtained from the specified URL. - * @throws SWORDClientException If there is an error accessing the URL. - */ - public ServiceDocument getServiceDocument(String url, String onBehalfOf) throws SWORDClientException; - - /** - * Post a file to the specified destination URL. - * - * @param message The message that defines the requirements for the operation. - * @return A DespoitResponse if the response is successful. If there was an error, - * null should be returned. - * @throws SWORDClientException If there is an error accessing the URL. - */ - public DepositResponse postFile(PostMessage message) throws SWORDClientException; -} diff --git a/dspace-sword/src/main/java/org/purl/sword/client/SWORDClientException.java b/dspace-sword/src/main/java/org/purl/sword/client/SWORDClientException.java deleted file mode 100644 index 1355b9d4ab6..00000000000 --- a/dspace-sword/src/main/java/org/purl/sword/client/SWORDClientException.java +++ /dev/null @@ -1,42 +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.purl.sword.client; - -/** - * Represents an exception thrown by the SWORD Client. - * - * @author Neil Taylor - */ -public class SWORDClientException extends Exception { - /** - * Create a new exception, without a message. - */ - public SWORDClientException() { - super(); - } - - /** - * Create a new exception with the specified message. - * - * @param message The message. - */ - public SWORDClientException(String message) { - super(message); - } - - /** - * Create a new exception with the specified message and set - * the exception that generated this error. - * - * @param message The message. - * @param cause The original exception. - */ - public SWORDClientException(String message, Exception cause) { - super(message, cause); - } -} diff --git a/dspace-sword/src/main/java/org/purl/sword/client/SWORDComboBox.java b/dspace-sword/src/main/java/org/purl/sword/client/SWORDComboBox.java deleted file mode 100644 index 42fcc082ff5..00000000000 --- a/dspace-sword/src/main/java/org/purl/sword/client/SWORDComboBox.java +++ /dev/null @@ -1,129 +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/ - */ - -/** - * Copyright (c) 2007, Aberystwyth University - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * - Redistributions of source code must retain the above - * copyright notice, this list of conditions and the - * following disclaimer. - * - * - Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * - Neither the name of the Centre for Advanced Software and - * Intelligent Systems (CASIS) nor the names of its - * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR - * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF - * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ -package org.purl.sword.client; - -import javax.swing.JComboBox; - -/** - * An extension of the JComboBox class. This adds a method that - * can update the list of items with the item. The update will only - * work on combo boxes that are set to editable. - * - * @author Neil Taylor - */ -public class SWORDComboBox extends JComboBox { - /** - * Create an instance of the SWORD Combo box. - */ - public SWORDComboBox() { - super(); - setEditable(true); - } - - /** - * Update the list for the Combo box with the currently selected - * item. This will only add an item to the list if: i) the control - * is editable, ii) the selected item is not empty and iii) the - * item is not already in the list. - */ - public void updateList() { - Object s = getSelectedItem(); - - if (!isEditable() || s == null || ((String) s).trim().length() == 0) { - // don't update with an empty item or if the combo box is not editable. - return; - } - - insertItem(s); - } - - /** - * Insert an item into the combo box. This will only be added - * if the item is not already present in the combo box. - * - * @param newItem The item to insert. - */ - public void insertItem(Object newItem) { - int count = getItemCount(); - - boolean found = false; - - for (int i = 0; i < count && !found; i++) { - Object item = getItemAt(i); - if (item != null && item.equals(newItem)) { - found = true; - } - } - - if (!found) { - addItem(newItem); - } - } - - /** - * Insert multiple items into the combo box. - * - * @param items The array of items. - */ - public void insertItems(String[] items) { - for (String item : items) { - insertItem(item); - } - } - - /** - * Get the text of the currently selected item in the combo box. - * @return The text. null is returned if no item - * is selected. - */ - public String getText() { - Object o = getSelectedItem(); - if (o != null) { - return o.toString().trim(); - } - - return null; - } -} diff --git a/dspace-sword/src/main/java/org/purl/sword/client/SWORDFormPanel.java b/dspace-sword/src/main/java/org/purl/sword/client/SWORDFormPanel.java deleted file mode 100644 index 81de59ae68c..00000000000 --- a/dspace-sword/src/main/java/org/purl/sword/client/SWORDFormPanel.java +++ /dev/null @@ -1,144 +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.purl.sword.client; - -import java.awt.Component; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import javax.swing.JPanel; - -/** - * Utility class. Creates a two column form. The left column is used to show - * the label for the row. The right column is used to show the control, e.g. - * text box, combo box or checkbox, for the row. - * - * @author Neil Taylor - */ -public class SWORDFormPanel extends JPanel { - /** - * Constraints used to control the layout on the panel. - */ - private GridBagConstraints labelConstraints; - - /** - * Constraints used to control the layout of the input controls on the panel. - */ - private GridBagConstraints controlConstraints; - - /** - * Index to the next row. - */ - private int rowIndex = 0; - - /** - * Insets for the top row of the label column. - */ - private Insets labelTop = new Insets(10, 10, 0, 0); - - /** - * Insets for the top row of the control column. - */ - private Insets controlTop = new Insets(10, 4, 0, 10); - - /** - * Insets for a general row in the label column. - */ - private Insets labelGeneral = new Insets(3, 10, 0, 0); - - /** - * Insets for a general row in the control column. - */ - private Insets controlGeneral = new Insets(3, 4, 0, 10); - - /** - * Create a new instance of the class. - */ - public SWORDFormPanel() { - super(); - setLayout(new GridBagLayout()); - - labelConstraints = new GridBagConstraints(); - labelConstraints.fill = GridBagConstraints.NONE; - labelConstraints.anchor = GridBagConstraints.LINE_END; - labelConstraints.weightx = 0.1; - - controlConstraints = new GridBagConstraints(); - controlConstraints.fill = GridBagConstraints.HORIZONTAL; - controlConstraints.weightx = 0.9; - } - - /** - * Add the specified component as the first row. It will occupy two - * columns. - * - * @param one The control to add. - */ - public void addFirstRow(Component one) { - addRow(one, null, labelTop, controlTop); - } - - /** - * Add the specified components as the first row in the form. - * - * @param one The label component. - * @param two The control component. - */ - public void addFirstRow(Component one, Component two) { - addRow(one, two, labelTop, controlTop); - } - - /** - * Add a component to the general row. This will be added in the label column. - * - * @param one The component. - */ - public void addRow(Component one) { - addRow(one, null); - } - - /** - * Add a component to the general row. - * - * @param one The component to add to the label column. - * @param two The component to add to the control column. - */ - public void addRow(Component one, Component two) { - addRow(one, two, labelGeneral, controlGeneral); - } - - /** - * Add a row to the table. - * - * @param one The component to display in the label column. - * @param two The component to display in the control column. - * @param labels The insets for the label column. - * @param controls The insets for the controls column. - */ - protected void addRow(Component one, Component two, Insets labels, Insets controls) { - labelConstraints.insets = labels; - labelConstraints.gridx = 0; - labelConstraints.gridy = rowIndex; - if (two == null) { - labelConstraints.gridwidth = 2; - } else { - labelConstraints.gridwidth = 1; - } - - add(one, labelConstraints); - - if (two != null) { - controlConstraints.insets = controls; - controlConstraints.gridx = 1; - controlConstraints.gridy = rowIndex; - add(two, controlConstraints); - } - - rowIndex++; - } -} diff --git a/dspace-sword/src/main/java/org/purl/sword/client/ServiceDialog.java b/dspace-sword/src/main/java/org/purl/sword/client/ServiceDialog.java deleted file mode 100644 index 03489f0d2e7..00000000000 --- a/dspace-sword/src/main/java/org/purl/sword/client/ServiceDialog.java +++ /dev/null @@ -1,227 +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/ - */ - -/** - * Copyright (c) 2007, Aberystwyth University - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * - Redistributions of source code must retain the above - * copyright notice, this list of conditions and the - * following disclaimer. - * - * - Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * - Neither the name of the Centre for Advanced Software and - * Intelligent Systems (CASIS) nor the names of its - * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR - * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF - * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ -package org.purl.sword.client; - -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JPasswordField; - -/** - * Dialog that prompts the user to enter the details for a service - * document location. - * - * @author Neil Taylor - */ -public class ServiceDialog { - /** - * The username. - */ - private SWORDComboBox username; - - /** - * The password. - */ - private JPasswordField password; - - /** - * Holds the URL for the collection. - */ - private SWORDComboBox location; - - /** - * The combo box that shows the list of onBehalfOf items. - */ - private SWORDComboBox onBehalfOf; - - /** - * Parent frame for the dialog. - */ - private JFrame parentFrame = null; - - /** - * The panel that holds the controls. - */ - private JPanel controls = null; - - /** - * List of buttons. - */ - private static Object[] options = {"Get Service Document", "Cancel"}; - - /** - * Create a new instance. - * - * @param parentFrame The parent frame. The dialog will be shown over the - * centre of this frame. - */ - public ServiceDialog(JFrame parentFrame) { - this.parentFrame = parentFrame; - controls = createControls(); - } - - /** - * Show the dialog. - * - * @return The close option. This is one of the dialog options from - * JOptionPane. - */ - public int show() { - int result = JOptionPane.showOptionDialog(parentFrame, - controls, - "Get Service Document", - JOptionPane.OK_CANCEL_OPTION, - JOptionPane.PLAIN_MESSAGE, - null, - options, - options[1]); - - if (result == JOptionPane.OK_OPTION) { - // update the combo boxes with the values - username.updateList(); - location.updateList(); - onBehalfOf.updateList(); - } - - return result; - } - - /** - * Create the controls that are displayed in the dialog. - * - * @return The panel that contains the controls. - */ - protected final JPanel createControls() { - username = new SWORDComboBox(); - username.setEditable(true); - password = new JPasswordField(); - location = new SWORDComboBox(); - location.setEditable(true); - onBehalfOf = new SWORDComboBox(); - onBehalfOf.setEditable(true); - - JLabel userLabel = new JLabel("Username:", JLabel.TRAILING); - JLabel passwordLabel = new JLabel("Password:", JLabel.TRAILING); - JLabel locationLabel = new JLabel("Location:", JLabel.TRAILING); - JLabel onBehalfOfLabel = new JLabel("On Behalf Of:", JLabel.TRAILING); - - SWORDFormPanel panel = new SWORDFormPanel(); - panel.addFirstRow(userLabel, username); - panel.addRow(passwordLabel, password); - panel.addRow(locationLabel, location); - panel.addRow(onBehalfOfLabel, onBehalfOf); - - return panel; - } - - /** - * Get the username from the controls on the dialog. - * - * @return The username. - */ - public String getUsername() { - return username.getText(); - } - - /** - * Get the password from the dialog. - * - * @return The password. - */ - public String getPassword() { - return new String(password.getPassword()); - } - - /** - * The location from the dialog. - * - * @return The location. - */ - public String getLocation() { - return location.getText(); - } - - /** - * The onBehalfOf value from the dialog. - * - * @return The onBehalfOf value. - */ - public String getOnBehalfOf() { - String text = onBehalfOf.getText().trim(); - if (text.length() == 0) { - return null; - } - return text; - } - - /** - * Add the list of user ids to the dialog. - * - * @param users The list of user ids. - */ - public void addUserIds(String[] users) { - username.insertItems(users); - } - - /** - * Add the list of service URLs. - * - * @param services The service URLs. - */ - public void addServiceUrls(String[] services) { - location.insertItems(services); - } - - /** - * Add a list of onBehalfOf names. - * - * @param users The list of onBehalfOf items. - */ - public void addOnBehalfOf(String[] users) { - onBehalfOf.insertItems(users); - } - -} diff --git a/dspace-sword/src/main/java/org/purl/sword/client/ServicePanel.java b/dspace-sword/src/main/java/org/purl/sword/client/ServicePanel.java deleted file mode 100644 index bc3031c3335..00000000000 --- a/dspace-sword/src/main/java/org/purl/sword/client/ServicePanel.java +++ /dev/null @@ -1,843 +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/ - */ - -/** - * Copyright (c) 2007, Aberystwyth University - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * - Redistributions of source code must retain the above - * copyright notice, this list of conditions and the - * following disclaimer. - * - * - Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * - Neither the name of the Centre for Advanced Software and - * Intelligent Systems (CASIS) nor the names of its - * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR - * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF - * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ -package org.purl.sword.client; - -import java.awt.BorderLayout; -import java.awt.Component; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.Iterator; -import java.util.List; -import javax.swing.Icon; -import javax.swing.ImageIcon; -import javax.swing.JComponent; -import javax.swing.JEditorPane; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JSplitPane; -import javax.swing.JTree; -import javax.swing.ToolTipManager; -import javax.swing.event.TreeSelectionEvent; -import javax.swing.event.TreeSelectionListener; -import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.DefaultTreeCellRenderer; -import javax.swing.tree.DefaultTreeModel; -import javax.swing.tree.TreePath; - -import org.purl.sword.atom.Author; -import org.purl.sword.atom.Content; -import org.purl.sword.atom.Contributor; -import org.purl.sword.atom.Generator; -import org.purl.sword.atom.Link; -import org.purl.sword.atom.TextConstruct; -import org.purl.sword.base.Collection; -import org.purl.sword.base.DepositResponse; -import org.purl.sword.base.SWORDEntry; -import org.purl.sword.base.Service; -import org.purl.sword.base.ServiceDocument; -import org.purl.sword.base.SwordAcceptPackaging; -import org.purl.sword.base.Workspace; - -/** - * The main panel for the GUI client. This contains the top-two sub-panels: the - * tree and the text area to show the details of the selected node. - * - * @author Neil Taylor - */ -public class ServicePanel extends JPanel - implements TreeSelectionListener { - /** - * The top level item in the tree that lists services. - */ - DefaultMutableTreeNode top; - - /** - * The tree model used to display the items. - */ - DefaultTreeModel treeModel = null; - - /** - * Tree that holds the list of services. - */ - private JTree services; - - /** - * The panel that shows an HTML table with any details for the selected - * node in the services tree. - */ - private JEditorPane details; - - /** - * A registered listener. This listener will be notified when there is a - * different node selected in the service tree. - */ - private ServiceSelectedListener listener; - - /** - * Create a new instance of the panel. - */ - public ServicePanel() { - super(); - setLayout(new BorderLayout()); - - top = new DefaultMutableTreeNode("Services & Posted Files"); - treeModel = new DefaultTreeModel(top); - - services = new JTree(treeModel); - services.setCellRenderer(new ServicePostTreeRenderer()); - - JScrollPane servicesPane = new JScrollPane(services, - JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, - JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); - - details = new JEditorPane("text/html", - "

    Details

    This panel will show the details for the currently " + - "selected item in the tree.

    "); - - JScrollPane detailsPane = new JScrollPane(details, - JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, - JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); - - JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, - servicesPane, - detailsPane); - splitPane.setOneTouchExpandable(true); - splitPane.setResizeWeight(0.5); - splitPane.setDividerLocation(200); - - services.addTreeSelectionListener(this); - ToolTipManager.sharedInstance().registerComponent(services); - - add(splitPane, BorderLayout.CENTER); - } - - /** - * Renderer that displays the icons for the tree nodes. - * - * @author Neil Taylor - */ - static class ServicePostTreeRenderer extends DefaultTreeCellRenderer { - Icon workspaceIcon; - Icon serviceIcon; - Icon collectionIcon; - Icon fileIcon; - - /** - * Initialise the renderer. Load the icons. - */ - public ServicePostTreeRenderer() { - ClassLoader loader = this.getClass().getClassLoader(); - workspaceIcon = new ImageIcon(loader.getResource("images/WorkspaceNodeImage.gif")); - serviceIcon = new ImageIcon(loader.getResource("images/ServiceNodeImage.gif")); - collectionIcon = new ImageIcon(loader.getResource("images/CollectionNodeImage.gif")); - fileIcon = new ImageIcon(loader.getResource("images/ServiceNodeImage.gif")); - } - - /** - * Return the cell renderer. This will be the default tree cell renderer - * with a different icon depending upon the type of data in the node. - * - * @param tree The JTree control. - * @param value The value to display. - * @param sel True if the node is selected. - * @param expanded True if the node is expanded. - * @param leaf True if the node is a leaf. - * @param row The row. - * @param hasFocus True if the node has focus. - */ - public Component getTreeCellRendererComponent( - JTree tree, - Object value, - boolean sel, - boolean expanded, - boolean leaf, - int row, - boolean hasFocus) { - - JComponent comp = (JComponent) super.getTreeCellRendererComponent( - tree, value, sel, - expanded, leaf, row, - hasFocus); - - DefaultMutableTreeNode node = - (DefaultMutableTreeNode) value; - - Object o = node.getUserObject(); - if (o instanceof TreeNodeWrapper) { - TreeNodeWrapper wrapper = (TreeNodeWrapper) o; - comp.setToolTipText(wrapper.toString()); - Object data = wrapper.getData(); - if (data instanceof Service) { - setIcon(serviceIcon); - } else if (data instanceof Workspace) { - setIcon(workspaceIcon); - } else if (data instanceof Collection) { - setIcon(collectionIcon); - } else if (data instanceof SWORDEntry) { - setIcon(fileIcon); - } - } else { - comp.setToolTipText(null); - } - return comp; - } - - - } - - /** - * Set the service selected listener. This listener will be notified when - * there is a selection change in the tree. - * - * @param listener The listener. - */ - public void setServiceSelectedListener(ServiceSelectedListener listener) { - this.listener = listener; - } - - /** - * Process the specified service document. Add the details as a new child of the - * root of the tree. - * - * @param url The url used to access the service document. - * @param doc The service document. - */ - public void processServiceDocument(String url, - ServiceDocument doc) { - TreeNodeWrapper wrapper = null; - - Service service = doc.getService(); - wrapper = new TreeNodeWrapper(url, service); - DefaultMutableTreeNode serviceNode = new DefaultMutableTreeNode(wrapper); - treeModel.insertNodeInto(serviceNode, top, top.getChildCount()); - services.scrollPathToVisible(new TreePath(serviceNode.getPath())); - - // process the workspaces - DefaultMutableTreeNode workspaceNode = null; - - Iterator workspaces = service.getWorkspaces(); - for (; workspaces.hasNext(); ) { - Workspace workspace = workspaces.next(); - wrapper = new TreeNodeWrapper(workspace.getTitle(), workspace); - workspaceNode = new DefaultMutableTreeNode(wrapper); - treeModel.insertNodeInto(workspaceNode, serviceNode, serviceNode.getChildCount()); - services.scrollPathToVisible(new TreePath(workspaceNode.getPath())); - - DefaultMutableTreeNode collectionNode = null; - Iterator collections = workspace.collectionIterator(); - for (; collections.hasNext(); ) { - Collection collection = collections.next(); - wrapper = new TreeNodeWrapper(collection.getTitle(), collection); - collectionNode = new DefaultMutableTreeNode(wrapper); - treeModel.insertNodeInto(collectionNode, workspaceNode, workspaceNode.getChildCount()); - services.scrollPathToVisible(new TreePath(collectionNode.getPath())); - } - } // for - } - - /** - * Holds the data for a tree node. It specifies the name that will be displayed - * in the node, and stores associated data. - * - * @author Neil Taylor - */ - static class TreeNodeWrapper { - /** - * The node name. - */ - private String name; - - /** - * The user data. - */ - private Object userObject; - - /** - * Create a new instance. - * - * @param name The name of the node. - * @param data The data in the node. - */ - public TreeNodeWrapper(String name, Object data) { - this.name = name; - this.userObject = data; - } - - /** - * Retrieve the data that is stored in this node. - * - * @return The data. - */ - public Object getData() { - return userObject; - } - - /** - * Get a string description for this node. - */ - public String toString() { - if (name == null || name.trim().equals("")) { - return "Unspecified"; - } - - return name; - } - } - - /** - * Respond to a changed tree selection event. Update the details panel to - * show an appropriate message for the newly selected node. Also, - * alert the selection listener for this panel. The listener will receive - * a path, if a collection has been selected. Otherwise, the listener - * will receive null. - */ - public void valueChanged(TreeSelectionEvent evt) { - // Get all nodes whose selection status has changed - TreePath[] paths = evt.getPaths(); - - for (int i = 0; i < paths.length; i++) { - if (evt.isAddedPath(i)) { - // process new selections - DefaultMutableTreeNode node; - node = (DefaultMutableTreeNode) (paths[i].getLastPathComponent()); - - Object o = node.getUserObject(); - if (o instanceof TreeNodeWrapper) { - try { - TreeNodeWrapper wrapper = (TreeNodeWrapper) o; - Object data = wrapper.getData(); - if (data instanceof Service) { - showService((Service) data); - alertListener(null); - } else if (data instanceof Workspace) { - showWorkspace((Workspace) data); - if (listener != null) { - alertListener(null); - } - } else if (data instanceof Collection) { - Collection c = (Collection) data; - showCollection(c); - alertListener(c.getLocation()); - } else if (data instanceof SWORDEntry) { - showEntry((SWORDEntry) data); - alertListener(null); - } else { - details.setText("unknown"); - alertListener(null); - } - } catch (Exception e) { - details.setText( - "An error occurred. The message was: " + e.getMessage() + ""); - alertListener(null); - e.printStackTrace(); - } - } else { - details.setText("please select one of the other nodes"); - alertListener(null); - } - } - - } - } - - /** - * Notify the listener that there has been a change to the currently selected - * item in the tree. - * - * @param value The value to send to the listener. - */ - private void alertListener(String value) { - if (listener != null) { - listener.selected(value); - } - } - - /** - * Add a new HTML table row to the specified StringBuffer. The label is displayed in - * the left column and the value is displayed in the right column. - * - * @param buffer The destination string buffer. - * @param label The label to add. - * @param value The corresponding value to add. - */ - private void addTableRow(StringBuffer buffer, String label, Object value) { - buffer.append(""); - buffer.append(label); - buffer.append(""); - buffer.append(displayableValue(value)); - buffer.append(""); - } - - /** - * Show the specified service data in the details panel. - * - * @param service The service node to display. - */ - private void showService(Service service) { - StringBuffer buffer = new StringBuffer(); - buffer.append(""); - buffer.append(""); - - buffer.append(""); - buffer.append(""); - addTableRow(buffer, "SWORD Version", service.getVersion()); - addTableRow(buffer, "NoOp Support ", service.isNoOp()); - addTableRow(buffer, "Verbose Support ", service.isVerbose()); - - String maxSize = ""; - - // Commented out the following code as the client code is out of step with the - // Sword 'base' library and wont compile. - Robin Taylor. - //if ( service.maxUploadIsDefined() ) - //{ - // maxSize = "" + service.getMaxUploadSize() + "kB"; - //} - //else - //{ - maxSize = "undefined"; - //} - - addTableRow(buffer, "Max File Upload Size ", maxSize); - - buffer.append("
    Service Summary
    "); - - buffer.append(""); - buffer.append(""); - details.setText(buffer.toString()); - } - - /** - * Display the workspace data in the details panel. - * - * @param workspace The workspace. - */ - private void showWorkspace(Workspace workspace) { - StringBuffer buffer = new StringBuffer(); - buffer.append(""); - buffer.append(""); - - buffer.append(""); - buffer - .append(""); - addTableRow(buffer, "Workspace Title", workspace.getTitle()); - buffer.append("
    Workspace Summary
    "); - - buffer.append(""); - buffer.append(""); - details.setText(buffer.toString()); - } - - /** - * Return the parameter unmodified if set, or the not defined text if null - * @param s - * @return s or ClientConstants.NOT_DEFINED_TEXT - */ - private Object displayableValue(Object s) { - if (null == s) { - return ClientConstants.NOT_DEFINED_TEXT; - } else { - return s; - } - } - - /** - * Add a string within paragraph tags. - * - * @param buffer The buffer to add the message to. - * @param message The message to add. - */ - private void addPara(StringBuffer buffer, String message) { - buffer.append("

    " + message + "

    "); - } - - /** - * Show the specified collection data in the details panel. - * - * @param collection The collection data. - */ - private void showCollection(Collection collection) { - StringBuffer buffer = new StringBuffer(); - buffer.append(""); - buffer.append(""); - - if (collection == null) { - addPara(buffer, "Invalid Collection object. Unable to display details."); - } else { - buffer.append(""); - buffer.append( - ""); - addTableRow(buffer, "Collection location", collection.getLocation()); - addTableRow(buffer, "Collection title", collection.getTitle()); - addTableRow(buffer, "Abstract", collection.getAbstract()); - addTableRow(buffer, "Collection Policy", collection.getCollectionPolicy()); - addTableRow(buffer, "Treatment", collection.getTreatment()); - addTableRow(buffer, "Mediation", collection.getMediation()); - addTableRow(buffer, "Nested Service Document", collection.getService()); - - String[] accepts = collection.getAccepts(); - StringBuilder acceptList = new StringBuilder(); - if (accepts != null && accepts.length == 0) { - acceptList.append("None specified"); - } else { - for (String s : accepts) { - acceptList.append(s).append("
    "); - } - } - addTableRow(buffer, "Accepts", acceptList.toString()); - - List acceptsPackaging = collection.getAcceptPackaging(); - - StringBuilder acceptPackagingList = new StringBuilder(); - for (Iterator i = acceptsPackaging.iterator(); i.hasNext(); ) { - SwordAcceptPackaging accept = (SwordAcceptPackaging) i.next(); - acceptPackagingList.append(accept.getContent()).append(" (").append(accept.getQualityValue()) - .append(")"); - - // add a , separator if there are any more items in the list - if (i.hasNext()) { - acceptPackagingList.append(", "); - } - } - - addTableRow(buffer, "Accepts Packaging", acceptPackagingList.toString()); - - buffer.append("
    Collection Summary
    "); - } - - buffer.append(""); - buffer.append(""); - details.setText(buffer.toString()); - } - - /** - * Display the contents of a Post entry in the display panel. - * - * @param entry The entry to display. - */ - private void showEntry(SWORDEntry entry) { - StringBuffer buffer = new StringBuffer(); - buffer.append(""); - buffer.append(""); - - if (entry == null) { - addPara(buffer, "Invalid Entry object. Unable to display details."); - } else { - buffer.append(""); - buffer - .append(""); - - // process atom:title - String titleString = getTextConstructDetails(entry.getSummary()); - addTableRow(buffer, "Title", titleString); - - // process id - addTableRow(buffer, "ID", entry.getId()); - - // process updated - addTableRow(buffer, "Date Updated", entry.getUpdated()); - - String authorString = getAuthorDetails(entry.getAuthors()); - addTableRow(buffer, "Authors", authorString); - - // process summary - String summaryString = getTextConstructDetails(entry.getSummary()); - addTableRow(buffer, "Summary", summaryString); - - // process content - Content content = entry.getContent(); - String contentString = ""; - if (content == null) { - contentString = "Not defined."; - } else { - contentString += "Source: '" + content.getSource() + "', Type: '" + - content.getType() + "'"; - } - addTableRow(buffer, "Content", contentString); - - // process links - Iterator links = entry.getLinks(); - StringBuffer linkBuffer = new StringBuffer(); - for (; links.hasNext(); ) { - Link link = links.next(); - linkBuffer.append("href: '"); - linkBuffer.append(link.getHref()); - linkBuffer.append("', href lang: '"); - linkBuffer.append(link.getHreflang()); - linkBuffer.append("', rel: '"); - linkBuffer.append(link.getRel()); - linkBuffer.append("')
    "); - } - if (linkBuffer.length() == 0) { - linkBuffer.append("Not defined"); - } - addTableRow(buffer, "Links", linkBuffer.toString()); - - // process contributors - String contributorString = getContributorDetails(entry.getContributors()); - addTableRow(buffer, "Contributors", contributorString); - - // process source - String sourceString = ""; - Generator generator = entry.getGenerator(); - if (generator != null) { - sourceString += "Content: '" + generator.getContent() + "'
    '"; - sourceString += "Version: '" + generator.getVersion() + "'
    '"; - sourceString += "Uri: '" + generator.getUri() + "'"; - } else { - sourceString += "No generator defined."; - } - - addTableRow(buffer, "Generator", sourceString); - - // process treatment - addTableRow(buffer, "Treatment", entry.getTreatment()); - - // process verboseDescription - addTableRow(buffer, "Verbose Description", entry.getVerboseDescription()); - - // process noOp - addTableRow(buffer, "NoOp", entry.isNoOp()); - - // process formatNamespace - addTableRow(buffer, "Packaging", entry.getPackaging()); - - // process userAgent - addTableRow(buffer, "User Agent", entry.getUserAgent()); - - - buffer.append("
    Entry Summary
    "); - } - - buffer.append(""); - buffer.append(""); - details.setText(buffer.toString()); - } - - /** - * Retrieve the details for a TextConstruct object. - * - * @param data The text construct object to display. - * - * @return Either 'Not defined' if the data is null, or - * details of the text content element. - */ - private String getTextConstructDetails(TextConstruct data) { - String summaryStr = ""; - if (data == null) { - summaryStr = "Not defined"; - } else { - summaryStr = "Content: '" + data.getContent() + "', Type: "; - if (data.getType() != null) { - summaryStr += "'" + data.getType().toString() + "'"; - } else { - summaryStr += "undefined."; - } - } - - return summaryStr; - } - - /** - * Get the author details and insert them into a string. - * - * @param authors the list of authors to process. - * - * @return A string containing the list of authors. - */ - private String getAuthorDetails(Iterator authors) { - // process author - StringBuffer authorBuffer = new StringBuffer(); - for (; authors.hasNext(); ) { - Author a = authors.next(); - authorBuffer.append(getAuthorDetails(a)); - } - - if (authorBuffer.length() == 0) { - authorBuffer.append("Not defined"); - } - - return authorBuffer.toString(); - } - - /** - * Get the contributor details and insert them into a string. - * - * @param contributors The contributors. - * - * @return The string that lists the details of the contributors. - */ - private String getContributorDetails(Iterator contributors) { - // process author - StringBuffer authorBuffer = new StringBuffer(); - for (; contributors.hasNext(); ) { - Contributor c = contributors.next(); - authorBuffer.append(getAuthorDetails(c)); - } - - if (authorBuffer.length() == 0) { - authorBuffer.append("Not defined"); - } - - return authorBuffer.toString(); - } - - /** - * Build a string that describes the specified author. - * - * @param author The author. - * - * @return The string description. - */ - private String getAuthorDetails(Author author) { - // process author - StringBuffer authorBuffer = new StringBuffer(); - authorBuffer.append(author.getName()); - authorBuffer.append(" (email: '"); - authorBuffer.append(author.getEmail()); - authorBuffer.append("', uri: '"); - authorBuffer.append(author.getUri()); - authorBuffer.append("')
    "); - - return authorBuffer.toString(); - } - - /** - * Process the deposit response and insert the details into the tree. If the url - * matches one of the collections in the tree, the deposit is added as a child - * node. Otherwise, the node is added as a child of the root. - * - * @param url The url of the collection that the file was posted to. - * - * @param response The details of the deposit. - */ - public void processDepositResponse(String url, - DepositResponse response) { - SWORDEntry entry = response.getEntry(); - Object title = entry.getTitle(); - if (title == null) { - title = "Undefined"; - } else { - title = entry.getTitle().getContent(); - } - - TreeNodeWrapper wrapper = new TreeNodeWrapper(title.toString(), entry); - DefaultMutableTreeNode entryNode = new DefaultMutableTreeNode(wrapper); - - DefaultMutableTreeNode newParentNode = top; - List nodes = getCollectionNodes(); - for (DefaultMutableTreeNode node : nodes) { - Object o = node.getUserObject(); - if (o instanceof TreeNodeWrapper) { - TreeNodeWrapper collectionWrapper = (TreeNodeWrapper) o; - Object data = collectionWrapper.getData(); - if (data instanceof Collection) { - Collection col = (Collection) data; - String location = col.getLocation(); - if (location != null && location.equals(url)) { - newParentNode = node; - break; - } - } - } - } - - treeModel.insertNodeInto(entryNode, newParentNode, newParentNode.getChildCount()); - services.scrollPathToVisible(new TreePath(entryNode.getPath())); - } - - /** - * Get a list of all current collections displayed in the tree. - * - * @return An array of the URLs for the collections. - */ - public String[] getCollectionLocations() { - List nodes = getCollectionNodes(); - String[] locations = new String[nodes.size()]; - - DefaultMutableTreeNode node; - for (int i = 0; i < nodes.size(); i++) { - node = nodes.get(i); - Object o = node.getUserObject(); - if (o instanceof TreeNodeWrapper) { - TreeNodeWrapper collectionWrapper = (TreeNodeWrapper) o; - Object data = collectionWrapper.getData(); - if (data instanceof Collection) { - Collection col = (Collection) data; - String location = col.getLocation(); - if (location != null) { - locations[i] = location; - } - } - } - } - return locations; - } - - /** - * Get a list of nodes that contain collections. - * - * @return A vector of the collection nodes. - */ - private List getCollectionNodes() { - List nodes = new ArrayList(); - - DefaultMutableTreeNode node; - Enumeration treeNodes = top.depthFirstEnumeration(); - - while (treeNodes.hasMoreElements()) { - node = (DefaultMutableTreeNode) treeNodes.nextElement(); - Object o = node.getUserObject(); - if (o instanceof TreeNodeWrapper) { - TreeNodeWrapper wrapper = (TreeNodeWrapper) o; - Object data = wrapper.getData(); - if (data instanceof Collection) { - nodes.add(node); - } - } - } - - return nodes; - } -} diff --git a/dspace-sword/src/main/java/org/purl/sword/client/ServiceSelectedListener.java b/dspace-sword/src/main/java/org/purl/sword/client/ServiceSelectedListener.java deleted file mode 100644 index fa97549fd92..00000000000 --- a/dspace-sword/src/main/java/org/purl/sword/client/ServiceSelectedListener.java +++ /dev/null @@ -1,23 +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.purl.sword.client; - -/** - * Listener for any objects that want to be notified when a collection has been selected in the - * ServicePanel. - * - * @author Neil Taylor - */ -public interface ServiceSelectedListener { - /** - * Called to provide an update on whether the selected node is a Collection. - * - * @param collection The location of the collection. null, otherwise. - */ - public void selected(String collection); -} diff --git a/dspace-sword/src/main/java/org/purl/sword/client/ServletClient.java b/dspace-sword/src/main/java/org/purl/sword/client/ServletClient.java deleted file mode 100644 index 963e9637dd7..00000000000 --- a/dspace-sword/src/main/java/org/purl/sword/client/ServletClient.java +++ /dev/null @@ -1,455 +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/ - */ - -/** - * Copyright (c) 2008, Aberystwyth University - * All rights reserved. - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * - Redistributions of source code must retain the above - * copyright notice, this list of conditions and the - * following disclaimer. - * - * - Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * - Neither the name of the Centre for Advanced Software and - * Intelligent Systems (CASIS) nor the names of its - * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR - * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF - * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -package org.purl.sword.client; - -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.util.Iterator; -import java.util.List; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.commons.fileupload.FileItem; -import org.apache.commons.fileupload.FileItemFactory; -import org.apache.commons.fileupload.disk.DiskFileItemFactory; -import org.apache.commons.fileupload.servlet.ServletFileUpload; -import org.purl.sword.base.DepositResponse; -import org.purl.sword.base.SWORDEntry; -import org.purl.sword.base.ServiceDocument; - -/** - * Example client that runs as a Servlet. - * - * @author Stuart Lewis - */ -public class ServletClient extends HttpServlet { - - /** - * The user agent name of this library - */ - public static final String userAgent = "SWORDAPP Java Client: SWORD version 1.3 compatible (http://sourceforge" + - ".net/projects/sword-app/)"; - - /** - * Temporary directory. - */ - private String tempDirectory; - - /** - * List of urls for the destination services to access. - */ - private String[] urls; - - /** - * Used to determine if a proxy value should be set. - */ - private boolean useProxy = false; - - /** - * The proxy host name. - */ - private String pHost; - - /** - * The proxy port name. - */ - private int pPort; - - /** Counter used during Deposit information. */ - private static int counter = 0; - - /** - * Initialise the servlet. - */ - public void init() { - tempDirectory = getServletContext().getInitParameter( - "upload-temp-directory"); - if ((tempDirectory == null) || (tempDirectory.equals(""))) { - tempDirectory = System.getProperty("java.io.tmpdir"); - } - String lots = getServletContext().getInitParameter("client-urls"); - urls = lots.split(","); - - pHost = getServletContext().getInitParameter("proxy-host"); - String pPortstr = getServletContext().getInitParameter("proxy-port"); - if (((pHost != null) && (!pHost.equals(""))) - && ((pPortstr != null) && (!pPortstr.equals("")))) { - try { - pPort = Integer.parseInt(pPortstr); - useProxy = true; - } catch (Exception e) { - // Port number not numeric - } - } - } - - /** - * Handle a get request. Simply show the default form (form.jsp) - * - * @param request The request details - * @param response The response to write to. - * - * @throws ServletException - * An exception that provides information on a database access error or other errors. - * @throws IOException - * A general class of exceptions produced by failed or interrupted I/O operations. - */ - protected void doGet(HttpServletRequest request, - HttpServletResponse response) throws ServletException, IOException { - // Get request, so show the default page - request.setAttribute("urls", urls); - request.getRequestDispatcher("form.jsp").forward(request, response); - } - - /** - * Process the post. Determine if the request is for a post or service - * document. Then, dispatch the request to the appropriate handler. - * - * @param request The request details. - * @param response The response to write to. - * - * @throws ServletException - * An exception that provides information on a database access error or other errors. - * @throws IOException - * A general class of exceptions produced by failed or interrupted I/O operations. - */ - - protected void doPost(HttpServletRequest request, - HttpServletResponse response) throws ServletException, IOException { - if (request.getParameter("servicedocument") != null) { - this.doServiceDocument(request, response); - } else if (request.getParameter("deposit") != null) { - request.setAttribute("url", request.getParameter("url")); - request.setAttribute("u", request.getParameter("u")); - request.setAttribute("p", request.getParameter("p")); - request.setAttribute("obo", request.getParameter("obo")); - request.setAttribute("abstract", request.getParameter("abstract")); - request.setAttribute("policy", request.getParameter("policy")); - request.setAttribute("treatment", request.getParameter("treatment")); - request.setAttribute("mediation", request.getParameter("mediation")); - request.setAttribute("accepts", request.getParameter("accepts")); - request.setAttribute("acceptsp", request.getParameter("acceptsp")); - request.setAttribute("maxuploadsize", request.getParameter("maxuploadsize")); - request.getRequestDispatcher("depositform.jsp").forward(request, response); - } else if (ServletFileUpload.isMultipartContent(request)) { - this.doDeposit(request, response); - } else { - request.setAttribute("urls", urls); - request.getRequestDispatcher("form.jsp").forward(request, response); - } - } - - /** - * Process the request for a service document. - * - * @param request The request details. - * @param response The response to write to. - * - * @throws ServletException - * An exception that provides information on a database access error or other errors. - * @throws IOException - * A general class of exceptions produced by failed or interrupted I/O operations. - */ - - private void doServiceDocument(HttpServletRequest request, - HttpServletResponse response) throws ServletException, IOException { - // Get the service document - Client client = new Client(); - // Which URL do we want? - URL url = new URL(request.getParameter("url")); - String theUrl = request.getParameter("url"); - - if ((request.getParameter("ownurl") != null) - && (!request.getParameter("ownurl").equals(""))) { - url = new URL(request.getParameter("ownurl")); - theUrl = request.getParameter("ownurl"); - } - - int port = url.getPort(); - if (port == -1) { - port = 80; - } - - // Set up the server - client.setServer(url.getHost(), port); - client.setCredentials(request.getParameter("u"), request.getParameter("p")); - if (useProxy) { - client.setProxy(pHost, pPort); - } - - try { - ServiceDocument sd = client.getServiceDocument(theUrl, - request.getParameter("obo")); - - // Set the status - Status status = client.getStatus(); - request.setAttribute("status", status.toString()); - if (status.getCode() == 200) { - // Set the debug response - String xml = sd.marshall(); - - String validateXml = xml; - validateXml = validateXml.replaceAll("&", "&"); - validateXml = validateXml.replaceAll("<", "<"); - validateXml = validateXml.replaceAll(">", ">"); - validateXml = validateXml.replaceAll("\"", """); - validateXml = validateXml.replaceAll("'", "'"); - request.setAttribute("xmlValidate", validateXml); // for passing to validation - - xml = xml.replaceAll("<", "<"); - xml = xml.replaceAll(">", ">"); - request.setAttribute("xml", xml); - - // Set the ServiceDocument and associated values - request.setAttribute("sd", sd); - request.setAttribute("sdURL", theUrl); - request.setAttribute("u", request.getParameter("u")); - request.setAttribute("p", request.getParameter("p")); - request.setAttribute("sdOBO", request.getParameter("obo")); - request.getRequestDispatcher("servicedocument.jsp").forward( - request, response); - return; - } else { - request.setAttribute("error", status.getCode() + " " - + status.getMessage()); - request.setAttribute("urls", urls); - request.getRequestDispatcher("form.jsp").forward(request, - response); - return; - } - } catch (SWORDClientException e) { - e.printStackTrace(); - request.setAttribute("error", e.toString()); - request.setAttribute("urls", urls); - request.getRequestDispatcher("form.jsp").forward(request, response); - } - } - - /** - * Process a deposit. - * - * @param request The request details. - * @param response The response to output to. - * - * @throws ServletException - * An exception that provides information on a database access error or other errors. - * @throws IOException - * A general class of exceptions produced by failed or interrupted I/O operations. - */ - private void doDeposit(HttpServletRequest request, - HttpServletResponse response) throws ServletException, IOException { - // Do the deposit - Client client = new Client(); - try { - PostMessage message = new PostMessage(); - message.setUserAgent(ClientConstants.SERVICE_NAME); - - // Get the file - FileItemFactory factory = new DiskFileItemFactory(); - - // Create a new file upload handler - ServletFileUpload upload = new ServletFileUpload(factory); - - // Parse the request - List items = upload.parseRequest(request); - Iterator iter = items.iterator(); - String u = null; - String p = null; - String contentDisposition = null; - String filetype = null; - boolean useMD5 = false; - boolean errorMD5 = false; - boolean verbose = false; - boolean noOp = false; - boolean login = false; - while (iter.hasNext()) { - FileItem item = iter.next(); - if (item.isFormField()) { - String name = item.getFieldName(); - String value = item.getString(); - if (name.equals("url")) { - message.setDestination(value); - URL url = new URL(value); - int port = url.getPort(); - if (port == -1) { - port = 80; - } - client.setServer(url.getHost(), port); - } else if (name.equals("usemd5")) { - useMD5 = true; - } else if (name.equals("errormd5")) { - errorMD5 = true; - } else if (name.equals("verbose")) { - verbose = true; - } else if (name.equals("noop")) { - noOp = true; - } else if (name.equals("obo")) { - message.setOnBehalfOf(value); - } else if (name.equals("slug")) { - if ((value != null) && (!value.trim().equals(""))) { - message.setSlug(value); - } - } else if (name.equals("cd")) { - contentDisposition = value; - } else if (name.equals("filetype")) { - filetype = value; - } else if (name.equals("formatnamespace")) { - if ((value != null) && (!value.trim().equals(""))) { - message.setFormatNamespace(value); - } - } else if (name.equals("u")) { - u = value; - login = true; - request.setAttribute("u", value); - } else if (name.equals("p")) { - p = value; - login = true; - } - request.setAttribute(name, value); - } else { - String fname = tempDirectory + File.separator + - "ServletClient-" + counter++; - if ((contentDisposition != null) && (!contentDisposition.equals(""))) { - fname = tempDirectory + File.separator + contentDisposition; - } - - File uploadedFile = new File(fname); - item.write(uploadedFile); - message.setFilepath(fname); - - if ((filetype == null) || (filetype.trim().equals(""))) { - message.setFiletype(item.getContentType()); - } else { - message.setFiletype(filetype); - } - } - } - - if (login) { - client.setCredentials(u, p); - } - - if (useProxy) { - client.setProxy(pHost, pPort); - } - - message.setUseMD5(useMD5); - message.setChecksumError(errorMD5); - message.setVerbose(verbose); - message.setNoOp(noOp); - - // Post the file - DepositResponse resp = client.postFile(message); - - // Set the status - Status status = client.getStatus(); - request.setAttribute("status", status.toString()); - if ((status.getCode() == 201) || (status.getCode() == 202)) { - // Set the debug response - String xml = resp.marshall(); - - String validateXml = xml; - validateXml = validateXml.replaceAll("&", "&"); - validateXml = validateXml.replaceAll("<", "<"); - validateXml = validateXml.replaceAll(">", ">"); - validateXml = validateXml.replaceAll("\"", """); - validateXml = validateXml.replaceAll("'", "'"); - request.setAttribute("xmlValidate", validateXml); // for passing to validation - - xml = xml.replaceAll("<", "<"); - xml = xml.replaceAll(">", ">"); - request.setAttribute("xml", xml); - SWORDEntry se = resp.getEntry(); - request.setAttribute("id", se.getId()); - request.setAttribute("authors", se.getAuthors()); - request.setAttribute("contributors", se.getContributors()); - request.setAttribute("title", se.getTitle().getContent()); - request.setAttribute("updated", se.getUpdated()); - request.setAttribute("categories", se.getCategories()); - request.setAttribute("treatment", se.getTreatment()); - request.setAttribute("summary", se.getSummary().getContent()); - request.setAttribute("generator", se.getGenerator().getContent()); - request.setAttribute("userAgent", se.getUserAgent()); - request.setAttribute("packaging", se.getPackaging()); - request.setAttribute("links", se.getLinks()); - request.setAttribute("location", resp.getLocation()); - - // Set the ServiceDocument and associated values - request.getRequestDispatcher("deposit.jsp").forward(request, response); - return; - } else { - String error = status.getCode() + " " + status.getMessage() + " - "; - try { - error += resp.getEntry().getSummary().getContent(); - } catch (Exception e) { - // Do nothing - we have default error message - e.printStackTrace(); - } - request.setAttribute("error", error); - - // Try and get an error document in xml - String xml = resp.marshall(); - xml = xml.replaceAll("<", "<"); - xml = xml.replaceAll(">", ">"); - request.setAttribute("xml", xml); - - request.getRequestDispatcher("depositform.jsp").forward(request, response); - return; - } - } catch (RuntimeException e) { - e.printStackTrace(); - request.setAttribute("error", "value: " + e.toString()); - request.setAttribute("urls", urls); - request.getRequestDispatcher("depositform.jsp").forward(request, response); - } catch (Exception e) { - e.printStackTrace(); - request.setAttribute("error", "value: " + e.toString()); - request.setAttribute("urls", urls); - request.getRequestDispatcher("depositform.jsp").forward(request, response); - } - } -} diff --git a/dspace-sword/src/main/java/org/purl/sword/client/Status.java b/dspace-sword/src/main/java/org/purl/sword/client/Status.java deleted file mode 100644 index ff80fa6e52e..00000000000 --- a/dspace-sword/src/main/java/org/purl/sword/client/Status.java +++ /dev/null @@ -1,61 +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.purl.sword.client; - -/** - * Representation of the status code and message. - * - * @author Neil Taylor - */ -public class Status { - /** - * The status code. - */ - private int code; - - /** - * The status message. - */ - private String message; - - /** - * Create a new status message. - * - * @param code The code. - * @param message The message. - */ - public Status(int code, String message) { - this.code = code; - this.message = message; - } - - /** - * Retrieve the code. - * - * @return The code. - */ - public int getCode() { - return code; - } - - /** - * Get the message. - * - * @return The message. - */ - public String getMessage() { - return message; - } - - /** - * Get a string representation of the status. - */ - public String toString() { - return "Code: " + code + ", Message: '" + message + "'"; - } -} From 68cffba5fa0557b4e3d1165373e4c35b00b33d1f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 23:48:45 +0000 Subject: [PATCH 481/939] Bump the test-tools group with 6 updates Bumps the test-tools group with 6 updates: | Package | From | To | | --- | --- | --- | | [io.netty:netty-buffer](https://github.com/netty/netty) | `4.1.118.Final` | `4.1.119.Final` | | [io.netty:netty-transport](https://github.com/netty/netty) | `4.1.118.Final` | `4.1.119.Final` | | [io.netty:netty-transport-native-unix-common](https://github.com/netty/netty) | `4.1.118.Final` | `4.1.119.Final` | | [io.netty:netty-common](https://github.com/netty/netty) | `4.1.118.Final` | `4.1.119.Final` | | [io.netty:netty-handler](https://github.com/netty/netty) | `4.1.118.Final` | `4.1.119.Final` | | [io.netty:netty-codec](https://github.com/netty/netty) | `4.1.118.Final` | `4.1.119.Final` | Updates `io.netty:netty-buffer` from 4.1.118.Final to 4.1.119.Final - [Commits](https://github.com/netty/netty/compare/netty-4.1.118.Final...netty-4.1.119.Final) Updates `io.netty:netty-transport` from 4.1.118.Final to 4.1.119.Final - [Commits](https://github.com/netty/netty/compare/netty-4.1.118.Final...netty-4.1.119.Final) Updates `io.netty:netty-transport-native-unix-common` from 4.1.118.Final to 4.1.119.Final - [Commits](https://github.com/netty/netty/compare/netty-4.1.118.Final...netty-4.1.119.Final) Updates `io.netty:netty-common` from 4.1.118.Final to 4.1.119.Final - [Commits](https://github.com/netty/netty/compare/netty-4.1.118.Final...netty-4.1.119.Final) Updates `io.netty:netty-handler` from 4.1.118.Final to 4.1.119.Final - [Commits](https://github.com/netty/netty/compare/netty-4.1.118.Final...netty-4.1.119.Final) Updates `io.netty:netty-codec` from 4.1.118.Final to 4.1.119.Final - [Commits](https://github.com/netty/netty/compare/netty-4.1.118.Final...netty-4.1.119.Final) --- updated-dependencies: - dependency-name: io.netty:netty-buffer dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-transport dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-transport-native-unix-common dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-common dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-handler dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-codec dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 322e1820d32..dc5ce96df3c 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -887,32 +887,32 @@ io.netty netty-buffer - 4.1.118.Final + 4.2.0.Final io.netty netty-transport - 4.1.118.Final + 4.2.0.Final io.netty netty-transport-native-unix-common - 4.1.118.Final + 4.2.0.Final io.netty netty-common - 4.1.118.Final + 4.2.0.Final io.netty netty-handler - 4.1.118.Final + 4.2.0.Final io.netty netty-codec - 4.1.118.Final + 4.2.0.Final org.apache.velocity From c8cc4253578affcd984dd66591598957363d356b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Apr 2025 14:23:43 +0000 Subject: [PATCH 482/939] Bump com.amazonaws:aws-java-sdk-s3 from 1.12.781 to 1.12.782 Bumps [com.amazonaws:aws-java-sdk-s3](https://github.com/aws/aws-sdk-java) from 1.12.781 to 1.12.782. - [Changelog](https://github.com/aws/aws-sdk-java/blob/master/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-java/compare/1.12.781...1.12.782) --- updated-dependencies: - dependency-name: com.amazonaws:aws-java-sdk-s3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 322e1820d32..50e5f44bc77 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -761,7 +761,7 @@ com.amazonaws aws-java-sdk-s3 - 1.12.781 + 1.12.782 From b64b79973d2f6e7ce5a570eb0d8da932b62fe23a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Apr 2025 15:35:47 +0000 Subject: [PATCH 483/939] Bump the fasterxml group with 3 updates Bumps the fasterxml group with 3 updates: [com.fasterxml.jackson.core:jackson-annotations](https://github.com/FasterXML/jackson), [com.fasterxml.jackson.core:jackson-core](https://github.com/FasterXML/jackson-core) and [com.fasterxml.jackson.core:jackson-databind](https://github.com/FasterXML/jackson). Updates `com.fasterxml.jackson.core:jackson-annotations` from 2.18.2 to 2.18.3 - [Commits](https://github.com/FasterXML/jackson/commits) Updates `com.fasterxml.jackson.core:jackson-core` from 2.18.2 to 2.18.3 - [Commits](https://github.com/FasterXML/jackson-core/compare/jackson-core-2.18.2...jackson-core-2.18.3) Updates `com.fasterxml.jackson.core:jackson-core` from 2.18.2 to 2.18.3 - [Commits](https://github.com/FasterXML/jackson-core/compare/jackson-core-2.18.2...jackson-core-2.18.3) Updates `com.fasterxml.jackson.core:jackson-databind` from 2.18.2 to 2.18.3 - [Commits](https://github.com/FasterXML/jackson/commits) --- updated-dependencies: - dependency-name: com.fasterxml.jackson.core:jackson-annotations dependency-type: direct:production update-type: version-update:semver-patch dependency-group: fasterxml - dependency-name: com.fasterxml.jackson.core:jackson-core dependency-type: direct:production update-type: version-update:semver-patch dependency-group: fasterxml - dependency-name: com.fasterxml.jackson.core:jackson-core dependency-type: direct:production update-type: version-update:semver-patch dependency-group: fasterxml - dependency-name: com.fasterxml.jackson.core:jackson-databind dependency-type: direct:production update-type: version-update:semver-patch dependency-group: fasterxml ... Signed-off-by: dependabot[bot] --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 886aa3fb36c..ec07adf6bf5 100644 --- a/pom.xml +++ b/pom.xml @@ -30,8 +30,8 @@ 3.10.8 2.31.0 - 2.18.2 - 2.18.2 + 2.18.3 + 2.18.3 1.3.2 2.3.1 2.3.9 From a5806fb518031e99d82a4af0bb625271f4830860 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Apr 2025 15:35:52 +0000 Subject: [PATCH 484/939] Bump the google-apis group across 1 directory with 4 updates Bumps the google-apis group with 4 updates in the / directory: [com.google.http-client:google-http-client](https://github.com/googleapis/google-http-java-client), [com.google.http-client:google-http-client-jackson2](https://github.com/googleapis/google-http-java-client), [com.google.oauth-client:google-oauth-client](https://github.com/googleapis/google-oauth-java-client) and [com.google.http-client:google-http-client-gson](https://github.com/googleapis/google-http-java-client). Updates `com.google.http-client:google-http-client` from 1.46.1 to 1.46.3 - [Release notes](https://github.com/googleapis/google-http-java-client/releases) - [Changelog](https://github.com/googleapis/google-http-java-client/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/google-http-java-client/compare/v1.46.1...v1.46.3) Updates `com.google.http-client:google-http-client-jackson2` from 1.46.1 to 1.46.3 - [Release notes](https://github.com/googleapis/google-http-java-client/releases) - [Changelog](https://github.com/googleapis/google-http-java-client/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/google-http-java-client/compare/v1.46.1...v1.46.3) Updates `com.google.oauth-client:google-oauth-client` from 1.37.0 to 1.38.0 - [Release notes](https://github.com/googleapis/google-oauth-java-client/releases) - [Changelog](https://github.com/googleapis/google-oauth-java-client/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/google-oauth-java-client/compare/v1.37.0...v1.38.0) Updates `com.google.http-client:google-http-client-gson` from 1.46.1 to 1.46.3 - [Release notes](https://github.com/googleapis/google-http-java-client/releases) - [Changelog](https://github.com/googleapis/google-http-java-client/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/google-http-java-client/compare/v1.46.1...v1.46.3) --- updated-dependencies: - dependency-name: com.google.http-client:google-http-client dependency-type: direct:production update-type: version-update:semver-patch dependency-group: google-apis - dependency-name: com.google.http-client:google-http-client-jackson2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: google-apis - dependency-name: com.google.oauth-client:google-oauth-client dependency-type: direct:production update-type: version-update:semver-minor dependency-group: google-apis - dependency-name: com.google.http-client:google-http-client-gson dependency-type: direct:production update-type: version-update:semver-patch dependency-group: google-apis ... Signed-off-by: dependabot[bot] --- pom.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 886aa3fb36c..6debf4a73f5 100644 --- a/pom.xml +++ b/pom.xml @@ -1714,7 +1714,7 @@ com.google.http-client google-http-client - 1.46.1 + 1.46.3 com.google.errorprone @@ -1736,7 +1736,7 @@ com.google.http-client google-http-client-jackson2 - 1.46.1 + 1.46.3 jackson-core @@ -1751,14 +1751,14 @@ com.google.oauth-client google-oauth-client - 1.37.0 + 1.39.0 com.google.http-client google-http-client-gson - 1.46.1 + 1.46.3 From bb2ed04ed3f42ff86f51fe00c9805f646a009049 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Apr 2025 15:36:26 +0000 Subject: [PATCH 485/939] Bump the build-tools group across 1 directory with 5 updates Bumps the build-tools group with 5 updates in the / directory: | Package | From | To | | --- | --- | --- | | [com.github.spotbugs:spotbugs](https://github.com/spotbugs/spotbugs) | `4.9.1` | `4.9.3` | | [org.apache.maven.plugins:maven-compiler-plugin](https://github.com/apache/maven-compiler-plugin) | `3.13.0` | `3.14.0` | | [com.github.spotbugs:spotbugs-maven-plugin](https://github.com/spotbugs/spotbugs-maven-plugin) | `4.9.1.0` | `4.9.3.0` | | [org.apache.maven.plugins:maven-clean-plugin](https://github.com/apache/maven-clean-plugin) | `3.4.0` | `3.4.1` | | [org.jacoco:jacoco-maven-plugin](https://github.com/jacoco/jacoco) | `0.8.12` | `0.8.13` | Updates `com.github.spotbugs:spotbugs` from 4.9.1 to 4.9.3 - [Release notes](https://github.com/spotbugs/spotbugs/releases) - [Changelog](https://github.com/spotbugs/spotbugs/blob/master/CHANGELOG.md) - [Commits](https://github.com/spotbugs/spotbugs/compare/4.9.1...4.9.3) Updates `org.apache.maven.plugins:maven-compiler-plugin` from 3.13.0 to 3.14.0 - [Release notes](https://github.com/apache/maven-compiler-plugin/releases) - [Commits](https://github.com/apache/maven-compiler-plugin/compare/maven-compiler-plugin-3.13.0...maven-compiler-plugin-3.14.0) Updates `com.github.spotbugs:spotbugs-maven-plugin` from 4.9.1.0 to 4.9.3.0 - [Release notes](https://github.com/spotbugs/spotbugs-maven-plugin/releases) - [Commits](https://github.com/spotbugs/spotbugs-maven-plugin/compare/spotbugs-maven-plugin-4.9.1.0...spotbugs-maven-plugin-4.9.3.0) Updates `org.apache.maven.plugins:maven-clean-plugin` from 3.4.0 to 3.4.1 - [Release notes](https://github.com/apache/maven-clean-plugin/releases) - [Commits](https://github.com/apache/maven-clean-plugin/compare/maven-clean-plugin-3.4.0...maven-clean-plugin-3.4.1) Updates `org.jacoco:jacoco-maven-plugin` from 0.8.12 to 0.8.13 - [Release notes](https://github.com/jacoco/jacoco/releases) - [Commits](https://github.com/jacoco/jacoco/compare/v0.8.12...v0.8.13) --- updated-dependencies: - dependency-name: com.github.spotbugs:spotbugs dependency-version: 4.9.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: build-tools - dependency-name: org.apache.maven.plugins:maven-compiler-plugin dependency-version: 3.14.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools - dependency-name: com.github.spotbugs:spotbugs-maven-plugin dependency-version: 4.9.3.0 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: build-tools - dependency-name: org.apache.maven.plugins:maven-clean-plugin dependency-version: 3.4.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: build-tools - dependency-name: org.jacoco:jacoco-maven-plugin dependency-version: 0.8.13 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: build-tools ... Signed-off-by: dependabot[bot] --- pom.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 886aa3fb36c..3e8092aa5ca 100644 --- a/pom.xml +++ b/pom.xml @@ -140,7 +140,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.13.0 + 3.14.0 11 @@ -295,7 +295,7 @@ com.github.spotbugs spotbugs-maven-plugin - 4.9.1.0 + 4.9.3.0 Max Low @@ -305,7 +305,7 @@ com.github.spotbugs spotbugs - 4.9.1 + 4.9.3 @@ -321,7 +321,7 @@ maven-clean-plugin - 3.4.0 + 3.4.1 @@ -392,7 +392,7 @@ org.jacoco jacoco-maven-plugin - 0.8.12 + 0.8.13 From 16239433f62233244d73f7e92715d7b6d2768694 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Apr 2025 15:47:34 +0000 Subject: [PATCH 486/939] Bump io.grpc:grpc-context from 1.70.0 to 1.71.0 Bumps [io.grpc:grpc-context](https://github.com/grpc/grpc-java) from 1.70.0 to 1.71.0. - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.70.0...v1.71.0) --- updated-dependencies: - dependency-name: io.grpc:grpc-context dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 886aa3fb36c..6bc32b12061 100644 --- a/pom.xml +++ b/pom.xml @@ -1731,7 +1731,7 @@ io.grpc grpc-context - 1.70.0 + 1.71.0 com.google.http-client From e122a90674e1e36222fd62f46538020d77d2a88f Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Tue, 11 Mar 2025 10:58:08 +0100 Subject: [PATCH 487/939] Implement a SEOHealthIndicator which verifies all relevant parameters for SEO are ok (cherry picked from commit 4bd8a24ca75f6d2e6384e850b45c96c4f1229f02) --- .../configuration/ActuatorConfiguration.java | 7 ++ .../app/rest/health/SEOHealthIndicator.java | 77 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/health/SEOHealthIndicator.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/configuration/ActuatorConfiguration.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/configuration/ActuatorConfiguration.java index ad78fe2db40..15b9bd9506f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/configuration/ActuatorConfiguration.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/configuration/ActuatorConfiguration.java @@ -14,6 +14,7 @@ import org.apache.solr.client.solrj.SolrServerException; import org.dspace.app.rest.DiscoverableEndpointsService; import org.dspace.app.rest.health.GeoIpHealthIndicator; +import org.dspace.app.rest.health.SEOHealthIndicator; import org.dspace.authority.AuthoritySolrServiceImpl; import org.dspace.discovery.SolrSearchCore; import org.dspace.statistics.SolrStatisticsCore; @@ -82,6 +83,12 @@ public SolrHealthIndicator solrOaiCoreHealthIndicator(SolrServerResolver solrSer return new SolrHealthIndicator(solrServerResolver.getServer()); } + @Bean + @ConditionalOnEnabledHealthIndicator("seo") + public SEOHealthIndicator seoHealthIndicator() { + return new SEOHealthIndicator(); + } + @Bean @ConditionalOnEnabledHealthIndicator("geoIp") public GeoIpHealthIndicator geoIpHealthIndicator() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/health/SEOHealthIndicator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/health/SEOHealthIndicator.java new file mode 100644 index 00000000000..d936fce635e --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/health/SEOHealthIndicator.java @@ -0,0 +1,77 @@ +/** + * 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.app.rest.health; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.springframework.boot.actuate.health.AbstractHealthIndicator; +import org.springframework.boot.actuate.health.Health; +import org.springframework.web.client.RestTemplate; + +/** + * Implementation of {@link org.springframework.boot.actuate.health.HealthIndicator} that verifies if the SEO of the + * DSpace instance is configured correctly. + * + * This is only relevant in a production environment, where the DSpace instance is exposed to the public. + */ +public class SEOHealthIndicator extends AbstractHealthIndicator { + + ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + + private final RestTemplate restTemplate = new RestTemplate(); + + @Override + protected void doHealthCheck(Health.Builder builder) { + String baseUrl = configurationService.getProperty("dspace.ui.url"); + + boolean sitemapOk = checkUrl(baseUrl + "/sitemap_index.xml") || checkUrl(baseUrl + "/sitemap_index.html"); + boolean robotsTxtOk = checkRobotsTxt(baseUrl + "/robots.txt"); + boolean ssrOk = checkSSR(baseUrl); + + if (sitemapOk && robotsTxtOk && ssrOk) { + builder.up() + .withDetail("sitemap", "OK") + .withDetail("robots.txt", "OK") + .withDetail("ssr", "OK"); + } else { + builder.down() + .withDetail("sitemap", sitemapOk ? "OK" : "Missing or inaccessible") + .withDetail("robots.txt", robotsTxtOk ? "OK" : "Empty or contains local URLs") + .withDetail("ssr", ssrOk ? "OK" : "Server-side rendering might be disabled"); + } + } + + private boolean checkUrl(String url) { + try { + restTemplate.getForEntity(url, String.class); + return true; + } catch (Exception e) { + return false; + } + } + + private boolean checkRobotsTxt(String url) { + try { + String content = restTemplate.getForObject(url, String.class); + return StringUtils.isNotBlank(content) && !content.contains("localhost"); + } catch (Exception e) { + return false; + } + } + + private boolean checkSSR(String url) { + try { + String content = restTemplate.getForObject(url, String.class); + return content != null && !content.contains(""); + } catch (Exception e) { + return false; + } + } +} + From 31549bdaceb9220272d3645100fc8b48f28c7fa2 Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Tue, 18 Mar 2025 10:04:36 +0100 Subject: [PATCH 488/939] Disable new actuator in IT (cherry picked from commit 20ab43ccccf84c83d6db9b431321e60256d30355) --- dspace-api/src/test/data/dspaceFolder/config/local.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/dspace-api/src/test/data/dspaceFolder/config/local.cfg b/dspace-api/src/test/data/dspaceFolder/config/local.cfg index 0b4fe8288cf..ab0d14de6ee 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/local.cfg +++ b/dspace-api/src/test/data/dspaceFolder/config/local.cfg @@ -158,6 +158,7 @@ proxies.trusted.include_ui_ip = true # For the tests we have to disable this health indicator because there isn't a mock server and the calculated status was DOWN management.health.solrOai.enabled = false +management.health.seo.enabled = false # Enable researcher profiles and orcid synchronization for tests researcher-profile.entity-type = Person From 7a876999f8af137ced2064f5c9d3b7360399e6c0 Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Tue, 1 Apr 2025 17:54:48 +0200 Subject: [PATCH 489/939] 127746: Implement different failures for robots file so we can differentiate between a missing file or an invalid file (cherry picked from commit 32c048428026f890c2f3bc7eca5a2f20717dc587) --- .../app/rest/health/SEOHealthIndicator.java | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/health/SEOHealthIndicator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/health/SEOHealthIndicator.java index d936fce635e..a071e12088b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/health/SEOHealthIndicator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/health/SEOHealthIndicator.java @@ -31,19 +31,28 @@ protected void doHealthCheck(Health.Builder builder) { String baseUrl = configurationService.getProperty("dspace.ui.url"); boolean sitemapOk = checkUrl(baseUrl + "/sitemap_index.xml") || checkUrl(baseUrl + "/sitemap_index.html"); - boolean robotsTxtOk = checkRobotsTxt(baseUrl + "/robots.txt"); + RobotsTxtStatus robotsTxtStatus = checkRobotsTxt(baseUrl + "/robots.txt"); boolean ssrOk = checkSSR(baseUrl); - if (sitemapOk && robotsTxtOk && ssrOk) { + if (sitemapOk && robotsTxtStatus == RobotsTxtStatus.VALID && ssrOk) { builder.up() .withDetail("sitemap", "OK") .withDetail("robots.txt", "OK") .withDetail("ssr", "OK"); } else { - builder.down() - .withDetail("sitemap", sitemapOk ? "OK" : "Missing or inaccessible") - .withDetail("robots.txt", robotsTxtOk ? "OK" : "Empty or contains local URLs") - .withDetail("ssr", ssrOk ? "OK" : "Server-side rendering might be disabled"); + builder.down(); + builder.withDetail("sitemap", sitemapOk ? "OK" : "Missing or inaccessible"); + + if (robotsTxtStatus == RobotsTxtStatus.MISSING) { + builder.withDetail("robots.txt", "Missing or inaccessible. Please see the DSpace Documentation on " + + "Search Engine Optimization for how to create a robots.txt."); + } else if (robotsTxtStatus == RobotsTxtStatus.INVALID) { + builder.withDetail("robots.txt", "Invalid because it contains localhost URLs. This is often a sign " + + "that a proxy is failing to pass X-Forwarded headers to DSpace. Please see the DSpace " + + "Documentation on Search Engine Optimization for how to pass X-Forwarded headers."); + } + + builder.withDetail("ssr", ssrOk ? "OK" : "Server-side rendering might be disabled"); } } @@ -56,12 +65,18 @@ private boolean checkUrl(String url) { } } - private boolean checkRobotsTxt(String url) { + private RobotsTxtStatus checkRobotsTxt(String url) { try { String content = restTemplate.getForObject(url, String.class); - return StringUtils.isNotBlank(content) && !content.contains("localhost"); + if (StringUtils.isBlank(content)) { + return RobotsTxtStatus.MISSING; + } + if (content.contains("localhost")) { + return RobotsTxtStatus.INVALID; + } + return RobotsTxtStatus.VALID; } catch (Exception e) { - return false; + return RobotsTxtStatus.MISSING; } } @@ -73,5 +88,9 @@ private boolean checkSSR(String url) { return false; } } + + private enum RobotsTxtStatus { + VALID, MISSING, INVALID + } } From 64c5f822091133e95e668d6d05a11887ef572153 Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Tue, 1 Apr 2025 17:58:47 +0200 Subject: [PATCH 490/939] 127746: Add more detailed information messages on how to solve problems (cherry picked from commit 170dc9a44c5b16c28298a9e3133534e70967147d) --- .../org/dspace/app/rest/health/SEOHealthIndicator.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/health/SEOHealthIndicator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/health/SEOHealthIndicator.java index a071e12088b..740c6ab6493 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/health/SEOHealthIndicator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/health/SEOHealthIndicator.java @@ -41,7 +41,8 @@ protected void doHealthCheck(Health.Builder builder) { .withDetail("ssr", "OK"); } else { builder.down(); - builder.withDetail("sitemap", sitemapOk ? "OK" : "Missing or inaccessible"); + builder.withDetail("sitemap", sitemapOk ? "OK" : "Sitemaps are missing or inaccessible. Please see the " + + "DSpace Documentation on Search Engine Optimization for how to enable Sitemaps."); if (robotsTxtStatus == RobotsTxtStatus.MISSING) { builder.withDetail("robots.txt", "Missing or inaccessible. Please see the DSpace Documentation on " + @@ -51,8 +52,9 @@ protected void doHealthCheck(Health.Builder builder) { "that a proxy is failing to pass X-Forwarded headers to DSpace. Please see the DSpace " + "Documentation on Search Engine Optimization for how to pass X-Forwarded headers."); } - - builder.withDetail("ssr", ssrOk ? "OK" : "Server-side rendering might be disabled"); + builder.withDetail("ssr", ssrOk ? "OK" : "Server-side rendering (SSR) appears to be disabled. Most " + + "search engines require enabling SSR for proper indexing. Please see the DSpace Documentation on" + + " Search Engine Optimization for more details."); } } From 5b8782509fd857214934dca457f304b77e2c43d8 Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Fri, 4 Apr 2025 16:51:56 +0200 Subject: [PATCH 491/939] 127746: Include success result for robots.txt check if other checks fail (cherry picked from commit 5dc12775fac0006dbc1d0106ffcdffbe893919d1) --- .../java/org/dspace/app/rest/health/SEOHealthIndicator.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/health/SEOHealthIndicator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/health/SEOHealthIndicator.java index 740c6ab6493..5b57f2d537f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/health/SEOHealthIndicator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/health/SEOHealthIndicator.java @@ -51,6 +51,8 @@ protected void doHealthCheck(Health.Builder builder) { builder.withDetail("robots.txt", "Invalid because it contains localhost URLs. This is often a sign " + "that a proxy is failing to pass X-Forwarded headers to DSpace. Please see the DSpace " + "Documentation on Search Engine Optimization for how to pass X-Forwarded headers."); + } else { + builder.withDetail("robots.txt", "OK"); } builder.withDetail("ssr", ssrOk ? "OK" : "Server-side rendering (SSR) appears to be disabled. Most " + "search engines require enabling SSR for proper indexing. Please see the DSpace Documentation on" + From dd62a57564a72e82f4c702c985927a7159dc912d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Apr 2025 23:11:31 +0000 Subject: [PATCH 492/939] Bump the apache-commons group with 2 updates Bumps the apache-commons group with 2 updates: commons-io:commons-io and org.apache.commons:commons-text. Updates `commons-io:commons-io` from 2.18.0 to 2.19.0 Updates `org.apache.commons:commons-text` from 1.13.0 to 1.13.1 --- updated-dependencies: - dependency-name: commons-io:commons-io dependency-version: 2.19.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: apache-commons - dependency-name: org.apache.commons:commons-text dependency-version: 1.13.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: apache-commons ... Signed-off-by: dependabot[bot] --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index d48979c1c0c..b5d4fdfb66d 100644 --- a/pom.xml +++ b/pom.xml @@ -1505,7 +1505,7 @@ commons-io commons-io - 2.18.0 + 2.19.0 org.apache.commons @@ -1532,7 +1532,7 @@ org.apache.commons commons-text - 1.13.0 + 1.13.1 commons-validator From 086e54cb31cfac31814ca642ef99a9b541520d51 Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Sun, 27 Oct 2024 09:40:05 +0100 Subject: [PATCH 493/939] Modify Solr query to find collections with submit permissions in searches with spaces (cherry picked from commit 425dc1556ed27d20ff57e0b043f7bf2d021c3096) --- .../dspace/content/CollectionServiceImpl.java | 3 ++- .../app/rest/CollectionRestRepositoryIT.java | 18 +++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java index 5fe8ca54d9c..1b051ae32ff 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -1021,7 +1021,8 @@ private DiscoverResult retrieveCollectionsWithSubmit(Context context, DiscoverQu if (StringUtils.isNotBlank(q)) { StringBuilder buildQuery = new StringBuilder(); String escapedQuery = ClientUtils.escapeQueryChars(q); - buildQuery.append("(").append(escapedQuery).append(" OR ").append(escapedQuery).append("*").append(")"); + buildQuery.append("(").append(escapedQuery).append(" OR dc.title_sort:*") + .append(escapedQuery).append("*").append(")"); discoverQuery.setQuery(buildQuery.toString()); } DiscoverResult resp = searchService.search(context, discoverQuery); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java index 50e60087adf..1e18723ca90 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java @@ -698,6 +698,10 @@ public void findAuthorizedCollectionsWithQueryTest() throws Exception { .withName("Testing autocomplete in submission") .withSubmitterGroup(eperson2) .build(); + Collection col5 = CollectionBuilder.createCollection(context, child2) + .withName("Colección de prueba") + .withSubmitterGroup(eperson2) + .build(); context.restoreAuthSystemState(); String tokenEPerson = getAuthToken(eperson.getEmail(), password); @@ -741,7 +745,19 @@ public void findAuthorizedCollectionsWithQueryTest() throws Exception { getClient(tokenEPerson2).perform(get("/api/core/collections/search/findSubmitAuthorized") .param("query", "testing auto")) .andExpect(status().isOk()) - .andExpect(jsonPath("$.page.totalElements", is(0))); + .andExpect(jsonPath("$._embedded.collections", Matchers.contains( + CollectionMatcher.matchProperties(col4.getName(), col4.getID(), col4.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + + // Diacritics test + getClient(tokenEPerson2).perform(get("/api/core/collections/search/findSubmitAuthorized") + .param("query", "coléccion de")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.collections", Matchers.contains( + CollectionMatcher.matchProperties(col5.getName(), col5.getID(), col5.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); String tokenAdmin = getAuthToken(admin.getEmail(), password); getClient(tokenAdmin).perform(get("/api/core/collections/search/findSubmitAuthorized") From 3a894e5f5d4146ce120fecad3a72f3632dada7d0 Mon Sep 17 00:00:00 2001 From: Piaget Bouaka Donfack Date: Wed, 16 Apr 2025 17:11:09 +0200 Subject: [PATCH 494/939] [DURACOM-346] SubscribeServiceImpl : the method "isSubscribed" returns incorrect result (cherry picked from commit 23468d4ee357363dddd0996a1a13366be88527c8) --- .../src/main/java/org/dspace/eperson/SubscribeServiceImpl.java | 3 ++- .../src/test/java/org/dspace/eperson/SubscribeServiceIT.java | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java index 2e4d94f4431..0f5d2ba319c 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java @@ -131,7 +131,8 @@ public List findAvailableSubscriptions(Context context, EPerson eper @Override public boolean isSubscribed(Context context, EPerson eperson, DSpaceObject dSpaceObject) throws SQLException { - return subscriptionDAO.findByEPersonAndDso(context, eperson, dSpaceObject, -1, -1) != null; + List subscriptions = subscriptionDAO.findByEPersonAndDso(context, eperson, dSpaceObject, -1, -1); + return subscriptions != null && !subscriptions.isEmpty(); } @Override diff --git a/dspace-api/src/test/java/org/dspace/eperson/SubscribeServiceIT.java b/dspace-api/src/test/java/org/dspace/eperson/SubscribeServiceIT.java index 945dd481d00..128b2e552b8 100644 --- a/dspace-api/src/test/java/org/dspace/eperson/SubscribeServiceIT.java +++ b/dspace-api/src/test/java/org/dspace/eperson/SubscribeServiceIT.java @@ -213,6 +213,7 @@ public void subscribingUserUnsubscribesTheirSubscription() throws Exception { secondCollection, 100, 0); assertEquals(subscriptions.size(), 1); + assertThat(subscribeService.isSubscribed(context, subscribingUser, secondCollection), is(true)); subscribeService.unsubscribe(context, subscribingUser, secondCollection); @@ -222,6 +223,7 @@ public void subscribingUserUnsubscribesTheirSubscription() throws Exception { secondCollection, 100, 0); assertEquals(subscriptions.size(), 0); + assertThat(subscribeService.isSubscribed(context, subscribingUser, secondCollection), is(false)); } @Test(expected = AuthorizeException.class) From d6ff41d9f5d8295e95f5ed40dd7f0ab8caf7c3a9 Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Tue, 18 Mar 2025 23:11:51 +0100 Subject: [PATCH 495/939] Refactor browse entries facet query to use JSON facet query (cherry picked from commit 8e88547932c9c0260c50307d7c90657182de45af) --- .../java/org/dspace/browse/SolrBrowseDAO.java | 67 +++++++------------ .../org/dspace/discovery/SolrServiceImpl.java | 56 +++++++++------- 2 files changed, 56 insertions(+), 67 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java b/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java index 1917dec423e..14dab3e5617 100644 --- a/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java +++ b/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java @@ -23,7 +23,6 @@ import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.core.Context; -import org.dspace.discovery.DiscoverFacetField; import org.dspace.discovery.DiscoverQuery; import org.dspace.discovery.DiscoverQuery.SORT_ORDER; import org.dspace.discovery.DiscoverResult; @@ -34,7 +33,6 @@ import org.dspace.discovery.SearchServiceException; import org.dspace.discovery.SearchUtils; import org.dspace.discovery.configuration.DiscoveryConfiguration; -import org.dspace.discovery.configuration.DiscoveryConfigurationParameters; import org.dspace.discovery.indexobject.IndexableItem; import org.dspace.services.factory.DSpaceServicesFactory; @@ -181,32 +179,28 @@ private DiscoverResult getSolrResponse() throws BrowseException { addLocationScopeFilter(query); addDefaultFilterQueries(query); if (distinct) { - DiscoverFacetField dff; - - // To get the number of distinct values we use the next "json.facet" query param - // {"entries_count": {"type":"terms","field": "_filter", "limit":0, "numBuckets":true}}" + // We use a json.facet query for metadata browsing because it allows us to limit the results + // while obtaining the total number of facet values with numBuckets:true and sort in reverse order + // Example of json.facet query: + // {"": {"type":"terms","field": "_filter", "limit":0, "offset":0, + // "sort":"index desc", "numBuckets":true, "prefix":""}} ObjectNode jsonFacet = JsonNodeFactory.instance.objectNode(); - ObjectNode entriesCount = JsonNodeFactory.instance.objectNode(); - entriesCount.put("type", "terms"); - entriesCount.put("field", facetField + "_filter"); - entriesCount.put("limit", 0); - entriesCount.put("numBuckets", true); - jsonFacet.set("entries_count", entriesCount); - + ObjectNode entriesFacet = JsonNodeFactory.instance.objectNode(); + entriesFacet.put("type", "terms"); + entriesFacet.put("field", facetField + "_filter"); + entriesFacet.put("limit", limit); + entriesFacet.put("offset", offset); + entriesFacet.put("numBuckets", true); + if (ascending) { + entriesFacet.put("sort", "index"); + } else { + entriesFacet.put("sort", "index desc"); + } if (StringUtils.isNotBlank(startsWith)) { - dff = new DiscoverFacetField(facetField, - DiscoveryConfigurationParameters.TYPE_TEXT, limit, - DiscoveryConfigurationParameters.SORT.VALUE, startsWith, offset); - // Add the prefix to the json facet query - entriesCount.put("prefix", startsWith); - } else { - dff = new DiscoverFacetField(facetField, - DiscoveryConfigurationParameters.TYPE_TEXT, limit, - DiscoveryConfigurationParameters.SORT.VALUE, offset); + entriesFacet.put("prefix", startsWith); } - query.addFacetField(dff); - query.setFacetMinCount(1); + jsonFacet.set(facetField, entriesFacet); query.setMaxResults(0); query.addProperty("json.facet", jsonFacet.toString()); } else { @@ -282,26 +276,15 @@ public List doValueQuery() throws BrowseException { DiscoverResult resp = getSolrResponse(); List facet = resp.getFacetResult(facetField); int count = doCountQuery(); - int start = 0; int max = facet.size(); List result = new ArrayList<>(); - if (ascending) { - for (int i = start; i < (start + max) && i < count; i++) { - FacetResult c = facet.get(i); - String freq = showFrequencies ? String.valueOf(c.getCount()) - : ""; - result.add(new String[] {c.getDisplayedValue(), - c.getAuthorityKey(), freq}); - } - } else { - for (int i = count - start - 1; i >= count - (start + max) - && i >= 0; i--) { - FacetResult c = facet.get(i); - String freq = showFrequencies ? String.valueOf(c.getCount()) - : ""; - result.add(new String[] {c.getDisplayedValue(), - c.getAuthorityKey(), freq}); - } + + for (int i = 0; i < max && i < count; i++) { + FacetResult c = facet.get(i); + String freq = showFrequencies ? String.valueOf(c.getCount()) + : ""; + result.add(new String[] {c.getDisplayedValue(), + c.getAuthorityKey(), freq}); } return result; diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java index 29b695b5e78..e4889cb8cba 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java @@ -42,6 +42,7 @@ import org.apache.solr.client.solrj.response.FacetField; import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.client.solrj.response.json.BucketBasedJsonFacet; +import org.apache.solr.client.solrj.response.json.BucketJsonFacet; import org.apache.solr.client.solrj.response.json.NestableJsonFacet; import org.apache.solr.client.solrj.util.ClientUtils; import org.apache.solr.common.SolrDocument; @@ -1057,8 +1058,8 @@ protected DiscoverResult retrieveResult(Context context, DiscoverQuery query) } //Resolve our facet field values resolveFacetFields(context, query, result, skipLoadingResponse, solrQueryResponse); - //Add total entries count for metadata browsing - resolveEntriesCount(result, solrQueryResponse); + //Resolve our json facet field values used for metadata browsing + resolveJsonFacetFields(context, result, solrQueryResponse); } // If any stale entries are found in the current page of results, // we remove those stale entries and rerun the same query again. @@ -1085,33 +1086,38 @@ protected DiscoverResult retrieveResult(Context context, DiscoverQuery query) } /** - * Stores the total count of entries for metadata index browsing. The count is calculated by the - * json.facet parameter with the following value: + * Process the 'json.facet' response, which is currently only used for metadata browsing * - *
    
    -     * {
    -     *     "entries_count": {
    -     *         "type": "terms",
    -     *         "field": "facetNameField_filter",
    -     *         "limit": 0,
    -     *         "prefix": "prefix_value",
    -     *         "numBuckets": true
    -     *     }
    -     * }
    -     * 
    - * - * This value is returned in the facets field of the Solr response. - * - * @param result DiscoverResult object where the total entries count will be stored - * @param solrQueryResponse QueryResponse object containing the solr response + * @param context context object + * @param result the result object to add the facet results to + * @param solrQueryResponse the solr query response + * @throws SQLException if database error */ - private void resolveEntriesCount(DiscoverResult result, QueryResponse solrQueryResponse) { + private void resolveJsonFacetFields(Context context, DiscoverResult result, QueryResponse solrQueryResponse) + throws SQLException { NestableJsonFacet response = solrQueryResponse.getJsonFacetingResponse(); - if (response != null) { - BucketBasedJsonFacet facet = response.getBucketBasedFacets("entries_count"); - if (facet != null) { - result.setTotalEntries(facet.getNumBucketsCount()); + if (response != null && response.getBucketBasedFacetNames() != null) { + for (String facetName : response.getBucketBasedFacetNames()) { + BucketBasedJsonFacet facet = response.getBucketBasedFacets(facetName); + if (facet != null) { + result.setTotalEntries(facet.getNumBucketsCount()); + for (BucketJsonFacet bucket : facet.getBuckets()) { + String facetValue = bucket.getVal() != null ? bucket.getVal().toString() : ""; + String field = facetName + "_filter"; + String displayedValue = transformDisplayedValue(context, field, facetValue); + String authorityValue = transformAuthorityValue(context, field, facetValue); + String sortValue = transformSortValue(context, field, facetValue); + String filterValue = displayedValue; + if (StringUtils.isNotBlank(authorityValue)) { + filterValue = authorityValue; + } + result.addFacetResult(facetName, + new DiscoverResult.FacetResult(filterValue, displayedValue, + authorityValue, sortValue, bucket.getCount(), + DiscoveryConfigurationParameters.TYPE_TEXT)); + } + } } } } From b2f44f57f9c75fc6594243207de9eed168dce6f3 Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Sun, 23 Mar 2025 23:53:58 +0100 Subject: [PATCH 496/939] Add test for browse entries pagination (cherry picked from commit a7bc82084ecf97ec9706c0ea77c24faa39b946fb) --- .../app/rest/BrowsesResourceControllerIT.java | 179 ++++++++++++++++++ 1 file changed, 179 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java index d1791ab872b..6b8113e5afa 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java @@ -259,6 +259,185 @@ public void findBrowseBySubjectEntries() throws Exception { ))); } + @Test + public void findBrowseBySubjectEntriesPagination() throws Exception { + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); + + //2. Three public items that are readable by Anonymous with different subjects + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + Item publicItem2 = ItemBuilder.createItem(context, col2) + .withTitle("Public item 2") + .withIssueDate("2016-02-13") + .withAuthor("Smith, Maria").withAuthor("Doe, Jane") + .withSubject("TestingForMore").withSubject("ExtraEntry") + .build(); + + Item publicItem3 = ItemBuilder.createItem(context, col2) + .withTitle("Public item 2") + .withIssueDate("2016-02-13") + .withAuthor("Smith, Maria").withAuthor("Doe, Jane") + .withSubject("AnotherTest").withSubject("TestingForMore") + .withSubject("ExtraEntry") + .build(); + Item withdrawnItem1 = ItemBuilder.createItem(context, col2) + .withTitle("Withdrawn item 1") + .withIssueDate("2016-02-13") + .withAuthor("Smith, Maria").withAuthor("Doe, Jane") + .withSubject("AnotherTest").withSubject("TestingForMore") + .withSubject("ExtraEntry").withSubject("WithdrawnEntry") + .withdrawn() + .build(); + Item privateItem1 = ItemBuilder.createItem(context, col2) + .withTitle("Private item 1") + .withIssueDate("2016-02-13") + .withAuthor("Smith, Maria").withAuthor("Doe, Jane") + .withSubject("AnotherTest").withSubject("TestingForMore") + .withSubject("ExtraEntry").withSubject("PrivateEntry") + .makeUnDiscoverable() + .build(); + + + + context.restoreAuthSystemState(); + + //** WHEN ** + //An anonymous user browses this endpoint to find which subjects are currently in the repository + getClient().perform(get("/api/discover/browses/subject/entries") + .param("projection", "full") + .param("size", "1")) + + //** THEN ** + //The status has to be 200 + .andExpect(status().isOk()) + + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.page.size", is(1))) + .andExpect(jsonPath("$.page.number", is(0))) + //Check that there are indeed 3 different subjects + .andExpect(jsonPath("$.page.totalElements", is(3))) + //Check that the subject matches as expected + .andExpect(jsonPath("$._embedded.entries", + contains(BrowseEntryResourceMatcher.matchBrowseEntry("AnotherTest", 1) + ))); + + getClient().perform(get("/api/discover/browses/subject/entries") + .param("projection", "full") + .param("size", "1") + .param("page","1")) + + //** THEN ** + //The status has to be 200 + .andExpect(status().isOk()) + + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.page.size", is(1))) + .andExpect(jsonPath("$.page.number", is(1))) + //Check that there are indeed 3 different subjects + .andExpect(jsonPath("$.page.totalElements", is(3))) + //Check that the subject matches as expected + .andExpect(jsonPath("$._embedded.entries", + contains(BrowseEntryResourceMatcher.matchBrowseEntry("ExtraEntry", 3) + ))); + + getClient().perform(get("/api/discover/browses/subject/entries") + .param("projection", "full") + .param("size", "1") + .param("page","2")) + + //** THEN ** + //The status has to be 200 + .andExpect(status().isOk()) + + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.page.size", is(1))) + .andExpect(jsonPath("$.page.number", is(2))) + //Check that there are indeed 3 different subjects + .andExpect(jsonPath("$.page.totalElements", is(3))) + //Check that the subject matches as expected + .andExpect(jsonPath("$._embedded.entries", + contains(BrowseEntryResourceMatcher.matchBrowseEntry("TestingForMore", 2) + ))); + + getClient().perform(get("/api/discover/browses/subject/entries") + .param("sort", "value,desc") + .param("size", "1")) + + //** THEN ** + //The status has to be 200 + .andExpect(status().isOk()) + + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.page.size", is(1))) + .andExpect(jsonPath("$.page.number", is(0))) + //Check that there are indeed 3 different subjects + .andExpect(jsonPath("$.page.totalElements", is(3))) + //Check that the subject matches as expected + .andExpect(jsonPath("$._embedded.entries", + contains(BrowseEntryResourceMatcher.matchBrowseEntry("TestingForMore", 2) + ))); + + getClient().perform(get("/api/discover/browses/subject/entries") + .param("sort", "value,desc") + .param("size", "1") + .param("page","1")) + + //** THEN ** + //The status has to be 200 + .andExpect(status().isOk()) + + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.page.size", is(1))) + .andExpect(jsonPath("$.page.number", is(1))) + //Check that there are indeed 3 different subjects + .andExpect(jsonPath("$.page.totalElements", is(3))) + //Check that the subject matches as expected + .andExpect(jsonPath("$._embedded.entries", + contains(BrowseEntryResourceMatcher.matchBrowseEntry("ExtraEntry", 3) + ))); + + getClient().perform(get("/api/discover/browses/subject/entries") + .param("sort", "value,desc") + .param("size", "1") + .param("page","2")) + + //** THEN ** + //The status has to be 200 + .andExpect(status().isOk()) + + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.page.size", is(1))) + .andExpect(jsonPath("$.page.number", is(2))) + //Check that there are indeed 3 different subjects + .andExpect(jsonPath("$.page.totalElements", is(3))) + //Check that the subject matches as expected + .andExpect(jsonPath("$._embedded.entries", + contains(BrowseEntryResourceMatcher.matchBrowseEntry("AnotherTest", 1) + ))); + } + @Test public void findBrowseBySubjectEntriesWithAuthority() throws Exception { configurationService.setProperty("choices.plugin.dc.subject", From 0a79903f3009569aada1a5ceac6d0857e466ba56 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Thu, 20 Feb 2025 18:21:21 +0100 Subject: [PATCH 497/939] 126885: Removed database connection leak on login Also: - Updated EPersonRestAuthenticationProvider to not open an additional DB connection, and reuse the existing one instead - Normalized the behaviour of OidcLoginFilter by not calling the redirectAfterSuccess instead of doing a chain.doFilter(req, res). This way we don't need to reopen a new Context (cherry picked from commit 518fb3b1d89795b4995b32e164e3ac9c9e48350e) --- .../EPersonRestAuthenticationProvider.java | 64 ++++++++----------- .../app/rest/security/OidcLoginFilter.java | 52 ++++++++++++++- ...JWTTokenRestAuthenticationServiceImpl.java | 3 + 3 files changed, 80 insertions(+), 39 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestAuthenticationProvider.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestAuthenticationProvider.java index e55734e513d..00c804ad2d6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestAuthenticationProvider.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestAuthenticationProvider.java @@ -85,7 +85,7 @@ public Authentication authenticate(Authentication authentication) throws Authent } else { // Otherwise, this is a new login & we need to attempt authentication log.debug("Request to authenticate new login"); - return authenticateNewLogin(authentication); + return authenticateNewLogin(context, authentication); } } @@ -107,56 +107,44 @@ private Authentication authenticateRefreshTokenRequest(Context context) { * If login is successful, returns a NEW Authentication class containing the logged in EPerson and their list of * GrantedAuthority objects. If login fails, a BadCredentialsException is thrown. If no valid login found implicit * or explicit, then null is returned. + * + * @param context The current DSpace context * @param authentication Authentication class to attempt authentication. * @return new Authentication class containing logged-in user information or null */ - private Authentication authenticateNewLogin(Authentication authentication) { - Context newContext = null; + private Authentication authenticateNewLogin(Context context, Authentication authentication) { Authentication output = null; if (authentication != null) { - try { - newContext = new Context(); - String name = authentication.getName(); - String password = Objects.toString(authentication.getCredentials(), null); + String name = authentication.getName(); + String password = Objects.toString(authentication.getCredentials(), null); - int implicitStatus = authenticationService.authenticateImplicit(newContext, null, null, null, request); + int implicitStatus = authenticationService.authenticateImplicit(context, null, null, null, request); - if (implicitStatus == AuthenticationMethod.SUCCESS) { - log.info(LogHelper.getHeader(newContext, "login", "type=implicit")); - output = createAuthentication(newContext); - } else { - int authenticateResult = authenticationService - .authenticate(newContext, name, password, null, request); - if (AuthenticationMethod.SUCCESS == authenticateResult) { + if (implicitStatus == AuthenticationMethod.SUCCESS) { + log.info(LogHelper.getHeader(context, "login", "type=implicit")); + output = createAuthentication(context); + } else { + int authenticateResult = authenticationService.authenticate(context, name, password, null, request); + if (AuthenticationMethod.SUCCESS == authenticateResult) { - log.info(LogHelper - .getHeader(newContext, "login", "type=explicit")); + log.info(LogHelper.getHeader(context, "login", "type=explicit")); - output = createAuthentication(newContext); + output = createAuthentication(context); - for (PostLoggedInAction action : postLoggedInActions) { - try { - action.loggedIn(newContext); - } catch (Exception ex) { - log.error("An error occurs performing post logged in action", ex); - } + for (PostLoggedInAction action : postLoggedInActions) { + try { + action.loggedIn(context); + } catch (Exception ex) { + log.error("An error occurs performing post logged in action", ex); } - - } else { - log.info(LogHelper.getHeader(newContext, "failed_login", "email=" - + name + ", result=" - + authenticateResult)); - throw new BadCredentialsException("Login failed"); - } - } - } finally { - if (newContext != null && newContext.isValid()) { - try { - newContext.complete(); - } catch (SQLException e) { - log.error(e.getMessage() + " occurred while trying to close", e); } + + } else { + log.info(LogHelper.getHeader(context, "failed_login", "email=" + + name + ", result=" + + authenticateResult)); + throw new BadCredentialsException("Login failed"); } } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OidcLoginFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OidcLoginFilter.java index c84840e7704..e73a3e8d3cc 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OidcLoginFilter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OidcLoginFilter.java @@ -10,11 +10,18 @@ import static org.dspace.authenticate.OidcAuthenticationBean.OIDC_AUTH_ATTRIBUTE; import java.io.IOException; +import java.util.ArrayList; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.core.Utils; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; @@ -27,6 +34,11 @@ public class OidcLoginFilter extends StatelessLoginFilter { + private static final Logger log = LogManager.getLogger(OidcLoginFilter.class); + + private final ConfigurationService configurationService = DSpaceServicesFactory.getInstance() + .getConfigurationService(); + public OidcLoginFilter(String url, AuthenticationManager authenticationManager, RestAuthenticationService restAuthenticationService) { super(url, authenticationManager, restAuthenticationService); @@ -44,7 +56,45 @@ public Authentication attemptAuthentication(HttpServletRequest req, HttpServletR protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain, Authentication auth) throws IOException, ServletException { restAuthenticationService.addAuthenticationDataForUser(req, res, (DSpaceAuthentication) auth, true); - chain.doFilter(req, res); + redirectAfterSuccess(req, res); + } + + /** + * After successful login, redirect to the DSpace URL specified by this OIDC + * request (in the "redirectUrl" request parameter). If that 'redirectUrl' is + * not valid or trusted for this DSpace site, then return a 400 error. + * @param request + * @param response + * @throws IOException + */ + private void redirectAfterSuccess(HttpServletRequest request, HttpServletResponse response) throws IOException { + // Get redirect URL from request parameter + String redirectUrl = request.getParameter("redirectUrl"); + + // If redirectUrl unspecified, default to the configured UI + if (StringUtils.isEmpty(redirectUrl)) { + redirectUrl = configurationService.getProperty("dspace.ui.url"); + } + + // Validate that the redirectURL matches either the server or UI hostname. It + // *cannot* be an arbitrary URL. + String redirectHostName = Utils.getHostName(redirectUrl); + String serverHostName = Utils.getHostName(configurationService.getProperty("dspace.server.url")); + ArrayList allowedHostNames = new ArrayList<>(); + allowedHostNames.add(serverHostName); + String[] allowedUrls = configurationService.getArrayProperty("rest.cors.allowed-origins"); + for (String url : allowedUrls) { + allowedHostNames.add(Utils.getHostName(url)); + } + + if (StringUtils.equalsAnyIgnoreCase(redirectHostName, allowedHostNames.toArray(new String[0]))) { + log.debug("OIDC redirecting to " + redirectUrl); + response.sendRedirect(redirectUrl); + } else { + log.error("Invalid OIDC redirectURL=" + redirectUrl + ". URL doesn't match hostname of server or UI!"); + response.sendError(HttpServletResponse.SC_BAD_REQUEST, + "Invalid redirectURL! Must match server or ui hostname."); + } } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java index c28729ff83a..425ba505b43 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java @@ -84,6 +84,9 @@ public void addAuthenticationDataForUser(HttpServletRequest request, HttpServlet String token = loginJWTTokenHandler.createTokenForEPerson(context, request, authentication.getPreviousLoginDate()); context.commit(); + // Close the Context, because the DSpaceRequestContextFilter is not called for requests that trigger + // the authentication filters (filters that extend AbstractAuthenticationProcessingFilter) + context.close(); // Add newly generated auth token to the response addTokenToResponse(request, response, token, addCookie); From 133c2808836713e58bdfb5ed98ddecd2aefb43ab Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Thu, 17 Apr 2025 00:57:54 +0200 Subject: [PATCH 498/939] 126885: Removed database connection leak on logout (cherry picked from commit b299a960763136b7b2b973575e9dd25901a365eb) --- .../java/org/dspace/app/rest/security/CustomLogoutHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/CustomLogoutHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/CustomLogoutHandler.java index b3f4a00d379..1f7331f5f0f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/CustomLogoutHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/CustomLogoutHandler.java @@ -45,7 +45,7 @@ public void logout(HttpServletRequest httpServletRequest, HttpServletResponse ht try { Context context = ContextUtil.obtainContext(httpServletRequest); restAuthenticationService.invalidateAuthenticationData(httpServletRequest, httpServletResponse, context); - context.commit(); + context.complete(); } catch (Exception e) { log.error("Unable to logout", e); From 5d880bcf2da5391700ae9bc5fec6d84dc3314bfe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Apr 2025 23:03:07 +0000 Subject: [PATCH 499/939] Bump io.grpc:grpc-context from 1.71.0 to 1.72.0 Bumps [io.grpc:grpc-context](https://github.com/grpc/grpc-java) from 1.71.0 to 1.72.0. - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.71.0...v1.72.0) --- updated-dependencies: - dependency-name: io.grpc:grpc-context dependency-version: 1.72.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b5d4fdfb66d..aff93df4a23 100644 --- a/pom.xml +++ b/pom.xml @@ -1731,7 +1731,7 @@ io.grpc grpc-context - 1.71.0 + 1.72.0 com.google.http-client From 440bb648090ebe7992faf406c491b9168bb91009 Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Thu, 20 Mar 2025 12:56:11 +0000 Subject: [PATCH 500/939] Update dim.xsl Added template to correctly parse elements under "others" metadata element (cherry picked from commit ac7da6a477cb7bb42b41560a751223efc870dafa) --- .../config/crosswalks/oai/metadataFormats/dim.xsl | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/dspace/config/crosswalks/oai/metadataFormats/dim.xsl b/dspace/config/crosswalks/oai/metadataFormats/dim.xsl index ea0aad18209..b659fef931a 100644 --- a/dspace/config/crosswalks/oai/metadataFormats/dim.xsl +++ b/dspace/config/crosswalks/oai/metadataFormats/dim.xsl @@ -36,6 +36,18 @@ + + + + + + + + + + + + @@ -62,6 +74,7 @@ + From 045a5c0b0eff8bc52f52d452e98fc9b22a8e45d4 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Tue, 1 Apr 2025 19:49:28 +0200 Subject: [PATCH 501/939] add method getMaxNumOfItemsPerRequest --- .../java/org/dspace/app/util/service/OpenSearchService.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/app/util/service/OpenSearchService.java b/dspace-api/src/main/java/org/dspace/app/util/service/OpenSearchService.java index 03f41e535c5..08900f8fff9 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/service/OpenSearchService.java +++ b/dspace-api/src/main/java/org/dspace/app/util/service/OpenSearchService.java @@ -117,4 +117,10 @@ public Document getResultsDoc(Context context, String format, String query, int public DSpaceObject resolveScope(Context context, String scope) throws SQLException; + /** + * Retrieves the maximum number of items that can be included in a single opensearch request. + * + * @return the maximum number of items allowed per request + */ + int getMaxNumOfItemsPerRequest(); } From 869d122eaca9d052693bb13fe2faf061b6836026 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Tue, 1 Apr 2025 19:50:48 +0200 Subject: [PATCH 502/939] implement method getMaxNumOfItemsPerRequest --- .../java/org/dspace/app/util/OpenSearchServiceImpl.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/app/util/OpenSearchServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/util/OpenSearchServiceImpl.java index bff741b5ca4..2075ef7a381 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/OpenSearchServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/util/OpenSearchServiceImpl.java @@ -101,6 +101,14 @@ protected String getBaseSearchUIURL() { configurationService.getProperty("websvc.opensearch.uicontext"); } + /** + * Get base search UI URL (websvc.opensearch.max_num_of_items_per_request) + */ + public int getMaxNumOfItemsPerRequest() { + return configurationService.getIntProperty( + "websvc.opensearch.max_num_of_items_per_request", 100); + } + @Override public String getContentType(String format) { return "html".equals(format) ? "text/html" : From ca3b2de1a83a3ee654c054cb70666d7c15f8bee4 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Tue, 1 Apr 2025 19:51:28 +0200 Subject: [PATCH 503/939] add configuration key websvc.opensearch.max_num_of_items_per_request --- dspace/config/dspace.cfg | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 91057ef1ec6..94b731cd348 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1362,7 +1362,8 @@ websvc.opensearch.tags = IR DSpace # result formats offered - use 1 or more comma-separated from: html,atom,rss # html uses the normal search module websvc.opensearch.formats = html,atom,rss - +# maximum number of item per request +websvc.opensearch.max_num_of_items_per_request = 100 #### Content Inline Disposition Threshold #### # From 1a619b28336030e01211497cd8e466d4f3beb92c Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Tue, 1 Apr 2025 19:59:48 +0200 Subject: [PATCH 504/939] restrict maximum value of URL parameter rpp --- .../dspace/app/rest/OpenSearchController.java | 39 ++++++++----------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/OpenSearchController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/OpenSearchController.java index fef6269e3ee..d4e0036449a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/OpenSearchController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/OpenSearchController.java @@ -21,17 +21,13 @@ import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.rest.utils.ScopeResolver; import org.dspace.app.util.SyndicationFeed; import org.dspace.app.util.factory.UtilServiceFactory; import org.dspace.app.util.service.OpenSearchService; -import org.dspace.authorize.factory.AuthorizeServiceFactory; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.CollectionService; -import org.dspace.content.service.CommunityService; import org.dspace.core.Context; import org.dspace.core.LogHelper; import org.dspace.core.Utils; @@ -50,7 +46,6 @@ import org.dspace.discovery.indexobject.IndexableItem; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -67,12 +62,9 @@ public class OpenSearchController { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(); - private static final String errorpath = "/error"; + private List searchIndices = null; - private CommunityService communityService; - private CollectionService collectionService; - private AuthorizeService authorizeService; private OpenSearchService openSearchService; @Autowired @@ -99,22 +91,28 @@ public void search(HttpServletRequest request, @RequestParam(name = "format", required = false) String format, @RequestParam(name = "sort", required = false) String sort, @RequestParam(name = "sort_direction", required = false) String sortDirection, - @RequestParam(name = "scope", required = false) String dsoObject, - Model model) throws IOException, ServletException { + @RequestParam(name = "scope", required = false) String dsoObject) + throws IOException, ServletException { context = ContextUtil.obtainContext(request); - if (start == null) { - start = 0; - } - if (count == null) { - count = -1; - } + if (openSearchService == null) { openSearchService = UtilServiceFactory.getInstance().getOpenSearchService(); } + if (openSearchService.isEnabled()) { init(); + + if (start == null) { + start = 0; + } + + if (count == null) { + count = -1; + } + count = Math.min(count, openSearchService.getMaxNumOfItemsPerRequest()); + // get enough request parameters to decide on action to take - if (format == null || "".equals(format)) { + if (StringUtils.isEmpty(format)) { // default to atom format = "atom"; } @@ -266,9 +264,6 @@ private void init() { searchIndices.add(sFilter.getIndexFieldName()); } } - communityService = ContentServiceFactory.getInstance().getCommunityService(); - collectionService = ContentServiceFactory.getInstance().getCollectionService(); - authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); } public void setOpenSearchService(OpenSearchService oSS) { From ac7dfc562fd90d161c7fa0f99cd54b00b16f64b3 Mon Sep 17 00:00:00 2001 From: Jesiel Viana Date: Tue, 22 Apr 2025 22:12:00 -0300 Subject: [PATCH 505/939] fix: import public email from ORCID person (cherry picked from commit 82ca80cd6b5d340f5d3d2e042803c2fc43d8a973) --- .../impl/OrcidV3AuthorDataProvider.java | 22 +- .../impl/OrcidV3AuthorDataProviderTest.java | 231 ++++++++++++++++++ .../provider/impl/orcid-person/person1.xml | 51 ++++ .../provider/impl/orcid-person/person2.xml | 64 +++++ .../provider/impl/orcid-person/person3.xml | 35 +++ .../provider/impl/orcid-person/search.xml | 25 ++ .../provider/orcid-v3-author/person1.xml | 51 ++++ .../provider/orcid-v3-author/person2.xml | 64 +++++ .../provider/orcid-v3-author/person3.xml | 35 +++ .../provider/orcid-v3-author/search.xml | 25 ++ 10 files changed, 596 insertions(+), 7 deletions(-) create mode 100644 dspace-api/src/test/java/org/dspace/external/provider/impl/OrcidV3AuthorDataProviderTest.java create mode 100644 dspace-api/src/test/resources/org/dspace/external/provider/impl/orcid-person/person1.xml create mode 100644 dspace-api/src/test/resources/org/dspace/external/provider/impl/orcid-person/person2.xml create mode 100644 dspace-api/src/test/resources/org/dspace/external/provider/impl/orcid-person/person3.xml create mode 100644 dspace-api/src/test/resources/org/dspace/external/provider/impl/orcid-person/search.xml create mode 100644 dspace-api/src/test/resources/org/dspace/external/provider/orcid-v3-author/person1.xml create mode 100644 dspace-api/src/test/resources/org/dspace/external/provider/orcid-v3-author/person2.xml create mode 100644 dspace-api/src/test/resources/org/dspace/external/provider/orcid-v3-author/person3.xml create mode 100644 dspace-api/src/test/resources/org/dspace/external/provider/orcid-v3-author/search.xml diff --git a/dspace-api/src/main/java/org/dspace/external/provider/impl/OrcidV3AuthorDataProvider.java b/dspace-api/src/main/java/org/dspace/external/provider/impl/OrcidV3AuthorDataProvider.java index c7e41171a5b..dfbd07a83a0 100644 --- a/dspace-api/src/main/java/org/dspace/external/provider/impl/OrcidV3AuthorDataProvider.java +++ b/dspace-api/src/main/java/org/dspace/external/provider/impl/OrcidV3AuthorDataProvider.java @@ -27,6 +27,7 @@ import org.dspace.external.provider.orcid.xml.XMLtoBio; import org.dspace.orcid.model.factory.OrcidFactoryUtils; import org.orcid.jaxb.model.v3.release.common.OrcidIdentifier; +import org.orcid.jaxb.model.v3.release.record.Email; import org.orcid.jaxb.model.v3.release.record.Person; import org.orcid.jaxb.model.v3.release.search.Result; import org.springframework.beans.factory.annotation.Autowired; @@ -114,13 +115,20 @@ protected ExternalDataObject convertToExternalDataObject(Person person) { if (person.getName().getFamilyName() != null) { lastName = person.getName().getFamilyName().getContent(); externalDataObject.addMetadata(new MetadataValueDTO("person", "familyName", null, null, - lastName)); + lastName)); } if (person.getName().getGivenNames() != null) { firstName = person.getName().getGivenNames().getContent(); externalDataObject.addMetadata(new MetadataValueDTO("person", "givenName", null, null, - firstName)); - + firstName)); + } + if (person.getEmails().getEmails() != null && !person.getEmails().getEmails().isEmpty()) { + Email email = person.getEmails().getEmails().get(0); + if (person.getEmails().getEmails().size() > 1) { + email = person.getEmails().getEmails().stream().filter(Email::isPrimary).findFirst().orElse(email); + } + externalDataObject.addMetadata(new MetadataValueDTO("person", "email", null, + null, email.getEmail())); } externalDataObject.setId(person.getName().getPath()); externalDataObject @@ -128,7 +136,7 @@ protected ExternalDataObject convertToExternalDataObject(Person person) { new MetadataValueDTO("person", "identifier", "orcid", null, person.getName().getPath())); externalDataObject .addMetadata(new MetadataValueDTO("dc", "identifier", "uri", null, - orcidUrl + "/" + person.getName().getPath())); + orcidUrl + "/" + person.getName().getPath())); if (!StringUtils.isBlank(lastName) && !StringUtils.isBlank(firstName)) { externalDataObject.setDisplayValue(lastName + ", " + firstName); externalDataObject.setValue(lastName + ", " + firstName); @@ -139,8 +147,8 @@ protected ExternalDataObject convertToExternalDataObject(Person person) { externalDataObject.setDisplayValue(firstName); externalDataObject.setValue(firstName); } - } else if (person.getPath() != null ) { - externalDataObject.setId(StringUtils.substringBetween(person.getPath(),"/","/person")); + } else if (person.getPath() != null) { + externalDataObject.setId(StringUtils.substringBetween(person.getPath(), "/", "/person")); } return externalDataObject; } @@ -204,7 +212,7 @@ public List searchExternalDataObjects(String query, int star for (Result result : results) { OrcidIdentifier orcidIdentifier = result.getOrcidIdentifier(); if (orcidIdentifier != null) { - log.debug("Found OrcidId=" + orcidIdentifier.toString()); + log.debug("Found OrcidId=" + orcidIdentifier.getPath()); String orcid = orcidIdentifier.getPath(); Person bio = getBio(orcid); if (bio != null) { diff --git a/dspace-api/src/test/java/org/dspace/external/provider/impl/OrcidV3AuthorDataProviderTest.java b/dspace-api/src/test/java/org/dspace/external/provider/impl/OrcidV3AuthorDataProviderTest.java new file mode 100644 index 00000000000..34b3a6838d4 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/external/provider/impl/OrcidV3AuthorDataProviderTest.java @@ -0,0 +1,231 @@ +/** + * 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.external.provider.impl; + +import org.dspace.AbstractDSpaceTest; +import org.dspace.external.OrcidRestConnector; +import org.dspace.external.model.ExternalDataObject; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.InputStream; +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +/** + * Unit tests for {@link OrcidV3AuthorDataProvider}. + * + * @author Jesiel Viana (jesielviana at proton.me) + * + */ +public class OrcidV3AuthorDataProviderTest extends AbstractDSpaceTest { + + private static final String SEARCH_XML_PATH = "org/dspace/external/provider/orcid-v3-author/search.xml"; + private static final String PERSON1_XML_PATH = "org/dspace/external/provider/orcid-v3-author/person1.xml"; + private static final String PERSON2_XML_PATH = "org/dspace/external/provider/orcid-v3-author/person2.xml"; + private static final String PERSON3_XML_PATH = "org/dspace/external/provider/orcid-v3-author/person3.xml"; + + public static final String ORCID_SEARCH_QUERY = "search?q=0000-0000-0000-0000"; + + private OrcidV3AuthorDataProvider dataProvider; + + @Before + public void setup() throws Exception { + dataProvider = new OrcidV3AuthorDataProvider(); + + OrcidRestConnector mockRestConnector = mock(OrcidRestConnector.class); + + dataProvider.setOrcidRestConnector(mockRestConnector); + dataProvider.setSourceIdentifier("orcid"); + dataProvider.setOrcidUrl("https://orcid.org"); + + dataProvider.setClientId("client-id"); + dataProvider.setClientSecret("client-secret"); + dataProvider.setOAUTHUrl("https://orcid.org/oauth"); + + InputStream searchXmlStream = getClass().getClassLoader().getResourceAsStream(SEARCH_XML_PATH); + InputStream person1XmlStream = getClass().getClassLoader().getResourceAsStream(PERSON1_XML_PATH); + InputStream person2XmlStream = getClass().getClassLoader().getResourceAsStream(PERSON2_XML_PATH); + InputStream person3XmlStream = getClass().getClassLoader().getResourceAsStream(PERSON3_XML_PATH); + + when(mockRestConnector.get("search?q=search%3Fq%3D0000-0000-0000-0000&start=0&rows=10",null )).thenReturn(searchXmlStream); + when(mockRestConnector.get("0000-0000-0000-0001/person",null )).thenReturn(person1XmlStream); + when(mockRestConnector.get("0000-0000-0000-0002/person",null )).thenReturn(person2XmlStream); + when(mockRestConnector.get("0000-0000-0000-0003/person",null )).thenReturn(person3XmlStream); + + } + + @Test + public void testGetExternalDataObjectSizeIsCorrect() { + List optional = dataProvider.searchExternalDataObjects(ORCID_SEARCH_QUERY, 0, 10); + assertThat(optional, hasSize(3)); + } + + @Test + public void testGetExternalDataObjectGetPersonWithAllFieldsPopulated() { + List optional = dataProvider.searchExternalDataObjects(ORCID_SEARCH_QUERY, 0, 10); + + assertThat(optional, hasSize(3)); + + ExternalDataObject externalDataObject1 = optional.get(0); + + // Basic field assertions + assertThat(externalDataObject1.getId(), equalTo("0000-0000-0000-0001")); + assertThat(externalDataObject1.getValue(), equalTo("FamilyName1, GivenNames1")); + assertThat(externalDataObject1.getSource(), equalTo("orcid")); + assertThat(externalDataObject1.getDisplayValue(), equalTo("FamilyName1, GivenNames1")); + + // Metadata assertions + assertThat(externalDataObject1.getMetadata(), hasItem( + allOf( + hasProperty("schema", equalTo("person")), + hasProperty("element", equalTo("familyName")), + hasProperty("value", equalTo("FamilyName1")) + ) + )); + assertThat(externalDataObject1.getMetadata(), hasItem( + allOf( + hasProperty("schema", equalTo("person")), + hasProperty("element", equalTo("givenName")), + hasProperty("value", equalTo("GivenNames1")) + ) + )); + assertThat(externalDataObject1.getMetadata(), hasItem( + allOf( + hasProperty("schema", equalTo("person")), + hasProperty("element", equalTo("email")), + hasProperty("value", equalTo("person1@email.com")) + ) + )); + assertThat(externalDataObject1.getMetadata(), hasItem( + allOf( + hasProperty("schema", equalTo("person")), + hasProperty("element", equalTo("identifier")), + hasProperty("qualifier", equalTo("orcid")), + hasProperty("value", equalTo("0000-0000-0000-0001")) + ) + )); + assertThat(externalDataObject1.getMetadata(), hasItem( + allOf( + hasProperty("schema", equalTo("dc")), + hasProperty("element", equalTo("identifier")), + hasProperty("qualifier", equalTo("uri")), + hasProperty("value", equalTo("https://orcid.org/0000-0000-0000-0001")) + ) + )); + } + + @Test + public void testGetExternalDataObjectGetPrimaryEmailFromPersonWithTwoEmails() { + List optional = dataProvider.searchExternalDataObjects(ORCID_SEARCH_QUERY, 0, 10); + + assertThat(optional, hasSize(3)); + + ExternalDataObject externalDataObject2 = optional.get(1); // Test person2 (with two emails) + + // Basic field assertions + assertThat(externalDataObject2.getId(), equalTo("0000-0000-0000-0002")); + assertThat(externalDataObject2.getValue(), equalTo("FamilyName2, GivenNames2")); + assertThat(externalDataObject2.getSource(), equalTo("orcid")); + assertThat(externalDataObject2.getDisplayValue(), equalTo("FamilyName2, GivenNames2")); + + // Metadata assertions + assertThat(externalDataObject2.getMetadata(), hasItem( + allOf( + hasProperty("schema", equalTo("person")), + hasProperty("element", equalTo("familyName")), + hasProperty("value", equalTo("FamilyName2")) + ) + )); + assertThat(externalDataObject2.getMetadata(), hasItem( + allOf( + hasProperty("schema", equalTo("person")), + hasProperty("element", equalTo("givenName")), + hasProperty("value", equalTo("GivenNames2")) + ) + )); + assertThat(externalDataObject2.getMetadata(), hasItem( + allOf( + hasProperty("schema", equalTo("person")), + hasProperty("element", equalTo("email")), + hasProperty("value", equalTo("person2primary@email.com")) // Primary email + ) + )); + assertThat(externalDataObject2.getMetadata(), hasItem( + allOf( + hasProperty("schema", equalTo("person")), + hasProperty("element", equalTo("identifier")), + hasProperty("qualifier", equalTo("orcid")), + hasProperty("value", equalTo("0000-0000-0000-0002")) + ) + )); + assertThat(externalDataObject2.getMetadata(), hasItem( + allOf( + hasProperty("schema", equalTo("dc")), + hasProperty("element", equalTo("identifier")), + hasProperty("qualifier", equalTo("uri")), + hasProperty("value", equalTo("https://orcid.org/0000-0000-0000-0002")) + ) + )); + } + + + @Test + public void testGetExternalDataObjectGetPersonOnlyWithNameFilled() { + List optional = dataProvider.searchExternalDataObjects(ORCID_SEARCH_QUERY, 0, 10); + + assertThat(optional, hasSize(3)); + + ExternalDataObject externalDataObject2 = optional.get(2); // Test person2 (with two emails) + + // Basic field assertions + assertThat(externalDataObject2.getId(), equalTo("0000-0000-0000-0003")); + assertThat(externalDataObject2.getValue(), equalTo("FamilyName3, GivenNames3")); + assertThat(externalDataObject2.getSource(), equalTo("orcid")); + assertThat(externalDataObject2.getDisplayValue(), equalTo("FamilyName3, GivenNames3")); + + // Metadata assertions + assertThat(externalDataObject2.getMetadata(), hasItem( + allOf( + hasProperty("schema", equalTo("person")), + hasProperty("element", equalTo("familyName")), + hasProperty("value", equalTo("FamilyName3")) + ) + )); + assertThat(externalDataObject2.getMetadata(), hasItem( + allOf( + hasProperty("schema", equalTo("person")), + hasProperty("element", equalTo("givenName")), + hasProperty("value", equalTo("GivenNames3")) + ) + )); + assertThat(externalDataObject2.getMetadata(), hasItem( + allOf( + hasProperty("schema", equalTo("person")), + hasProperty("element", equalTo("identifier")), + hasProperty("qualifier", equalTo("orcid")), + hasProperty("value", equalTo("0000-0000-0000-0003")) + ) + )); + assertThat(externalDataObject2.getMetadata(), hasItem( + allOf( + hasProperty("schema", equalTo("dc")), + hasProperty("element", equalTo("identifier")), + hasProperty("qualifier", equalTo("uri")), + hasProperty("value", equalTo("https://orcid.org/0000-0000-0000-0003")) + ) + )); + } +} diff --git a/dspace-api/src/test/resources/org/dspace/external/provider/impl/orcid-person/person1.xml b/dspace-api/src/test/resources/org/dspace/external/provider/impl/orcid-person/person1.xml new file mode 100644 index 00000000000..64e4b292b92 --- /dev/null +++ b/dspace-api/src/test/resources/org/dspace/external/provider/impl/orcid-person/person1.xml @@ -0,0 +1,51 @@ + + + 2025-04-21T22:28:18.862Z + + 2025-04-11T15:41:21.340Z + 2025-04-11T15:41:21.340Z + GivenNames1 + FamilyName1 + + + + + 2025-04-21T22:28:18.862Z + + 2025-04-21T22:23:14.698Z + 2025-04-21T22:28:18.862Z + + + https://sandbox.orcid.org/0000-0000-0000-0001 + 0000-0000-0000-0001 + sandbox.orcid.org + + GivenNames1 FamilyName1 + + person1@email.com + + + + + + diff --git a/dspace-api/src/test/resources/org/dspace/external/provider/impl/orcid-person/person2.xml b/dspace-api/src/test/resources/org/dspace/external/provider/impl/orcid-person/person2.xml new file mode 100644 index 00000000000..c91b0207247 --- /dev/null +++ b/dspace-api/src/test/resources/org/dspace/external/provider/impl/orcid-person/person2.xml @@ -0,0 +1,64 @@ + + + 2025-04-21T22:28:18.862Z + + 2025-04-11T15:41:21.340Z + 2025-04-11T15:41:21.340Z + GivenNames2 + FamilyName2 + + + + + 2025-04-21T22:28:18.862Z + + 2025-04-21T22:23:14.698Z + 2025-04-21T22:28:18.862Z + + + https://sandbox.orcid.org/0000-0000-0000-0002 + 0000-0000-0000-0002 + sandbox.orcid.org + + GivenNames2 FamilyName2 + + person2@email.com + + + 2025-04-21T16:42:54.961Z + 2025-04-21T16:48:32.642Z + + + https://sandbox.orcid.org/0000-0000-0000-0001 + 0000-0000-0000-0001 + sandbox.orcid.org + + GivenNames1 FamilyName1 + + person2primary@email.com + + + + + + diff --git a/dspace-api/src/test/resources/org/dspace/external/provider/impl/orcid-person/person3.xml b/dspace-api/src/test/resources/org/dspace/external/provider/impl/orcid-person/person3.xml new file mode 100644 index 00000000000..b24ed9d3547 --- /dev/null +++ b/dspace-api/src/test/resources/org/dspace/external/provider/impl/orcid-person/person3.xml @@ -0,0 +1,35 @@ + + + + 2024-06-11T20:01:28.538Z + 2024-06-11T20:01:28.538Z + GivenNames3 + FamilyName3 + + + + + + + + diff --git a/dspace-api/src/test/resources/org/dspace/external/provider/impl/orcid-person/search.xml b/dspace-api/src/test/resources/org/dspace/external/provider/impl/orcid-person/search.xml new file mode 100644 index 00000000000..98ec721be9b --- /dev/null +++ b/dspace-api/src/test/resources/org/dspace/external/provider/impl/orcid-person/search.xml @@ -0,0 +1,25 @@ + + + + + https://sandbox.orcid.org/0000-0000-0000-0001 + 0000-0000-0000-0001 + sandbox.orcid.org + + + + + https://sandbox.orcid.org/0000-0000-0000-0002 + 0000-0000-0000-0002 + sandbox.orcid.org + + + + + https://sandbox.orcid.org/0000-0000-0000-0003 + 0000-0000-0000-0003 + sandbox.orcid.org + + + diff --git a/dspace-api/src/test/resources/org/dspace/external/provider/orcid-v3-author/person1.xml b/dspace-api/src/test/resources/org/dspace/external/provider/orcid-v3-author/person1.xml new file mode 100644 index 00000000000..64e4b292b92 --- /dev/null +++ b/dspace-api/src/test/resources/org/dspace/external/provider/orcid-v3-author/person1.xml @@ -0,0 +1,51 @@ + + + 2025-04-21T22:28:18.862Z + + 2025-04-11T15:41:21.340Z + 2025-04-11T15:41:21.340Z + GivenNames1 + FamilyName1 + + + + + 2025-04-21T22:28:18.862Z + + 2025-04-21T22:23:14.698Z + 2025-04-21T22:28:18.862Z + + + https://sandbox.orcid.org/0000-0000-0000-0001 + 0000-0000-0000-0001 + sandbox.orcid.org + + GivenNames1 FamilyName1 + + person1@email.com + + + + + + diff --git a/dspace-api/src/test/resources/org/dspace/external/provider/orcid-v3-author/person2.xml b/dspace-api/src/test/resources/org/dspace/external/provider/orcid-v3-author/person2.xml new file mode 100644 index 00000000000..c91b0207247 --- /dev/null +++ b/dspace-api/src/test/resources/org/dspace/external/provider/orcid-v3-author/person2.xml @@ -0,0 +1,64 @@ + + + 2025-04-21T22:28:18.862Z + + 2025-04-11T15:41:21.340Z + 2025-04-11T15:41:21.340Z + GivenNames2 + FamilyName2 + + + + + 2025-04-21T22:28:18.862Z + + 2025-04-21T22:23:14.698Z + 2025-04-21T22:28:18.862Z + + + https://sandbox.orcid.org/0000-0000-0000-0002 + 0000-0000-0000-0002 + sandbox.orcid.org + + GivenNames2 FamilyName2 + + person2@email.com + + + 2025-04-21T16:42:54.961Z + 2025-04-21T16:48:32.642Z + + + https://sandbox.orcid.org/0000-0000-0000-0001 + 0000-0000-0000-0001 + sandbox.orcid.org + + GivenNames1 FamilyName1 + + person2primary@email.com + + + + + + diff --git a/dspace-api/src/test/resources/org/dspace/external/provider/orcid-v3-author/person3.xml b/dspace-api/src/test/resources/org/dspace/external/provider/orcid-v3-author/person3.xml new file mode 100644 index 00000000000..b24ed9d3547 --- /dev/null +++ b/dspace-api/src/test/resources/org/dspace/external/provider/orcid-v3-author/person3.xml @@ -0,0 +1,35 @@ + + + + 2024-06-11T20:01:28.538Z + 2024-06-11T20:01:28.538Z + GivenNames3 + FamilyName3 + + + + + + + + diff --git a/dspace-api/src/test/resources/org/dspace/external/provider/orcid-v3-author/search.xml b/dspace-api/src/test/resources/org/dspace/external/provider/orcid-v3-author/search.xml new file mode 100644 index 00000000000..98ec721be9b --- /dev/null +++ b/dspace-api/src/test/resources/org/dspace/external/provider/orcid-v3-author/search.xml @@ -0,0 +1,25 @@ + + + + + https://sandbox.orcid.org/0000-0000-0000-0001 + 0000-0000-0000-0001 + sandbox.orcid.org + + + + + https://sandbox.orcid.org/0000-0000-0000-0002 + 0000-0000-0000-0002 + sandbox.orcid.org + + + + + https://sandbox.orcid.org/0000-0000-0000-0003 + 0000-0000-0000-0003 + sandbox.orcid.org + + + From cf9e5c1cd4c91f2b3d072c2a18eed3ca2441454a Mon Sep 17 00:00:00 2001 From: Jesiel Viana Date: Tue, 22 Apr 2025 23:19:56 -0300 Subject: [PATCH 506/939] fix: Checkstyle violations (cherry picked from commit 9a831e53933771228c7e141db3d0217651d5b32c) --- .../impl/OrcidV3AuthorDataProviderTest.java | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/external/provider/impl/OrcidV3AuthorDataProviderTest.java b/dspace-api/src/test/java/org/dspace/external/provider/impl/OrcidV3AuthorDataProviderTest.java index 34b3a6838d4..a68c0519bae 100644 --- a/dspace-api/src/test/java/org/dspace/external/provider/impl/OrcidV3AuthorDataProviderTest.java +++ b/dspace-api/src/test/java/org/dspace/external/provider/impl/OrcidV3AuthorDataProviderTest.java @@ -7,22 +7,24 @@ */ package org.dspace.external.provider.impl; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.hasItem; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.beans.HasPropertyWithValue.hasProperty; +import static org.hamcrest.collection.IsCollectionWithSize.hasSize; +import static org.hamcrest.core.AllOf.allOf; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.InputStream; +import java.util.List; + import org.dspace.AbstractDSpaceTest; import org.dspace.external.OrcidRestConnector; import org.dspace.external.model.ExternalDataObject; -import org.junit.After; import org.junit.Before; import org.junit.Test; -import java.io.InputStream; -import java.util.List; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.assertThrows; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.*; /** * Unit tests for {@link OrcidV3AuthorDataProvider}. @@ -60,10 +62,11 @@ public void setup() throws Exception { InputStream person2XmlStream = getClass().getClassLoader().getResourceAsStream(PERSON2_XML_PATH); InputStream person3XmlStream = getClass().getClassLoader().getResourceAsStream(PERSON3_XML_PATH); - when(mockRestConnector.get("search?q=search%3Fq%3D0000-0000-0000-0000&start=0&rows=10",null )).thenReturn(searchXmlStream); - when(mockRestConnector.get("0000-0000-0000-0001/person",null )).thenReturn(person1XmlStream); - when(mockRestConnector.get("0000-0000-0000-0002/person",null )).thenReturn(person2XmlStream); - when(mockRestConnector.get("0000-0000-0000-0003/person",null )).thenReturn(person3XmlStream); + when(mockRestConnector.get("search?q=search%3Fq%3D0000-0000-0000-0000&start=0&rows=10", null)) + .thenReturn(searchXmlStream); + when(mockRestConnector.get("0000-0000-0000-0001/person", null)).thenReturn(person1XmlStream); + when(mockRestConnector.get("0000-0000-0000-0002/person", null)).thenReturn(person2XmlStream); + when(mockRestConnector.get("0000-0000-0000-0003/person", null)).thenReturn(person3XmlStream); } From 106936967e6d2bcd861cefe9ecc05bbb47cb9a93 Mon Sep 17 00:00:00 2001 From: Jesiel Viana Date: Tue, 22 Apr 2025 23:44:10 -0300 Subject: [PATCH 507/939] removing duplicated files (cherry picked from commit c6d1121cbe90ee9c12847f283a3d00601acceaa3) --- .../provider/impl/orcid-person/person1.xml | 51 --------------- .../provider/impl/orcid-person/person2.xml | 64 ------------------- .../provider/impl/orcid-person/person3.xml | 35 ---------- .../provider/impl/orcid-person/search.xml | 25 -------- 4 files changed, 175 deletions(-) delete mode 100644 dspace-api/src/test/resources/org/dspace/external/provider/impl/orcid-person/person1.xml delete mode 100644 dspace-api/src/test/resources/org/dspace/external/provider/impl/orcid-person/person2.xml delete mode 100644 dspace-api/src/test/resources/org/dspace/external/provider/impl/orcid-person/person3.xml delete mode 100644 dspace-api/src/test/resources/org/dspace/external/provider/impl/orcid-person/search.xml diff --git a/dspace-api/src/test/resources/org/dspace/external/provider/impl/orcid-person/person1.xml b/dspace-api/src/test/resources/org/dspace/external/provider/impl/orcid-person/person1.xml deleted file mode 100644 index 64e4b292b92..00000000000 --- a/dspace-api/src/test/resources/org/dspace/external/provider/impl/orcid-person/person1.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - 2025-04-21T22:28:18.862Z - - 2025-04-11T15:41:21.340Z - 2025-04-11T15:41:21.340Z - GivenNames1 - FamilyName1 - - - - - 2025-04-21T22:28:18.862Z - - 2025-04-21T22:23:14.698Z - 2025-04-21T22:28:18.862Z - - - https://sandbox.orcid.org/0000-0000-0000-0001 - 0000-0000-0000-0001 - sandbox.orcid.org - - GivenNames1 FamilyName1 - - person1@email.com - - - - - - diff --git a/dspace-api/src/test/resources/org/dspace/external/provider/impl/orcid-person/person2.xml b/dspace-api/src/test/resources/org/dspace/external/provider/impl/orcid-person/person2.xml deleted file mode 100644 index c91b0207247..00000000000 --- a/dspace-api/src/test/resources/org/dspace/external/provider/impl/orcid-person/person2.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - 2025-04-21T22:28:18.862Z - - 2025-04-11T15:41:21.340Z - 2025-04-11T15:41:21.340Z - GivenNames2 - FamilyName2 - - - - - 2025-04-21T22:28:18.862Z - - 2025-04-21T22:23:14.698Z - 2025-04-21T22:28:18.862Z - - - https://sandbox.orcid.org/0000-0000-0000-0002 - 0000-0000-0000-0002 - sandbox.orcid.org - - GivenNames2 FamilyName2 - - person2@email.com - - - 2025-04-21T16:42:54.961Z - 2025-04-21T16:48:32.642Z - - - https://sandbox.orcid.org/0000-0000-0000-0001 - 0000-0000-0000-0001 - sandbox.orcid.org - - GivenNames1 FamilyName1 - - person2primary@email.com - - - - - - diff --git a/dspace-api/src/test/resources/org/dspace/external/provider/impl/orcid-person/person3.xml b/dspace-api/src/test/resources/org/dspace/external/provider/impl/orcid-person/person3.xml deleted file mode 100644 index b24ed9d3547..00000000000 --- a/dspace-api/src/test/resources/org/dspace/external/provider/impl/orcid-person/person3.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - 2024-06-11T20:01:28.538Z - 2024-06-11T20:01:28.538Z - GivenNames3 - FamilyName3 - - - - - - - - diff --git a/dspace-api/src/test/resources/org/dspace/external/provider/impl/orcid-person/search.xml b/dspace-api/src/test/resources/org/dspace/external/provider/impl/orcid-person/search.xml deleted file mode 100644 index 98ec721be9b..00000000000 --- a/dspace-api/src/test/resources/org/dspace/external/provider/impl/orcid-person/search.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - https://sandbox.orcid.org/0000-0000-0000-0001 - 0000-0000-0000-0001 - sandbox.orcid.org - - - - - https://sandbox.orcid.org/0000-0000-0000-0002 - 0000-0000-0000-0002 - sandbox.orcid.org - - - - - https://sandbox.orcid.org/0000-0000-0000-0003 - 0000-0000-0000-0003 - sandbox.orcid.org - - - From b949bdd4c0c54a42edb343d65af1696e43ebbc4a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Apr 2025 23:58:24 +0000 Subject: [PATCH 508/939] Bump the fasterxml group with 3 updates Bumps the fasterxml group with 3 updates: [com.fasterxml.jackson.core:jackson-annotations](https://github.com/FasterXML/jackson), [com.fasterxml.jackson.core:jackson-core](https://github.com/FasterXML/jackson-core) and [com.fasterxml.jackson.core:jackson-databind](https://github.com/FasterXML/jackson). Updates `com.fasterxml.jackson.core:jackson-annotations` from 2.18.3 to 2.19.0 - [Commits](https://github.com/FasterXML/jackson/commits) Updates `com.fasterxml.jackson.core:jackson-core` from 2.18.3 to 2.19.0 - [Commits](https://github.com/FasterXML/jackson-core/compare/jackson-core-2.18.3...jackson-core-2.19.0) Updates `com.fasterxml.jackson.core:jackson-core` from 2.18.3 to 2.19.0 - [Commits](https://github.com/FasterXML/jackson-core/compare/jackson-core-2.18.3...jackson-core-2.19.0) Updates `com.fasterxml.jackson.core:jackson-databind` from 2.18.3 to 2.19.0 - [Commits](https://github.com/FasterXML/jackson/commits) --- updated-dependencies: - dependency-name: com.fasterxml.jackson.core:jackson-annotations dependency-version: 2.19.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: fasterxml - dependency-name: com.fasterxml.jackson.core:jackson-core dependency-version: 2.19.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: fasterxml - dependency-name: com.fasterxml.jackson.core:jackson-core dependency-version: 2.19.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: fasterxml - dependency-name: com.fasterxml.jackson.core:jackson-databind dependency-version: 2.19.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: fasterxml ... Signed-off-by: dependabot[bot] --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index aff93df4a23..39dddad33f6 100644 --- a/pom.xml +++ b/pom.xml @@ -30,8 +30,8 @@ 3.10.8 2.31.0 - 2.18.3 - 2.18.3 + 2.19.0 + 2.19.0 1.3.2 2.3.1 2.3.9 From ff67241bc9d336e488dad643d7a4af35ddb1518b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Apr 2025 00:01:34 +0000 Subject: [PATCH 509/939] Bump the apache-commons group with 2 updates Bumps the apache-commons group with 2 updates: org.apache.commons:commons-collections4 and org.apache.commons:commons-configuration2. Updates `org.apache.commons:commons-collections4` from 4.4 to 4.5.0 Updates `org.apache.commons:commons-configuration2` from 2.11.0 to 2.12.0 --- updated-dependencies: - dependency-name: org.apache.commons:commons-collections4 dependency-version: 4.5.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: apache-commons - dependency-name: org.apache.commons:commons-configuration2 dependency-version: 2.12.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: apache-commons ... Signed-off-by: dependabot[bot] --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index aff93df4a23..ac8dde9b55a 100644 --- a/pom.xml +++ b/pom.xml @@ -1485,12 +1485,12 @@ org.apache.commons commons-collections4 - 4.4 + 4.5.0 org.apache.commons commons-configuration2 - 2.11.0 + 2.12.0 org.apache.commons From 5400e3f8a815bc12fa5e50a56a3626643a8b5bb6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Apr 2025 00:02:22 +0000 Subject: [PATCH 510/939] Bump the google-apis group with 3 updates Bumps the google-apis group with 3 updates: [com.google.http-client:google-http-client](https://github.com/googleapis/google-http-java-client), [com.google.http-client:google-http-client-jackson2](https://github.com/googleapis/google-http-java-client) and [com.google.http-client:google-http-client-gson](https://github.com/googleapis/google-http-java-client). Updates `com.google.http-client:google-http-client` from 1.46.3 to 1.47.0 - [Release notes](https://github.com/googleapis/google-http-java-client/releases) - [Changelog](https://github.com/googleapis/google-http-java-client/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/google-http-java-client/compare/v1.46.3...v1.47.0) Updates `com.google.http-client:google-http-client-jackson2` from 1.46.3 to 1.47.0 - [Release notes](https://github.com/googleapis/google-http-java-client/releases) - [Changelog](https://github.com/googleapis/google-http-java-client/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/google-http-java-client/compare/v1.46.3...v1.47.0) Updates `com.google.http-client:google-http-client-gson` from 1.46.3 to 1.47.0 - [Release notes](https://github.com/googleapis/google-http-java-client/releases) - [Changelog](https://github.com/googleapis/google-http-java-client/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/google-http-java-client/compare/v1.46.3...v1.47.0) --- updated-dependencies: - dependency-name: com.google.http-client:google-http-client dependency-version: 1.47.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: google-apis - dependency-name: com.google.http-client:google-http-client-jackson2 dependency-version: 1.47.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: google-apis - dependency-name: com.google.http-client:google-http-client-gson dependency-version: 1.47.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: google-apis ... Signed-off-by: dependabot[bot] --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index aff93df4a23..d847bcc2811 100644 --- a/pom.xml +++ b/pom.xml @@ -1714,7 +1714,7 @@ com.google.http-client google-http-client - 1.46.3 + 1.47.0 com.google.errorprone @@ -1736,7 +1736,7 @@ com.google.http-client google-http-client-jackson2 - 1.46.3 + 1.47.0 jackson-core @@ -1758,7 +1758,7 @@ com.google.http-client google-http-client-gson - 1.46.3 + 1.47.0 From 19010353c9293e87ae4710c4e829ba3f07a60507 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Apr 2025 00:15:34 +0000 Subject: [PATCH 511/939] Bump pdfbox-version from 2.0.33 to 2.0.34 Bumps `pdfbox-version` from 2.0.33 to 2.0.34. Updates `org.apache.pdfbox:pdfbox` from 2.0.33 to 2.0.34 Updates `org.apache.pdfbox:fontbox` from 2.0.33 to 2.0.34 --- updated-dependencies: - dependency-name: org.apache.pdfbox:pdfbox dependency-version: 2.0.34 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.pdfbox:fontbox dependency-version: 2.0.34 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index aff93df4a23..8b079f29ad5 100644 --- a/pom.xml +++ b/pom.xml @@ -39,7 +39,7 @@ 9.4.57.v20241219 2.24.3 - 2.0.33 + 2.0.34 1.19.0 1.7.36 2.9.3 From e94d934a5ca371a66e65297fefef9cf1f7c37488 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 29 Apr 2025 17:35:19 +0200 Subject: [PATCH 512/939] Make getAllFacetConfigs unique Improve performance and debuggability by refactoring getAllFacetConfigs to getAllUniqueFacetConfigs. Used only by ChoiceAuthorityService to generate hierarchical vocabulary map for the browse menu, etc. (cherry picked from commit 159bd18529b5f0918cc0bc07e2b4fee16e4f2511) --- .../authority/ChoiceAuthorityServiceImpl.java | 2 +- .../DiscoveryConfigurationService.java | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java index f4d1f02710e..bbe8e4461fe 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java @@ -577,7 +577,7 @@ public DSpaceControlledVocabularyIndex getVocabularyIndex(String nameVocab) { .collect(Collectors.toList())); } DiscoverySearchFilterFacet matchingFacet = null; - for (DiscoverySearchFilterFacet facetConfig : searchConfigurationService.getAllFacetsConfig()) { + for (DiscoverySearchFilterFacet facetConfig : searchConfigurationService.getAllUniqueFacetsConfig()) { boolean coversAllFieldsFromVocab = true; for (String fieldFromVocab: metadataFields) { boolean coversFieldFromVocab = false; diff --git a/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryConfigurationService.java b/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryConfigurationService.java index 6cb93e2993f..9d603941de3 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryConfigurationService.java +++ b/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryConfigurationService.java @@ -10,8 +10,10 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; @@ -197,15 +199,19 @@ public List getIndexAlwaysConfigurations() { } /** - * @return All configurations for {@link org.dspace.discovery.configuration.DiscoverySearchFilterFacet} + * Get the unique set of configured Discovery facets. This is used when inspecting configuration + * to include hierarchical vocabularies in the browse menu. + * + * @return All unique instances of {@link org.dspace.discovery.configuration.DiscoverySearchFilterFacet} + * included in "sidebarFacets" bean, across all Discovery configurations. */ - public List getAllFacetsConfig() { - List configs = new ArrayList<>(); + public List getAllUniqueFacetsConfig() { + Set configs = new LinkedHashSet<>(); for (String key : map.keySet()) { DiscoveryConfiguration config = map.get(key); configs.addAll(config.getSidebarFacets()); } - return configs; + return new ArrayList<>(configs); } public static void main(String[] args) { From 98921724f41b3e18d21540bb34340c751ff3edd6 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 23 Apr 2025 18:55:29 +0200 Subject: [PATCH 513/939] Add help opt and javadoc to InitializeEntities (cherry picked from commit 5240a029965807e4757316aa1a965c4102c0d4f4) --- .../dspace/app/util/InitializeEntities.java | 76 +++++++++++++++++-- 1 file changed, 68 insertions(+), 8 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/util/InitializeEntities.java b/dspace-api/src/main/java/org/dspace/app/util/InitializeEntities.java index 0a072a9819e..8d3964a3e3c 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/InitializeEntities.java +++ b/dspace-api/src/main/java/org/dspace/app/util/InitializeEntities.java @@ -64,20 +64,36 @@ private InitializeEntities() { */ public static void main(String[] argv) throws SQLException, AuthorizeException, ParseException { InitializeEntities initializeEntities = new InitializeEntities(); + // Set up command-line options and parse arguments CommandLineParser parser = new DefaultParser(); Options options = createCommandLineOptions(); CommandLine line = parser.parse(options,argv); - String fileLocation = getFileLocationFromCommandLine(line); + // First of all, check if the help option was entered or a required argument is missing checkHelpEntered(options, line); + // Get the file location from the command line + String fileLocation = getFileLocationFromCommandLine(line); + // Run the script initializeEntities.run(fileLocation); } + + /** + * Check if the help option was entered or a required argument is missing. If so, print help and exit. + * @param options the defined command-line options + * @param line the parsed command-line arguments + */ private static void checkHelpEntered(Options options, CommandLine line) { - if (line.hasOption("h")) { + if (line.hasOption("h") || !line.hasOption("f")) { HelpFormatter formatter = new HelpFormatter(); formatter.printHelp("Intialize Entities", options); System.exit(0); } } + + /** + * Get the file path from the command-line argument. Exits with exit code 1 if no file argument was entered. + * @param line the parsed command-line arguments + * @return the file path + */ private static String getFileLocationFromCommandLine(CommandLine line) { String query = line.getOptionValue("f"); if (StringUtils.isEmpty(query)) { @@ -88,13 +104,25 @@ private static String getFileLocationFromCommandLine(CommandLine line) { return query; } + /** + * Create the command-line options + * @return the command-line options + */ protected static Options createCommandLineOptions() { Options options = new Options(); - options.addOption("f", "file", true, "the location for the file containing the xml data"); + options.addOption("f", "file", true, "the path to the file containing the " + + "relationship definitions (e.g. ${dspace.dir}/config/entities/relationship-types.xml)"); + options.addOption("h", "help", false, "print this message"); return options; } + /** + * Run the script for the given file location + * @param fileLocation the file location + * @throws SQLException If something goes wrong initializing context or inserting relationship types + * @throws AuthorizeException If the script user fails to authorize while inserting relationship types + */ private void run(String fileLocation) throws SQLException, AuthorizeException { Context context = new Context(); context.turnOffAuthorisationSystem(); @@ -102,6 +130,12 @@ private void run(String fileLocation) throws SQLException, AuthorizeException { context.complete(); } + /** + * Parse the XML file at fileLocation to create relationship types in the database + * @param context DSpace context + * @param fileLocation the full or relative file path to the relationship types XML + * @throws AuthorizeException If the script user fails to authorize while inserting relationship types + */ private void parseXMLToRelations(Context context, String fileLocation) throws AuthorizeException { try { File fXmlFile = new File(fileLocation); @@ -158,15 +192,15 @@ private void parseXMLToRelations(Context context, String fileLocation) throws Au for (int j = 0; j < leftCardinalityList.getLength(); j++) { Node node = leftCardinalityList.item(j); - leftCardinalityMin = getString(leftCardinalityMin,(Element) node, "min"); - leftCardinalityMax = getString(leftCardinalityMax,(Element) node, "max"); + leftCardinalityMin = getCardinalityMinString(leftCardinalityMin,(Element) node, "min"); + leftCardinalityMax = getCardinalityMinString(leftCardinalityMax,(Element) node, "max"); } for (int j = 0; j < rightCardinalityList.getLength(); j++) { Node node = rightCardinalityList.item(j); - rightCardinalityMin = getString(rightCardinalityMin,(Element) node, "min"); - rightCardinalityMax = getString(rightCardinalityMax,(Element) node, "max"); + rightCardinalityMin = getCardinalityMinString(rightCardinalityMin,(Element) node, "min"); + rightCardinalityMax = getCardinalityMinString(rightCardinalityMax,(Element) node, "max"); } populateRelationshipType(context, leftType, rightType, leftwardType, rightwardType, @@ -182,13 +216,39 @@ private void parseXMLToRelations(Context context, String fileLocation) throws Au } } - private String getString(String leftCardinalityMin,Element node, String minOrMax) { + /** + * Extract the min or max value for the left or right cardinality from the node text content + * @param leftCardinalityMin current left cardinality min + * @param node node to extract the min or max value from + * @param minOrMax element tag name to parse + * @return final left cardinality min + */ + private String getCardinalityMinString(String leftCardinalityMin, Element node, String minOrMax) { if (node.getElementsByTagName(minOrMax).getLength() > 0) { leftCardinalityMin = node.getElementsByTagName(minOrMax).item(0).getTextContent(); } return leftCardinalityMin; } + /** + * Populate the relationship type based on values parsed from the XML relationship types configuration + * + * @param context DSpace context + * @param leftType left relationship type (e.g. "Publication"). + * @param rightType right relationship type (e.g. "Journal"). + * @param leftwardType leftward relationship type (e.g. "isAuthorOfPublication"). + * @param rightwardType rightward relationship type (e.g. "isPublicationOfAuthor"). + * @param leftCardinalityMin left cardinality min + * @param leftCardinalityMax left cardinality max + * @param rightCardinalityMin right cardinality min + * @param rightCardinalityMax right cardinality max + * @param copyToLeft copy metadata values to left if right side is deleted + * @param copyToRight copy metadata values to right if left side is deleted + * @param tilted set a tilted relationship side (left or right) if there are many relationships going one way + * to help performance (e.g. authors with 1000s of publications) + * @throws SQLException if database error occurs while saving the relationship type + * @throws AuthorizeException if authorization error occurs while saving the relationship type + */ private void populateRelationshipType(Context context, String leftType, String rightType, String leftwardType, String rightwardType, String leftCardinalityMin, String leftCardinalityMax, String rightCardinalityMin, String rightCardinalityMax, From fbb496e1c616e10dc667d794e19cac84e6e04913 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 23 Apr 2025 19:02:30 +0200 Subject: [PATCH 514/939] Improve help and docs for RegistryLoader And a few other small improvements (cherry picked from commit f1b4e6ef174559f901354176f934b05e3d698d58) --- .../org/dspace/administer/RegistryLoader.java | 132 +++++++++++++----- 1 file changed, 94 insertions(+), 38 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/administer/RegistryLoader.java b/dspace-api/src/main/java/org/dspace/administer/RegistryLoader.java index bbf320a0d5e..d503bfc00b7 100644 --- a/dspace-api/src/main/java/org/dspace/administer/RegistryLoader.java +++ b/dspace-api/src/main/java/org/dspace/administer/RegistryLoader.java @@ -21,6 +21,13 @@ import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.content.BitstreamFormat; @@ -41,7 +48,7 @@ *

    * RegistryLoader -bitstream bitstream-formats.xml *

    - * RegistryLoader -dc dc-types.xml + * RegistryLoader -metadata dc-types.xml * * @author Robert Tansley * @version $Revision$ @@ -50,7 +57,7 @@ public class RegistryLoader { /** * log4j category */ - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(RegistryLoader.class); + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(RegistryLoader.class); protected static BitstreamFormatService bitstreamFormatService = ContentServiceFactory.getInstance() .getBitstreamFormatService(); @@ -67,48 +74,97 @@ private RegistryLoader() { } * @throws Exception if error */ public static void main(String[] argv) throws Exception { - String usage = "Usage: " + RegistryLoader.class.getName() - + " (-bitstream | -metadata) registry-file.xml"; - - Context context = null; + // Set up command-line options and parse arguments + CommandLineParser parser = new DefaultParser(); + Options options = createCommandLineOptions(); try { - context = new Context(); + CommandLine line = parser.parse(options, argv); + + // Check if help option was entered or no options provided + if (line.hasOption('h') || line.getOptions().length == 0) { + printHelp(options); + System.exit(0); + } + + Context context = new Context(); // Can't update registries anonymously, so we need to turn off // authorisation context.turnOffAuthorisationSystem(); - // Work out what we're loading - if (argv[0].equalsIgnoreCase("-bitstream")) { - RegistryLoader.loadBitstreamFormats(context, argv[1]); - } else if (argv[0].equalsIgnoreCase("-metadata")) { - // Call MetadataImporter, as it handles Metadata schema updates - MetadataImporter.loadRegistry(argv[1], true); - } else { - System.err.println(usage); + try { + // Work out what we're loading + if (line.hasOption('b')) { + String filename = line.getOptionValue('b'); + if (StringUtils.isEmpty(filename)) { + System.err.println("No file path provided for bitstream format registry"); + printHelp(options); + System.exit(1); + } + RegistryLoader.loadBitstreamFormats(context, filename); + } else if (line.hasOption('m')) { + String filename = line.getOptionValue('m'); + if (StringUtils.isEmpty(filename)) { + System.err.println("No file path provided for metadata registry"); + printHelp(options); + System.exit(1); + } + // Call MetadataImporter, as it handles Metadata schema updates + MetadataImporter.loadRegistry(filename, true); + } else { + System.err.println("No registry type specified"); + printHelp(options); + System.exit(1); + } + + // Commit changes and close Context + context.complete(); + System.exit(0); + } catch (Exception e) { + log.fatal(LogHelper.getHeader(context, "error_loading_registries", ""), e); + System.err.println("Error: \n - " + e.getMessage()); + System.exit(1); + } finally { + // Clean up our context, if it still exists & it was never completed + if (context != null && context.isValid()) { + context.abort(); + } } + } catch (ParseException e) { + System.err.println("Error parsing command-line arguments: " + e.getMessage()); + printHelp(options); + System.exit(1); + } + } - // Commit changes and close Context - context.complete(); + /** + * Create the command-line options + * @return the command-line options + */ + private static Options createCommandLineOptions() { + Options options = new Options(); - System.exit(0); - } catch (ArrayIndexOutOfBoundsException ae) { - System.err.println(usage); + options.addOption("b", "bitstream", true, "load bitstream format registry from specified file"); + options.addOption("m", "metadata", true, "load metadata registry from specified file"); + options.addOption("h", "help", false, "print this help message"); - System.exit(1); - } catch (Exception e) { - log.fatal(LogHelper.getHeader(context, "error_loading_registries", - ""), e); + return options; + } - System.err.println("Error: \n - " + e.getMessage()); - System.exit(1); - } finally { - // Clean up our context, if it still exists & it was never completed - if (context != null && context.isValid()) { - context.abort(); - } - } + /** + * Print the help message + * @param options the command-line options + */ + private static void printHelp(Options options) { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("RegistryLoader", + "Load bitstream format or metadata registries into the database\n", + options, + "\nExamples:\n" + + " RegistryLoader -b bitstream-formats.xml\n" + + " RegistryLoader -m dc-types.xml", + true); } /** @@ -221,7 +277,7 @@ private static Document loadXML(String filename) throws IOException, * contains: *

    * - * <foo><mimetype>application/pdf</mimetype></foo> + * application/pdf * * passing this the foo node and mimetype will * return application/pdf. @@ -262,10 +318,10 @@ private static String getElementData(Node parentElement, String childName) * document contains: *

    * - * <foo> - * <bar>val1</bar> - * <bar>val2</bar> - * </foo> + * + * val1 + * val2 + * * * passing this the foo node and bar will * return val1 and val2. @@ -295,4 +351,4 @@ private static String[] getRepeatedElementData(Node parentElement, return data; } -} +} \ No newline at end of file From 85a9e4b731982df482f88277027748a2c0b2e0d9 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 23 Apr 2025 19:22:12 +0200 Subject: [PATCH 515/939] Let Curation CLI accept uuid identifiers (cherry picked from commit 5020689095549054c987c1fe4481a37aa86f3877) --- .../main/java/org/dspace/curate/Curation.java | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/curate/Curation.java b/dspace-api/src/main/java/org/dspace/curate/Curation.java index 625692a866b..b894dcd85f0 100644 --- a/dspace-api/src/main/java/org/dspace/curate/Curation.java +++ b/dspace-api/src/main/java/org/dspace/curate/Curation.java @@ -24,6 +24,8 @@ import org.apache.commons.cli.ParseException; import org.apache.commons.io.output.NullOutputStream; +import org.dspace.app.util.DSpaceObjectUtilsImpl; +import org.dspace.app.util.service.DSpaceObjectUtils; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; import org.dspace.content.factory.ContentServiceFactory; @@ -35,6 +37,7 @@ import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.handle.service.HandleService; import org.dspace.scripts.DSpaceRunnable; +import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.utils.DSpace; /** @@ -45,7 +48,9 @@ public class Curation extends DSpaceRunnable { protected EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); - + protected DSpaceObjectUtils dspaceObjectUtils = DSpaceServicesFactory.getInstance().getServiceManager() + .getServiceByName(DSpaceObjectUtilsImpl.class.getName(), DSpaceObjectUtilsImpl.class); + HandleService handleService = HandleServiceFactory.getInstance().getHandleService(); protected Context context; private CurationClientOptions curationClientOptions; @@ -345,9 +350,29 @@ private void initTaskLineOptionsAndCheckIfValid() { if (this.commandLine.hasOption('i')) { this.id = this.commandLine.getOptionValue('i').toLowerCase(); + DSpaceObject dso; if (!this.id.equalsIgnoreCase("all")) { - HandleService handleService = HandleServiceFactory.getInstance().getHandleService(); - DSpaceObject dso; + // First, try to parse the id as a UUID. If that fails, treat it as a handle. + UUID uuid = null; + try { + uuid = UUID.fromString(id); + } catch (Exception e) { + // It's not a UUID, proceed to treat it as a handle. + } + if (uuid != null) { + try { + dso = dspaceObjectUtils.findDSpaceObject(context, uuid); + if (dso != null) { + // We already resolved an object, return early + return; + } + } catch (SQLException e) { + String error = "SQLException trying to find dso with uuid " + uuid; + super.handler.logError(error); + throw new RuntimeException(error, e); + } + } + // If we get here, the id is not a UUID, so we assume it's a handle. try { dso = handleService.resolveToObject(this.context, id); } catch (SQLException e) { From 6fe9af84bdb291bff8ddcf510372aab157c6b30e Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 29 Apr 2025 14:51:19 -0500 Subject: [PATCH 516/939] Potential fix for code scanning alert no. 30: Resolving XML external entity in user-controlled data Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> (cherry picked from commit a0ce50b2a497dcb1711f48ad35cda14eeabf686f) --- .../pubmed/service/PubmedImportMetadataSourceServiceImpl.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java index a6cfa625bbc..13201b8fcde 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java @@ -234,6 +234,8 @@ private String getSingleElementValue(String src, String elementName) { try { SAXBuilder saxBuilder = new SAXBuilder(); + saxBuilder.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + saxBuilder.setFeature("http://xml.org/sax/features/external-general-entities", false); Document document = saxBuilder.build(new StringReader(src)); Element root = document.getRootElement(); From 90ea371e0b0a12a245b094ea057bbcf4117f9849 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 29 Apr 2025 16:57:31 -0500 Subject: [PATCH 517/939] Cannot disable DTDs with PubMed, so instead disallow external entities & entity expansion (cherry picked from commit f9614c41a6ceaa54756f780164fec40a3b185483) --- .../pubmed/service/PubmedImportMetadataSourceServiceImpl.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java index 13201b8fcde..000ef19eaec 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java @@ -234,8 +234,10 @@ private String getSingleElementValue(String src, String elementName) { try { SAXBuilder saxBuilder = new SAXBuilder(); - saxBuilder.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + // Disallow external entities & entity expansion to protect against XXE attacks + // (NOTE: We receive errors if we disable all DTDs for PubMed, so this is the best we can do) saxBuilder.setFeature("http://xml.org/sax/features/external-general-entities", false); + saxBuilder.setFeature("http://xml.org/sax/features/external-parameter-entities", false); Document document = saxBuilder.build(new StringReader(src)); Element root = document.getRootElement(); From c6098c0232d73144804663df2ba5eb401efa9dbb Mon Sep 17 00:00:00 2001 From: Stefano Maffei Date: Tue, 29 Apr 2025 11:57:38 +0200 Subject: [PATCH 518/939] [DURACOM-357] fix Collection Admin cannot see withdrawn item metadata (cherry picked from commit 5e2bb4fb9270a22a28a39ebcbcbac3f2781f097d) --- .../app/rest/converter/ItemConverter.java | 6 +- .../dspace/app/rest/ItemRestRepositoryIT.java | 62 +++++++++++++++++++ 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ItemConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ItemConverter.java index fc64b66e8a1..38f829be347 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ItemConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ItemConverter.java @@ -8,7 +8,6 @@ package org.dspace.app.rest.converter; import java.sql.SQLException; -import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Objects; @@ -76,8 +75,9 @@ public MetadataValueList getPermissionFilteredMetadata(Context context, Item obj List returnList = new LinkedList<>(); try { if (obj.isWithdrawn() && (Objects.isNull(context) || - Objects.isNull(context.getCurrentUser()) || !authorizeService.isAdmin(context))) { - return new MetadataValueList(new ArrayList()); + Objects.isNull(context.getCurrentUser()) || + !(authorizeService.isAdmin(context) || authorizeService.isCollectionAdmin(context)))) { + return new MetadataValueList(List.of()); } if (context != null && (authorizeService.isAdmin(context) || itemService.canEdit(context, obj))) { return new MetadataValueList(fullList); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java index 714ad0b419a..48e70a8d5f1 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java @@ -422,6 +422,68 @@ public void findOneTest() throws Exception { .andExpect(jsonPath("$", publicItem1Matcher)); } + @Test + public void findOneWithdrawnAsCollectionAdminTest() throws Exception { + context.turnOffAuthorisationSystem(); + + // Create collection admin account + EPerson collectionAdmin = EPersonBuilder.createEPerson(context) + .withEmail("collection-admin@dspace.com") + .withPassword("test") + .withCanLogin(true) + .build(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + + // Create collection + Collection adminCollection = CollectionBuilder.createCollection(context, child1) + .withName("Collection Admin col") + .withAdminGroup(collectionAdmin) + .build(); + Collection noAdminCollection = + CollectionBuilder.createCollection(context, child1).withName("Collection non Admin") + .build(); + + // both items are withdrawn + Item administeredItem = ItemBuilder.createItem(context, adminCollection) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("ExtraEntry") + .withdrawn() + .build(); + + Item nonAdministeredItem = ItemBuilder.createItem(context, noAdminCollection) + .withTitle("Public item 2") + .withIssueDate("2016-02-13") + .withAuthor("Smith, Maria").withAuthor("Doe, Jane") + .withSubject("TestingForMore").withSubject("ExtraEntry") + .withdrawn() + .build(); + + context.restoreAuthSystemState(); + + String collectionAdmintoken = getAuthToken(collectionAdmin.getEmail(), "test"); + + // Metadata are retrieved since user is administering the item's collection + getClient(collectionAdmintoken).perform(get("/api/core/items/" + administeredItem.getID()) + .param("projection", "full")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.metadata").isNotEmpty()); + + // No metadata is retrieved since user is not administering the item's collection + getClient().perform(get("/api/core/items/" + nonAdministeredItem.getID()) + .param("projection", "full")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.metadata").isEmpty()); + + + } + @Test public void findOneFullProjectionTest() throws Exception { context.turnOffAuthorisationSystem(); From 8ca8bd4543a9727173278f9752cf635fb2abd292 Mon Sep 17 00:00:00 2001 From: Stefano Maffei Date: Wed, 30 Apr 2025 08:31:01 +0200 Subject: [PATCH 519/939] [DURACOM-357] improved check for authorization on objects in ItemConverter (cherry picked from commit a70dede20b9310ec85ec6f441be16c0437c796cf) --- .../main/java/org/dspace/app/rest/converter/ItemConverter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ItemConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ItemConverter.java index 38f829be347..0c5a36d5fb5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ItemConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ItemConverter.java @@ -76,7 +76,7 @@ public MetadataValueList getPermissionFilteredMetadata(Context context, Item obj try { if (obj.isWithdrawn() && (Objects.isNull(context) || Objects.isNull(context.getCurrentUser()) || - !(authorizeService.isAdmin(context) || authorizeService.isCollectionAdmin(context)))) { + !(authorizeService.isAdmin(context) || authorizeService.isAdmin(context, obj)))) { return new MetadataValueList(List.of()); } if (context != null && (authorizeService.isAdmin(context) || itemService.canEdit(context, obj))) { From 2429a0ba2932296d7a2f680743fd4106115d03a3 Mon Sep 17 00:00:00 2001 From: Stefano Maffei Date: Wed, 30 Apr 2025 08:56:31 +0200 Subject: [PATCH 520/939] [DURACOM-357] improved admin check (cherry picked from commit bb3935a0473a13fd1804dd6c88cc49a9196809bb) --- .../main/java/org/dspace/app/rest/converter/ItemConverter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ItemConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ItemConverter.java index 0c5a36d5fb5..abcd707118c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ItemConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ItemConverter.java @@ -76,7 +76,7 @@ public MetadataValueList getPermissionFilteredMetadata(Context context, Item obj try { if (obj.isWithdrawn() && (Objects.isNull(context) || Objects.isNull(context.getCurrentUser()) || - !(authorizeService.isAdmin(context) || authorizeService.isAdmin(context, obj)))) { + !authorizeService.isAdmin(context, obj))) { return new MetadataValueList(List.of()); } if (context != null && (authorizeService.isAdmin(context) || itemService.canEdit(context, obj))) { From 4a89a68736de365d0ca93a3b22f48309f49c88d6 Mon Sep 17 00:00:00 2001 From: Stefano Maffei Date: Wed, 30 Apr 2025 11:33:40 +0200 Subject: [PATCH 521/939] [DURACOM-357] improved javadoc (cherry picked from commit f1cb3c3ad144444f9ee9baf699a359f19680f38c) --- .../main/java/org/dspace/app/rest/converter/ItemConverter.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ItemConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ItemConverter.java index abcd707118c..a1e9442f746 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ItemConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ItemConverter.java @@ -63,6 +63,9 @@ public ItemRest convert(Item obj, Projection projection) { /** * Retrieves the metadata list filtered according to the hidden metadata configuration * When the context is null, it will return the metadatalist as for an anonymous user + * When the context is not null, it will return the full metadata list if the user + * is allowed to edit the item or if the user is an admin. Otherwise, it will + * return the metadata list filtered according to the hidden metadata configuration * Overrides the parent method to include virtual metadata * @param context The context * @param obj The object of which the filtered metadata will be retrieved From b04eb9d72513141ba6ab39a286ba8cee3221bae3 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 1 May 2025 10:52:39 -0500 Subject: [PATCH 522/939] Replace MethodNotFoundException with more appropriate UnsupportedOperationException --- .../external/ads/ADSImportMetadataSourceServiceImpl.java | 3 +-- .../service/ArXivImportMetadataSourceServiceImpl.java | 3 +-- .../cinii/CiniiImportMetadataSourceServiceImpl.java | 3 +-- .../crossref/CrossRefImportMetadataSourceServiceImpl.java | 3 +-- .../datacite/DataCiteImportMetadataSourceServiceImpl.java | 3 +-- .../PubmedEuropeMetadataSourceServiceImpl.java | 3 +-- .../service/ScieloImportMetadataSourceServiceImpl.java | 7 +++---- .../service/ScopusImportMetadataSourceServiceImpl.java | 3 +-- .../vufind/VuFindImportMetadataSourceServiceImpl.java | 3 +-- .../wos/service/WOSImportMetadataSourceServiceImpl.java | 7 +++---- .../dspace/app/rest/ADSImportMetadataSourceServiceIT.java | 3 +-- .../app/rest/CrossRefImportMetadataSourceServiceIT.java | 3 +-- .../app/rest/DataCiteImportMetadataSourceServiceIT.java | 3 +-- .../app/rest/ScieloImportMetadataSourceServiceIT.java | 5 ++--- .../app/rest/VuFindImportMetadataSourceServiceIT.java | 3 +-- 15 files changed, 20 insertions(+), 35 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/ads/ADSImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/ads/ADSImportMetadataSourceServiceImpl.java index 8fbe4ef2cf5..ca3f48da611 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/ads/ADSImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/ads/ADSImportMetadataSourceServiceImpl.java @@ -17,7 +17,6 @@ import java.util.List; import java.util.Map; import java.util.concurrent.Callable; -import javax.el.MethodNotFoundException; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; @@ -99,7 +98,7 @@ public Collection findMatchingRecords(Query query) throws Metadata @Override public Collection findMatchingRecords(Item item) throws MetadataSourceException { - throw new MethodNotFoundException("This method is not implemented for CrossRef"); + throw new UnsupportedOperationException("This method is not implemented for CrossRef"); } @Override diff --git a/dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java index 96689e62ba7..4369b0d48b4 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java @@ -14,7 +14,6 @@ import java.util.Collection; import java.util.List; import java.util.concurrent.Callable; -import javax.el.MethodNotFoundException; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Invocation; @@ -162,7 +161,7 @@ public String getImportSource() { @Override public Collection findMatchingRecords(Item item) throws MetadataSourceException { // FIXME: we need this method? - throw new MethodNotFoundException("This method is not implemented for ArXiv"); + throw new UnsupportedOperationException("This method is not implemented for ArXiv"); } /** diff --git a/dspace-api/src/main/java/org/dspace/importer/external/cinii/CiniiImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/cinii/CiniiImportMetadataSourceServiceImpl.java index 587ad5b2583..82a4b2d7796 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/cinii/CiniiImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/cinii/CiniiImportMetadataSourceServiceImpl.java @@ -19,7 +19,6 @@ import java.util.Map; import java.util.Objects; import java.util.concurrent.Callable; -import javax.el.MethodNotFoundException; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; @@ -113,7 +112,7 @@ public Collection findMatchingRecords(Query query) throws Metadata @Override public Collection findMatchingRecords(Item item) throws MetadataSourceException { - throw new MethodNotFoundException("This method is not implemented for Cinii"); + throw new UnsupportedOperationException("This method is not implemented for Cinii"); } public String getUrl() { diff --git a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefImportMetadataSourceServiceImpl.java index 419f6ca8a08..88aeec8d949 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefImportMetadataSourceServiceImpl.java @@ -16,7 +16,6 @@ import java.util.Map; import java.util.Objects; import java.util.concurrent.Callable; -import javax.el.MethodNotFoundException; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; @@ -112,7 +111,7 @@ public Collection findMatchingRecords(Query query) throws Metadata @Override public Collection findMatchingRecords(Item item) throws MetadataSourceException { - throw new MethodNotFoundException("This method is not implemented for CrossRef"); + throw new UnsupportedOperationException("This method is not implemented for CrossRef"); } public String getID(String id) { diff --git a/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java index 6c65d96b375..34405cc3eec 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java @@ -13,7 +13,6 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import javax.el.MethodNotFoundException; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; @@ -188,7 +187,7 @@ public Collection findMatchingRecords(Query query) throws Metadata @Override public Collection findMatchingRecords(Item item) throws MetadataSourceException { - throw new MethodNotFoundException("This method is not implemented for DataCite"); + throw new UnsupportedOperationException("This method is not implemented for DataCite"); } public String getID(String query) { diff --git a/dspace-api/src/main/java/org/dspace/importer/external/pubmedeurope/PubmedEuropeMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/pubmedeurope/PubmedEuropeMetadataSourceServiceImpl.java index 92d7d9fbd3f..7cd297eb281 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/pubmedeurope/PubmedEuropeMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/pubmedeurope/PubmedEuropeMetadataSourceServiceImpl.java @@ -17,7 +17,6 @@ import java.util.Map; import java.util.Objects; import java.util.concurrent.Callable; -import javax.el.MethodNotFoundException; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpException; @@ -153,7 +152,7 @@ public Collection findMatchingRecords(Query query) throws Metadata @Override public Collection findMatchingRecords(Item item) throws MetadataSourceException { - throw new MethodNotFoundException("This method is not implemented for PubMed Europe"); + throw new UnsupportedOperationException("This method is not implemented for PubMed Europe"); } @Override diff --git a/dspace-api/src/main/java/org/dspace/importer/external/scielo/service/ScieloImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/scielo/service/ScieloImportMetadataSourceServiceImpl.java index 4f83ffe978f..87b501470b8 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/scielo/service/ScieloImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/scielo/service/ScieloImportMetadataSourceServiceImpl.java @@ -20,7 +20,6 @@ import java.util.concurrent.Callable; import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.el.MethodNotFoundException; import javax.ws.rs.BadRequestException; import org.apache.commons.collections4.CollectionUtils; @@ -99,17 +98,17 @@ public int getRecordsCount(String query) throws MetadataSourceException { @Override public int getRecordsCount(Query query) throws MetadataSourceException { - throw new MethodNotFoundException("This method is not implemented for Scielo"); + throw new UnsupportedOperationException("This method is not implemented for Scielo"); } @Override public Collection findMatchingRecords(Item item) throws MetadataSourceException { - throw new MethodNotFoundException("This method is not implemented for Scielo"); + throw new UnsupportedOperationException("This method is not implemented for Scielo"); } @Override public Collection findMatchingRecords(Query query) throws MetadataSourceException { - throw new MethodNotFoundException("This method is not implemented for Scielo"); + throw new UnsupportedOperationException("This method is not implemented for Scielo"); } /** diff --git a/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/ScopusImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/ScopusImportMetadataSourceServiceImpl.java index e61ca052868..a3f74694bec 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/ScopusImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/ScopusImportMetadataSourceServiceImpl.java @@ -22,7 +22,6 @@ import java.util.concurrent.Callable; import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.el.MethodNotFoundException; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; @@ -152,7 +151,7 @@ public ImportRecord getRecord(Query query) throws MetadataSourceException { @Override public Collection findMatchingRecords(Item item) throws MetadataSourceException { - throw new MethodNotFoundException("This method is not implemented for Scopus"); + throw new UnsupportedOperationException("This method is not implemented for Scopus"); } @Override diff --git a/dspace-api/src/main/java/org/dspace/importer/external/vufind/VuFindImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/vufind/VuFindImportMetadataSourceServiceImpl.java index a4f90fa5ba6..7eb3743d207 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/vufind/VuFindImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/vufind/VuFindImportMetadataSourceServiceImpl.java @@ -15,7 +15,6 @@ import java.util.Map; import java.util.Objects; import java.util.concurrent.Callable; -import javax.el.MethodNotFoundException; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; @@ -104,7 +103,7 @@ public Collection findMatchingRecords(Query query) throws Metadata @Override public Collection findMatchingRecords(Item item) throws MetadataSourceException { - throw new MethodNotFoundException("This method is not implemented for VuFind"); + throw new UnsupportedOperationException("This method is not implemented for VuFind"); } @Override diff --git a/dspace-api/src/main/java/org/dspace/importer/external/wos/service/WOSImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/wos/service/WOSImportMetadataSourceServiceImpl.java index f550b659952..c33a2f89012 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/wos/service/WOSImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/wos/service/WOSImportMetadataSourceServiceImpl.java @@ -21,7 +21,6 @@ import java.util.concurrent.Callable; import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.el.MethodNotFoundException; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; @@ -109,17 +108,17 @@ public int getRecordsCount(String query) throws MetadataSourceException { @Override public int getRecordsCount(Query query) throws MetadataSourceException { - throw new MethodNotFoundException("This method is not implemented for WOS"); + throw new UnsupportedOperationException("This method is not implemented for WOS"); } @Override public Collection findMatchingRecords(Item item) throws MetadataSourceException { - throw new MethodNotFoundException("This method is not implemented for WOS"); + throw new UnsupportedOperationException("This method is not implemented for WOS"); } @Override public Collection findMatchingRecords(Query query) throws MetadataSourceException { - throw new MethodNotFoundException("This method is not implemented for WOS"); + throw new UnsupportedOperationException("This method is not implemented for WOS"); } /** diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ADSImportMetadataSourceServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ADSImportMetadataSourceServiceIT.java index 4878cdecab8..4150ca580e4 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ADSImportMetadataSourceServiceIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ADSImportMetadataSourceServiceIT.java @@ -17,7 +17,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; -import javax.el.MethodNotFoundException; import org.apache.commons.io.IOUtils; import org.apache.http.client.methods.CloseableHttpResponse; @@ -117,7 +116,7 @@ public void adsImportMetadataGetRecordsCountByQueryTest() throws Exception { } } - @Test(expected = MethodNotFoundException.class) + @Test(expected = UnsupportedOperationException.class) public void adsImportMetadataFindMatchingRecordsTest() throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CrossRefImportMetadataSourceServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CrossRefImportMetadataSourceServiceIT.java index a182e7d8907..27b5e6933ba 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CrossRefImportMetadataSourceServiceIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CrossRefImportMetadataSourceServiceIT.java @@ -17,7 +17,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; -import javax.el.MethodNotFoundException; import org.apache.commons.io.IOUtils; import org.apache.http.client.methods.CloseableHttpResponse; @@ -135,7 +134,7 @@ public void crossRefImportMetadataGetRecordByIdTest() throws Exception { } } - @Test(expected = MethodNotFoundException.class) + @Test(expected = UnsupportedOperationException.class) public void crossRefImportMetadataFindMatchingRecordsTest() throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DataCiteImportMetadataSourceServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DataCiteImportMetadataSourceServiceIT.java index c1481f0573c..a8cd59a1afc 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DataCiteImportMetadataSourceServiceIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DataCiteImportMetadataSourceServiceIT.java @@ -15,7 +15,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -import javax.el.MethodNotFoundException; import org.apache.commons.io.IOUtils; import org.apache.http.client.methods.CloseableHttpResponse; @@ -90,7 +89,7 @@ public void dataCiteImportMetadataGetRecordsCountTest() throws Exception { } } - @Test(expected = MethodNotFoundException.class) + @Test(expected = UnsupportedOperationException.class) public void dataCiteImportMetadataFindMatchingRecordsTest() throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScieloImportMetadataSourceServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScieloImportMetadataSourceServiceIT.java index aafc75a065a..af23ff4d14a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScieloImportMetadataSourceServiceIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScieloImportMetadataSourceServiceIT.java @@ -17,7 +17,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; -import javax.el.MethodNotFoundException; import org.apache.commons.io.IOUtils; import org.apache.http.client.methods.CloseableHttpResponse; @@ -92,7 +91,7 @@ public void scieloImportMetadataGetRecordsCountTest() throws Exception { } } - @Test(expected = MethodNotFoundException.class) + @Test(expected = UnsupportedOperationException.class) public void scieloImportMetadataFindMatchingRecordsTest() throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) @@ -111,7 +110,7 @@ public void scieloImportMetadataFindMatchingRecordsTest() throws Exception { scieloServiceImpl.findMatchingRecords(testItem); } - @Test(expected = MethodNotFoundException.class) + @Test(expected = UnsupportedOperationException.class) public void scieloImportMetadataGetRecordsCountByQueryTest() throws Exception { Query q = new Query(); q.addParameter("query", "test query"); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VuFindImportMetadataSourceServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VuFindImportMetadataSourceServiceIT.java index c3063ca2345..89b67bbb4aa 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VuFindImportMetadataSourceServiceIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VuFindImportMetadataSourceServiceIT.java @@ -17,7 +17,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; -import javax.el.MethodNotFoundException; import org.apache.commons.io.IOUtils; import org.apache.http.client.methods.CloseableHttpResponse; @@ -119,7 +118,7 @@ public void vuFindImportMetadataGetRecordByIdTest() throws Exception { } } - @Test(expected = MethodNotFoundException.class) + @Test(expected = UnsupportedOperationException.class) public void vuFindImportMetadataFindMatchingRecordsTest() throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) From d83779022e3d62ea45539a8b051d869a3b19743d Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 1 May 2025 12:53:41 -0500 Subject: [PATCH 523/939] Remove javax.el dependency --- dspace-api/pom.xml | 5 ----- pom.xml | 6 ------ 2 files changed, 11 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 3de7099ad66..bb246c20ad8 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -505,11 +505,6 @@ javax.servlet-api provided - - - javax.el - javax.el-api - javax.annotation javax.annotation-api diff --git a/pom.xml b/pom.xml index a58392cd80c..e2683a5844b 100644 --- a/pom.xml +++ b/pom.xml @@ -1554,12 +1554,6 @@ javax.servlet-api 3.1.0 - - - javax.el - javax.el-api - 3.0.0 - From 4ec611bf796ac58f60c53f808facfb3211810cff Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Tue, 19 Nov 2024 12:13:37 +0100 Subject: [PATCH 524/939] 119612: configurable limit on exporting items since it can take up a bunch of resources (cherry picked from commit b634e1e38070e65128c49d702affa3b19842ebb6) --- .../app/bulkedit/MetadataExportSearch.java | 2 +- .../MetadataDSpaceCsvExportServiceImpl.java | 43 +++++++++++++++++-- .../MetadataDSpaceCsvExportService.java | 6 ++- .../MetadataDSpaceCsvExportServiceImplIT.java | 8 +++- dspace/config/dspace.cfg | 9 ++++ 5 files changed, 59 insertions(+), 9 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportSearch.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportSearch.java index 027ad116a7e..e4bbe335d63 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportSearch.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportSearch.java @@ -143,7 +143,7 @@ public void internalRun() throws Exception { Iterator itemIterator = searchService.iteratorSearch(context, dso, discoverQuery); handler.logDebug("creating dspacecsv"); - DSpaceCSV dSpaceCSV = metadataDSpaceCsvExportService.export(context, itemIterator, true); + DSpaceCSV dSpaceCSV = metadataDSpaceCsvExportService.export(context, itemIterator, true, handler); handler.logDebug("writing to file " + getFileNameOrExportFile()); handler.writeFilestream(context, getFileNameOrExportFile(), dSpaceCSV.getInputStream(), EXPORT_CSV); context.restoreAuthSystemState(); diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java index 8bc34d3f5ed..7e313b79511 100644 --- a/dspace-api/src/main/java/org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java @@ -15,14 +15,17 @@ import java.util.Set; import java.util.UUID; +import org.apache.commons.collections.IteratorUtils; import org.dspace.app.bulkedit.DSpaceCSV; import org.dspace.app.util.service.DSpaceObjectUtils; import org.dspace.content.service.ItemService; import org.dspace.content.service.MetadataDSpaceCsvExportService; import org.dspace.core.Constants; import org.dspace.core.Context; +import org.dspace.eperson.service.GroupService; import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.scripts.handler.DSpaceRunnableHandler; +import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; /** @@ -36,6 +39,12 @@ public class MetadataDSpaceCsvExportServiceImpl implements MetadataDSpaceCsvExpo @Autowired private DSpaceObjectUtils dSpaceObjectUtils; + @Autowired + private ConfigurationService configurationService; + + @Autowired + private GroupService groupService; + @Override public DSpaceCSV handleExport(Context context, boolean exportAllItems, boolean exportAllMetadata, String identifier, DSpaceRunnableHandler handler) throws Exception { @@ -74,17 +83,19 @@ public DSpaceCSV handleExport(Context context, boolean exportAllItems, boolean e } } - DSpaceCSV csv = this.export(context, toExport, exportAllMetadata); + DSpaceCSV csv = this.export(context, toExport, exportAllMetadata, handler); return csv; } @Override - public DSpaceCSV export(Context context, Iterator toExport, boolean exportAll) throws Exception { + public DSpaceCSV export(Context context, Iterator toExport, + boolean exportAll, DSpaceRunnableHandler handler) throws Exception { Context.Mode originalMode = context.getCurrentMode(); context.setMode(Context.Mode.READ_ONLY); // Process each item DSpaceCSV csv = new DSpaceCSV(exportAll); + toExport = setItemsToExportWithLimit(context, toExport, handler); while (toExport.hasNext()) { Item item = toExport.next(); csv.addItem(item); @@ -97,8 +108,32 @@ public DSpaceCSV export(Context context, Iterator toExport, boolean export } @Override - public DSpaceCSV export(Context context, Community community, boolean exportAll) throws Exception { - return export(context, buildFromCommunity(context, community), exportAll); + public DSpaceCSV export(Context context, Community community, + boolean exportAll, DSpaceRunnableHandler handler) throws Exception { + return export(context, buildFromCommunity(context, community), exportAll, handler); + } + + private Iterator setItemsToExportWithLimit(Context context, Iterator toExport, + DSpaceRunnableHandler handler) throws SQLException { + int itemExportLimit = configurationService.getIntProperty( + "metadataexport.max.items", 500); + String[] ignoreLimitGroups = configurationService.getArrayProperty( + "metadataexport.admin.groups"); + + for (String group : ignoreLimitGroups) { + if (groupService.isMember(context, context.getCurrentUser(), group)) { + itemExportLimit = Integer.MAX_VALUE; + break; + } + } + + List items = IteratorUtils.toList(toExport); + if (items.size() > itemExportLimit) { + handler.logWarning("The amount of items to export is higher than the limit of " + itemExportLimit + + " items. Only the first " + itemExportLimit + " items will be exported."); + items = items.subList(0, itemExportLimit); + } + return items.iterator(); } /** diff --git a/dspace-api/src/main/java/org/dspace/content/service/MetadataDSpaceCsvExportService.java b/dspace-api/src/main/java/org/dspace/content/service/MetadataDSpaceCsvExportService.java index d3fc2e82366..052754a1633 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/MetadataDSpaceCsvExportService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/MetadataDSpaceCsvExportService.java @@ -44,7 +44,8 @@ public DSpaceCSV handleExport(Context context, boolean exportAllItems, boolean e * @return A DSpaceCSV object containing the exported information * @throws Exception If something goes wrong */ - public DSpaceCSV export(Context context, Iterator toExport, boolean exportAll) throws Exception; + public DSpaceCSV export(Context context, Iterator toExport, + boolean exportAll, DSpaceRunnableHandler handler) throws Exception; /** * This method will export all the Items within the given Community to a DSpaceCSV @@ -54,6 +55,7 @@ public DSpaceCSV handleExport(Context context, boolean exportAllItems, boolean e * @return A DSpaceCSV object containing the exported information * @throws Exception If something goes wrong */ - public DSpaceCSV export(Context context, Community community, boolean exportAll) throws Exception; + public DSpaceCSV export(Context context, Community community, + boolean exportAll, DSpaceRunnableHandler handler) throws Exception; } \ No newline at end of file diff --git a/dspace-api/src/test/java/org/dspace/content/MetadataDSpaceCsvExportServiceImplIT.java b/dspace-api/src/test/java/org/dspace/content/MetadataDSpaceCsvExportServiceImplIT.java index c2d4f56ca61..5a341264648 100644 --- a/dspace-api/src/test/java/org/dspace/content/MetadataDSpaceCsvExportServiceImplIT.java +++ b/dspace-api/src/test/java/org/dspace/content/MetadataDSpaceCsvExportServiceImplIT.java @@ -16,6 +16,7 @@ import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.app.bulkedit.DSpaceCSV; import org.dspace.app.bulkedit.DSpaceCSVLine; +import org.dspace.app.scripts.handler.impl.TestDSpaceRunnableHandler; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.ItemBuilder; @@ -31,6 +32,9 @@ */ public class MetadataDSpaceCsvExportServiceImplIT extends AbstractIntegrationTestWithDatabase { + + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + /** * Test of handleExport method, of class MetadataDSpaceCsvExportServiceImpl. * @throws java.lang.Exception passed through. @@ -66,7 +70,7 @@ public void testExport_3args_1() boolean exportAll = false; MetadataDSpaceCsvExportServiceImpl instance = new MetadataDSpaceCsvExportServiceImpl(); DSpaceCSV expResult = null; - DSpaceCSV result = instance.export(context, toExport, exportAll); + DSpaceCSV result = instance.export(context, toExport, exportAll, testDSpaceRunnableHandler); assertEquals(expResult, result); // TODO review the generated test code and remove the default call to fail. fail("The test case is a prototype."); @@ -105,7 +109,7 @@ public void testMappedItem() .getServiceManager() .getServiceByName(MetadataDSpaceCsvExportServiceImpl.class.getCanonicalName(), MetadataDSpaceCsvExportService.class); - DSpaceCSV result = instance.export(context, parentCommunity, false); + DSpaceCSV result = instance.export(context, parentCommunity, false, testDSpaceRunnableHandler); // Examine the result. List csvLines = result.getCSVLines(); diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 94b731cd348..32598bd8ecd 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -893,6 +893,15 @@ org.dspace.app.itemexport.life.span.hours = 48 # cumulative sizes are more than this entry the export is not kicked off org.dspace.app.itemexport.max.size = 200 +### Bulkedit Metadata export settings +# The maximum amount of items that can be exported using the "metadata-export" script / process +# Recommend to keep this at a feasible number, as exporting large amounts of items can be resource intensive +metadataexport.max.items = 1 + +# A list of groups that are allowed to use the metadata-export script without any restrictions +#metadataexport.admin.groups = Administrator + + ### Batch Item import settings ### # The directory where the results of imports will be placed (mapfile, upload file) org.dspace.app.batchitemimport.work.dir = ${dspace.dir}/imports From 4626b06d7fd95da1b805ef879d174c155ba65650 Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Wed, 20 Nov 2024 17:09:38 +0100 Subject: [PATCH 525/939] 119612: property should be commented by default and have a normal limit (cherry picked from commit a8b98bb7b78c9220e159c587a55def1bff3cb023) --- dspace/config/dspace.cfg | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 32598bd8ecd..023bc45ff7c 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -894,9 +894,10 @@ org.dspace.app.itemexport.life.span.hours = 48 org.dspace.app.itemexport.max.size = 200 ### Bulkedit Metadata export settings -# The maximum amount of items that can be exported using the "metadata-export" script / process +# The maximum amount of items that can be exported using the "metadata-export" / "metadata-export-search" script # Recommend to keep this at a feasible number, as exporting large amounts of items can be resource intensive -metadataexport.max.items = 1 +# If not set, this will default to 500 items +# metadataexport.max.items = 500 # A list of groups that are allowed to use the metadata-export script without any restrictions #metadataexport.admin.groups = Administrator From c5c8417848d7d59f975e32144b99b5fa83549748 Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Mon, 20 Jan 2025 15:46:26 +0100 Subject: [PATCH 526/939] 119612: Remove group configuration and expose property to angular (cherry picked from commit c73c739deb007c657641f198414da886c2953807) --- .../content/MetadataDSpaceCsvExportServiceImpl.java | 13 ------------- dspace/config/dspace.cfg | 3 --- dspace/config/modules/rest.cfg | 1 + 3 files changed, 1 insertion(+), 16 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java index 7e313b79511..3dd39658934 100644 --- a/dspace-api/src/main/java/org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java @@ -22,7 +22,6 @@ import org.dspace.content.service.MetadataDSpaceCsvExportService; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.dspace.eperson.service.GroupService; import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.scripts.handler.DSpaceRunnableHandler; import org.dspace.services.ConfigurationService; @@ -42,9 +41,6 @@ public class MetadataDSpaceCsvExportServiceImpl implements MetadataDSpaceCsvExpo @Autowired private ConfigurationService configurationService; - @Autowired - private GroupService groupService; - @Override public DSpaceCSV handleExport(Context context, boolean exportAllItems, boolean exportAllMetadata, String identifier, DSpaceRunnableHandler handler) throws Exception { @@ -117,15 +113,6 @@ private Iterator setItemsToExportWithLimit(Context context, Iterator DSpaceRunnableHandler handler) throws SQLException { int itemExportLimit = configurationService.getIntProperty( "metadataexport.max.items", 500); - String[] ignoreLimitGroups = configurationService.getArrayProperty( - "metadataexport.admin.groups"); - - for (String group : ignoreLimitGroups) { - if (groupService.isMember(context, context.getCurrentUser(), group)) { - itemExportLimit = Integer.MAX_VALUE; - break; - } - } List items = IteratorUtils.toList(toExport); if (items.size() > itemExportLimit) { diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 023bc45ff7c..2e5c06c707c 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -899,9 +899,6 @@ org.dspace.app.itemexport.max.size = 200 # If not set, this will default to 500 items # metadataexport.max.items = 500 -# A list of groups that are allowed to use the metadata-export script without any restrictions -#metadataexport.admin.groups = Administrator - ### Batch Item import settings ### # The directory where the results of imports will be placed (mapfile, upload file) diff --git a/dspace/config/modules/rest.cfg b/dspace/config/modules/rest.cfg index 537eedbd087..86f0b801dbd 100644 --- a/dspace/config/modules/rest.cfg +++ b/dspace/config/modules/rest.cfg @@ -55,6 +55,7 @@ rest.properties.exposed = cc.license.jurisdiction rest.properties.exposed = identifiers.item-status.register-doi rest.properties.exposed = authentication-password.domain.valid rest.properties.exposed = handle.canonical.prefix +rest.properties.exposed = metadataexport.max.items #---------------------------------------------------------------# # These configs are used by the deprecated REST (v4-6) module # From 5929fdc926a5bd3ae5df08bd69860b4562490614 Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Mon, 3 Feb 2025 10:09:47 +0100 Subject: [PATCH 527/939] 124504: Move configuration to be included in the bulkedit module and apply the configured limit earlier, never obtaining a larger list than actually required (cherry picked from commit b63ffd2eb4a869d17d2caca2d726d7702b05d564) --- .../app/bulkedit/MetadataExportSearch.java | 2 + .../MetadataDSpaceCsvExportServiceImpl.java | 42 +++++++++---------- .../MetadataDSpaceCsvExportService.java | 4 +- dspace/config/dspace.cfg | 7 ---- dspace/config/modules/bulkedit.cfg | 5 +++ dspace/config/modules/rest.cfg | 2 +- 6 files changed, 31 insertions(+), 31 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportSearch.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportSearch.java index e4bbe335d63..04c3a75dedd 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportSearch.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportSearch.java @@ -139,6 +139,8 @@ public void internalRun() throws Exception { DiscoverQuery discoverQuery = queryBuilder.buildQuery(context, dso, discoveryConfiguration, query, queryBuilderSearchFilters, "Item", 10, Long.getLong("0"), null, SortOption.DESCENDING); + // add configured limit + discoverQuery.setMaxResults(metadataDSpaceCsvExportService.getCsvExportLimit()); handler.logDebug("creating iterator"); Iterator itemIterator = searchService.iteratorSearch(context, dso, discoverQuery); diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java index 3dd39658934..33e87a43fcd 100644 --- a/dspace-api/src/main/java/org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java @@ -15,7 +15,6 @@ import java.util.Set; import java.util.UUID; -import org.apache.commons.collections.IteratorUtils; import org.dspace.app.bulkedit.DSpaceCSV; import org.dspace.app.util.service.DSpaceObjectUtils; import org.dspace.content.service.ItemService; @@ -41,6 +40,8 @@ public class MetadataDSpaceCsvExportServiceImpl implements MetadataDSpaceCsvExpo @Autowired private ConfigurationService configurationService; + private int csxExportLimit = -1; + @Override public DSpaceCSV handleExport(Context context, boolean exportAllItems, boolean exportAllMetadata, String identifier, DSpaceRunnableHandler handler) throws Exception { @@ -48,7 +49,7 @@ public DSpaceCSV handleExport(Context context, boolean exportAllItems, boolean e if (exportAllItems) { handler.logInfo("Exporting whole repository WARNING: May take some time!"); - toExport = itemService.findAll(context); + toExport = itemService.findAll(context, getCsvExportLimit(), 0); } else { DSpaceObject dso = HandleServiceFactory.getInstance().getHandleService() .resolveToObject(context, identifier); @@ -68,7 +69,7 @@ public DSpaceCSV handleExport(Context context, boolean exportAllItems, boolean e } else if (dso.getType() == Constants.COLLECTION) { handler.logInfo("Exporting collection '" + dso.getName() + "' (" + identifier + ")"); Collection collection = (Collection) dso; - toExport = itemService.findByCollection(context, collection); + toExport = itemService.findByCollection(context, collection, getCsvExportLimit(), 0); } else if (dso.getType() == Constants.COMMUNITY) { handler.logInfo("Exporting community '" + dso.getName() + "' (" + identifier + ")"); toExport = buildFromCommunity(context, (Community) dso); @@ -91,7 +92,6 @@ public DSpaceCSV export(Context context, Iterator toExport, // Process each item DSpaceCSV csv = new DSpaceCSV(exportAll); - toExport = setItemsToExportWithLimit(context, toExport, handler); while (toExport.hasNext()) { Item item = toExport.next(); csv.addItem(item); @@ -109,20 +109,6 @@ public DSpaceCSV export(Context context, Community community, return export(context, buildFromCommunity(context, community), exportAll, handler); } - private Iterator setItemsToExportWithLimit(Context context, Iterator toExport, - DSpaceRunnableHandler handler) throws SQLException { - int itemExportLimit = configurationService.getIntProperty( - "metadataexport.max.items", 500); - - List items = IteratorUtils.toList(toExport); - if (items.size() > itemExportLimit) { - handler.logWarning("The amount of items to export is higher than the limit of " + itemExportLimit - + " items. Only the first " + itemExportLimit + " items will be exported."); - items = items.subList(0, itemExportLimit); - } - return items.iterator(); - } - /** * Build a Java Collection of item IDs that are in a Community (including * its sub-Communities and Collections) @@ -135,25 +121,37 @@ private Iterator setItemsToExportWithLimit(Context context, Iterator private Iterator buildFromCommunity(Context context, Community community) throws SQLException { Set result = new HashSet<>(); + int itemsAdded = 0; // Add all the collections List collections = community.getCollections(); for (Collection collection : collections) { - Iterator items = itemService.findByCollection(context, collection); - while (items.hasNext()) { + // Never obtain more items than the configured limit + Iterator items = itemService.findByCollection(context, collection, getCsvExportLimit(), 0); + while (itemsAdded <= getCsvExportLimit() && items.hasNext()) { result.add(items.next()); + itemsAdded++; } } - // Add all the sub-communities + // Add all the sub-communities List communities = community.getSubcommunities(); for (Community subCommunity : communities) { Iterator items = buildFromCommunity(context, subCommunity); - while (items.hasNext()) { + while (itemsAdded <= getCsvExportLimit() && items.hasNext()) { result.add(items.next()); + itemsAdded++; } } return result.iterator(); } + + @Override + public int getCsvExportLimit() { + if (csxExportLimit == -1) { + csxExportLimit = configurationService.getIntProperty("bulkedit.export.max.items", 500); + } + return csxExportLimit; + } } diff --git a/dspace-api/src/main/java/org/dspace/content/service/MetadataDSpaceCsvExportService.java b/dspace-api/src/main/java/org/dspace/content/service/MetadataDSpaceCsvExportService.java index 052754a1633..a951e8cf776 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/MetadataDSpaceCsvExportService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/MetadataDSpaceCsvExportService.java @@ -58,4 +58,6 @@ public DSpaceCSV export(Context context, Iterator toExport, public DSpaceCSV export(Context context, Community community, boolean exportAll, DSpaceRunnableHandler handler) throws Exception; -} \ No newline at end of file + int getCsvExportLimit(); + +} diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 2e5c06c707c..94b731cd348 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -893,13 +893,6 @@ org.dspace.app.itemexport.life.span.hours = 48 # cumulative sizes are more than this entry the export is not kicked off org.dspace.app.itemexport.max.size = 200 -### Bulkedit Metadata export settings -# The maximum amount of items that can be exported using the "metadata-export" / "metadata-export-search" script -# Recommend to keep this at a feasible number, as exporting large amounts of items can be resource intensive -# If not set, this will default to 500 items -# metadataexport.max.items = 500 - - ### Batch Item import settings ### # The directory where the results of imports will be placed (mapfile, upload file) org.dspace.app.batchitemimport.work.dir = ${dspace.dir}/imports diff --git a/dspace/config/modules/bulkedit.cfg b/dspace/config/modules/bulkedit.cfg index 6174af53a0f..e326e007f88 100644 --- a/dspace/config/modules/bulkedit.cfg +++ b/dspace/config/modules/bulkedit.cfg @@ -40,3 +40,8 @@ bulkedit.allow-bulk-deletion = dspace.agreements.end-user # By default this is set to 100 bulkedit.change.commit.count = 100 +### Bulkedit Metadata export settings +# The maximum amount of items that can be exported using the "metadata-export" / "metadata-export-search" script +# Recommend to keep this at a feasible number, as exporting large amounts of items can be resource intensive +# If not set, this will default to 500 items +# bulkedit.export.max.items = 500 diff --git a/dspace/config/modules/rest.cfg b/dspace/config/modules/rest.cfg index 86f0b801dbd..b123b5b654b 100644 --- a/dspace/config/modules/rest.cfg +++ b/dspace/config/modules/rest.cfg @@ -55,7 +55,7 @@ rest.properties.exposed = cc.license.jurisdiction rest.properties.exposed = identifiers.item-status.register-doi rest.properties.exposed = authentication-password.domain.valid rest.properties.exposed = handle.canonical.prefix -rest.properties.exposed = metadataexport.max.items +rest.properties.exposed = bulkedit.export.max.items #---------------------------------------------------------------# # These configs are used by the deprecated REST (v4-6) module # From fae4130d41212972358b8290b1bcacfa6fb82f65 Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Wed, 2 Apr 2025 11:09:45 +0200 Subject: [PATCH 528/939] 119612: Fix limit not applying on export (cherry picked from commit bcf48821d94d30cee10c3c2c94b33c52b0bccdb0) --- .../dspace/app/bulkedit/MetadataExportSearch.java | 2 -- .../content/MetadataDSpaceCsvExportServiceImpl.java | 13 ++++++------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportSearch.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportSearch.java index 04c3a75dedd..e4bbe335d63 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportSearch.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportSearch.java @@ -139,8 +139,6 @@ public void internalRun() throws Exception { DiscoverQuery discoverQuery = queryBuilder.buildQuery(context, dso, discoveryConfiguration, query, queryBuilderSearchFilters, "Item", 10, Long.getLong("0"), null, SortOption.DESCENDING); - // add configured limit - discoverQuery.setMaxResults(metadataDSpaceCsvExportService.getCsvExportLimit()); handler.logDebug("creating iterator"); Iterator itemIterator = searchService.iteratorSearch(context, dso, discoverQuery); diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java index 33e87a43fcd..757c5d0cd52 100644 --- a/dspace-api/src/main/java/org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java @@ -90,9 +90,11 @@ public DSpaceCSV export(Context context, Iterator toExport, Context.Mode originalMode = context.getCurrentMode(); context.setMode(Context.Mode.READ_ONLY); - // Process each item + // Process each item until we reach the limit + int itemExportLimit = getCsvExportLimit(); DSpaceCSV csv = new DSpaceCSV(exportAll); - while (toExport.hasNext()) { + + for (int itemsAdded = 0; toExport.hasNext() && itemsAdded < itemExportLimit; itemsAdded++) { Item item = toExport.next(); csv.addItem(item); context.uncacheEntity(item); @@ -121,16 +123,14 @@ public DSpaceCSV export(Context context, Community community, private Iterator buildFromCommunity(Context context, Community community) throws SQLException { Set result = new HashSet<>(); - int itemsAdded = 0; // Add all the collections List collections = community.getCollections(); for (Collection collection : collections) { // Never obtain more items than the configured limit Iterator items = itemService.findByCollection(context, collection, getCsvExportLimit(), 0); - while (itemsAdded <= getCsvExportLimit() && items.hasNext()) { + while (result.size() < getCsvExportLimit() && items.hasNext()) { result.add(items.next()); - itemsAdded++; } } @@ -138,9 +138,8 @@ private Iterator buildFromCommunity(Context context, Community community) List communities = community.getSubcommunities(); for (Community subCommunity : communities) { Iterator items = buildFromCommunity(context, subCommunity); - while (itemsAdded <= getCsvExportLimit() && items.hasNext()) { + while (result.size() < getCsvExportLimit() && items.hasNext()) { result.add(items.next()); - itemsAdded++; } } From eb5f09f3f21180afeb7e52ff62ab409368fab3c2 Mon Sep 17 00:00:00 2001 From: Adamo Date: Fri, 2 May 2025 12:42:47 +0200 Subject: [PATCH 529/939] [DURACOM-355] Update to avoid NPE during WOS live import when no api key is found (cherry picked from commit 70f1c83bf0a7b32c10eaf22c5cc990c6b10e5784) --- .../WOSImportMetadataSourceServiceImpl.java | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/wos/service/WOSImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/wos/service/WOSImportMetadataSourceServiceImpl.java index c33a2f89012..9bffa2a84a1 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/wos/service/WOSImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/wos/service/WOSImportMetadataSourceServiceImpl.java @@ -56,7 +56,7 @@ public class WOSImportMetadataSourceServiceImpl extends AbstractImportMetadataSo private static final String AI_PATTERN = "^AI=(.*)"; private static final Pattern ISI_PATTERN = Pattern.compile("^\\d{15}$"); - private int timeout = 1000; + private final int timeout = 1000; private String url; private String urlSearch; @@ -126,7 +126,7 @@ public Collection findMatchingRecords(Query query) throws Metadata */ private class SearchNBByQueryCallable implements Callable { - private String query; + private final String query; private SearchNBByQueryCallable(String queryString) { this.query = queryString; @@ -155,7 +155,8 @@ public Integer call() throws Exception { Element tot = xpath.evaluateFirst(root); return Integer.valueOf(tot.getValue()); } - return null; + log.warn("API key is missing: cannot execute count request."); + return 0; } } @@ -166,7 +167,7 @@ public Integer call() throws Exception { */ private class FindByIdCallable implements Callable> { - private String doi; + private final String doi; private FindByIdCallable(String doi) { this.doi = URLEncoder.encode(doi, StandardCharsets.UTF_8); @@ -185,6 +186,8 @@ public List call() throws Exception { for (Element record : elements) { results.add(transformSourceRecords(record)); } + } else { + log.warn("API key is missing: cannot execute live import request."); } return results; } @@ -202,7 +205,7 @@ public List call() throws Exception { */ private class SearchByQueryCallable implements Callable> { - private Query query; + private final Query query; private SearchByQueryCallable(String queryString, Integer maxResult, Integer start) { query = new Query(); @@ -232,6 +235,8 @@ public List call() throws Exception { for (Element el : omElements) { results.add(transformSourceRecords(el)); } + } else { + log.warn("API key is missing: cannot execute live import request."); } return results; } @@ -270,9 +275,7 @@ private String checkQuery(String query) { } else if (isIsi(query)) { return "UT=(" + query + ")"; } - StringBuilder queryBuilder = new StringBuilder("TS=("); - queryBuilder.append(query).append(")"); - return queryBuilder.toString(); + return "TS=(" + query + ")"; } private boolean isIsi(String query) { From 605956b0739372decfc398d1300a0ff3620b4b6c Mon Sep 17 00:00:00 2001 From: Adamo Date: Fri, 2 May 2025 18:08:15 +0200 Subject: [PATCH 530/939] [DURACOM-311] Ensure stable pagination in bulk access control by adding explicit sort (cherry picked from commit ced9e9b9f731162c43d5823a9d2fd05e74ab233a) --- .../bulkaccesscontrol/BulkAccessControl.java | 2 +- .../BulkAccessControlIT.java | 84 +++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControl.java b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControl.java index 7bef232f045..30f68efaf3c 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControl.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControl.java @@ -416,7 +416,7 @@ private DiscoverQuery buildDiscoveryQuery(String query, int start, int limit) { discoverQuery.setQuery(query); discoverQuery.setStart(start); discoverQuery.setMaxResults(limit); - + discoverQuery.setSortField("search.resourceid", DiscoverQuery.SORT_ORDER.asc); return discoverQuery; } diff --git a/dspace-api/src/test/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlIT.java b/dspace-api/src/test/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlIT.java index 73f02e40494..106b176ee5b 100644 --- a/dspace-api/src/test/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlIT.java +++ b/dspace-api/src/test/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlIT.java @@ -32,8 +32,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; @@ -1831,6 +1833,88 @@ public void performBulkAccessWithHelpParamTest() throws Exception { assertThat(testDSpaceRunnableHandler.getWarningMessages(), empty()); } + + @Test + public void bulkAccessControlShouldProcessEachItemOnceWithPagination() throws Exception { + context.turnOffAuthorisationSystem(); + + Community community = CommunityBuilder.createCommunity(context) + .withName("Test Community") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("Test Collection") + .build(); + + List createdItemIDs = new ArrayList<>(); + + for (int i = 0; i < 25; i++) { + Item item = ItemBuilder.createItem(context, collection).build(); + + Bundle bundle = BundleBuilder.createBundle(context, item) + .withName("ORIGINAL") + .build(); + + BitstreamBuilder.createBitstream(context, bundle, + IOUtils.toInputStream("Bitstream content " + i, + CharEncoding.UTF_8)) + .withName("bitstream_" + i) + .build(); + + createdItemIDs.add(item.getID()); + } + + context.restoreAuthSystemState(); + + // JSON without constraints: apply to ALL items + String json = """ + { "item": { + "mode": "add", + "accessConditions": [ + { + "name": "openaccess" + } + ] + }} + """; + + buildJsonFile(json); + + String[] args = { + "bulk-access-control", + "-u", community.getID().toString(), + "-f", tempFilePath, + "-e", admin.getEmail() + }; + + TestDSpaceRunnableHandler testHandler = new TestDSpaceRunnableHandler(); + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testHandler, kernelImpl); + + assertThat(testHandler.getErrorMessages(), empty()); + assertThat(testHandler.getWarningMessages(), empty()); + + // Collect item IDs from the info messages + List infoMessages = testHandler.getInfoMessages(); + List updatedItemIDs = infoMessages.stream() + .map(msg -> { + int startIdx = msg.indexOf("Item {") + 6; + int endIdx = msg.indexOf("}", startIdx); + return UUID.fromString(msg.substring(startIdx, endIdx)); + }) + .toList(); + + Set uniqueUpdatedItemIDs = new HashSet<>(updatedItemIDs); + + // Check if any item was processed multiple times + assertThat("Some items were processed more than once!", + uniqueUpdatedItemIDs.size(), is(updatedItemIDs.size())); + + // Check all items were updated once + assertThat("Not all created items were updated!", + createdItemIDs, containsInAnyOrder(uniqueUpdatedItemIDs.toArray())); + } + + private List findItems(String query) throws SearchServiceException { DiscoverQuery discoverQuery = new DiscoverQuery(); From 3f9f5639f64e32ebc56c521f3c67e913fca35de3 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 2 May 2025 15:49:00 -0500 Subject: [PATCH 531/939] Must use older Java syntax in DSpace 7 --- .../BulkAccessControlIT.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlIT.java b/dspace-api/src/test/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlIT.java index 106b176ee5b..0dd1bf07616 100644 --- a/dspace-api/src/test/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlIT.java +++ b/dspace-api/src/test/java/org/dspace/app/bulkaccesscontrol/BulkAccessControlIT.java @@ -1867,16 +1867,16 @@ public void bulkAccessControlShouldProcessEachItemOnceWithPagination() throws Ex context.restoreAuthSystemState(); // JSON without constraints: apply to ALL items - String json = """ - { "item": { - "mode": "add", - "accessConditions": [ - { - "name": "openaccess" - } - ] - }} - """; + String json = "{\n" + + " \"item\": {\n" + + " \"mode\": \"add\",\n" + + " \"accessConditions\": [\n" + + " {\n" + + " \"name\": \"openaccess\"\n" + + " }\n" + + " ]\n" + + " }\n" + + "}"; buildJsonFile(json); @@ -1901,7 +1901,7 @@ public void bulkAccessControlShouldProcessEachItemOnceWithPagination() throws Ex int endIdx = msg.indexOf("}", startIdx); return UUID.fromString(msg.substring(startIdx, endIdx)); }) - .toList(); + .collect(Collectors.toList()); Set uniqueUpdatedItemIDs = new HashSet<>(updatedItemIDs); From a0c2891226156e749e0806ae6728fe92abc313e4 Mon Sep 17 00:00:00 2001 From: Adamo Date: Thu, 1 May 2025 18:52:26 +0200 Subject: [PATCH 532/939] [DURACOM-356] Updated Sherpa mapping to use creativeworkseries.issn instead of dc.identifier.issn --- .../provider/impl/SHERPAv2JournalISSNDataProvider.java | 3 +-- dspace/config/spring/api/sherpa.xml | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/external/provider/impl/SHERPAv2JournalISSNDataProvider.java b/dspace-api/src/main/java/org/dspace/external/provider/impl/SHERPAv2JournalISSNDataProvider.java index 9e61b9ac2ac..860334847cc 100644 --- a/dspace-api/src/main/java/org/dspace/external/provider/impl/SHERPAv2JournalISSNDataProvider.java +++ b/dspace-api/src/main/java/org/dspace/external/provider/impl/SHERPAv2JournalISSNDataProvider.java @@ -106,8 +106,7 @@ private ExternalDataObject constructExternalDataObjectFromSherpaJournal(SHERPAJo String issn = sherpaJournal.getIssns().get(0); externalDataObject.setId(issn); externalDataObject.addMetadata(new MetadataValueDTO( - "dc", "identifier", "issn", null, issn)); - + "creativeworkseries", "issn", null, null, issn)); } log.debug("New external data object. Title=" + externalDataObject.getValue() + ". ID=" diff --git a/dspace/config/spring/api/sherpa.xml b/dspace/config/spring/api/sherpa.xml index 0414f3f8e4b..da42880218c 100644 --- a/dspace/config/spring/api/sherpa.xml +++ b/dspace/config/spring/api/sherpa.xml @@ -17,7 +17,7 @@ - dc.identifier.issn + creativeworkseries.issn From bacbe06b2c8b121eb3877b4b9520a5d5726dc381 Mon Sep 17 00:00:00 2001 From: Adamo Date: Fri, 2 May 2025 19:23:37 +0200 Subject: [PATCH 533/939] [DURACOM-356] Fixed tests --- .../org/dspace/app/sherpa/SHERPADataProviderTest.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/app/sherpa/SHERPADataProviderTest.java b/dspace-api/src/test/java/org/dspace/app/sherpa/SHERPADataProviderTest.java index 6b9666c8303..134561f65e5 100644 --- a/dspace-api/src/test/java/org/dspace/app/sherpa/SHERPADataProviderTest.java +++ b/dspace-api/src/test/java/org/dspace/app/sherpa/SHERPADataProviderTest.java @@ -86,9 +86,8 @@ public void testGetJournalISSNExternalObject() { if (metadataValue.getSchema().equalsIgnoreCase("dc") && metadataValue.getElement().equalsIgnoreCase("title")) { title = metadataValue.getValue(); - } else if (metadataValue.getSchema().equalsIgnoreCase("dc") - && metadataValue.getElement().equalsIgnoreCase("identifier") - && metadataValue.getQualifier().equalsIgnoreCase("issn")) { + } else if (metadataValue.getSchema().equalsIgnoreCase("creativeworkseries") + && metadataValue.getElement().equalsIgnoreCase("issn")) { identifier = metadataValue.getValue(); } } @@ -135,9 +134,8 @@ public void testSearchJournalISSNExternalObjects() { if (metadataValue.getSchema().equalsIgnoreCase("dc") && metadataValue.getElement().equalsIgnoreCase("title")) { title = metadataValue.getValue(); - } else if (metadataValue.getSchema().equalsIgnoreCase("dc") - && metadataValue.getElement().equalsIgnoreCase("identifier") - && metadataValue.getQualifier().equalsIgnoreCase("issn")) { + } else if (metadataValue.getSchema().equalsIgnoreCase("creativeworkseries") + && metadataValue.getElement().equalsIgnoreCase("issn")) { identifier = metadataValue.getValue(); } } From 5dd4e4248c847927fda47cb89c984eba4e055400 Mon Sep 17 00:00:00 2001 From: Adamo Date: Mon, 5 May 2025 12:46:52 +0200 Subject: [PATCH 534/939] [DURACOM-356] Updated Sherpa Journal mapping to use creativeworkseries.issn instead of dc.identifier.issn --- .../external/provider/impl/SHERPAv2JournalDataProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/external/provider/impl/SHERPAv2JournalDataProvider.java b/dspace-api/src/main/java/org/dspace/external/provider/impl/SHERPAv2JournalDataProvider.java index a4276c83ed7..f0ce6a979a1 100644 --- a/dspace-api/src/main/java/org/dspace/external/provider/impl/SHERPAv2JournalDataProvider.java +++ b/dspace-api/src/main/java/org/dspace/external/provider/impl/SHERPAv2JournalDataProvider.java @@ -97,7 +97,7 @@ private ExternalDataObject constructExternalDataObjectFromSherpaJournal(SHERPAJo if (CollectionUtils.isNotEmpty(sherpaJournal.getIssns())) { String issn = sherpaJournal.getIssns().get(0); externalDataObject.addMetadata(new MetadataValueDTO( - "dc", "identifier", "issn", null, issn)); + "creativeworkseries", "issn", null, null, issn)); } From d7948d5f7bb36f23221844bc81fee571faa6b419 Mon Sep 17 00:00:00 2001 From: Adamo Date: Mon, 5 May 2025 12:49:16 +0200 Subject: [PATCH 535/939] [DURACOM-356] Updated tests to use metadata constants --- .../app/sherpa/SHERPADataProviderTest.java | 77 +++++++++++-------- 1 file changed, 43 insertions(+), 34 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/app/sherpa/SHERPADataProviderTest.java b/dspace-api/src/test/java/org/dspace/app/sherpa/SHERPADataProviderTest.java index 134561f65e5..210eedc9fbb 100644 --- a/dspace-api/src/test/java/org/dspace/app/sherpa/SHERPADataProviderTest.java +++ b/dspace-api/src/test/java/org/dspace/app/sherpa/SHERPADataProviderTest.java @@ -37,6 +37,12 @@ public class SHERPADataProviderTest extends AbstractDSpaceTest { ExternalDataProvider sherpaPublisherProvider; ExternalDataProvider sherpaJournalIssnProvider; + private static final MetadataFieldRef TITLE_FIELD = new MetadataFieldRef("dc", "title", null); + private static final MetadataFieldRef ISSN_FIELD = new MetadataFieldRef("creativeworkseries", "issn", null); + private static final MetadataFieldRef SHERPA_PUBLISHER_FIELD = + new MetadataFieldRef("dc", "identifier", "sherpaPublisher"); + private static final MetadataFieldRef OTHER_FIELD = new MetadataFieldRef("dc", "identifier", "other"); + @BeforeClass public static void setUpClass() { } @@ -83,11 +89,9 @@ public void testGetJournalISSNExternalObject() { String title = null; String identifier = null; for (MetadataValueDTO metadataValue : dataObject.getMetadata()) { - if (metadataValue.getSchema().equalsIgnoreCase("dc") && - metadataValue.getElement().equalsIgnoreCase("title")) { + if (TITLE_FIELD.matches(metadataValue)) { title = metadataValue.getValue(); - } else if (metadataValue.getSchema().equalsIgnoreCase("creativeworkseries") - && metadataValue.getElement().equalsIgnoreCase("issn")) { + } else if (ISSN_FIELD.matches(metadataValue)) { identifier = metadataValue.getValue(); } } @@ -131,11 +135,9 @@ public void testSearchJournalISSNExternalObjects() { String title = null; String identifier = null; for (MetadataValueDTO metadataValue : dataObject.getMetadata()) { - if (metadataValue.getSchema().equalsIgnoreCase("dc") && - metadataValue.getElement().equalsIgnoreCase("title")) { + if (TITLE_FIELD.matches(metadataValue)) { title = metadataValue.getValue(); - } else if (metadataValue.getSchema().equalsIgnoreCase("creativeworkseries") - && metadataValue.getElement().equalsIgnoreCase("issn")) { + } else if (ISSN_FIELD.matches(metadataValue)) { identifier = metadataValue.getValue(); } } @@ -171,12 +173,9 @@ public void testGetJournalExternalObject() { String title = null; String identifier = null; for (MetadataValueDTO metadataValue : dataObject.getMetadata()) { - if (metadataValue.getSchema().equalsIgnoreCase("dc") && - metadataValue.getElement().equalsIgnoreCase("title")) { + if (TITLE_FIELD.matches(metadataValue)) { title = metadataValue.getValue(); - } else if (metadataValue.getSchema().equalsIgnoreCase("dc") - && metadataValue.getElement().equalsIgnoreCase("identifier") - && metadataValue.getQualifier().equalsIgnoreCase("issn")) { + } else if (ISSN_FIELD.matches(metadataValue)) { identifier = metadataValue.getValue(); } } @@ -221,12 +220,9 @@ public void testSearchJournalObjects() { String title = null; String identifier = null; for (MetadataValueDTO metadataValue : dataObject.getMetadata()) { - if (metadataValue.getSchema().equalsIgnoreCase("dc") && - metadataValue.getElement().equalsIgnoreCase("title")) { + if (TITLE_FIELD.matches(metadataValue)) { title = metadataValue.getValue(); - } else if (metadataValue.getSchema().equalsIgnoreCase("dc") - && metadataValue.getElement().equalsIgnoreCase("identifier") - && metadataValue.getQualifier().equalsIgnoreCase("issn")) { + } else if (ISSN_FIELD.matches(metadataValue)) { identifier = metadataValue.getValue(); } } @@ -267,16 +263,11 @@ public void testGetPublisherExternalObject() { String identifier = null; String url = null; for (MetadataValueDTO metadataValue : dataObject.getMetadata()) { - if (metadataValue.getSchema().equalsIgnoreCase("dc") && - metadataValue.getElement().equalsIgnoreCase("title")) { + if (TITLE_FIELD.matches(metadataValue)) { title = metadataValue.getValue(); - } else if (metadataValue.getSchema().equalsIgnoreCase("dc") - && metadataValue.getElement().equalsIgnoreCase("identifier") - && metadataValue.getQualifier().equalsIgnoreCase("sherpaPublisher")) { + } else if (SHERPA_PUBLISHER_FIELD.matches(metadataValue)) { identifier = metadataValue.getValue(); - } else if (metadataValue.getSchema().equalsIgnoreCase("dc") - && metadataValue.getElement().equalsIgnoreCase("identifier") - && metadataValue.getQualifier().equalsIgnoreCase("other")) { + } else if (OTHER_FIELD.matches(metadataValue)) { url = metadataValue.getValue(); } } @@ -327,16 +318,11 @@ public void testSearchPublisherExternalObjects() { String identifier = null; String url = null; for (MetadataValueDTO metadataValue : dataObject.getMetadata()) { - if (metadataValue.getSchema().equalsIgnoreCase("dc") && - metadataValue.getElement().equalsIgnoreCase("title")) { + if (TITLE_FIELD.matches(metadataValue)) { title = metadataValue.getValue(); - } else if (metadataValue.getSchema().equalsIgnoreCase("dc") - && metadataValue.getElement().equalsIgnoreCase("identifier") - && metadataValue.getQualifier().equalsIgnoreCase("sherpaPublisher")) { + } else if (SHERPA_PUBLISHER_FIELD.matches(metadataValue)) { identifier = metadataValue.getValue(); - } else if (metadataValue.getSchema().equalsIgnoreCase("dc") - && metadataValue.getElement().equalsIgnoreCase("identifier") - && metadataValue.getQualifier().equalsIgnoreCase("other")) { + } else if (OTHER_FIELD.matches(metadataValue)) { url = metadataValue.getValue(); } } @@ -350,4 +336,27 @@ public void testSearchPublisherExternalObjects() { // Does dc.identifier.other match the expected value? assertEquals("Publisher URL must equal " + validUrl, validUrl, url); } + + private static class MetadataFieldRef { + public final String schema; + public final String element; + public final String qualifier; + + public MetadataFieldRef(String schema, String element, String qualifier) { + this.schema = schema; + this.element = element; + this.qualifier = qualifier; + } + + public boolean matches(MetadataValueDTO value) { + return schema.equalsIgnoreCase(value.getSchema()) && + element.equalsIgnoreCase(value.getElement()) && + (qualifier == null ? value.getQualifier() == null + : qualifier.equalsIgnoreCase(value.getQualifier())); + } + + public MetadataValueDTO toMetadata(String value) { + return new MetadataValueDTO(schema, element, qualifier, null, value); + } + } } \ No newline at end of file From a26ef22a460bc417a6b05f40a2ba3e84f9993c10 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 May 2025 22:58:31 +0000 Subject: [PATCH 536/939] Bump com.amazonaws:aws-java-sdk-s3 from 1.12.782 to 1.12.783 Bumps [com.amazonaws:aws-java-sdk-s3](https://github.com/aws/aws-sdk-java) from 1.12.782 to 1.12.783. - [Changelog](https://github.com/aws/aws-sdk-java/blob/master/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-java/compare/1.12.782...1.12.783) --- updated-dependencies: - dependency-name: com.amazonaws:aws-java-sdk-s3 dependency-version: 1.12.783 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index bb246c20ad8..df1549a98b6 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -756,7 +756,7 @@ com.amazonaws aws-java-sdk-s3 - 1.12.782 + 1.12.783 From f5e83433c4402ec66170c01e7f87bf2f3b9598e8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 May 2025 23:02:34 +0000 Subject: [PATCH 537/939] Bump tika.version from 2.9.3 to 2.9.4 Bumps `tika.version` from 2.9.3 to 2.9.4. Updates `org.apache.tika:tika-core` from 2.9.3 to 2.9.4 - [Changelog](https://github.com/apache/tika/blob/2.9.4/CHANGES.txt) - [Commits](https://github.com/apache/tika/compare/2.9.3...2.9.4) Updates `org.apache.tika:tika-parsers-standard-package` from 2.9.3 to 2.9.4 --- updated-dependencies: - dependency-name: org.apache.tika:tika-core dependency-version: 2.9.4 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.tika:tika-parsers-standard-package dependency-version: 2.9.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e2683a5844b..107318fccb1 100644 --- a/pom.xml +++ b/pom.xml @@ -42,7 +42,7 @@ 2.0.34 1.19.0 1.7.36 - 2.9.3 + 2.9.4 1.80 From 8b089be727a2f3888c0b9c3f84727f5cb8d01675 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 May 2025 23:11:48 +0000 Subject: [PATCH 538/939] Bump com.opencsv:opencsv from 5.10 to 5.11 Bumps com.opencsv:opencsv from 5.10 to 5.11. --- updated-dependencies: - dependency-name: com.opencsv:opencsv dependency-version: '5.11' dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index bb246c20ad8..5e3e67d704c 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -805,7 +805,7 @@ com.opencsv opencsv - 5.10 + 5.11 From 930565effff8b46cfda9e8b3906ecbbee32227f7 Mon Sep 17 00:00:00 2001 From: abhinav Date: Wed, 19 Mar 2025 15:50:43 +0100 Subject: [PATCH 539/939] 129614: Fixed tests failing in TikaTextExtractionFilterTest when textextractor.use-temp-file is set to true (cherry picked from commit f9f29f49cb804f55ccc58d7a8558777f20061ec5) --- .../org/dspace/app/mediafilter/TikaTextExtractionFilter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/TikaTextExtractionFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/TikaTextExtractionFilter.java index e83bf706ed0..b7a6063165b 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/TikaTextExtractionFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/TikaTextExtractionFilter.java @@ -138,7 +138,7 @@ private InputStream extractUsingTempFile(InputStream source, boolean verbose) @Override public void characters(char[] ch, int start, int length) throws SAXException { try { - writer.append(new String(ch), start, length); + writer.append(new String(ch, start, length)); } catch (IOException e) { String errorMsg = String.format("Could not append to temporary file at %s " + "when performing text extraction", @@ -156,7 +156,7 @@ public void characters(char[] ch, int start, int length) throws SAXException { @Override public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { try { - writer.append(new String(ch), start, length); + writer.append(new String(ch, start, length)); } catch (IOException e) { String errorMsg = String.format("Could not append to temporary file at %s " + "when performing text extraction", From c2c41e65f86dab0a9d4f4128e16336fcd7b9c215 Mon Sep 17 00:00:00 2001 From: Elios Buzo Date: Wed, 16 Apr 2025 14:03:01 +0200 Subject: [PATCH 540/939] [DURACOM-109] Configured proxy settings for all clients --- .../app/client/DSpaceHttpClientFactory.java | 152 +++++++++++ .../app/client/DSpaceProxyRoutePlanner.java | 73 +++++ .../org/dspace/app/sherpa/SHERPAService.java | 8 +- .../dspace/app/util/WebAppServiceImpl.java | 4 +- .../oidc/impl/OidcClientImpl.java | 4 +- .../ctask/general/MetadataWebService.java | 4 +- .../ctask/general/MicrosoftTranslator.java | 4 +- .../dspace/eperson/CaptchaServiceImpl.java | 4 +- .../external/OpenAIRERestConnector.java | 6 +- .../dspace/external/OrcidRestConnector.java | 4 +- .../CCLicenseConnectorServiceImpl.java | 9 +- .../dspace/orcid/client/OrcidClientImpl.java | 15 +- .../impl/HttpConnectionPoolService.java | 4 +- .../statistics/SolrLoggerServiceImpl.java | 6 +- .../export/service/OpenUrlServiceImpl.java | 6 +- .../client/DSpaceHttpClientFactoryTest.java | 256 ++++++++++++++++++ dspace/config/spring/api/core-services.xml | 3 + 17 files changed, 519 insertions(+), 43 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/app/client/DSpaceHttpClientFactory.java create mode 100644 dspace-api/src/main/java/org/dspace/app/client/DSpaceProxyRoutePlanner.java create mode 100644 dspace-api/src/test/java/org/dspace/app/client/DSpaceHttpClientFactoryTest.java diff --git a/dspace-api/src/main/java/org/dspace/app/client/DSpaceHttpClientFactory.java b/dspace-api/src/main/java/org/dspace/app/client/DSpaceHttpClientFactory.java new file mode 100644 index 00000000000..999f13c1b38 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/client/DSpaceHttpClientFactory.java @@ -0,0 +1,152 @@ +/** + * 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.app.client; + +import static org.apache.commons.collections4.ListUtils.emptyIfNull; + +import java.util.List; + +import org.apache.http.HttpRequestInterceptor; +import org.apache.http.HttpResponseInterceptor; +import org.apache.http.client.HttpClient; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.dspace.services.ConfigurationService; +import org.dspace.utils.DSpace; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Factory of {@link HttpClient} with common configurations. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public class DSpaceHttpClientFactory { + + @Autowired + private ConfigurationService configurationService; + + @Autowired + private DSpaceProxyRoutePlanner proxyRoutePlanner; + + @Autowired(required = false) + private List requestInterceptors; + + @Autowired(required = false) + private List responseInterceptors; + + /** + * Get an instance of {@link DSpaceHttpClientFactory} from the Spring context. + * @return the bean instance + */ + public static DSpaceHttpClientFactory getInstance() { + return new DSpace().getSingletonService(DSpaceHttpClientFactory.class); + } + + /** + * Build an instance of {@link HttpClient} setting the proxy if configured. + * + * @return the client + */ + public CloseableHttpClient build() { + return build(HttpClientBuilder.create(), true); + } + + /** + * return a Builder if an instance of {@link HttpClient} pre-setting the proxy if configured. + * + * @return the client + */ + public HttpClientBuilder builder(boolean setProxy) { + HttpClientBuilder clientBuilder = HttpClientBuilder.create(); + if (setProxy) { + clientBuilder.setRoutePlanner(proxyRoutePlanner); + } + getRequestInterceptors().forEach(clientBuilder::addInterceptorLast); + getResponseInterceptors().forEach(clientBuilder::addInterceptorLast); + return clientBuilder; + } + + /** + * Build an instance of {@link HttpClient} without setting the proxy, even if + * configured. + * + * @return the client + */ + public CloseableHttpClient buildWithoutProxy() { + return build(HttpClientBuilder.create(), false); + } + + /** + * Build an instance of {@link HttpClient} setting the proxy if configured, + * disabling automatic retries and setting the maximum total connection. + * + * @param maxConnTotal the maximum total connection value + * @return the client + */ + public CloseableHttpClient buildWithoutAutomaticRetries(int maxConnTotal) { + HttpClientBuilder clientBuilder = HttpClientBuilder.create() + .disableAutomaticRetries() + .setMaxConnTotal(5); + return build(clientBuilder, true); + } + + /** + * Build an instance of {@link HttpClient} setting the proxy if configured with + * the given request configuration. + * @param requestConfig the request configuration + * @return the client + */ + public CloseableHttpClient buildWithRequestConfig(RequestConfig requestConfig) { + HttpClientBuilder httpClientBuilder = HttpClientBuilder.create() + .setDefaultRequestConfig(requestConfig); + return build(httpClientBuilder, true); + } + + private CloseableHttpClient build(HttpClientBuilder clientBuilder, boolean setProxy) { + if (setProxy) { + clientBuilder.setRoutePlanner(proxyRoutePlanner); + } + getRequestInterceptors().forEach(clientBuilder::addInterceptorLast); + getResponseInterceptors().forEach(clientBuilder::addInterceptorLast); + return clientBuilder.build(); + } + + public ConfigurationService getConfigurationService() { + return configurationService; + } + + public void setConfigurationService(ConfigurationService configurationService) { + this.configurationService = configurationService; + } + + public List getRequestInterceptors() { + return emptyIfNull(requestInterceptors); + } + + public void setRequestInterceptors(List requestInterceptors) { + this.requestInterceptors = requestInterceptors; + } + + public List getResponseInterceptors() { + return emptyIfNull(responseInterceptors); + } + + public void setResponseInterceptors(List responseInterceptors) { + this.responseInterceptors = responseInterceptors; + } + + public DSpaceProxyRoutePlanner getProxyRoutePlanner() { + return proxyRoutePlanner; + } + + public void setProxyRoutePlanner(DSpaceProxyRoutePlanner proxyRoutePlanner) { + this.proxyRoutePlanner = proxyRoutePlanner; + } +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/app/client/DSpaceProxyRoutePlanner.java b/dspace-api/src/main/java/org/dspace/app/client/DSpaceProxyRoutePlanner.java new file mode 100644 index 00000000000..1df7fa4a298 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/client/DSpaceProxyRoutePlanner.java @@ -0,0 +1,73 @@ +/** + * 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.app.client; + +import java.util.Arrays; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpException; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.impl.conn.DefaultRoutePlanner; +import org.apache.http.protocol.HttpContext; +import org.dspace.services.ConfigurationService; + +/** + * Extension of {@link DefaultRoutePlanner} that determine the proxy based on + * the configuration service, ignoring configured hosts. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public class DSpaceProxyRoutePlanner extends DefaultRoutePlanner { + + private ConfigurationService configurationService; + + public DSpaceProxyRoutePlanner(ConfigurationService configurationService) { + super(null); + this.configurationService = configurationService; + } + + @Override + protected HttpHost determineProxy(HttpHost target, HttpRequest request, HttpContext context) throws HttpException { + if (isTargetHostConfiguredToBeIgnored(target)) { + return null; + } + String proxyHost = configurationService.getProperty("http.proxy.host"); + String proxyPort = configurationService.getProperty("http.proxy.port"); + if (StringUtils.isAnyBlank(proxyHost, proxyPort)) { + return null; + } + try { + return new HttpHost(proxyHost, Integer.parseInt(proxyPort), "http"); + } catch (NumberFormatException e) { + throw new RuntimeException("Invalid proxy port configuration: " + proxyPort); + } + } + + private boolean isTargetHostConfiguredToBeIgnored(HttpHost target) { + String[] hostsToIgnore = configurationService.getArrayProperty("http.proxy.hosts-to-ignore"); + if (ArrayUtils.isEmpty(hostsToIgnore)) { + return false; + } + return Arrays.stream(hostsToIgnore) + .anyMatch(host -> matchesHost(host, target.getHostName())); + } + + private boolean matchesHost(String hostPattern, String hostName) { + if (hostName.equals(hostPattern)) { + return true; + } else if (hostPattern.startsWith("*")) { + return hostName.endsWith(StringUtils.removeStart(hostPattern, "*")); + } else if (hostPattern.endsWith("*")) { + return hostName.startsWith(StringUtils.removeEnd(hostPattern, "*")); + } + return false; + } +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAService.java b/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAService.java index ead725e842c..73ec0cc5893 100644 --- a/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAService.java +++ b/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAService.java @@ -23,9 +23,9 @@ import org.apache.http.client.methods.HttpGet; import org.apache.http.client.utils.URIBuilder; import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dspace.app.client.DSpaceHttpClientFactory; import org.dspace.app.sherpa.v2.SHERPAPublisherResponse; import org.dspace.app.sherpa.v2.SHERPAResponse; import org.dspace.app.sherpa.v2.SHERPAUtils; @@ -63,13 +63,9 @@ public class SHERPAService { * Create a new HTTP builder with sensible defaults in constructor */ public SHERPAService() { - HttpClientBuilder builder = HttpClientBuilder.create(); // httpclient 4.3+ doesn't appear to have any sensible defaults any more. Setting conservative defaults as // not to hammer the SHERPA service too much. - client = builder - .disableAutomaticRetries() - .setMaxConnTotal(5) - .build(); + client = DSpaceHttpClientFactory.getInstance().buildWithoutAutomaticRetries(5); } /** diff --git a/dspace-api/src/main/java/org/dspace/app/util/WebAppServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/util/WebAppServiceImpl.java index 8dcd78c8823..064a9fdc3e9 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/WebAppServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/util/WebAppServiceImpl.java @@ -17,8 +17,8 @@ import org.apache.http.HttpStatus; import org.apache.http.client.methods.HttpHead; import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; import org.apache.logging.log4j.Logger; +import org.dspace.app.client.DSpaceHttpClientFactory; import org.dspace.app.util.dao.WebAppDAO; import org.dspace.app.util.service.WebAppService; import org.dspace.core.Context; @@ -77,7 +77,7 @@ public List getApps() { for (WebApp app : webApps) { method = new HttpHead(app.getUrl()); int status; - try (CloseableHttpClient client = HttpClientBuilder.create().build()) { + try (CloseableHttpClient client = DSpaceHttpClientFactory.getInstance().build()) { HttpResponse response = client.execute(method); status = response.getStatusLine().getStatusCode(); } diff --git a/dspace-api/src/main/java/org/dspace/authenticate/oidc/impl/OidcClientImpl.java b/dspace-api/src/main/java/org/dspace/authenticate/oidc/impl/OidcClientImpl.java index ddab01e8cb5..35856d3756d 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/oidc/impl/OidcClientImpl.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/oidc/impl/OidcClientImpl.java @@ -26,8 +26,8 @@ import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.methods.RequestBuilder; -import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.message.BasicNameValuePair; +import org.dspace.app.client.DSpaceHttpClientFactory; import org.dspace.authenticate.oidc.OidcClient; import org.dspace.authenticate.oidc.OidcClientException; import org.dspace.authenticate.oidc.model.OidcTokenResponseDTO; @@ -84,7 +84,7 @@ public Map getUserInfo(String accessToken) throws OidcClientExce private T executeAndParseJson(HttpUriRequest httpUriRequest, Class clazz) { - HttpClient client = HttpClientBuilder.create().build(); + HttpClient client = DSpaceHttpClientFactory.getInstance().build(); return executeAndReturns(() -> { diff --git a/dspace-api/src/main/java/org/dspace/ctask/general/MetadataWebService.java b/dspace-api/src/main/java/org/dspace/ctask/general/MetadataWebService.java index 5891fa017cb..da7588be6c6 100644 --- a/dspace-api/src/main/java/org/dspace/ctask/general/MetadataWebService.java +++ b/dspace-api/src/main/java/org/dspace/ctask/general/MetadataWebService.java @@ -34,9 +34,9 @@ import org.apache.http.HttpStatus; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dspace.app.client.DSpaceHttpClientFactory; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; @@ -257,7 +257,7 @@ public int perform(DSpaceObject dso) throws IOException { protected int callService(String value, Item item, StringBuilder resultSb) throws IOException { String callUrl = urlTemplate.replaceAll("\\{" + templateParam + "\\}", value); - CloseableHttpClient client = HttpClientBuilder.create().build(); + CloseableHttpClient client = DSpaceHttpClientFactory.getInstance().build(); HttpGet req = new HttpGet(callUrl); for (Map.Entry entry : headers.entrySet()) { req.addHeader(entry.getKey(), entry.getValue()); diff --git a/dspace-api/src/main/java/org/dspace/ctask/general/MicrosoftTranslator.java b/dspace-api/src/main/java/org/dspace/ctask/general/MicrosoftTranslator.java index 49c0c36a591..0d682d9406f 100644 --- a/dspace-api/src/main/java/org/dspace/ctask/general/MicrosoftTranslator.java +++ b/dspace-api/src/main/java/org/dspace/ctask/general/MicrosoftTranslator.java @@ -15,9 +15,9 @@ import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dspace.app.client.DSpaceHttpClientFactory; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; @@ -60,7 +60,7 @@ protected String translateText(String from, String to, String text) throws IOExc String url = baseUrl + "?appId=" + apiKey; url += "&to=" + to + "&from=" + from + "&text=" + text; - try (CloseableHttpClient client = HttpClientBuilder.create().build()) { + try (CloseableHttpClient client = DSpaceHttpClientFactory.getInstance().build()) { HttpGet hm = new HttpGet(url); HttpResponse httpResponse = client.execute(hm); log.debug("Response code from API call is " + httpResponse); diff --git a/dspace-api/src/main/java/org/dspace/eperson/CaptchaServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/CaptchaServiceImpl.java index 0ab66aea5c2..e10de57e47a 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/CaptchaServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/CaptchaServiceImpl.java @@ -22,10 +22,10 @@ import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; -import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.message.BasicNameValuePair; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dspace.app.client.DSpaceHttpClientFactory; import org.dspace.eperson.service.CaptchaService; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; @@ -82,7 +82,7 @@ public void processResponse(String response, String action) throws InvalidReCapt throw new RuntimeException(e.getMessage(), e); } - HttpClient httpClient = HttpClientBuilder.create().build(); + HttpClient httpClient = DSpaceHttpClientFactory.getInstance().build(); HttpResponse httpResponse; GoogleCaptchaResponse googleResponse; final ObjectMapper objectMapper = new ObjectMapper(); diff --git a/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java b/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java index b0aa4aba13a..06a4b836143 100644 --- a/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java +++ b/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java @@ -32,9 +32,9 @@ import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; -import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.message.BasicNameValuePair; import org.apache.logging.log4j.Logger; +import org.dspace.app.client.DSpaceHttpClientFactory; import org.dspace.app.util.Util; import org.json.JSONObject; import org.springframework.beans.factory.annotation.Autowired; @@ -121,7 +121,7 @@ public OpenAIRERestToken grabNewAccessToken() throws IOException { params.add(new BasicNameValuePair("grant_type", "client_credentials")); httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8")); - HttpClient httpClient = HttpClientBuilder.create().build(); + HttpClient httpClient = DSpaceHttpClientFactory.getInstance().build(); HttpResponse getResponse = httpClient.execute(httpPost); JSONObject responseObject = null; @@ -172,7 +172,7 @@ public InputStream get(String file, String accessToken) { httpGet.addHeader("Authorization", "Bearer " + accessToken); } - HttpClient httpClient = HttpClientBuilder.create().build(); + HttpClient httpClient = DSpaceHttpClientFactory.getInstance().build(); getResponse = httpClient.execute(httpGet); StatusLine status = getResponse.getStatusLine(); diff --git a/dspace-api/src/main/java/org/dspace/external/OrcidRestConnector.java b/dspace-api/src/main/java/org/dspace/external/OrcidRestConnector.java index d45be7e6b56..3ccbd257e28 100644 --- a/dspace-api/src/main/java/org/dspace/external/OrcidRestConnector.java +++ b/dspace-api/src/main/java/org/dspace/external/OrcidRestConnector.java @@ -15,9 +15,9 @@ import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.HttpClientBuilder; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dspace.app.client.DSpaceHttpClientFactory; /** * @author Antoine Snyers (antoine at atmire.com) @@ -50,7 +50,7 @@ public InputStream get(String path, String accessToken) { httpGet.addHeader("Authorization","Bearer " + accessToken); } try { - HttpClient httpClient = HttpClientBuilder.create().build(); + HttpClient httpClient = DSpaceHttpClientFactory.getInstance().build(); getResponse = httpClient.execute(httpGet); //do not close this httpClient result = getResponse.getEntity().getContent(); diff --git a/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java b/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java index cdecadba524..524ff04face 100644 --- a/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java @@ -28,9 +28,9 @@ import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.mime.MultipartEntityBuilder; import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.util.EntityUtils; import org.apache.logging.log4j.Logger; +import org.dspace.app.client.DSpaceHttpClientFactory; import org.dspace.services.ConfigurationService; import org.jdom2.Attribute; import org.jdom2.Document; @@ -70,12 +70,7 @@ public class CCLicenseConnectorServiceImpl implements CCLicenseConnectorService, @Override public void afterPropertiesSet() throws Exception { - HttpClientBuilder builder = HttpClientBuilder.create(); - - client = builder - .disableAutomaticRetries() - .setMaxConnTotal(5) - .build(); + client = DSpaceHttpClientFactory.getInstance().buildWithoutAutomaticRetries(5); // disallow DTD parsing to ensure no XXE attacks can occur. // See https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html diff --git a/dspace-api/src/main/java/org/dspace/orcid/client/OrcidClientImpl.java b/dspace-api/src/main/java/org/dspace/orcid/client/OrcidClientImpl.java index 1532abe6341..2285bb11c78 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/client/OrcidClientImpl.java +++ b/dspace-api/src/main/java/org/dspace/orcid/client/OrcidClientImpl.java @@ -40,8 +40,8 @@ import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.methods.RequestBuilder; import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.message.BasicNameValuePair; +import org.dspace.app.client.DSpaceHttpClientFactory; import org.dspace.orcid.OrcidToken; import org.dspace.orcid.exception.OrcidClientException; import org.dspace.orcid.model.OrcidEntityType; @@ -76,9 +76,12 @@ public class OrcidClientImpl implements OrcidClient { private final ObjectMapper objectMapper; - public OrcidClientImpl(OrcidConfiguration orcidConfiguration) { + private final DSpaceHttpClientFactory httpClientFactory; + + public OrcidClientImpl(OrcidConfiguration orcidConfiguration, DSpaceHttpClientFactory httpClientFactory) { this.orcidConfiguration = orcidConfiguration; this.objectMapper = new ObjectMapper(); + this.httpClientFactory = httpClientFactory; } private static Map, String> initializePathsMap() { @@ -255,7 +258,7 @@ private HttpUriRequest buildDeleteUriRequest(String accessToken, String relative private void executeSuccessful(HttpUriRequest httpUriRequest) { try { - HttpClient client = HttpClientBuilder.create().build(); + HttpClient client = httpClientFactory.build(); HttpResponse response = client.execute(httpUriRequest); if (isNotSuccessfull(response)) { @@ -273,7 +276,7 @@ private void executeSuccessful(HttpUriRequest httpUriRequest) { private T executeAndParseJson(HttpUriRequest httpUriRequest, Class clazz) { - HttpClient client = HttpClientBuilder.create().build(); + HttpClient client = httpClientFactory.build(); return executeAndReturns(() -> { @@ -302,7 +305,7 @@ private T executeAndParseJson(HttpUriRequest httpUriRequest, Class clazz) */ private T executeAndUnmarshall(HttpUriRequest httpUriRequest, boolean handleNotFoundAsNull, Class clazz) { - HttpClient client = HttpClientBuilder.create().build(); + HttpClient client = httpClientFactory.build(); return executeAndReturns(() -> { @@ -322,7 +325,7 @@ private T executeAndUnmarshall(HttpUriRequest httpUriRequest, boolean handle } private OrcidResponse execute(HttpUriRequest httpUriRequest, boolean handleNotFoundAsNull) { - HttpClient client = HttpClientBuilder.create().build(); + HttpClient client = httpClientFactory.build(); return executeAndReturns(() -> { diff --git a/dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.java b/dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.java index c5f7c46b586..27f2ad3e412 100644 --- a/dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.java +++ b/dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.java @@ -19,11 +19,11 @@ import org.apache.http.conn.ConnectionKeepAliveStrategy; import org.apache.http.conn.HttpClientConnectionManager; import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.message.BasicHeaderElementIterator; import org.apache.http.protocol.HTTP; import org.apache.http.protocol.HttpContext; +import org.dspace.app.client.DSpaceHttpClientFactory; import org.dspace.services.ConfigurationService; /** @@ -112,7 +112,7 @@ protected void init() { * @return the client. */ public CloseableHttpClient getClient() { - CloseableHttpClient httpClient = HttpClientBuilder.create() + CloseableHttpClient httpClient = DSpaceHttpClientFactory.getInstance().builder(false).create() .setKeepAliveStrategy(keepAliveStrategy) .setConnectionManager(connManager) .build(); diff --git a/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java b/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java index 5f976bbfd94..da9c7eb3bf2 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java @@ -51,7 +51,6 @@ import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.solr.client.solrj.SolrClient; @@ -81,6 +80,7 @@ import org.apache.solr.common.params.ModifiableSolrParams; import org.apache.solr.common.params.ShardParams; import org.apache.solr.common.util.NamedList; +import org.dspace.app.client.DSpaceHttpClientFactory; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; @@ -1303,7 +1303,7 @@ public void shardSolrIndex() throws IOException, SolrServerException { + "." + i + ".csv"); - try ( CloseableHttpClient hc = HttpClientBuilder.create().build(); ) { + try (CloseableHttpClient hc = DSpaceHttpClientFactory.getInstance().build()) { HttpResponse response = hc.execute(get); csvInputstream = response.getEntity().getContent(); //Write the csv ouput to a file ! @@ -1445,7 +1445,7 @@ public void reindexBitstreamHits(boolean removeDeletedBitstreams) throws Excepti HttpGet get = new HttpGet(solrRequestUrl); List rows; - try ( CloseableHttpClient hc = HttpClientBuilder.create().build(); ) { + try (CloseableHttpClient hc = DSpaceHttpClientFactory.getInstance().build()) { HttpResponse response = hc.execute(get); InputStream csvOutput = response.getEntity().getContent(); Reader csvReader = new InputStreamReader(csvOutput); diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlServiceImpl.java b/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlServiceImpl.java index b7a9562fb54..d8f4aa6b7e7 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlServiceImpl.java @@ -18,9 +18,9 @@ import org.apache.http.client.HttpClient; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.HttpClientBuilder; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dspace.app.client.DSpaceHttpClientFactory; import org.dspace.core.Context; import org.dspace.statistics.export.OpenURLTracker; import org.springframework.beans.factory.annotation.Autowired; @@ -75,9 +75,7 @@ protected int getResponseCodeFromUrl(final String urlStr) throws IOException { } protected HttpClient getHttpClient(RequestConfig requestConfig) { - return HttpClientBuilder.create() - .setDefaultRequestConfig(requestConfig) - .build(); + return DSpaceHttpClientFactory.getInstance().buildWithRequestConfig(requestConfig); } protected RequestConfig getHttpClientRequestConfig() { diff --git a/dspace-api/src/test/java/org/dspace/app/client/DSpaceHttpClientFactoryTest.java b/dspace-api/src/test/java/org/dspace/app/client/DSpaceHttpClientFactoryTest.java new file mode 100644 index 00000000000..b518f19ff4d --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/client/DSpaceHttpClientFactoryTest.java @@ -0,0 +1,256 @@ +/** + * 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.app.client; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.arrayWithSize; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.apache.http.HttpRequestInterceptor; +import org.apache.http.HttpResponse; +import org.apache.http.HttpResponseInterceptor; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.protocol.HttpContext; +import org.dspace.services.ConfigurationService; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +/** + * Unit tests for {@link DSpaceHttpClientFactory}. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +@RunWith(MockitoJUnitRunner.class) +public class DSpaceHttpClientFactoryTest { + + @InjectMocks + private DSpaceHttpClientFactory httpClientFactory; + + @Mock + private ConfigurationService configurationService; + + private MockWebServer mockProxy; + + private MockWebServer mockServer; + + @Before + public void init() { + this.httpClientFactory.setProxyRoutePlanner(new DSpaceProxyRoutePlanner(configurationService)); + this.mockProxy = new MockWebServer(); + this.mockProxy.enqueue(new MockResponse().setResponseCode(200).addHeader("From", "Proxy")); + this.mockServer = new MockWebServer(); + this.mockServer.enqueue(new MockResponse().setResponseCode(200).addHeader("From", "Server")); + } + + @Test + public void testBuildWithProxyConfigured() throws Exception { + setHttpProxyOnConfigurationService(); + CloseableHttpClient httpClient = httpClientFactory.build(); + assertThat(mockProxy.getRequestCount(), is(0)); + assertThat(mockServer.getRequestCount(), is(0)); + CloseableHttpResponse httpResponse = httpClient.execute(new HttpGet(mockServer.url("").toString())); + assertThat(httpResponse.getHeaders("From"), arrayWithSize(1)); + assertThat(httpResponse.getHeaders("From")[0].getValue(), is("Proxy")); + assertThat(mockProxy.getRequestCount(), is(1)); + assertThat(mockServer.getRequestCount(), is(0)); + RecordedRequest request = mockProxy.takeRequest(100, TimeUnit.MILLISECONDS); + assertThat(request, notNullValue()); + assertThat(request.getRequestUrl(), is(mockProxy.url(""))); + assertThat(request.getRequestLine(), is("GET " + mockServer.url("").toString() + " HTTP/1.1")); + verify(configurationService).getProperty("http.proxy.host"); + verify(configurationService).getProperty("http.proxy.port"); + verify(configurationService).getArrayProperty("http.proxy.hosts-to-ignore"); + verifyNoMoreInteractions(configurationService); + } + + @Test + public void testBuildWithProxyConfiguredAndHostToIgnoreSet() throws Exception { + setHttpProxyOnConfigurationService(mockServer.getHostName()); + CloseableHttpClient httpClient = httpClientFactory.build(); + assertThat(mockProxy.getRequestCount(), is(0)); + assertThat(mockServer.getRequestCount(), is(0)); + CloseableHttpResponse httpResponse = httpClient.execute(new HttpGet(mockServer.url("").toString())); + assertThat(httpResponse.getHeaders("From"), arrayWithSize(1)); + assertThat(httpResponse.getHeaders("From")[0].getValue(), is("Server")); + assertThat(mockProxy.getRequestCount(), is(0)); + assertThat(mockServer.getRequestCount(), is(1)); + verify(configurationService).getArrayProperty("http.proxy.hosts-to-ignore"); + verifyNoMoreInteractions(configurationService); + } + + @Test + public void testBuildWithProxyConfiguredAndHostPrefixToIgnoreSet() throws Exception { + setHttpProxyOnConfigurationService("local*", "www.test.com"); + CloseableHttpClient httpClient = httpClientFactory.build(); + assertThat(mockProxy.getRequestCount(), is(0)); + assertThat(mockServer.getRequestCount(), is(0)); + CloseableHttpResponse httpResponse = httpClient.execute(new HttpGet(mockServer.url("").toString())); + assertThat(httpResponse.getHeaders("From"), arrayWithSize(1)); + assertThat(httpResponse.getHeaders("From")[0].getValue(), is("Server")); + assertThat(mockProxy.getRequestCount(), is(0)); + assertThat(mockServer.getRequestCount(), is(1)); + verify(configurationService).getArrayProperty("http.proxy.hosts-to-ignore"); + verifyNoMoreInteractions(configurationService); + } + + @Test + public void testBuildWithProxyConfiguredAndHostSuffixToIgnoreSet() throws Exception { + setHttpProxyOnConfigurationService("www.test.com", "*host"); + CloseableHttpClient httpClient = httpClientFactory.build(); + assertThat(mockProxy.getRequestCount(), is(0)); + assertThat(mockServer.getRequestCount(), is(0)); + CloseableHttpResponse httpResponse = httpClient.execute(new HttpGet(mockServer.url("").toString())); + assertThat(httpResponse.getHeaders("From"), arrayWithSize(1)); + assertThat(httpResponse.getHeaders("From")[0].getValue(), is("Server")); + assertThat(mockProxy.getRequestCount(), is(0)); + assertThat(mockServer.getRequestCount(), is(1)); + verify(configurationService).getArrayProperty("http.proxy.hosts-to-ignore"); + verifyNoMoreInteractions(configurationService); + } + + @Test + public void testBuildWithoutConfiguredProxy() throws Exception { + CloseableHttpClient httpClient = httpClientFactory.build(); + assertThat(mockProxy.getRequestCount(), is(0)); + assertThat(mockServer.getRequestCount(), is(0)); + CloseableHttpResponse httpResponse = httpClient.execute(new HttpGet(mockServer.url("").toString())); + assertThat(httpResponse.getHeaders("From"), arrayWithSize(1)); + assertThat(httpResponse.getHeaders("From")[0].getValue(), is("Server")); + assertThat(mockProxy.getRequestCount(), is(0)); + assertThat(mockServer.getRequestCount(), is(1)); + RecordedRequest request = mockServer.takeRequest(100, TimeUnit.MILLISECONDS); + assertThat(request, notNullValue()); + assertThat(request.getRequestUrl(), is(mockServer.url(""))); + assertThat(request.getRequestLine(), is("GET / HTTP/1.1")); + verify(configurationService).getProperty("http.proxy.host"); + verify(configurationService).getProperty("http.proxy.port"); + verify(configurationService).getArrayProperty("http.proxy.hosts-to-ignore"); + verifyNoMoreInteractions(configurationService); + } + + @Test + public void testBuildWithoutProxy() throws Exception { + CloseableHttpClient httpClient = httpClientFactory.buildWithoutProxy(); + assertThat(mockProxy.getRequestCount(), is(0)); + assertThat(mockServer.getRequestCount(), is(0)); + httpClient.execute(new HttpGet(mockServer.url("").toString())); + assertThat(mockServer.getRequestCount(), is(1)); + assertThat(mockProxy.getRequestCount(), is(0)); + RecordedRequest request = mockServer.takeRequest(100, TimeUnit.MILLISECONDS); + assertThat(request, notNullValue()); + assertThat(request.getRequestUrl(), is(mockServer.url(""))); + assertThat(request.getRequestLine(), is("GET / HTTP/1.1")); + verifyNoInteractions(configurationService); + } + + @Test + public void testBuildWithoutAutomaticRetries() throws Exception { + setHttpProxyOnConfigurationService("www.test.com"); + CloseableHttpClient httpClient = httpClientFactory.buildWithoutAutomaticRetries(10); + httpClient.execute(new HttpGet(mockServer.url("").toString())); + assertThat(mockProxy.getRequestCount(), is(1)); + assertThat(mockServer.getRequestCount(), is(0)); + RecordedRequest request = mockProxy.takeRequest(100, TimeUnit.MILLISECONDS); + assertThat(request, notNullValue()); + assertThat(request.getRequestUrl(), is(mockProxy.url(""))); + assertThat(request.getRequestLine(), is("GET " + mockServer.url("").toString() + " HTTP/1.1")); + verify(configurationService).getProperty("http.proxy.host"); + verify(configurationService).getProperty("http.proxy.port"); + verify(configurationService).getArrayProperty("http.proxy.hosts-to-ignore"); + verifyNoMoreInteractions(configurationService); + } + + @Test + public void testBuildWithHttpRequestInterceptor() throws Exception { + setHttpProxyOnConfigurationService("*test.com", "www.dspace.com"); + AtomicReference contextReference = new AtomicReference(); + HttpRequestInterceptor interceptor = (request, context) -> contextReference.set(context); + httpClientFactory.setRequestInterceptors(List.of(interceptor)); + CloseableHttpClient httpClient = httpClientFactory.build(); + httpClient.execute(new HttpGet(mockServer.url("").toString())); + assertThat(mockProxy.getRequestCount(), is(1)); + assertThat(mockServer.getRequestCount(), is(0)); + HttpContext httpContext = contextReference.get(); + assertThat(httpContext, notNullValue()); + Object httpRouteObj = httpContext.getAttribute("http.route"); + assertThat(httpRouteObj, notNullValue()); + assertThat(httpRouteObj, instanceOf(HttpRoute.class)); + HttpRoute httpRoute = (HttpRoute) httpRouteObj; + assertThat(httpRoute.getHopCount(), is(2)); + assertThat(httpRoute.getHopTarget(0).getPort(), is(mockProxy.getPort())); + assertThat(httpRoute.getHopTarget(1).getPort(), is(mockServer.getPort())); + } + + @Test + public void testBuildWithHttpResponseInterceptor() throws Exception { + AtomicReference responseReference = new AtomicReference(); + HttpResponseInterceptor responseInterceptor = (response, context) -> responseReference.set(response); + httpClientFactory.setResponseInterceptors(List.of(responseInterceptor)); + CloseableHttpClient httpClient = httpClientFactory.build(); + httpClient.execute(new HttpGet(mockServer.url("").toString())); + assertThat(mockProxy.getRequestCount(), is(0)); + assertThat(mockServer.getRequestCount(), is(1)); + HttpResponse httpResponse = responseReference.get(); + assertThat(httpResponse, notNullValue()); + assertThat(httpResponse.getHeaders("From"), arrayWithSize(1)); + assertThat(httpResponse.getHeaders("From")[0].getValue(), is("Server")); + } + + @Test + public void testBuildWithRequestConfig() throws Exception { + setHttpProxyOnConfigurationService(); + RequestConfig requestConfig = RequestConfig.custom() + .setConnectTimeout(2500) + .build(); + AtomicReference contextReference = new AtomicReference(); + HttpRequestInterceptor interceptor = (request, context) -> contextReference.set(context); + httpClientFactory.setRequestInterceptors(List.of(interceptor)); + CloseableHttpClient httpClient = httpClientFactory.buildWithRequestConfig(requestConfig); + httpClient.execute(new HttpGet(mockServer.url("").toString())); + assertThat(mockProxy.getRequestCount(), is(1)); + assertThat(mockServer.getRequestCount(), is(0)); + HttpContext httpContext = contextReference.get(); + assertThat(httpContext, notNullValue()); + Object httpRequestConfigObj = httpContext.getAttribute("http.request-config"); + assertThat(httpRequestConfigObj, notNullValue()); + assertThat(httpRequestConfigObj, instanceOf(RequestConfig.class)); + assertThat(((RequestConfig) httpRequestConfigObj).getConnectTimeout(), is(2500)); + verify(configurationService).getProperty("http.proxy.host"); + verify(configurationService).getProperty("http.proxy.port"); + verify(configurationService).getArrayProperty("http.proxy.hosts-to-ignore"); + verifyNoMoreInteractions(configurationService); + } + + private void setHttpProxyOnConfigurationService(String... hostsToIgnore) { + when(configurationService.getProperty("http.proxy.host")).thenReturn(mockProxy.getHostName()); + when(configurationService.getProperty("http.proxy.port")).thenReturn(String.valueOf(mockProxy.getPort())); + when(configurationService.getArrayProperty("http.proxy.hosts-to-ignore")).thenReturn(hostsToIgnore); + } +} \ No newline at end of file diff --git a/dspace/config/spring/api/core-services.xml b/dspace/config/spring/api/core-services.xml index 7a5642f4643..be0e5977ede 100644 --- a/dspace/config/spring/api/core-services.xml +++ b/dspace/config/spring/api/core-services.xml @@ -153,6 +153,9 @@ + + + From a8d33d3ad06310a5a49865daf59d4f1511bfdf7a Mon Sep 17 00:00:00 2001 From: Elios Buzo Date: Thu, 17 Apr 2025 17:55:05 +0200 Subject: [PATCH 541/939] [DURACOM-109] Continued configuring proxy for other classes --- .../app/client/DSpaceHttpClientFactory.java | 2 +- .../packager/AbstractMETSIngester.java | 12 ++++--- .../ctask/general/BasicLinkChecker.java | 18 ++++++---- .../dspace/iiif/IIIFApiQueryServiceImpl.java | 15 ++++---- .../service/LiveImportClientImpl.java | 35 ++++--------------- .../CCLicenseConnectorServiceImpl.java | 18 ++-------- .../statistics/SolrLoggerServiceImpl.java | 4 +-- dspace/config/dspace.cfg | 2 ++ 8 files changed, 39 insertions(+), 67 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/client/DSpaceHttpClientFactory.java b/dspace-api/src/main/java/org/dspace/app/client/DSpaceHttpClientFactory.java index 999f13c1b38..59c8172f722 100644 --- a/dspace-api/src/main/java/org/dspace/app/client/DSpaceHttpClientFactory.java +++ b/dspace-api/src/main/java/org/dspace/app/client/DSpaceHttpClientFactory.java @@ -93,7 +93,7 @@ public CloseableHttpClient buildWithoutProxy() { public CloseableHttpClient buildWithoutAutomaticRetries(int maxConnTotal) { HttpClientBuilder clientBuilder = HttpClientBuilder.create() .disableAutomaticRetries() - .setMaxConnTotal(5); + .setMaxConnTotal(maxConnTotal); return build(clientBuilder, true); } diff --git a/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java index 0ed0abe2182..c43d8e797b0 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java @@ -11,8 +11,6 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; -import java.net.URL; -import java.net.URLConnection; import java.sql.SQLException; import java.util.Iterator; import java.util.List; @@ -21,7 +19,11 @@ import java.util.zip.ZipFile; import org.apache.commons.collections4.CollectionUtils; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; import org.apache.logging.log4j.Logger; +import org.dspace.app.client.DSpaceHttpClientFactory; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Bitstream; import org.dspace.content.BitstreamFormat; @@ -1312,11 +1314,11 @@ protected static InputStream getFileInputStream(File pkgFile, // we will assume all external files are available via URLs. try { // attempt to open a connection to given URL - URL fileURL = new URL(path); - URLConnection connection = fileURL.openConnection(); + CloseableHttpClient httpClient = DSpaceHttpClientFactory.getInstance().build(); + CloseableHttpResponse httpResponse = httpClient.execute(new HttpGet(path)); // open stream to access file contents - return connection.getInputStream(); + return httpResponse.getEntity().getContent(); } catch (IOException io) { log .error("Unable to retrieve external file from URL '" diff --git a/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java b/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java index a302159ea9a..fd6d69c98f0 100644 --- a/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java +++ b/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java @@ -9,11 +9,15 @@ import java.io.IOException; import java.net.HttpURLConnection; -import java.net.URL; import java.util.ArrayList; import java.util.List; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; import org.apache.logging.log4j.Logger; +import org.dspace.app.client.DSpaceHttpClientFactory; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.MetadataValue; @@ -136,15 +140,15 @@ protected boolean checkURL(String url, StringBuilder results) { */ protected int getResponseStatus(String url, int redirects) { try { - URL theURL = new URL(url); - HttpURLConnection connection = (HttpURLConnection) theURL.openConnection(); - connection.setInstanceFollowRedirects(true); - int statusCode = connection.getResponseCode(); + RequestConfig config = RequestConfig.custom().setRedirectsEnabled(true).build(); + CloseableHttpClient httpClient = DSpaceHttpClientFactory.getInstance().buildWithRequestConfig(config); + CloseableHttpResponse httpResponse = httpClient.execute(new HttpGet(url)); + int statusCode = httpResponse.getStatusLine().getStatusCode(); int maxRedirect = configurationService.getIntProperty("curate.checklinks.max-redirect", 0); if ((statusCode == HttpURLConnection.HTTP_MOVED_TEMP || statusCode == HttpURLConnection.HTTP_MOVED_PERM || statusCode == HttpURLConnection.HTTP_SEE_OTHER)) { - connection.disconnect(); - String newUrl = connection.getHeaderField("Location"); + httpClient.close(); + String newUrl = httpResponse.getFirstHeader("Location").getValue(); if (newUrl != null && (maxRedirect >= redirects || maxRedirect == -1)) { redirects++; return getResponseStatus(newUrl, redirects); diff --git a/dspace-api/src/main/java/org/dspace/iiif/IIIFApiQueryServiceImpl.java b/dspace-api/src/main/java/org/dspace/iiif/IIIFApiQueryServiceImpl.java index 7c6336ed3c7..87bd7770648 100644 --- a/dspace-api/src/main/java/org/dspace/iiif/IIIFApiQueryServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/iiif/IIIFApiQueryServiceImpl.java @@ -12,12 +12,14 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URL; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; import org.apache.logging.log4j.Logger; +import org.dspace.app.client.DSpaceHttpClientFactory; import org.dspace.content.Bitstream; import org.dspace.iiif.util.IIIFSharedUtils; @@ -35,14 +37,11 @@ public class IIIFApiQueryServiceImpl implements IIIFApiQueryService { public int[] getImageDimensions(Bitstream bitstream) { int[] arr = new int[2]; String path = IIIFSharedUtils.getInfoJsonPath(bitstream); - URL url; BufferedReader in = null; try { - url = new URL(path); - HttpURLConnection con = (HttpURLConnection) url.openConnection(); - con.setRequestMethod("GET"); - in = new BufferedReader( - new InputStreamReader(con.getInputStream())); + CloseableHttpClient httpClient = DSpaceHttpClientFactory.getInstance().build(); + CloseableHttpResponse httpResponse = httpClient.execute(new HttpGet(path)); + in = new BufferedReader(new InputStreamReader(httpResponse.getEntity().getContent())); String inputLine; StringBuilder response = new StringBuilder(); while ((inputLine = in.readLine()) != null) { diff --git a/dspace-api/src/main/java/org/dspace/importer/external/liveimportclient/service/LiveImportClientImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/liveimportclient/service/LiveImportClientImpl.java index 1a8a7a7861e..2cb8236842a 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/liveimportclient/service/LiveImportClientImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/liveimportclient/service/LiveImportClientImpl.java @@ -17,19 +17,16 @@ import org.apache.commons.collections.MapUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.http.HttpHost; import org.apache.http.HttpResponse; import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.config.RequestConfig.Builder; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.utils.URIBuilder; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClients; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dspace.app.client.DSpaceHttpClientFactory; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; @@ -53,16 +50,12 @@ public class LiveImportClientImpl implements LiveImportClient { @Override public String executeHttpGetRequest(int timeout, String URL, Map> params) { HttpGet method = null; + RequestConfig config = RequestConfig.custom().setConnectionRequestTimeout(timeout).build(); try (CloseableHttpClient httpClient = Optional.ofNullable(this.httpClient) - .orElseGet(HttpClients::createDefault)) { - - Builder requestConfigBuilder = RequestConfig.custom(); - requestConfigBuilder.setConnectionRequestTimeout(timeout); - RequestConfig defaultRequestConfig = requestConfigBuilder.build(); - + .orElse(DSpaceHttpClientFactory.getInstance().buildWithRequestConfig(config))) { String uri = buildUrl(URL, params.get(URI_PARAMETERS)); method = new HttpGet(uri); - method.setConfig(defaultRequestConfig); + method.setConfig(config); Map headerParams = params.get(HEADER_PARAMETERS); if (MapUtils.isNotEmpty(headerParams)) { @@ -71,7 +64,6 @@ public String executeHttpGetRequest(int timeout, String URL, Map> params, String entry) { HttpPost method = null; + RequestConfig config = RequestConfig.custom().build(); try (CloseableHttpClient httpClient = Optional.ofNullable(this.httpClient) - .orElseGet(HttpClients::createDefault)) { - - Builder requestConfigBuilder = RequestConfig.custom(); - RequestConfig defaultRequestConfig = requestConfigBuilder.build(); + .orElse(DSpaceHttpClientFactory.getInstance().buildWithRequestConfig(config))) { String uri = buildUrl(URL, params.get(URI_PARAMETERS)); method = new HttpPost(uri); - method.setConfig(defaultRequestConfig); if (StringUtils.isNotBlank(entry)) { method.setEntity(new StringEntity(entry)); } setHeaderParams(method, params); - configureProxy(method, defaultRequestConfig); if (log.isDebugEnabled()) { log.debug("Performing POST request to \"" + uri + "\"..." ); } @@ -129,17 +117,6 @@ public String executeHttpPostRequest(String URL, Map return StringUtils.EMPTY; } - private void configureProxy(HttpRequestBase method, RequestConfig defaultRequestConfig) { - String proxyHost = configurationService.getProperty("http.proxy.host"); - String proxyPort = configurationService.getProperty("http.proxy.port"); - if (StringUtils.isNotBlank(proxyHost) && StringUtils.isNotBlank(proxyPort)) { - RequestConfig requestConfig = RequestConfig.copy(defaultRequestConfig) - .setProxy(new HttpHost(proxyHost, Integer.parseInt(proxyPort), "http")) - .build(); - method.setConfig(requestConfig); - } - } - /** * Allows to set the header parameters to the HTTP Post method * diff --git a/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java b/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java index 524ff04face..e9fe49e9b4f 100644 --- a/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java @@ -10,9 +10,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.StringReader; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLConnection; import java.text.MessageFormat; import java.util.Collections; import java.util.HashMap; @@ -328,23 +325,14 @@ private String createAnswerString(final Map parameterMap) { @Override public Document retrieveLicenseRDFDoc(String licenseURI) throws IOException { String ccLicenseUrl = configurationService.getProperty("cc.api.rooturl"); - String issueUrl = ccLicenseUrl + "/details?license-uri=" + licenseURI; - - URL request_url; - try { - request_url = new URL(issueUrl); - } catch (MalformedURLException e) { - return null; - } - URLConnection connection = request_url.openConnection(); - connection.setDoOutput(true); try { + CloseableHttpClient httpClient = DSpaceHttpClientFactory.getInstance().build(); + CloseableHttpResponse httpResponse = httpClient.execute(new HttpPost(issueUrl)); // parsing document from input stream - InputStream stream = connection.getInputStream(); + InputStream stream = httpResponse.getEntity().getContent(); Document doc = parser.build(stream); return doc; - } catch (Exception e) { log.error("Error while retrieving the license document for URI: " + licenseURI, e); } diff --git a/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java b/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java index da9c7eb3bf2..6c3ccdc082e 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java @@ -1303,7 +1303,7 @@ public void shardSolrIndex() throws IOException, SolrServerException { + "." + i + ".csv"); - try (CloseableHttpClient hc = DSpaceHttpClientFactory.getInstance().build()) { + try (CloseableHttpClient hc = DSpaceHttpClientFactory.getInstance().buildWithoutProxy()) { HttpResponse response = hc.execute(get); csvInputstream = response.getEntity().getContent(); //Write the csv ouput to a file ! @@ -1445,7 +1445,7 @@ public void reindexBitstreamHits(boolean removeDeletedBitstreams) throws Excepti HttpGet get = new HttpGet(solrRequestUrl); List rows; - try (CloseableHttpClient hc = DSpaceHttpClientFactory.getInstance().build()) { + try (CloseableHttpClient hc = DSpaceHttpClientFactory.getInstance().buildWithoutProxy()) { HttpResponse response = hc.execute(get); InputStream csvOutput = response.getEntity().getContent(); Reader csvReader = new InputStreamReader(csvOutput); diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 94b731cd348..4621ca51dc1 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -411,6 +411,8 @@ http.proxy.host = # port number of proxy server http.proxy.port = +http.proxy.hosts-to-ignore = 127.0.0.1, localhost + # If enabled, the logging and the Solr statistics system will look for an X-Forwarded-For header. # If it finds it, it will use this for the user IP address. # NOTE: This is required to be enabled if you plan to use the Angular UI, as the server-side rendering provided in From 32dd1a3dd22c70363c0c7593d395570247ee00aa Mon Sep 17 00:00:00 2001 From: Elios Buzo Date: Fri, 18 Apr 2025 10:24:35 +0200 Subject: [PATCH 542/939] [DURACOM-109] Minor fix --- .../external/liveimportclient/service/LiveImportClientImpl.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/liveimportclient/service/LiveImportClientImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/liveimportclient/service/LiveImportClientImpl.java index 2cb8236842a..84475b62c07 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/liveimportclient/service/LiveImportClientImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/liveimportclient/service/LiveImportClientImpl.java @@ -55,7 +55,6 @@ public String executeHttpGetRequest(int timeout, String URL, Map headerParams = params.get(HEADER_PARAMETERS); if (MapUtils.isNotEmpty(headerParams)) { From b9352c9149b90ae3c5f007ddae15632b26d9e246 Mon Sep 17 00:00:00 2001 From: Elios Buzo Date: Fri, 18 Apr 2025 15:00:38 +0200 Subject: [PATCH 543/939] [DURACOM-109] Fixed http connection leaks --- .../org/dspace/app/sherpa/SHERPAService.java | 21 +--- .../dspace/app/util/WebAppServiceImpl.java | 4 +- .../oidc/impl/OidcClientImpl.java | 29 +++-- .../packager/AbstractMETSIngester.java | 3 +- .../ctask/general/BasicLinkChecker.java | 5 +- .../dspace/external/OrcidRestConnector.java | 10 +- .../dspace/iiif/IIIFApiQueryServiceImpl.java | 3 +- .../CCLicenseConnectorServiceImpl.java | 3 +- .../dspace/orcid/client/OrcidClientImpl.java | 103 ++++++++---------- 9 files changed, 73 insertions(+), 108 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAService.java b/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAService.java index 73ec0cc5893..87042c502d8 100644 --- a/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAService.java +++ b/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAService.java @@ -17,9 +17,9 @@ import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.utils.URIBuilder; import org.apache.http.impl.client.CloseableHttpClient; @@ -45,8 +45,6 @@ */ public class SHERPAService { - private CloseableHttpClient client = null; - private int maxNumberOfTries; private long sleepBetweenTimeouts; private int timeout = 5000; @@ -59,15 +57,6 @@ public class SHERPAService { @Autowired ConfigurationService configurationService; - /** - * Create a new HTTP builder with sensible defaults in constructor - */ - public SHERPAService() { - // httpclient 4.3+ doesn't appear to have any sensible defaults any more. Setting conservative defaults as - // not to hammer the SHERPA service too much. - client = DSpaceHttpClientFactory.getInstance().buildWithoutAutomaticRetries(5); - } - /** * Complete initialization of the Bean. */ @@ -128,14 +117,14 @@ public SHERPAPublisherResponse performPublisherRequest(String type, String field timeout, sleepBetweenTimeouts)); - try { + try (CloseableHttpClient client = DSpaceHttpClientFactory.getInstance().build()) { Thread.sleep(sleepBetweenTimeouts); // Construct a default HTTP method (first result) method = constructHttpGet(type, field, predicate, value, start, limit); // Execute the method - HttpResponse response = client.execute(method); + CloseableHttpResponse response = client.execute(method); int statusCode = response.getStatusLine().getStatusCode(); log.debug(response.getStatusLine().getStatusCode() + ": " @@ -231,14 +220,14 @@ public SHERPAResponse performRequest(String type, String field, String predicate timeout, sleepBetweenTimeouts)); - try { + try (CloseableHttpClient client = DSpaceHttpClientFactory.getInstance().build()) { Thread.sleep(sleepBetweenTimeouts); // Construct a default HTTP method (first result) method = constructHttpGet(type, field, predicate, value, start, limit); // Execute the method - HttpResponse response = client.execute(method); + CloseableHttpResponse response = client.execute(method); int statusCode = response.getStatusLine().getStatusCode(); log.debug(response.getStatusLine().getStatusCode() + ": " diff --git a/dspace-api/src/main/java/org/dspace/app/util/WebAppServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/util/WebAppServiceImpl.java index 064a9fdc3e9..fa23903ba43 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/WebAppServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/util/WebAppServiceImpl.java @@ -13,8 +13,8 @@ import java.util.Date; import java.util.List; -import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; +import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpHead; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.logging.log4j.Logger; @@ -78,7 +78,7 @@ public List getApps() { method = new HttpHead(app.getUrl()); int status; try (CloseableHttpClient client = DSpaceHttpClientFactory.getInstance().build()) { - HttpResponse response = client.execute(method); + CloseableHttpResponse response = client.execute(method); status = response.getStatusLine().getStatusCode(); } if (status != HttpStatus.SC_OK) { diff --git a/dspace-api/src/main/java/org/dspace/authenticate/oidc/impl/OidcClientImpl.java b/dspace-api/src/main/java/org/dspace/authenticate/oidc/impl/OidcClientImpl.java index 35856d3756d..8dd60995112 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/oidc/impl/OidcClientImpl.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/oidc/impl/OidcClientImpl.java @@ -22,10 +22,11 @@ import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; -import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.methods.RequestBuilder; +import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.message.BasicNameValuePair; import org.dspace.app.client.DSpaceHttpClientFactory; import org.dspace.authenticate.oidc.OidcClient; @@ -83,21 +84,17 @@ public Map getUserInfo(String accessToken) throws OidcClientExce } private T executeAndParseJson(HttpUriRequest httpUriRequest, Class clazz) { - - HttpClient client = DSpaceHttpClientFactory.getInstance().build(); - - return executeAndReturns(() -> { - - HttpResponse response = client.execute(httpUriRequest); - - if (isNotSuccessfull(response)) { - throw new OidcClientException(getStatusCode(response), formatErrorMessage(response)); - } - - return objectMapper.readValue(getContent(response), clazz); - - }); - + try (CloseableHttpClient client = DSpaceHttpClientFactory.getInstance().build()) { + return executeAndReturns(() -> { + CloseableHttpResponse response = client.execute(httpUriRequest); + if (isNotSuccessfull(response)) { + throw new OidcClientException(getStatusCode(response), formatErrorMessage(response)); + } + return objectMapper.readValue(getContent(response), clazz); + }); + } catch (IOException e) { + throw new RuntimeException(e); + } } private T executeAndReturns(ThrowingSupplier supplier) { diff --git a/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java index c43d8e797b0..835b9f0e9b6 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java @@ -1312,9 +1312,8 @@ protected static InputStream getFileInputStream(File pkgFile, if (params.getBooleanProperty("manifestOnly", false)) { // NOTE: since we are only dealing with a METS manifest, // we will assume all external files are available via URLs. - try { + try (CloseableHttpClient httpClient = DSpaceHttpClientFactory.getInstance().build()) { // attempt to open a connection to given URL - CloseableHttpClient httpClient = DSpaceHttpClientFactory.getInstance().build(); CloseableHttpResponse httpResponse = httpClient.execute(new HttpGet(path)); // open stream to access file contents diff --git a/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java b/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java index fd6d69c98f0..189f7773388 100644 --- a/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java +++ b/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java @@ -139,9 +139,8 @@ protected boolean checkURL(String url, StringBuilder results) { * @return The HTTP response code (e.g. 200 / 301 / 404 / 500) */ protected int getResponseStatus(String url, int redirects) { - try { - RequestConfig config = RequestConfig.custom().setRedirectsEnabled(true).build(); - CloseableHttpClient httpClient = DSpaceHttpClientFactory.getInstance().buildWithRequestConfig(config); + RequestConfig config = RequestConfig.custom().setRedirectsEnabled(true).build(); + try (CloseableHttpClient httpClient = DSpaceHttpClientFactory.getInstance().buildWithRequestConfig(config)) { CloseableHttpResponse httpResponse = httpClient.execute(new HttpGet(url)); int statusCode = httpResponse.getStatusLine().getStatusCode(); int maxRedirect = configurationService.getIntProperty("curate.checklinks.max-redirect", 0); diff --git a/dspace-api/src/main/java/org/dspace/external/OrcidRestConnector.java b/dspace-api/src/main/java/org/dspace/external/OrcidRestConnector.java index 3ccbd257e28..f2c6246f4d0 100644 --- a/dspace-api/src/main/java/org/dspace/external/OrcidRestConnector.java +++ b/dspace-api/src/main/java/org/dspace/external/OrcidRestConnector.java @@ -12,9 +12,9 @@ import java.util.Scanner; import org.apache.commons.lang3.StringUtils; -import org.apache.http.HttpResponse; -import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.app.client.DSpaceHttpClientFactory; @@ -39,7 +39,7 @@ public OrcidRestConnector(String url) { } public InputStream get(String path, String accessToken) { - HttpResponse getResponse = null; + CloseableHttpResponse getResponse = null; InputStream result = null; path = trimSlashes(path); @@ -49,10 +49,8 @@ public InputStream get(String path, String accessToken) { httpGet.addHeader("Content-Type", "application/vnd.orcid+xml"); httpGet.addHeader("Authorization","Bearer " + accessToken); } - try { - HttpClient httpClient = DSpaceHttpClientFactory.getInstance().build(); + try (CloseableHttpClient httpClient = DSpaceHttpClientFactory.getInstance().build()) { getResponse = httpClient.execute(httpGet); - //do not close this httpClient result = getResponse.getEntity().getContent(); } catch (Exception e) { getGotError(e, fullPath); diff --git a/dspace-api/src/main/java/org/dspace/iiif/IIIFApiQueryServiceImpl.java b/dspace-api/src/main/java/org/dspace/iiif/IIIFApiQueryServiceImpl.java index 87bd7770648..ccb2c170d94 100644 --- a/dspace-api/src/main/java/org/dspace/iiif/IIIFApiQueryServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/iiif/IIIFApiQueryServiceImpl.java @@ -38,8 +38,7 @@ public int[] getImageDimensions(Bitstream bitstream) { int[] arr = new int[2]; String path = IIIFSharedUtils.getInfoJsonPath(bitstream); BufferedReader in = null; - try { - CloseableHttpClient httpClient = DSpaceHttpClientFactory.getInstance().build(); + try (CloseableHttpClient httpClient = DSpaceHttpClientFactory.getInstance().build()) { CloseableHttpResponse httpResponse = httpClient.execute(new HttpGet(path)); in = new BufferedReader(new InputStreamReader(httpResponse.getEntity().getContent())); String inputLine; diff --git a/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java b/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java index e9fe49e9b4f..1d777a2e13c 100644 --- a/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java @@ -326,8 +326,7 @@ private String createAnswerString(final Map parameterMap) { public Document retrieveLicenseRDFDoc(String licenseURI) throws IOException { String ccLicenseUrl = configurationService.getProperty("cc.api.rooturl"); String issueUrl = ccLicenseUrl + "/details?license-uri=" + licenseURI; - try { - CloseableHttpClient httpClient = DSpaceHttpClientFactory.getInstance().build(); + try (CloseableHttpClient httpClient = DSpaceHttpClientFactory.getInstance().build()) { CloseableHttpResponse httpResponse = httpClient.execute(new HttpPost(issueUrl)); // parsing document from input stream InputStream stream = httpResponse.getEntity().getContent(); diff --git a/dspace-api/src/main/java/org/dspace/orcid/client/OrcidClientImpl.java b/dspace-api/src/main/java/org/dspace/orcid/client/OrcidClientImpl.java index 2285bb11c78..954336da257 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/client/OrcidClientImpl.java +++ b/dspace-api/src/main/java/org/dspace/orcid/client/OrcidClientImpl.java @@ -35,11 +35,12 @@ import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.NameValuePair; -import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.methods.RequestBuilder; import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.message.BasicNameValuePair; import org.dspace.app.client.DSpaceHttpClientFactory; import org.dspace.orcid.OrcidToken; @@ -76,12 +77,9 @@ public class OrcidClientImpl implements OrcidClient { private final ObjectMapper objectMapper; - private final DSpaceHttpClientFactory httpClientFactory; - - public OrcidClientImpl(OrcidConfiguration orcidConfiguration, DSpaceHttpClientFactory httpClientFactory) { + public OrcidClientImpl(OrcidConfiguration orcidConfiguration) { this.orcidConfiguration = orcidConfiguration; this.objectMapper = new ObjectMapper(); - this.httpClientFactory = httpClientFactory; } private static Map, String> initializePathsMap() { @@ -257,10 +255,8 @@ private HttpUriRequest buildDeleteUriRequest(String accessToken, String relative } private void executeSuccessful(HttpUriRequest httpUriRequest) { - try { - HttpClient client = httpClientFactory.build(); - HttpResponse response = client.execute(httpUriRequest); - + try (CloseableHttpClient client = DSpaceHttpClientFactory.getInstance().build()) { + CloseableHttpResponse response = client.execute(httpUriRequest); if (isNotSuccessfull(response)) { throw new OrcidClientException( getStatusCode(response), @@ -275,21 +271,17 @@ private void executeSuccessful(HttpUriRequest httpUriRequest) { } private T executeAndParseJson(HttpUriRequest httpUriRequest, Class clazz) { - - HttpClient client = httpClientFactory.build(); - - return executeAndReturns(() -> { - - HttpResponse response = client.execute(httpUriRequest); - - if (isNotSuccessfull(response)) { - throw new OrcidClientException(getStatusCode(response), formatErrorMessage(response)); - } - - return objectMapper.readValue(response.getEntity().getContent(), clazz); - - }); - + try (CloseableHttpClient client = DSpaceHttpClientFactory.getInstance().build()) { + return executeAndReturns(() -> { + CloseableHttpResponse response = client.execute(httpUriRequest); + if (isNotSuccessfull(response)) { + throw new OrcidClientException(getStatusCode(response), formatErrorMessage(response)); + } + return objectMapper.readValue(response.getEntity().getContent(), clazz); + }); + } catch (IOException e) { + throw new RuntimeException(e); + } } /** @@ -304,44 +296,37 @@ private T executeAndParseJson(HttpUriRequest httpUriRequest, Class clazz) * @throws OrcidClientException if the incoming response is not successfull */ private T executeAndUnmarshall(HttpUriRequest httpUriRequest, boolean handleNotFoundAsNull, Class clazz) { - - HttpClient client = httpClientFactory.build(); - - return executeAndReturns(() -> { - - HttpResponse response = client.execute(httpUriRequest); - - if (handleNotFoundAsNull && isNotFound(response)) { - return null; - } - - if (isNotSuccessfull(response)) { - throw new OrcidClientException(getStatusCode(response), formatErrorMessage(response)); - } - - return unmarshall(response.getEntity(), clazz); - - }); + try (CloseableHttpClient client = DSpaceHttpClientFactory.getInstance().build()) { + return executeAndReturns(() -> { + CloseableHttpResponse response = client.execute(httpUriRequest); + if (handleNotFoundAsNull && isNotFound(response)) { + return null; + } + if (isNotSuccessfull(response)) { + throw new OrcidClientException(getStatusCode(response), formatErrorMessage(response)); + } + return unmarshall(response.getEntity(), clazz); + }); + } catch (IOException e) { + throw new RuntimeException(e); + } } private OrcidResponse execute(HttpUriRequest httpUriRequest, boolean handleNotFoundAsNull) { - HttpClient client = httpClientFactory.build(); - - return executeAndReturns(() -> { - - HttpResponse response = client.execute(httpUriRequest); - - if (handleNotFoundAsNull && isNotFound(response)) { - return new OrcidResponse(getStatusCode(response), null, getContent(response)); - } - - if (isNotSuccessfull(response)) { - throw new OrcidClientException(getStatusCode(response), formatErrorMessage(response)); - } - - return new OrcidResponse(getStatusCode(response), getPutCode(response), getContent(response)); - - }); + try (CloseableHttpClient client = DSpaceHttpClientFactory.getInstance().build()) { + return executeAndReturns(() -> { + CloseableHttpResponse response = client.execute(httpUriRequest); + if (handleNotFoundAsNull && isNotFound(response)) { + return new OrcidResponse(getStatusCode(response), null, getContent(response)); + } + if (isNotSuccessfull(response)) { + throw new OrcidClientException(getStatusCode(response), formatErrorMessage(response)); + } + return new OrcidResponse(getStatusCode(response), getPutCode(response), getContent(response)); + }); + } catch (IOException e) { + throw new RuntimeException(e); + } } private T executeAndReturns(ThrowingSupplier supplier) { From f9307b617c28575f8b3cfbc3563bf4a2764d902e Mon Sep 17 00:00:00 2001 From: Elios Buzo Date: Fri, 18 Apr 2025 15:12:55 +0200 Subject: [PATCH 544/939] [DURACOM-109] Minor fix --- .../java/org/dspace/service/impl/HttpConnectionPoolService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.java b/dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.java index 27f2ad3e412..69fea9c72c5 100644 --- a/dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.java +++ b/dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.java @@ -112,7 +112,7 @@ protected void init() { * @return the client. */ public CloseableHttpClient getClient() { - CloseableHttpClient httpClient = DSpaceHttpClientFactory.getInstance().builder(false).create() + CloseableHttpClient httpClient = DSpaceHttpClientFactory.getInstance().builder(true).create() .setKeepAliveStrategy(keepAliveStrategy) .setConnectionManager(connManager) .build(); From f4390fef52c9ae352ef2b305668c000d4c84853f Mon Sep 17 00:00:00 2001 From: Elios Buzo Date: Wed, 23 Apr 2025 12:31:11 +0200 Subject: [PATCH 545/939] [DURACOM-109] Continued fixing http connection leaks --- .../packager/AbstractMETSIngester.java | 8 +- .../ctask/general/BasicLinkChecker.java | 24 +++--- .../ctask/general/MetadataWebService.java | 85 +++++++++---------- .../ctask/general/MicrosoftTranslator.java | 26 +++--- .../export/service/OpenUrlServiceImpl.java | 16 ++-- .../service/OpenUrlServiceImplTest.java | 10 +-- 6 files changed, 82 insertions(+), 87 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java index 835b9f0e9b6..77236be9d52 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java @@ -1314,10 +1314,10 @@ protected static InputStream getFileInputStream(File pkgFile, // we will assume all external files are available via URLs. try (CloseableHttpClient httpClient = DSpaceHttpClientFactory.getInstance().build()) { // attempt to open a connection to given URL - CloseableHttpResponse httpResponse = httpClient.execute(new HttpGet(path)); - - // open stream to access file contents - return httpResponse.getEntity().getContent(); + try (CloseableHttpResponse httpResponse = httpClient.execute(new HttpGet(path))) { + // open stream to access file contents + return httpResponse.getEntity().getContent(); + } } catch (IOException io) { log .error("Unable to retrieve external file from URL '" diff --git a/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java b/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java index 189f7773388..a6621fcb2b4 100644 --- a/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java +++ b/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java @@ -141,21 +141,19 @@ protected boolean checkURL(String url, StringBuilder results) { protected int getResponseStatus(String url, int redirects) { RequestConfig config = RequestConfig.custom().setRedirectsEnabled(true).build(); try (CloseableHttpClient httpClient = DSpaceHttpClientFactory.getInstance().buildWithRequestConfig(config)) { - CloseableHttpResponse httpResponse = httpClient.execute(new HttpGet(url)); - int statusCode = httpResponse.getStatusLine().getStatusCode(); - int maxRedirect = configurationService.getIntProperty("curate.checklinks.max-redirect", 0); - if ((statusCode == HttpURLConnection.HTTP_MOVED_TEMP || statusCode == HttpURLConnection.HTTP_MOVED_PERM || - statusCode == HttpURLConnection.HTTP_SEE_OTHER)) { - httpClient.close(); - String newUrl = httpResponse.getFirstHeader("Location").getValue(); - if (newUrl != null && (maxRedirect >= redirects || maxRedirect == -1)) { - redirects++; - return getResponseStatus(newUrl, redirects); + try (CloseableHttpResponse httpResponse = httpClient.execute(new HttpGet(url))) { + int statusCode = httpResponse.getStatusLine().getStatusCode(); + int maxRedirect = configurationService.getIntProperty("curate.checklinks.max-redirect", 0); + if ((statusCode == HttpURLConnection.HTTP_MOVED_TEMP || statusCode == HttpURLConnection.HTTP_MOVED_PERM || + statusCode == HttpURLConnection.HTTP_SEE_OTHER)) { + String newUrl = httpResponse.getFirstHeader("Location").getValue(); + if (newUrl != null && (maxRedirect >= redirects || maxRedirect == -1)) { + redirects++; + return getResponseStatus(newUrl, redirects); + } } - + return statusCode; } - return statusCode; - } catch (IOException ioe) { // Must be a bad URL log.debug("Bad link: " + ioe.getMessage()); diff --git a/dspace-api/src/main/java/org/dspace/ctask/general/MetadataWebService.java b/dspace-api/src/main/java/org/dspace/ctask/general/MetadataWebService.java index da7588be6c6..7f61cad412e 100644 --- a/dspace-api/src/main/java/org/dspace/ctask/general/MetadataWebService.java +++ b/dspace-api/src/main/java/org/dspace/ctask/general/MetadataWebService.java @@ -30,8 +30,8 @@ import javax.xml.xpath.XPathFactory; import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; +import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.logging.log4j.LogManager; @@ -255,53 +255,50 @@ public int perform(DSpaceObject dso) throws IOException { } protected int callService(String value, Item item, StringBuilder resultSb) throws IOException { - String callUrl = urlTemplate.replaceAll("\\{" + templateParam + "\\}", value); - CloseableHttpClient client = DSpaceHttpClientFactory.getInstance().build(); - HttpGet req = new HttpGet(callUrl); - for (Map.Entry entry : headers.entrySet()) { - req.addHeader(entry.getKey(), entry.getValue()); - } - HttpResponse resp = client.execute(req); - int status = Curator.CURATE_ERROR; - int statusCode = resp.getStatusLine().getStatusCode(); - if (statusCode == HttpStatus.SC_OK) { - HttpEntity entity = resp.getEntity(); - if (entity != null) { - // boiler-plate handling taken from Apache 4.1 javadoc - InputStream instream = entity.getContent(); - try { - // This next line triggers a false-positive XXE warning from LGTM, even though we disallow DTD - // parsing during initialization of docBuilder in init() - Document doc = docBuilder.parse(instream); // lgtm [java/xxe] - status = processResponse(doc, item, resultSb); - } catch (SAXException saxE) { - log.error("caught exception: " + saxE); - resultSb.append(" unable to read response document"); - } catch (RuntimeException ex) { - // In case of an unexpected exception you may want to abort - // the HTTP request in order to shut down the underlying - // connection and release it back to the connection manager. - req.abort(); - log.error("caught exception: " + ex); - throw ex; - } finally { - // Closing the input stream will trigger connection release - instream.close(); + try (CloseableHttpClient client = DSpaceHttpClientFactory.getInstance().build()) { + HttpGet req = new HttpGet(callUrl); + for (Map.Entry entry : headers.entrySet()) { + req.addHeader(entry.getKey(), entry.getValue()); + } + try (CloseableHttpResponse resp = client.execute(req)) { + int status = Curator.CURATE_ERROR; + int statusCode = resp.getStatusLine().getStatusCode(); + if (statusCode == HttpStatus.SC_OK) { + HttpEntity entity = resp.getEntity(); + if (entity != null) { + // boiler-plate handling taken from Apache 4.1 javadoc + InputStream instream = entity.getContent(); + try { + // This next line triggers a false-positive XXE warning from LGTM, even though we disallow DTD + // parsing during initialization of docBuilder in init() + Document doc = docBuilder.parse(instream); // lgtm [java/xxe] + status = processResponse(doc, item, resultSb); + } catch (SAXException saxE) { + log.error("caught exception: " + saxE); + resultSb.append(" unable to read response document"); + } catch (RuntimeException ex) { + // In case of an unexpected exception you may want to abort + // the HTTP request in order to shut down the underlying + // connection and release it back to the connection manager. + req.abort(); + log.error("caught exception: " + ex); + throw ex; + } finally { + // Closing the input stream will trigger connection release + instream.close(); + } + } else { + log.error(" obtained no valid service response"); + resultSb.append("no service response"); + } + } else { + log.error("service returned non-OK status: " + statusCode); + resultSb.append("no service response"); } - // When HttpClient instance is no longer needed, - // shut down the connection manager to ensure - // immediate deallocation of all system resources - client.close(); - } else { - log.error(" obtained no valid service response"); - resultSb.append("no service response"); + return status; } - } else { - log.error("service returned non-OK status: " + statusCode); - resultSb.append("no service response"); } - return status; } protected int processResponse(Document doc, Item item, StringBuilder resultSb) throws IOException { diff --git a/dspace-api/src/main/java/org/dspace/ctask/general/MicrosoftTranslator.java b/dspace-api/src/main/java/org/dspace/ctask/general/MicrosoftTranslator.java index 0d682d9406f..af9c2de0c8b 100644 --- a/dspace-api/src/main/java/org/dspace/ctask/general/MicrosoftTranslator.java +++ b/dspace-api/src/main/java/org/dspace/ctask/general/MicrosoftTranslator.java @@ -12,7 +12,7 @@ import java.nio.charset.StandardCharsets; import org.apache.commons.io.IOUtils; -import org.apache.http.HttpResponse; +import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.logging.log4j.LogManager; @@ -62,20 +62,18 @@ protected String translateText(String from, String to, String text) throws IOExc try (CloseableHttpClient client = DSpaceHttpClientFactory.getInstance().build()) { HttpGet hm = new HttpGet(url); - HttpResponse httpResponse = client.execute(hm); - log.debug("Response code from API call is " + httpResponse); - - if (httpResponse.getStatusLine().getStatusCode() == 200) { - String response = IOUtils.toString(httpResponse.getEntity().getContent(), - StandardCharsets.ISO_8859_1); - response = response - .replaceAll("", ""); - response = response.replaceAll("", ""); - translatedText = response; + try (CloseableHttpResponse httpResponse = client.execute(hm)) { + log.debug("Response code from API call is " + httpResponse); + if (httpResponse.getStatusLine().getStatusCode() == 200) { + String response = IOUtils.toString(httpResponse.getEntity().getContent(), + StandardCharsets.ISO_8859_1); + response = response + .replaceAll("", ""); + response = response.replaceAll("", ""); + translatedText = response; + } } } - return translatedText; } -} - +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlServiceImpl.java b/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlServiceImpl.java index d8f4aa6b7e7..64014fcbc07 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlServiceImpl.java @@ -14,10 +14,10 @@ import java.util.List; import org.apache.commons.lang.StringUtils; -import org.apache.http.HttpResponse; -import org.apache.http.client.HttpClient; import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.app.client.DSpaceHttpClientFactory; @@ -68,13 +68,15 @@ public void processUrl(Context c, String urlStr) throws SQLException { * @throws IOException */ protected int getResponseCodeFromUrl(final String urlStr) throws IOException { - HttpGet httpGet = new HttpGet(urlStr); - HttpClient httpClient = getHttpClient(getHttpClientRequestConfig()); - HttpResponse httpResponse = httpClient.execute(httpGet); - return httpResponse.getStatusLine().getStatusCode(); + try (CloseableHttpClient httpClient = getHttpClient(getHttpClientRequestConfig())) { + HttpGet httpGet = new HttpGet(urlStr); + try (CloseableHttpResponse httpResponse = httpClient.execute(httpGet)) { + return httpResponse.getStatusLine().getStatusCode(); + } + } } - protected HttpClient getHttpClient(RequestConfig requestConfig) { + protected CloseableHttpClient getHttpClient(RequestConfig requestConfig) { return DSpaceHttpClientFactory.getInstance().buildWithRequestConfig(requestConfig); } diff --git a/dspace-api/src/test/java/org/dspace/statistics/export/service/OpenUrlServiceImplTest.java b/dspace-api/src/test/java/org/dspace/statistics/export/service/OpenUrlServiceImplTest.java index d214050e6b5..718ef701e13 100644 --- a/dspace-api/src/test/java/org/dspace/statistics/export/service/OpenUrlServiceImplTest.java +++ b/dspace-api/src/test/java/org/dspace/statistics/export/service/OpenUrlServiceImplTest.java @@ -27,9 +27,9 @@ import java.util.Date; import java.util.List; -import org.apache.http.HttpResponse; import org.apache.http.StatusLine; -import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.impl.client.CloseableHttpClient; import org.dspace.core.Context; import org.dspace.statistics.export.OpenURLTracker; import org.junit.Before; @@ -55,7 +55,7 @@ public class OpenUrlServiceImplTest { private FailedOpenURLTrackerService failedOpenURLTrackerService; @Mock - private HttpClient httpClient; + private CloseableHttpClient httpClient; @Before public void setUp() throws Exception { @@ -74,11 +74,11 @@ public void setUp() throws Exception { * @param statusCode the http status code to use in the mock. * @return a mocked http response. */ - protected HttpResponse createMockHttpResponse(int statusCode) { + protected CloseableHttpResponse createMockHttpResponse(int statusCode) { StatusLine statusLine = mock(StatusLine.class); when(statusLine.getStatusCode()).thenReturn(statusCode); - HttpResponse httpResponse = mock(HttpResponse.class); + CloseableHttpResponse httpResponse = mock(CloseableHttpResponse.class); when(httpResponse.getStatusLine()).thenReturn(statusLine); return httpResponse; From 7f865ad95657e9712d6ebec41a765bc9d8b6f2c7 Mon Sep 17 00:00:00 2001 From: Elios Buzo Date: Wed, 23 Apr 2025 15:34:52 +0200 Subject: [PATCH 546/939] [DURACOM-109] Linter error fix --- .../ctask/general/BasicLinkChecker.java | 21 +++++++++---------- .../ctask/general/MetadataWebService.java | 4 ++-- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java b/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java index a6621fcb2b4..02033184270 100644 --- a/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java +++ b/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java @@ -141,19 +141,18 @@ protected boolean checkURL(String url, StringBuilder results) { protected int getResponseStatus(String url, int redirects) { RequestConfig config = RequestConfig.custom().setRedirectsEnabled(true).build(); try (CloseableHttpClient httpClient = DSpaceHttpClientFactory.getInstance().buildWithRequestConfig(config)) { - try (CloseableHttpResponse httpResponse = httpClient.execute(new HttpGet(url))) { - int statusCode = httpResponse.getStatusLine().getStatusCode(); - int maxRedirect = configurationService.getIntProperty("curate.checklinks.max-redirect", 0); - if ((statusCode == HttpURLConnection.HTTP_MOVED_TEMP || statusCode == HttpURLConnection.HTTP_MOVED_PERM || - statusCode == HttpURLConnection.HTTP_SEE_OTHER)) { - String newUrl = httpResponse.getFirstHeader("Location").getValue(); - if (newUrl != null && (maxRedirect >= redirects || maxRedirect == -1)) { - redirects++; - return getResponseStatus(newUrl, redirects); - } + CloseableHttpResponse httpResponse = httpClient.execute(new HttpGet(url)); + int statusCode = httpResponse.getStatusLine().getStatusCode(); + int maxRedirect = configurationService.getIntProperty("curate.checklinks.max-redirect", 0); + if ((statusCode == HttpURLConnection.HTTP_MOVED_TEMP || statusCode == HttpURLConnection.HTTP_MOVED_PERM || + statusCode == HttpURLConnection.HTTP_SEE_OTHER)) { + String newUrl = httpResponse.getFirstHeader("Location").getValue(); + if (newUrl != null && (maxRedirect >= redirects || maxRedirect == -1)) { + redirects++; + return getResponseStatus(newUrl, redirects); } - return statusCode; } + return statusCode; } catch (IOException ioe) { // Must be a bad URL log.debug("Bad link: " + ioe.getMessage()); diff --git a/dspace-api/src/main/java/org/dspace/ctask/general/MetadataWebService.java b/dspace-api/src/main/java/org/dspace/ctask/general/MetadataWebService.java index 7f61cad412e..fc62d7a4b23 100644 --- a/dspace-api/src/main/java/org/dspace/ctask/general/MetadataWebService.java +++ b/dspace-api/src/main/java/org/dspace/ctask/general/MetadataWebService.java @@ -270,8 +270,8 @@ protected int callService(String value, Item item, StringBuilder resultSb) throw // boiler-plate handling taken from Apache 4.1 javadoc InputStream instream = entity.getContent(); try { - // This next line triggers a false-positive XXE warning from LGTM, even though we disallow DTD - // parsing during initialization of docBuilder in init() + // This next line triggers a false-positive XXE warning from LGTM, even though + // we disallow DTD parsing during initialization of docBuilder in init() Document doc = docBuilder.parse(instream); // lgtm [java/xxe] status = processResponse(doc, item, resultSb); } catch (SAXException saxE) { From 0f1679ed7283a799364ac317c8a6fcbbfc290dcf Mon Sep 17 00:00:00 2001 From: Stefano Maffei Date: Mon, 28 Apr 2025 08:51:06 +0200 Subject: [PATCH 547/939] [DURACOM-109] fix typo and correct logic for ORCID connector --- dspace-api/pom.xml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index bb246c20ad8..247d2da63c1 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -521,10 +521,6 @@ org.apache.pdfbox pdfbox - - org.apache.pdfbox - fontbox - com.ibm.icu icu4j From 9d6c482cc41a4f7923feb5ca03cd11d5552a7d64 Mon Sep 17 00:00:00 2001 From: Stefano Maffei Date: Mon, 28 Apr 2025 10:50:32 +0200 Subject: [PATCH 548/939] [DURACOM-109] Orcid connector fix and improvement --- .../provider/impl/OrcidV3AuthorDataProvider.java | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/external/provider/impl/OrcidV3AuthorDataProvider.java b/dspace-api/src/main/java/org/dspace/external/provider/impl/OrcidV3AuthorDataProvider.java index dfbd07a83a0..a9e10f92948 100644 --- a/dspace-api/src/main/java/org/dspace/external/provider/impl/OrcidV3AuthorDataProvider.java +++ b/dspace-api/src/main/java/org/dspace/external/provider/impl/OrcidV3AuthorDataProvider.java @@ -169,13 +169,7 @@ public Person getBio(String id) { } initializeAccessToken(); InputStream bioDocument = orcidRestConnector.get(id + ((id.endsWith("/person")) ? "" : "/person"), accessToken); - Person person = converter.convertSinglePerson(bioDocument); - try { - bioDocument.close(); - } catch (IOException e) { - log.error(e.getMessage(), e); - } - return person; + return converter.convertSinglePerson(bioDocument); } /** @@ -220,11 +214,6 @@ public List searchExternalDataObjects(String query, int star } } } - try { - bioDocument.close(); - } catch (IOException e) { - log.error(e.getMessage(), e); - } return bios.stream().map(bio -> convertToExternalDataObject(bio)).collect(Collectors.toList()); } From dbcaac4b0839a922e5e99f9104d617f6b650be8e Mon Sep 17 00:00:00 2001 From: Stefano Maffei Date: Tue, 29 Apr 2025 08:52:36 +0200 Subject: [PATCH 549/939] [DURACOM-109] added checkstyle rules to forbid usage of HttpClientBuilder.create() --- checkstyle-suppressions.xml | 1 + checkstyle.xml | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/checkstyle-suppressions.xml b/checkstyle-suppressions.xml index 77e27b8768a..46bd9ca80d6 100644 --- a/checkstyle-suppressions.xml +++ b/checkstyle-suppressions.xml @@ -7,4 +7,5 @@ + diff --git a/checkstyle.xml b/checkstyle.xml index a33fc483195..36d2b15bd89 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -136,5 +136,22 @@ For more information on CheckStyle configurations below, see: http://checkstyle. + + + + + + + + + + + + + + + + + From 409b775d3531357bf1ed45f02571447d504c2f33 Mon Sep 17 00:00:00 2001 From: Stefano Maffei Date: Tue, 29 Apr 2025 09:07:25 +0200 Subject: [PATCH 550/939] [DURACOM-109] fix TruncatedChunkException error --- .../main/java/org/dspace/external/OrcidRestConnector.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/external/OrcidRestConnector.java b/dspace-api/src/main/java/org/dspace/external/OrcidRestConnector.java index f2c6246f4d0..aa16af7a524 100644 --- a/dspace-api/src/main/java/org/dspace/external/OrcidRestConnector.java +++ b/dspace-api/src/main/java/org/dspace/external/OrcidRestConnector.java @@ -7,6 +7,7 @@ */ package org.dspace.external; +import java.io.ByteArrayInputStream; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.Scanner; @@ -51,7 +52,11 @@ public InputStream get(String path, String accessToken) { } try (CloseableHttpClient httpClient = DSpaceHttpClientFactory.getInstance().build()) { getResponse = httpClient.execute(httpGet); - result = getResponse.getEntity().getContent(); + try (InputStream responseStream = getResponse.getEntity().getContent()) { + // Read all the content of the response stream into a byte array to prevent TruncatedChunkException + byte[] content = responseStream.readAllBytes(); + result = new ByteArrayInputStream(content); + } } catch (Exception e) { getGotError(e, fullPath); } From 032252664b3c7997231fdb8b10f3fe3c4951cdd9 Mon Sep 17 00:00:00 2001 From: Stefano Maffei Date: Wed, 26 Feb 2025 10:53:25 +0100 Subject: [PATCH 551/939] [DURACOM-328] fix error in check for Patch request --- .../dspaceFolder/config/item-submission.xml | 12 +++++ .../config/spring/api/access-conditions.xml | 13 ++++++ .../app/rest/submit/step/UploadStep.java | 18 ++++++-- .../rest/WorkflowItemRestRepositoryIT.java | 46 +++++++++++++++++++ 4 files changed, 84 insertions(+), 5 deletions(-) diff --git a/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml b/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml index 2b4cee04491..0ffac4b49db 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml @@ -26,6 +26,7 @@ + @@ -179,6 +180,11 @@ submission + + submit.progressbar.upload-no-required-metadata + org.dspace.app.rest.submit.step.UploadStep + upload + @@ -267,6 +273,12 @@ + + + + + + diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/access-conditions.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/access-conditions.xml index a9af7c66f5e..bf02e6a23ed 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/access-conditions.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/access-conditions.xml @@ -55,6 +55,7 @@ + @@ -116,4 +117,16 @@ + + + + + + + + + + + + diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/UploadStep.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/UploadStep.java index b91916ca319..89003549f73 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/UploadStep.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/UploadStep.java @@ -10,6 +10,7 @@ import java.io.BufferedInputStream; import java.io.InputStream; import java.util.List; +import java.util.regex.Pattern; import javax.servlet.http.HttpServletRequest; import org.apache.logging.log4j.Logger; @@ -48,6 +49,13 @@ public class UploadStep extends AbstractProcessingStep public static final String UPLOAD_STEP_METADATA_SECTION = "bitstream-metadata"; + private static final Pattern UPDATE_METADATA_PATTERN = + Pattern.compile("^/sections/[^/]+/files/[^/]+/metadata/[^/]+(/[^/]+)?$"); + private static final Pattern PRIMARY_FLAG_PATTERN = + Pattern.compile("^/sections/[^/]+/primary$"); + private static final Pattern ACCESS_CONDITION_PATTERN = + Pattern.compile("^/sections/[^/]+/files/[^/]+/accessConditions(/[^/]+)?$"); + @Override public DataUpload getData(SubmissionService submissionService, InProgressSubmission obj, SubmissionStepConfig config) throws Exception { @@ -69,23 +77,23 @@ public void doPatchProcessing(Context context, HttpServletRequest currentRequest String instance = null; if ("remove".equals(op.getOp())) { - if (op.getPath().contains(UPLOAD_STEP_METADATA_PATH)) { + if (UPDATE_METADATA_PATTERN.matcher(op.getPath()).matches()) { instance = UPLOAD_STEP_METADATA_OPERATION_ENTRY; - } else if (op.getPath().contains(UPLOAD_STEP_ACCESSCONDITIONS_OPERATION_ENTRY)) { + } else if (ACCESS_CONDITION_PATTERN.matcher(op.getPath()).matches()) { instance = stepConf.getType() + "." + UPLOAD_STEP_ACCESSCONDITIONS_OPERATION_ENTRY; } else { instance = UPLOAD_STEP_REMOVE_OPERATION_ENTRY; } } else if ("move".equals(op.getOp())) { - if (op.getPath().contains(UPLOAD_STEP_METADATA_PATH)) { + if (UPDATE_METADATA_PATTERN.matcher(op.getPath()).matches()) { instance = UPLOAD_STEP_METADATA_OPERATION_ENTRY; } else { instance = UPLOAD_STEP_MOVE_OPERATION_ENTRY; } } else { - if (op.getPath().contains(UPLOAD_STEP_ACCESSCONDITIONS_OPERATION_ENTRY)) { + if (ACCESS_CONDITION_PATTERN.matcher(op.getPath()).matches()) { instance = stepConf.getType() + "." + UPLOAD_STEP_ACCESSCONDITIONS_OPERATION_ENTRY; - } else if (op.getPath().contains(UPLOAD_STEP_METADATA_PATH)) { + } else if (UPDATE_METADATA_PATTERN.matcher(op.getPath()).matches()) { instance = UPLOAD_STEP_METADATA_OPERATION_ENTRY; } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java index fe8e67bbf56..de98f1b0cea 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java @@ -9,6 +9,7 @@ import static com.jayway.jsonpath.JsonPath.read; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; @@ -2218,4 +2219,49 @@ public void testWorkflowWithHiddenSections() throws Exception { } + @Test + public void deleteBitstreamTest() + throws Exception { + context.turnOffAuthorisationSystem(); + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("submitter@example.com") + .withPassword(password) + .build(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection collection1 = CollectionBuilder.createCollection(context, parentCommunity, + "123456789/collection-test-patch") + .withName("Collection 1") + .build(); + Bitstream bitstream = null; + WorkspaceItem witem = null; + String bitstreamContent = "0123456789"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, Charset.defaultCharset())) { + context.setCurrentUser(submitter); + witem = WorkspaceItemBuilder.createWorkspaceItem(context, collection1) + .withTitle("Test WorkspaceItem") + .withIssueDate("2019-10-01") + .grantLicense() + .build(); + bitstream = BitstreamBuilder.createBitstream(context, witem.getItem(), is) + .withName("Test bitstream") + .withDescription("This is a bitstream to test range requests") + .withMimeType("text/plain") + .build(); + } + context.restoreAuthSystemState(); + String tokenSubmitter = getAuthToken(submitter.getEmail(), password); + List deleteFile = new ArrayList<>(); + deleteFile.add(new RemoveOperation("/sections/bitstream-metadata-publication/files/0/")); + getClient(tokenSubmitter).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(getPatchContent(deleteFile)) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + // verify that the patch removed bitstream + getClient(tokenSubmitter).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.bitstream-metadata-publication.files",hasSize(0))); + } + } From 0ef3b27189dab67f3176831168f8ee4fc26d0c96 Mon Sep 17 00:00:00 2001 From: Stefano Maffei Date: Wed, 26 Feb 2025 11:18:36 +0100 Subject: [PATCH 552/939] [DURACOM-328] fix test --- .../org/dspace/app/rest/WorkflowItemRestRepositoryIT.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java index de98f1b0cea..8a19678f510 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java @@ -2253,7 +2253,7 @@ public void deleteBitstreamTest() context.restoreAuthSystemState(); String tokenSubmitter = getAuthToken(submitter.getEmail(), password); List deleteFile = new ArrayList<>(); - deleteFile.add(new RemoveOperation("/sections/bitstream-metadata-publication/files/0/")); + deleteFile.add(new RemoveOperation("/sections/upload-no-required-metadata/files/0/")); getClient(tokenSubmitter).perform(patch("/api/submission/workspaceitems/" + witem.getID()) .content(getPatchContent(deleteFile)) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) @@ -2261,7 +2261,7 @@ public void deleteBitstreamTest() // verify that the patch removed bitstream getClient(tokenSubmitter).perform(get("/api/submission/workspaceitems/" + witem.getID())) .andExpect(status().isOk()) - .andExpect(jsonPath("$.sections.bitstream-metadata-publication.files",hasSize(0))); + .andExpect(jsonPath("$.sections.upload-no-required-metadata.files",hasSize(0))); } } From ac20eefe4b6554439ac54c5ffe95348ec8466a7a Mon Sep 17 00:00:00 2001 From: Stefano Maffei Date: Wed, 7 May 2025 10:43:19 +0200 Subject: [PATCH 553/939] [DURACOM-328] fix tests --- .../org/dspace/app/rest/SubmissionDefinitionsControllerIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java index 1a1a4576dc8..a4ef63afa6e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java @@ -35,7 +35,7 @@ public class SubmissionDefinitionsControllerIT extends AbstractControllerIntegra // The total number of expected submission definitions is referred to in multiple tests and assertions as // is the last page (totalDefinitions - 1) // This integer should be maintained along with any changes to item-submissions.xml - private static final int totalDefinitions = 9; + private static final int totalDefinitions = 10; @Test public void findAll() throws Exception { From 1ade96098802cd30880b75dee37cbe92cca6542c Mon Sep 17 00:00:00 2001 From: Elios Buzo Date: Wed, 7 May 2025 12:28:43 +0200 Subject: [PATCH 554/939] [DURACOM-109] Fixed conflicts --- .../org/dspace/app/sherpa/SHERPAService.java | 118 +++++++++--------- .../dspace/eperson/CaptchaServiceImpl.java | 19 ++- .../external/OpenAIRERestConnector.java | 118 +++++++++--------- .../client/GoogleAnalyticsClientImpl.java | 4 +- .../identifier/doi/DataCiteConnector.java | 4 +- .../dspace/identifier/ezid/EZIDRequest.java | 6 +- .../model/factory/OrcidFactoryUtils.java | 4 +- 7 files changed, 138 insertions(+), 135 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAService.java b/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAService.java index 87042c502d8..cb1125f61c4 100644 --- a/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAService.java +++ b/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAService.java @@ -117,46 +117,47 @@ public SHERPAPublisherResponse performPublisherRequest(String type, String field timeout, sleepBetweenTimeouts)); - try (CloseableHttpClient client = DSpaceHttpClientFactory.getInstance().build()) { + try (CloseableHttpClient client = DSpaceHttpClientFactory.getInstance().buildWithoutAutomaticRetries(5)) { Thread.sleep(sleepBetweenTimeouts); // Construct a default HTTP method (first result) method = constructHttpGet(type, field, predicate, value, start, limit); // Execute the method - CloseableHttpResponse response = client.execute(method); - int statusCode = response.getStatusLine().getStatusCode(); + try (CloseableHttpResponse response = client.execute(method)) { + int statusCode = response.getStatusLine().getStatusCode(); - log.debug(response.getStatusLine().getStatusCode() + ": " - + response.getStatusLine().getReasonPhrase()); + log.debug(response.getStatusLine().getStatusCode() + ": " + + response.getStatusLine().getReasonPhrase()); - if (statusCode != HttpStatus.SC_OK) { - sherpaResponse = new SHERPAPublisherResponse("SHERPA/RoMEO return not OK status: " - + statusCode); - String errorBody = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); - log.error("Error from SHERPA HTTP request: " + errorBody); - } + if (statusCode != HttpStatus.SC_OK) { + sherpaResponse = new SHERPAPublisherResponse("SHERPA/RoMEO return not OK status: " + + statusCode); + String errorBody = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + log.error("Error from SHERPA HTTP request: " + errorBody); + } - HttpEntity responseBody = response.getEntity(); - - // If the response body is valid, pass to SHERPAResponse for parsing as JSON - if (null != responseBody) { - log.debug("Non-null SHERPA resonse received for query of " + value); - InputStream content = null; - try { - content = responseBody.getContent(); - sherpaResponse = - new SHERPAPublisherResponse(content, SHERPAPublisherResponse.SHERPAFormat.JSON); - } catch (IOException e) { - log.error("Encountered exception while contacting SHERPA/RoMEO: " + e.getMessage(), e); - } finally { - if (content != null) { - content.close(); + HttpEntity responseBody = response.getEntity(); + + // If the response body is valid, pass to SHERPAResponse for parsing as JSON + if (null != responseBody) { + log.debug("Non-null SHERPA response received for query of " + value); + InputStream content = null; + try { + content = responseBody.getContent(); + sherpaResponse = + new SHERPAPublisherResponse(content, SHERPAPublisherResponse.SHERPAFormat.JSON); + } catch (IOException e) { + log.error("Encountered exception while contacting SHERPA/RoMEO: " + e.getMessage(), e); + } finally { + if (content != null) { + content.close(); + } } + } else { + log.debug("Empty SHERPA response body for query on " + value); + sherpaResponse = new SHERPAPublisherResponse("SHERPA/RoMEO returned no response"); } - } else { - log.debug("Empty SHERPA response body for query on " + value); - sherpaResponse = new SHERPAPublisherResponse("SHERPA/RoMEO returned no response"); } } catch (URISyntaxException e) { String errorMessage = "Error building SHERPA v2 API URI: " + e.getMessage(); @@ -220,45 +221,46 @@ public SHERPAResponse performRequest(String type, String field, String predicate timeout, sleepBetweenTimeouts)); - try (CloseableHttpClient client = DSpaceHttpClientFactory.getInstance().build()) { + try (CloseableHttpClient client = DSpaceHttpClientFactory.getInstance().buildWithoutAutomaticRetries(5)) { Thread.sleep(sleepBetweenTimeouts); // Construct a default HTTP method (first result) method = constructHttpGet(type, field, predicate, value, start, limit); // Execute the method - CloseableHttpResponse response = client.execute(method); - int statusCode = response.getStatusLine().getStatusCode(); + try (CloseableHttpResponse response = client.execute(method)) { + int statusCode = response.getStatusLine().getStatusCode(); - log.debug(response.getStatusLine().getStatusCode() + ": " - + response.getStatusLine().getReasonPhrase()); + log.debug(response.getStatusLine().getStatusCode() + ": " + + response.getStatusLine().getReasonPhrase()); - if (statusCode != HttpStatus.SC_OK) { - sherpaResponse = new SHERPAResponse("SHERPA/RoMEO return not OK status: " - + statusCode); - String errorBody = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); - log.error("Error from SHERPA HTTP request: " + errorBody); - } + if (statusCode != HttpStatus.SC_OK) { + sherpaResponse = new SHERPAResponse("SHERPA/RoMEO return not OK status: " + + statusCode); + String errorBody = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + log.error("Error from SHERPA HTTP request: " + errorBody); + } - HttpEntity responseBody = response.getEntity(); - - // If the response body is valid, pass to SHERPAResponse for parsing as JSON - if (null != responseBody) { - log.debug("Non-null SHERPA resonse received for query of " + value); - InputStream content = null; - try { - content = responseBody.getContent(); - sherpaResponse = new SHERPAResponse(content, SHERPAResponse.SHERPAFormat.JSON); - } catch (IOException e) { - log.error("Encountered exception while contacting SHERPA/RoMEO: " + e.getMessage(), e); - } finally { - if (content != null) { - content.close(); + HttpEntity responseBody = response.getEntity(); + + // If the response body is valid, pass to SHERPAResponse for parsing as JSON + if (null != responseBody) { + log.debug("Non-null SHERPA response received for query of " + value); + InputStream content = null; + try { + content = responseBody.getContent(); + sherpaResponse = new SHERPAResponse(content, SHERPAResponse.SHERPAFormat.JSON); + } catch (IOException e) { + log.error("Encountered exception while contacting SHERPA/RoMEO: " + e.getMessage(), e); + } finally { + if (content != null) { + content.close(); + } } + } else { + log.debug("Empty SHERPA response body for query on " + value); + sherpaResponse = new SHERPAResponse("SHERPA/RoMEO returned no response"); } - } else { - log.debug("Empty SHERPA response body for query on " + value); - sherpaResponse = new SHERPAResponse("SHERPA/RoMEO returned no response"); } } catch (URISyntaxException e) { String errorMessage = "Error building SHERPA v2 API URI: " + e.getMessage(); @@ -268,7 +270,7 @@ public SHERPAResponse performRequest(String type, String field, String predicate String errorMessage = "Encountered exception while contacting SHERPA/RoMEO: " + e.getMessage(); log.error(errorMessage, e); sherpaResponse = new SHERPAResponse(errorMessage); - } catch (InterruptedException e) { + } catch (InterruptedException e) { String errorMessage = "Encountered exception while sleeping thread: " + e.getMessage(); log.error(errorMessage, e); sherpaResponse = new SHERPAResponse(errorMessage); diff --git a/dspace-api/src/main/java/org/dspace/eperson/CaptchaServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/CaptchaServiceImpl.java index e10de57e47a..3e967d59b6f 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/CaptchaServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/CaptchaServiceImpl.java @@ -17,11 +17,11 @@ import javax.annotation.PostConstruct; import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; -import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; +import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.message.BasicNameValuePair; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -82,18 +82,17 @@ public void processResponse(String response, String action) throws InvalidReCapt throw new RuntimeException(e.getMessage(), e); } - HttpClient httpClient = DSpaceHttpClientFactory.getInstance().build(); - HttpResponse httpResponse; - GoogleCaptchaResponse googleResponse; - final ObjectMapper objectMapper = new ObjectMapper(); - try { - httpResponse = httpClient.execute(httpPost); - googleResponse = objectMapper.readValue(httpResponse.getEntity().getContent(), GoogleCaptchaResponse.class); + try (CloseableHttpClient httpClient = DSpaceHttpClientFactory.getInstance().build()) { + final ObjectMapper objectMapper = new ObjectMapper(); + try (CloseableHttpResponse httpResponse = httpClient.execute(httpPost)) { + GoogleCaptchaResponse googleResponse = objectMapper.readValue(httpResponse.getEntity().getContent(), + GoogleCaptchaResponse.class); + validateGoogleResponse(googleResponse, action); + } } catch (IOException e) { log.error(e.getMessage(), e); throw new RuntimeException("Error during verify google recaptcha site", e); } - validateGoogleResponse(googleResponse, action); } private boolean responseSanityCheck(String response) { diff --git a/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java b/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java index 06a4b836143..8b5fb1e523c 100644 --- a/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java +++ b/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java @@ -28,10 +28,10 @@ import org.apache.http.NameValuePair; import org.apache.http.NoHttpResponseException; import org.apache.http.StatusLine; -import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; +import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.message.BasicNameValuePair; import org.apache.logging.log4j.Logger; import org.dspace.app.client.DSpaceHttpClientFactory; @@ -121,33 +121,34 @@ public OpenAIRERestToken grabNewAccessToken() throws IOException { params.add(new BasicNameValuePair("grant_type", "client_credentials")); httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8")); - HttpClient httpClient = DSpaceHttpClientFactory.getInstance().build(); - HttpResponse getResponse = httpClient.execute(httpPost); - - JSONObject responseObject = null; - try (InputStream is = getResponse.getEntity().getContent(); - BufferedReader streamReader = new BufferedReader(new InputStreamReader(is, "UTF-8"))) { - String inputStr; - // verify if we have basic json - while ((inputStr = streamReader.readLine()) != null && responseObject == null) { - if (inputStr.startsWith("{") && inputStr.endsWith("}") && inputStr.contains("access_token") - && inputStr.contains("expires_in")) { - try { - responseObject = new JSONObject(inputStr); - } catch (Exception e) { - // Not as valid as I'd hoped, move along - responseObject = null; + try (CloseableHttpClient httpClient = DSpaceHttpClientFactory.getInstance().build()) { + HttpResponse getResponse = httpClient.execute(httpPost); + + JSONObject responseObject = null; + try (InputStream is = getResponse.getEntity().getContent(); + BufferedReader streamReader = new BufferedReader(new InputStreamReader(is, "UTF-8"))) { + String inputStr; + // verify if we have basic json + while ((inputStr = streamReader.readLine()) != null && responseObject == null) { + if (inputStr.startsWith("{") && inputStr.endsWith("}") && inputStr.contains("access_token") + && inputStr.contains("expires_in")) { + try { + responseObject = new JSONObject(inputStr); + } catch (Exception e) { + // Not as valid as I'd hoped, move along + responseObject = null; + } } } } - } - if (responseObject == null || !responseObject.has("access_token") || !responseObject.has("expires_in")) { - throw new IOException("Unable to grab the access token using provided service url, client id and secret"); - } - - return new OpenAIRERestToken(responseObject.get("access_token").toString(), - Long.valueOf(responseObject.get("expires_in").toString())); + if (responseObject == null || !responseObject.has("access_token") || !responseObject.has("expires_in")) { + throw new IOException("Unable to grab the access token using provided service url, " + + "client id and secret"); + } + return new OpenAIRERestToken(responseObject.get("access_token").toString(), + Long.valueOf(responseObject.get("expires_in").toString())); + } } /** @@ -172,42 +173,43 @@ public InputStream get(String file, String accessToken) { httpGet.addHeader("Authorization", "Bearer " + accessToken); } - HttpClient httpClient = DSpaceHttpClientFactory.getInstance().build(); - getResponse = httpClient.execute(httpGet); - - StatusLine status = getResponse.getStatusLine(); - - // registering errors - switch (status.getStatusCode()) { - case HttpStatus.SC_NOT_FOUND: - // 404 - Not found - case HttpStatus.SC_FORBIDDEN: - // 403 - Invalid Access Token - case 429: - // 429 - Rate limit abuse for unauthenticated user - Header[] limitUsed = getResponse.getHeaders("x-ratelimit-used"); - Header[] limitMax = getResponse.getHeaders("x-ratelimit-limit"); - - if (limitUsed.length > 0) { - String limitMsg = limitUsed[0].getValue(); - if (limitMax.length > 0) { - limitMsg = limitMsg.concat(" of " + limitMax[0].getValue()); + try (CloseableHttpClient httpClient = DSpaceHttpClientFactory.getInstance().build()) { + getResponse = httpClient.execute(httpGet); + + StatusLine status = getResponse.getStatusLine(); + + // registering errors + switch (status.getStatusCode()) { + case HttpStatus.SC_NOT_FOUND: + // 404 - Not found + case HttpStatus.SC_FORBIDDEN: + // 403 - Invalid Access Token + case 429: + // 429 - Rate limit abuse for unauthenticated user + Header[] limitUsed = getResponse.getHeaders("x-ratelimit-used"); + Header[] limitMax = getResponse.getHeaders("x-ratelimit-limit"); + + if (limitUsed.length > 0) { + String limitMsg = limitUsed[0].getValue(); + if (limitMax.length > 0) { + limitMsg = limitMsg.concat(" of " + limitMax[0].getValue()); + } + getGotError(new NoHttpResponseException(status.getReasonPhrase() + " with usage limit " + + limitMsg), + url + '/' + file); + } else { + // 429 - Rate limit abuse + getGotError(new NoHttpResponseException(status.getReasonPhrase()), url + '/' + file); } - getGotError( - new NoHttpResponseException(status.getReasonPhrase() + " with usage limit " + limitMsg), - url + '/' + file); - } else { - // 429 - Rate limit abuse - getGotError(new NoHttpResponseException(status.getReasonPhrase()), url + '/' + file); - } - break; - default: - // 200 or other - break; - } + break; + default: + // 200 or other + break; + } - // do not close this httpClient - result = getResponse.getEntity().getContent(); + // do not close this httpClient + result = getResponse.getEntity().getContent(); + } } catch (MalformedURLException e1) { getGotError(e1, url + '/' + file); } catch (Exception e) { diff --git a/dspace-api/src/main/java/org/dspace/google/client/GoogleAnalyticsClientImpl.java b/dspace-api/src/main/java/org/dspace/google/client/GoogleAnalyticsClientImpl.java index b5ee1806cd5..6fa748cfd45 100644 --- a/dspace-api/src/main/java/org/dspace/google/client/GoogleAnalyticsClientImpl.java +++ b/dspace-api/src/main/java/org/dspace/google/client/GoogleAnalyticsClientImpl.java @@ -18,7 +18,7 @@ import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClients; +import org.dspace.app.client.DSpaceHttpClientFactory; import org.dspace.google.GoogleAnalyticsEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,7 +42,7 @@ public class GoogleAnalyticsClientImpl implements GoogleAnalyticsClient { public GoogleAnalyticsClientImpl(String keyPrefix, GoogleAnalyticsClientRequestBuilder requestBuilder) { this.keyPrefix = keyPrefix; this.requestBuilder = requestBuilder; - this.httpclient = HttpClients.createDefault(); + this.httpclient = DSpaceHttpClientFactory.getInstance().build(); } @Override diff --git a/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java b/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java index 5bb37add2d9..86c7b8322e8 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java +++ b/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java @@ -36,8 +36,8 @@ import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.util.EntityUtils; +import org.dspace.app.client.DSpaceHttpClientFactory; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; import org.dspace.content.crosswalk.CrosswalkException; @@ -722,7 +722,7 @@ protected DataCiteResponse sendHttpRequest(HttpUriRequest req, String doi) httpContext.setCredentialsProvider(credentialsProvider); HttpEntity entity = null; - try ( CloseableHttpClient httpclient = HttpClientBuilder.create().build(); ) { + try (CloseableHttpClient httpclient = DSpaceHttpClientFactory.getInstance().build()) { HttpResponse response = httpclient.execute(req, httpContext); StatusLine status = response.getStatusLine(); diff --git a/dspace-api/src/main/java/org/dspace/identifier/ezid/EZIDRequest.java b/dspace-api/src/main/java/org/dspace/identifier/ezid/EZIDRequest.java index 525ad46b255..8da40470d6a 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/ezid/EZIDRequest.java +++ b/dspace-api/src/main/java/org/dspace/identifier/ezid/EZIDRequest.java @@ -26,7 +26,7 @@ import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; +import org.dspace.app.client.DSpaceHttpClientFactory; import org.dspace.identifier.DOI; import org.dspace.identifier.IdentifierException; import org.slf4j.Logger; @@ -87,7 +87,7 @@ public class EZIDRequest { this.authority = authority; } - client = HttpClientBuilder.create().build(); + client = DSpaceHttpClientFactory.getInstance().build(); httpContext = HttpClientContext.create(); if (null != username) { URI uri = new URI(scheme, host, path, null); @@ -124,7 +124,7 @@ public class EZIDRequest { this.authority = authority; } - client = HttpClientBuilder.create().build(); + client = DSpaceHttpClientFactory.getInstance().build(); httpContext = HttpClientContext.create(); if (null != username) { CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); diff --git a/dspace-api/src/main/java/org/dspace/orcid/model/factory/OrcidFactoryUtils.java b/dspace-api/src/main/java/org/dspace/orcid/model/factory/OrcidFactoryUtils.java index 38aa611ff3a..ce68ab47c26 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/model/factory/OrcidFactoryUtils.java +++ b/dspace-api/src/main/java/org/dspace/orcid/model/factory/OrcidFactoryUtils.java @@ -20,7 +20,7 @@ import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; +import org.dspace.app.client.DSpaceHttpClientFactory; import org.json.JSONObject; /** @@ -97,7 +97,7 @@ public static Optional retrieveAccessToken(String clientId, String clien httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded"); HttpResponse response; - try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { + try (CloseableHttpClient httpClient = DSpaceHttpClientFactory.getInstance().build()) { response = httpClient.execute(httpPost); } JSONObject responseObject = null; From 2c6f02f74c64603c830eaf510d43883147959e30 Mon Sep 17 00:00:00 2001 From: Stefano Maffei Date: Fri, 9 May 2025 15:59:50 +0200 Subject: [PATCH 555/939] [DURACOM-109] fix missing dependency --- dspace-api/pom.xml | 10 ++++++++++ .../org/dspace/google/GoogleRecorderEventListener.java | 4 ++-- pom.xml | 7 ++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 6898bc13fbb..44bbbeee192 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -521,6 +521,10 @@ org.apache.pdfbox pdfbox + + org.apache.pdfbox + fontbox + com.ibm.icu icu4j @@ -937,6 +941,12 @@ 2.13.16 test + + com.squareup.okhttp3 + mockwebserver + 4.12.0 + test + diff --git a/dspace-api/src/main/java/org/dspace/google/GoogleRecorderEventListener.java b/dspace-api/src/main/java/org/dspace/google/GoogleRecorderEventListener.java index 4159661b1ce..90248321e5e 100644 --- a/dspace-api/src/main/java/org/dspace/google/GoogleRecorderEventListener.java +++ b/dspace-api/src/main/java/org/dspace/google/GoogleRecorderEventListener.java @@ -21,9 +21,9 @@ import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicNameValuePair; import org.apache.logging.log4j.Logger; +import org.dspace.app.client.DSpaceHttpClientFactory; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.core.Constants; import org.dspace.service.ClientInfoService; @@ -56,7 +56,7 @@ public class GoogleRecorderEventListener extends AbstractUsageEventListener { public GoogleRecorderEventListener() { // httpclient is threadsafe so we only need one. - httpclient = HttpClients.createDefault(); + httpclient = DSpaceHttpClientFactory.getInstance().build(); } @Autowired diff --git a/pom.xml b/pom.xml index 107318fccb1..132b3bb4e1f 100644 --- a/pom.xml +++ b/pom.xml @@ -1754,7 +1754,12 @@ google-http-client-gson 1.47.0 - + + com.squareup.okhttp3 + mockwebserver + 4.12.0 + test + com.google.code.findbugs From 53e41479107aefffbf571e6dceb52834f8f4c674 Mon Sep 17 00:00:00 2001 From: Stefano Maffei Date: Fri, 9 May 2025 16:12:50 +0200 Subject: [PATCH 556/939] [DURACOM-109] fix dependency error --- dspace-api/pom.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 44bbbeee192..5f8943bcdb9 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -871,6 +871,12 @@ + + + com.squareup.okhttp3 + mockwebserver + test + @@ -941,12 +947,6 @@ 2.13.16 test - - com.squareup.okhttp3 - mockwebserver - 4.12.0 - test - From 086a26d3b489fad3026f0f3cbd64681558965163 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 9 May 2025 12:32:19 -0500 Subject: [PATCH 557/939] Potential fix for code scanning alert no. 3549: Arbitrary file access during archive extraction ("Zip Slip") Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> (cherry picked from commit 5fbdfc218f0c4da45fc430f5ee50c3813e7bb851) --- .../org/dspace/sword2/SimpleZipContentIngester.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/dspace-swordv2/src/main/java/org/dspace/sword2/SimpleZipContentIngester.java b/dspace-swordv2/src/main/java/org/dspace/sword2/SimpleZipContentIngester.java index bd8301617ba..7899bf861b3 100644 --- a/dspace-swordv2/src/main/java/org/dspace/sword2/SimpleZipContentIngester.java +++ b/dspace-swordv2/src/main/java/org/dspace/sword2/SimpleZipContentIngester.java @@ -138,12 +138,18 @@ private List unzipToBundle(Context context, File depositFile, Enumeration zenum = zip.entries(); while (zenum.hasMoreElements()) { ZipEntry entry = (ZipEntry) zenum.nextElement(); + String entryName = entry.getName(); + java.nio.file.Path entryPath = java.nio.file.Paths.get(entryName).normalize(); + if (entryPath.isAbsolute() || entryPath.startsWith("..")) { + throw new SwordError(UriRegistry.ERROR_BAD_REQUEST, "Invalid zip entry: " + entryName); + } + InputStream stream = zip.getInputStream(entry); Bitstream bs = bitstreamService.create(context, target, stream); BitstreamFormat format = this - .getFormat(context, entry.getName()); + .getFormat(context, entryName); bs.setFormat(context, format); - bs.setName(context, entry.getName()); + bs.setName(context, entryName); bitstreamService.update(context, bs); derivedResources.add(bs); } From 42e979a021191a545df073099c358c69293bcfea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 22:48:26 +0000 Subject: [PATCH 558/939] Bump the test-tools group with 6 updates Bumps the test-tools group with 6 updates: | Package | From | To | | --- | --- | --- | | [io.netty:netty-buffer](https://github.com/netty/netty) | `4.2.0.Final` | `4.2.1.Final` | | [io.netty:netty-transport](https://github.com/netty/netty) | `4.2.0.Final` | `4.2.1.Final` | | [io.netty:netty-transport-native-unix-common](https://github.com/netty/netty) | `4.2.0.Final` | `4.2.1.Final` | | [io.netty:netty-common](https://github.com/netty/netty) | `4.2.0.Final` | `4.2.1.Final` | | [io.netty:netty-handler](https://github.com/netty/netty) | `4.2.0.Final` | `4.2.1.Final` | | [io.netty:netty-codec](https://github.com/netty/netty) | `4.2.0.Final` | `4.2.1.Final` | Updates `io.netty:netty-buffer` from 4.2.0.Final to 4.2.1.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.0.Final...netty-4.2.1.Final) Updates `io.netty:netty-transport` from 4.2.0.Final to 4.2.1.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.0.Final...netty-4.2.1.Final) Updates `io.netty:netty-transport-native-unix-common` from 4.2.0.Final to 4.2.1.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.0.Final...netty-4.2.1.Final) Updates `io.netty:netty-common` from 4.2.0.Final to 4.2.1.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.0.Final...netty-4.2.1.Final) Updates `io.netty:netty-handler` from 4.2.0.Final to 4.2.1.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.0.Final...netty-4.2.1.Final) Updates `io.netty:netty-codec` from 4.2.0.Final to 4.2.1.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.0.Final...netty-4.2.1.Final) --- updated-dependencies: - dependency-name: io.netty:netty-buffer dependency-version: 4.2.1.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-transport dependency-version: 4.2.1.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-transport-native-unix-common dependency-version: 4.2.1.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-common dependency-version: 4.2.1.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-handler dependency-version: 4.2.1.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-codec dependency-version: 4.2.1.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 5f8943bcdb9..05cb803cdb8 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -888,32 +888,32 @@ io.netty netty-buffer - 4.2.0.Final + 4.2.1.Final io.netty netty-transport - 4.2.0.Final + 4.2.1.Final io.netty netty-transport-native-unix-common - 4.2.0.Final + 4.2.1.Final io.netty netty-common - 4.2.0.Final + 4.2.1.Final io.netty netty-handler - 4.2.0.Final + 4.2.1.Final io.netty netty-codec - 4.2.0.Final + 4.2.1.Final org.apache.velocity From 5765bff79ce9e8f35bd23ea9db761160b50882aa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 22:49:31 +0000 Subject: [PATCH 559/939] Bump org.webjars.npm:json-editor__json-editor in the webjars group Bumps the webjars group with 1 update: [org.webjars.npm:json-editor__json-editor](https://github.com/json-editor/json-editor). Updates `org.webjars.npm:json-editor__json-editor` from 2.15.1 to 2.15.2 - [Changelog](https://github.com/json-editor/json-editor/blob/master/CHANGELOG.md) - [Commits](https://github.com/json-editor/json-editor/compare/2.15.1...2.15.2) --- updated-dependencies: - dependency-name: org.webjars.npm:json-editor__json-editor dependency-version: 2.15.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: webjars ... Signed-off-by: dependabot[bot] --- dspace-server-webapp/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 52105803889..15ee58e6e1b 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -364,7 +364,7 @@ org.webjars.npm json-editor__json-editor - 2.15.1 + 2.15.2 ${basedir}/.. - 3.4.0 + 3.4.1 5.87.0.RELEASE From a2a68383904566c186bf6374ad86ed295d8dd896 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 22:15:28 +0000 Subject: [PATCH 569/939] Bump org.xmlunit:xmlunit-core from 2.10.0 to 2.10.2 Bumps [org.xmlunit:xmlunit-core](https://github.com/xmlunit/xmlunit) from 2.10.0 to 2.10.2. - [Release notes](https://github.com/xmlunit/xmlunit/releases) - [Changelog](https://github.com/xmlunit/xmlunit/blob/main/RELEASE_NOTES.md) - [Commits](https://github.com/xmlunit/xmlunit/compare/v2.10.0...v2.10.2) --- updated-dependencies: - dependency-name: org.xmlunit:xmlunit-core dependency-version: 2.10.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 05cb803cdb8..58ac06c44f7 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -923,7 +923,7 @@ org.xmlunit xmlunit-core - 2.10.0 + 2.10.2 test From 3fef856ef06a85befb99003b24fad030db17e38a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 May 2025 16:25:45 +0000 Subject: [PATCH 570/939] Bump commons-beanutils:commons-beanutils in the apache-commons group Bumps the apache-commons group with 1 update: commons-beanutils:commons-beanutils. Updates `commons-beanutils:commons-beanutils` from 1.10.1 to 1.11.0 --- updated-dependencies: - dependency-name: commons-beanutils:commons-beanutils dependency-version: 1.11.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: apache-commons ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 132b3bb4e1f..a6c30c0e57f 100644 --- a/pom.xml +++ b/pom.xml @@ -1470,7 +1470,7 @@ commons-beanutils commons-beanutils - 1.10.1 + 1.11.0 commons-cli From b7960bd42e726f4607b4c41cc3e839d471e30f7e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 May 2025 16:26:54 +0000 Subject: [PATCH 571/939] Bump io.grpc:grpc-context from 1.72.0 to 1.73.0 Bumps [io.grpc:grpc-context](https://github.com/grpc/grpc-java) from 1.72.0 to 1.73.0. - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.72.0...v1.73.0) --- updated-dependencies: - dependency-name: io.grpc:grpc-context dependency-version: 1.73.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 132b3bb4e1f..86fec0602c8 100644 --- a/pom.xml +++ b/pom.xml @@ -1725,7 +1725,7 @@ io.grpc grpc-context - 1.72.0 + 1.73.0 com.google.http-client From 4367eebef221d475733160b654d8afbcafaaf285 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 May 2025 16:28:25 +0000 Subject: [PATCH 572/939] Bump org.postgresql:postgresql from 42.7.5 to 42.7.6 Bumps [org.postgresql:postgresql](https://github.com/pgjdbc/pgjdbc) from 42.7.5 to 42.7.6. - [Release notes](https://github.com/pgjdbc/pgjdbc/releases) - [Changelog](https://github.com/pgjdbc/pgjdbc/blob/master/CHANGELOG.md) - [Commits](https://github.com/pgjdbc/pgjdbc/compare/REL42.7.5...REL42.7.6) --- updated-dependencies: - dependency-name: org.postgresql:postgresql dependency-version: 42.7.6 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 132b3bb4e1f..59b126b1095 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ 5.7.14 5.6.15.Final 6.2.5.Final - 42.7.5 + 42.7.6 8.11.4 3.10.8 From 32bd615ba8dee1ae433799e75d2dea70607d6700 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 02:30:57 +0000 Subject: [PATCH 573/939] Bump com.opencsv:opencsv from 5.11 to 5.11.1 Bumps com.opencsv:opencsv from 5.11 to 5.11.1. --- updated-dependencies: - dependency-name: com.opencsv:opencsv dependency-version: 5.11.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 58ac06c44f7..f89aee3074a 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -805,7 +805,7 @@ com.opencsv opencsv - 5.11 + 5.11.1 From 19e22c10f1a4ad2b2de4d90b97485ed97b826ff0 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 2 Jun 2025 09:16:27 -0500 Subject: [PATCH 574/939] Alphabetize importers by service name --- .../config/spring/api/external-services.xml | 196 ++++++++++-------- 1 file changed, 105 insertions(+), 91 deletions(-) diff --git a/dspace/config/spring/api/external-services.xml b/dspace/config/spring/api/external-services.xml index 6d7d50c39f1..4792a4fd858 100644 --- a/dspace/config/spring/api/external-services.xml +++ b/dspace/config/spring/api/external-services.xml @@ -7,72 +7,62 @@ - - - - - - - - - + + + + + - Journal + Publication + none - - - - - - - - - + + + + + + - Journal + Publication + none - - - - - - - - - + + + + + + - OrgUnit + Publication + none - - - - - - - + + + + + + - Person + Publication + none - - - - - - - + + + + @@ -82,9 +72,10 @@ - - - + + + + @@ -93,7 +84,8 @@ - + + @@ -104,21 +96,28 @@ - - - - + + + + + + + - Publication - none + Person - - - + + + + + + + + @@ -140,9 +139,10 @@ - - - + + + + @@ -152,9 +152,10 @@ - - - + + + + @@ -164,45 +165,57 @@ - - - - + + + + + + + + + + - Publication - none + Journal - - - - - + + + + + + + + + - Publication - none + Journal - - - - - + + + + + + + + + - Publication - none + OrgUnit - - - + + + + @@ -212,10 +225,11 @@ - - - - + + + + + Publication From 25ad6039dcfe93e7fce3fb6ebe029650ce0e43e3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Jun 2025 14:25:05 +0000 Subject: [PATCH 575/939] Bump org.apache.maven.plugins:maven-clean-plugin Bumps the build-tools group with 1 update: [org.apache.maven.plugins:maven-clean-plugin](https://github.com/apache/maven-clean-plugin). Updates `org.apache.maven.plugins:maven-clean-plugin` from 3.4.1 to 3.5.0 - [Release notes](https://github.com/apache/maven-clean-plugin/releases) - [Commits](https://github.com/apache/maven-clean-plugin/compare/maven-clean-plugin-3.4.1...maven-clean-plugin-3.5.0) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-clean-plugin dependency-version: 3.5.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fc0df5c2de2..af2c99bb775 100644 --- a/pom.xml +++ b/pom.xml @@ -321,7 +321,7 @@ maven-clean-plugin - 3.4.1 + 3.5.0 From 21c56aeda7fa07c35b97510bc963f0cd05f2395e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Jun 2025 14:25:16 +0000 Subject: [PATCH 576/939] Bump bouncycastle.version from 1.80 to 1.81 Bumps `bouncycastle.version` from 1.80 to 1.81. Updates `org.bouncycastle:bcpkix-jdk18on` from 1.80 to 1.81 - [Changelog](https://github.com/bcgit/bc-java/blob/main/docs/releasenotes.html) - [Commits](https://github.com/bcgit/bc-java/commits) Updates `org.bouncycastle:bcprov-jdk18on` from 1.80 to 1.81 - [Changelog](https://github.com/bcgit/bc-java/blob/main/docs/releasenotes.html) - [Commits](https://github.com/bcgit/bc-java/commits) Updates `org.bouncycastle:bcutil-jdk18on` from 1.80 to 1.81 - [Changelog](https://github.com/bcgit/bc-java/blob/main/docs/releasenotes.html) - [Commits](https://github.com/bcgit/bc-java/commits) --- updated-dependencies: - dependency-name: org.bouncycastle:bcpkix-jdk18on dependency-version: '1.81' dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.bouncycastle:bcprov-jdk18on dependency-version: '1.81' dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.bouncycastle:bcutil-jdk18on dependency-version: '1.81' dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fc0df5c2de2..ad009b631e1 100644 --- a/pom.xml +++ b/pom.xml @@ -44,7 +44,7 @@ 1.7.36 2.9.4 - 1.80 + 1.81 From a6cc912e6230f2bfa8d4e06a4f8794d526d5e191 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Jun 2025 14:25:57 +0000 Subject: [PATCH 577/939] Bump com.amazonaws:aws-java-sdk-s3 from 1.12.783 to 1.12.785 Bumps [com.amazonaws:aws-java-sdk-s3](https://github.com/aws/aws-sdk-java) from 1.12.783 to 1.12.785. - [Changelog](https://github.com/aws/aws-sdk-java/blob/master/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-java/compare/1.12.783...1.12.785) --- updated-dependencies: - dependency-name: com.amazonaws:aws-java-sdk-s3 dependency-version: 1.12.785 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index f89aee3074a..6d5ff4306bc 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -756,7 +756,7 @@ com.amazonaws aws-java-sdk-s3 - 1.12.783 + 1.12.785 From 87ce9fd136c0c5293872af6eb3009d9d15b7ec33 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Wed, 28 May 2025 17:27:07 +0200 Subject: [PATCH 578/939] Add HTTP timeouts to improve robustness (cherry picked from commit cabf5a7a44b29cd022a4c78690818bce7e5e704c) --- .../liveimportclient/service/LiveImportClientImpl.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/liveimportclient/service/LiveImportClientImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/liveimportclient/service/LiveImportClientImpl.java index 84475b62c07..987f2fde34d 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/liveimportclient/service/LiveImportClientImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/liveimportclient/service/LiveImportClientImpl.java @@ -50,7 +50,11 @@ public class LiveImportClientImpl implements LiveImportClient { @Override public String executeHttpGetRequest(int timeout, String URL, Map> params) { HttpGet method = null; - RequestConfig config = RequestConfig.custom().setConnectionRequestTimeout(timeout).build(); + RequestConfig config = RequestConfig.custom() + .setConnectionRequestTimeout(timeout) + .setConnectTimeout(timeout) + .setSocketTimeout(timeout) + .build(); try (CloseableHttpClient httpClient = Optional.ofNullable(this.httpClient) .orElse(DSpaceHttpClientFactory.getInstance().buildWithRequestConfig(config))) { String uri = buildUrl(URL, params.get(URI_PARAMETERS)); From 0bc41bfaaf5285c5ba65f0a201f5007777cfe706 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 03:07:54 +0000 Subject: [PATCH 579/939] Bump the test-tools group with 6 updates Bumps the test-tools group with 6 updates: | Package | From | To | | --- | --- | --- | | [io.netty:netty-buffer](https://github.com/netty/netty) | `4.2.1.Final` | `4.2.2.Final` | | [io.netty:netty-transport](https://github.com/netty/netty) | `4.2.1.Final` | `4.2.2.Final` | | [io.netty:netty-transport-native-unix-common](https://github.com/netty/netty) | `4.2.1.Final` | `4.2.2.Final` | | [io.netty:netty-common](https://github.com/netty/netty) | `4.2.1.Final` | `4.2.2.Final` | | [io.netty:netty-handler](https://github.com/netty/netty) | `4.2.1.Final` | `4.2.2.Final` | | [io.netty:netty-codec](https://github.com/netty/netty) | `4.2.1.Final` | `4.2.2.Final` | Updates `io.netty:netty-buffer` from 4.2.1.Final to 4.2.2.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.1.Final...netty-4.2.2.Final) Updates `io.netty:netty-transport` from 4.2.1.Final to 4.2.2.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.1.Final...netty-4.2.2.Final) Updates `io.netty:netty-transport-native-unix-common` from 4.2.1.Final to 4.2.2.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.1.Final...netty-4.2.2.Final) Updates `io.netty:netty-common` from 4.2.1.Final to 4.2.2.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.1.Final...netty-4.2.2.Final) Updates `io.netty:netty-handler` from 4.2.1.Final to 4.2.2.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.1.Final...netty-4.2.2.Final) Updates `io.netty:netty-codec` from 4.2.1.Final to 4.2.2.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.1.Final...netty-4.2.2.Final) --- updated-dependencies: - dependency-name: io.netty:netty-buffer dependency-version: 4.2.2.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-transport dependency-version: 4.2.2.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-transport-native-unix-common dependency-version: 4.2.2.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-common dependency-version: 4.2.2.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-handler dependency-version: 4.2.2.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-codec dependency-version: 4.2.2.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 6d5ff4306bc..1f591e2ce2a 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -888,32 +888,32 @@ io.netty netty-buffer - 4.2.1.Final + 4.2.2.Final io.netty netty-transport - 4.2.1.Final + 4.2.2.Final io.netty netty-transport-native-unix-common - 4.2.1.Final + 4.2.2.Final io.netty netty-common - 4.2.1.Final + 4.2.2.Final io.netty netty-handler - 4.2.1.Final + 4.2.2.Final io.netty netty-codec - 4.2.1.Final + 4.2.2.Final org.apache.velocity From a458fbd6ce0aa9a227153b969445534b9e23035e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 03:08:40 +0000 Subject: [PATCH 580/939] Bump org.codehaus.mojo:build-helper-maven-plugin Bumps the build-tools group with 1 update: [org.codehaus.mojo:build-helper-maven-plugin](https://github.com/mojohaus/build-helper-maven-plugin). Updates `org.codehaus.mojo:build-helper-maven-plugin` from 3.6.0 to 3.6.1 - [Release notes](https://github.com/mojohaus/build-helper-maven-plugin/releases) - [Commits](https://github.com/mojohaus/build-helper-maven-plugin/compare/3.6.0...3.6.1) --- updated-dependencies: - dependency-name: org.codehaus.mojo:build-helper-maven-plugin dependency-version: 3.6.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: build-tools ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 6d5ff4306bc..95fd4c103da 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -102,7 +102,7 @@ org.codehaus.mojo build-helper-maven-plugin - 3.6.0 + 3.6.1 validate From 358c00c1992053d2617bc1234f35d7d97caaa3a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 03:09:56 +0000 Subject: [PATCH 581/939] Bump net.handle:handle from 9.3.1 to 9.3.2 Bumps net.handle:handle from 9.3.1 to 9.3.2. --- updated-dependencies: - dependency-name: net.handle:handle dependency-version: 9.3.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index af2c99bb775..8f9e210029e 100644 --- a/pom.xml +++ b/pom.xml @@ -1382,7 +1382,7 @@ net.handle handle - 9.3.1 + 9.3.2 From a5ae3705e7ea3de503b6e4dd6e35fac3bacb4d96 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 9 Jun 2025 10:15:00 -0500 Subject: [PATCH 582/939] Remove commons-fileupload as it is no longer used. --- dspace-api/pom.xml | 4 ---- dspace-sword/pom.xml | 5 ----- pom.xml | 5 ----- 3 files changed, 14 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 756f65ff696..7b3ab4a315e 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -475,10 +475,6 @@ org.apache.commons commons-dbcp2 - - commons-fileupload - commons-fileupload - commons-io diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index d0ff123d741..fc436f66892 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -54,11 +54,6 @@ - - - commons-fileupload - commons-fileupload - org.apache.httpcomponents httpclient diff --git a/pom.xml b/pom.xml index af2c99bb775..cd7606e06c0 100644 --- a/pom.xml +++ b/pom.xml @@ -1497,11 +1497,6 @@ commons-dbcp2 2.13.0 - - commons-fileupload - commons-fileupload - 1.5 - commons-io commons-io From 1f174f465715744d0fa743f464b38dd37db32c07 Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Wed, 9 Apr 2025 17:06:43 +0200 Subject: [PATCH 583/939] 129944: Introduce custom abstract xpath contributor for pubmed to respect their labelled structure - modify IT for it (cherry picked from commit 28bc4970b7a5471f164d400a1bbd3e560b8e3e30) --- .../PubmedAbstractMetadatumContributor.java | 67 +++++++++++++++++++ .../PubmedImportMetadataSourceServiceIT.java | 64 ++++++++---------- .../app/rest/pubmedimport-search-test.xml | 4 +- .../config/spring/api/pubmed-integration.xml | 2 +- 4 files changed, 100 insertions(+), 37 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/pubmed/metadatamapping/contributor/PubmedAbstractMetadatumContributor.java diff --git a/dspace-api/src/main/java/org/dspace/importer/external/pubmed/metadatamapping/contributor/PubmedAbstractMetadatumContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/pubmed/metadatamapping/contributor/PubmedAbstractMetadatumContributor.java new file mode 100644 index 00000000000..727d6b92ae3 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/pubmed/metadatamapping/contributor/PubmedAbstractMetadatumContributor.java @@ -0,0 +1,67 @@ +/** + * 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.importer.external.pubmed.metadatamapping.contributor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.dspace.importer.external.metadatamapping.MetadatumDTO; +import org.dspace.importer.external.metadatamapping.contributor.SimpleXpathMetadatumContributor; +import org.jdom2.Element; +import org.jdom2.Namespace; +import org.jdom2.filter.Filters; +import org.jdom2.xpath.XPathExpression; +import org.jdom2.xpath.XPathFactory; + +/** + * This class is responsible for extracting the abstract from a PubMed XML document. + * It uses XPath to find the relevant elements and constructs a formatted string for the abstract, respecting + * PubMed's labelled abstract format, and including the labels in the output. + */ +public class PubmedAbstractMetadatumContributor extends SimpleXpathMetadatumContributor { + + @Override + public Collection contributeMetadata(Element t) { + List values = new LinkedList<>(); + + List namespaces = new ArrayList<>(); + for (String ns : prefixToNamespaceMapping.keySet()) { + namespaces.add(Namespace.getNamespace(prefixToNamespaceMapping.get(ns), ns)); + } + + XPathExpression xpath = XPathFactory.instance().compile(query, Filters.element(), null, namespaces); + List nodes = xpath.evaluate(t); + StringBuilder sb = new StringBuilder(); + + for (Element el : nodes) { + String label = el.getAttributeValue("Label"); + String text = el.getTextNormalize(); + + if (text == null || text.isEmpty()) { + continue; + } + + if (sb.length() > 0) { + sb.append("\n\n"); + } + + if (label != null && !label.equalsIgnoreCase("UNLABELLED")) { + sb.append(label).append(": "); + } + sb.append(text); + } + + String fullAbstract = sb.toString().trim(); + if (!fullAbstract.isEmpty()) { + values.add(metadataFieldMapping.toDCValue(field, fullAbstract)); + } + return values; + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PubmedImportMetadataSourceServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PubmedImportMetadataSourceServiceIT.java index 3b39d251216..5cac853c07b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PubmedImportMetadataSourceServiceIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PubmedImportMetadataSourceServiceIT.java @@ -101,35 +101,34 @@ private ArrayList getRecords() { //define first record MetadatumDTO title = createMetadatumDTO("dc","title", null, "Teaching strategies of clinical reasoning in advanced nursing clinical practice: A scoping review."); - MetadatumDTO description1 = createMetadatumDTO("dc", "description", "abstract", "To report and synthesize" - + " the main strategies for teaching clinical reasoning described in the literature in the context of" - + " advanced clinical practice and promote new areas of research to improve the pedagogical approach" - + " to clinical reasoning in Advanced Practice Nursing."); - MetadatumDTO description2 = createMetadatumDTO("dc", "description", "abstract", "Clinical reasoning and" - + " clinical thinking are essential elements in the advanced nursing clinical practice decision-making" - + " process. The quality improvement of care is related to the development of those skills." - + " Therefore, it is crucial to optimize teaching strategies that can enhance the role of clinical" - + " reasoning in advanced clinical practice."); - MetadatumDTO description3 = createMetadatumDTO("dc", "description", "abstract", "A scoping review was" - + " conducted using the framework developed by Arksey and O'Malley as a research strategy." - + " Consistent with the nature of scoping reviews, a study protocol has been established."); - MetadatumDTO description4 = createMetadatumDTO("dc", "description", "abstract", "The studies included and" - + " analyzed in this scoping review cover from January 2016 to June 2022. Primary studies and secondary" - + " revision studies, published in biomedical databases, were selected, including qualitative ones." - + " Electronic databases used were: CINAHL, PubMed, Cochrane Library, Scopus, and OVID." - + " Three authors independently evaluated the articles for titles, abstracts, and full text."); - MetadatumDTO description5 = createMetadatumDTO("dc", "description", "abstract", "1433 articles were examined," - + " applying the eligibility and exclusion criteria 73 studies were assessed for eligibility," - + " and 27 were included in the scoping review. The results that emerged from the review were" - + " interpreted and grouped into three macro strategies (simulations-based education, art and visual" - + " thinking, and other learning approaches) and nineteen educational interventions."); - MetadatumDTO description6 = createMetadatumDTO("dc", "description", "abstract", "Among the different" - + " strategies, the simulations are the most used. Despite this, our scoping review reveals that is" - + " necessary to use different teaching strategies to stimulate critical thinking, improve diagnostic" - + " reasoning, refine clinical judgment, and strengthen decision-making. However, it is not possible to" - + " demonstrate which methodology is more effective in obtaining the learning outcomes necessary to" - + " acquire an adequate level of judgment and critical thinking. Therefore, it will be" - + " necessary to relate teaching methodologies with the skills developed."); + MetadatumDTO description1 = createMetadatumDTO("dc", "description", "abstract", + "AIM/OBJECTIVE: To report and synthesize the main strategies for teaching clinical reasoning " + + "described in the literature in the context of advanced clinical practice and promote new " + + "areas of research to improve the pedagogical approach to clinical reasoning in Advanced " + + "Practice Nursing.\n\nBACKGROUND: Clinical reasoning and clinical thinking are essential " + + "elements in the advanced nursing clinical practice decision-making process. The quality " + + "improvement of care is related to the development of those skills. Therefore, it is crucial " + + "to optimize teaching strategies that can enhance the role of clinical reasoning in advanced " + + "clinical practice.\n\nDESIGN: A scoping review was conducted using the framework developed " + + "by Arksey and O'Malley as a research strategy. Consistent with the nature of scoping reviews" + + ", a study protocol has been established.\n\nMETHODS: The studies included and analyzed in " + + "this scoping review cover from January 2016 to June 2022. Primary studies and secondary " + + "revision studies, published in biomedical databases, were selected, including qualitative " + + "ones. Electronic databases used were: CINAHL, PubMed, Cochrane Library, Scopus, and OVID. " + + "Three authors independently evaluated the articles for titles, abstracts, and full text.\n\n" + + "RESULTS: 1433 articles were examined, applying the eligibility and exclusion criteria 73 " + + "studies were assessed for eligibility, and 27 were included in the scoping review. The " + + "results that emerged from the review were interpreted and grouped into three macro " + + "strategies (simulations-based education, art and visual thinking, and other learning " + + "approaches) and nineteen educational interventions.\n\nCONCLUSIONS: Among the different " + + "strategies, the simulations are the most used. Despite this, our scoping review reveals " + + "that is necessary to use different teaching strategies to stimulate critical thinking, " + + "improve diagnostic reasoning, refine clinical judgment, and strengthen decision-making. " + + "However, it is not possible to demonstrate which methodology is more effective in obtaining " + + "the learning outcomes necessary to acquire an adequate level of judgment and critical " + + "thinking. Therefore, it will be necessary to relate teaching methodologies with the skills " + + "developed.\n\nAn unlabeled section of an abstract.\n\nAn abstract section with no attributes" + + " at all, concerning."); MetadatumDTO identifierOther = createMetadatumDTO("dc", "identifier", "other", "36708638"); MetadatumDTO author1 = createMetadatumDTO("dc", "contributor", "author", "Giuffrida, Silvia"); MetadatumDTO author2 = createMetadatumDTO("dc", "contributor", "author", "Silano, Verdiana"); @@ -148,11 +147,6 @@ private ArrayList getRecords() { metadatums.add(title); metadatums.add(description1); - metadatums.add(description2); - metadatums.add(description3); - metadatums.add(description4); - metadatums.add(description5); - metadatums.add(description6); metadatums.add(identifierOther); metadatums.add(author1); metadatums.add(author2); @@ -210,4 +204,4 @@ private ArrayList getRecords2() { return records; } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/test/resources/org/dspace/app/rest/pubmedimport-search-test.xml b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/pubmedimport-search-test.xml index 666fb1e7d55..6af52724690 100644 --- a/dspace-server-webapp/src/test/resources/org/dspace/app/rest/pubmedimport-search-test.xml +++ b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/pubmedimport-search-test.xml @@ -41,6 +41,8 @@ The studies included and analyzed in this scoping review cover from January 2016 to June 2022. Primary studies and secondary revision studies, published in biomedical databases, were selected, including qualitative ones. Electronic databases used were: CINAHL, PubMed, Cochrane Library, Scopus, and OVID. Three authors independently evaluated the articles for titles, abstracts, and full text. 1433 articles were examined, applying the eligibility and exclusion criteria 73 studies were assessed for eligibility, and 27 were included in the scoping review. The results that emerged from the review were interpreted and grouped into three macro strategies (simulations-based education, art and visual thinking, and other learning approaches) and nineteen educational interventions. Among the different strategies, the simulations are the most used. Despite this, our scoping review reveals that is necessary to use different teaching strategies to stimulate critical thinking, improve diagnostic reasoning, refine clinical judgment, and strengthen decision-making. However, it is not possible to demonstrate which methodology is more effective in obtaining the learning outcomes necessary to acquire an adequate level of judgment and critical thinking. Therefore, it will be necessary to relate teaching methodologies with the skills developed. + An unlabeled section of an abstract. + An abstract section with no attributes at all, concerning. Copyright © 2023 Elsevier Ltd. All rights reserved. @@ -191,4 +193,4 @@ - \ No newline at end of file + diff --git a/dspace/config/spring/api/pubmed-integration.xml b/dspace/config/spring/api/pubmed-integration.xml index adec4456ea0..f488327789f 100644 --- a/dspace/config/spring/api/pubmed-integration.xml +++ b/dspace/config/spring/api/pubmed-integration.xml @@ -57,7 +57,7 @@ - + From 2c400bf2da591bcb9d0978dfd60a44f0de04112e Mon Sep 17 00:00:00 2001 From: DSpace Bot <68393067+dspace-bot@users.noreply.github.com> Date: Wed, 11 Jun 2025 07:20:59 -0500 Subject: [PATCH 584/939] [Port dspace-7_x] Optimization of Solr Queries: Transition to Filter Queries (#10887) * use filter query instead of generic query (cherry picked from commit f2417feeca653aefdd7a201460afe3d9429bacae) * use filter query instead of generic query (cherry picked from commit d83a2525ad96e9d4cb935481c505537bdee0cae5) * use filter query instead of generic query (cherry picked from commit f3a976107e2afef5c2d0fb19e580bbbd754acc14) * remove obsolete comment (cherry picked from commit 3ee2dbcc56c157499c6db96cc3b8e90c2ae47e8f) * use filter query instead of generic query (cherry picked from commit 318afc769a6f0958c795da7506b25a28cf298f09) * add static imports (cherry picked from commit 8ad19c42df754daa6ed70c6e9d1c9146e72c0b32) * move static import to the top of the import block (cherry picked from commit b85585c34e528ce2165e25a0cf23351283360ac7) * move static imports to the top of the import block (cherry picked from commit 4b446e24a068027e8cf3c3935ad256f39b4b65ef) --------- Co-authored-by: Sascha Szott --- .../java/org/dspace/app/sitemap/GenerateSitemaps.java | 11 ++++++++--- .../app/solrdatabaseresync/SolrDatabaseResyncCli.java | 3 ++- .../main/java/org/dspace/browse/SolrBrowseDAO.java | 9 +++++++-- .../java/org/dspace/discovery/SolrSearchCore.java | 8 +++++--- .../org/dspace/statistics/SolrLoggerServiceImpl.java | 1 - 5 files changed, 22 insertions(+), 10 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/sitemap/GenerateSitemaps.java b/dspace-api/src/main/java/org/dspace/app/sitemap/GenerateSitemaps.java index 90962d12aa7..41b0b0f6b3d 100644 --- a/dspace-api/src/main/java/org/dspace/app/sitemap/GenerateSitemaps.java +++ b/dspace-api/src/main/java/org/dspace/app/sitemap/GenerateSitemaps.java @@ -7,6 +7,8 @@ */ package org.dspace.app.sitemap; +import static org.dspace.discovery.SearchUtils.RESOURCE_TYPE_FIELD; + import java.io.File; import java.io.IOException; import java.sql.SQLException; @@ -189,7 +191,8 @@ public static void generateSitemaps(boolean makeHTMLMap, boolean makeSitemapOrg) try { DiscoverQuery discoveryQuery = new DiscoverQuery(); discoveryQuery.setMaxResults(PAGE_SIZE); - discoveryQuery.setQuery("search.resourcetype:Community"); + discoveryQuery.setQuery("*:*"); + discoveryQuery.addFilterQueries(RESOURCE_TYPE_FIELD + ":Community"); do { discoveryQuery.setStart(offset); DiscoverResult discoverResult = searchService.search(c, discoveryQuery); @@ -213,7 +216,8 @@ public static void generateSitemaps(boolean makeHTMLMap, boolean makeSitemapOrg) offset = 0; discoveryQuery = new DiscoverQuery(); discoveryQuery.setMaxResults(PAGE_SIZE); - discoveryQuery.setQuery("search.resourcetype:Collection"); + discoveryQuery.setQuery("*:*"); + discoveryQuery.addFilterQueries(RESOURCE_TYPE_FIELD + ":Collection"); do { discoveryQuery.setStart(offset); DiscoverResult discoverResult = searchService.search(c, discoveryQuery); @@ -237,7 +241,8 @@ public static void generateSitemaps(boolean makeHTMLMap, boolean makeSitemapOrg) offset = 0; discoveryQuery = new DiscoverQuery(); discoveryQuery.setMaxResults(PAGE_SIZE); - discoveryQuery.setQuery("search.resourcetype:Item"); + discoveryQuery.setQuery("*:*"); + discoveryQuery.addFilterQueries(RESOURCE_TYPE_FIELD + ":Item"); discoveryQuery.addSearchField("search.entitytype"); do { diff --git a/dspace-api/src/main/java/org/dspace/app/solrdatabaseresync/SolrDatabaseResyncCli.java b/dspace-api/src/main/java/org/dspace/app/solrdatabaseresync/SolrDatabaseResyncCli.java index aac42ce1acf..0ce6b1a9ef3 100644 --- a/dspace-api/src/main/java/org/dspace/app/solrdatabaseresync/SolrDatabaseResyncCli.java +++ b/dspace-api/src/main/java/org/dspace/app/solrdatabaseresync/SolrDatabaseResyncCli.java @@ -98,7 +98,8 @@ public void internalRun() throws Exception { private void performStatusUpdate(Context context) throws SearchServiceException, SolrServerException, IOException { SolrQuery solrQuery = new SolrQuery(); - solrQuery.setQuery(STATUS_FIELD + ":" + STATUS_FIELD_PREDB); + solrQuery.setQuery("*:*"); + solrQuery.addFilterQuery(STATUS_FIELD + ":" + STATUS_FIELD_PREDB); solrQuery.addFilterQuery(SearchUtils.RESOURCE_TYPE_FIELD + ":" + IndexableItem.TYPE); String dateRangeFilter = SearchUtils.LAST_INDEXED_FIELD + ":[* TO " + maxTime + "]"; logDebugAndOut("Date range filter used; " + dateRangeFilter); diff --git a/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java b/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java index 14dab3e5617..a0a7725fa13 100644 --- a/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java +++ b/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java @@ -7,6 +7,9 @@ */ package org.dspace.browse; +import static org.dspace.discovery.SearchUtils.RESOURCE_ID_FIELD; +import static org.dspace.discovery.SearchUtils.RESOURCE_TYPE_FIELD; + import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; @@ -308,8 +311,10 @@ public List doQuery() throws BrowseException { public String doMaxQuery(String column, String table, int itemID) throws BrowseException { DiscoverQuery query = new DiscoverQuery(); - query.setQuery("search.resourceid:" + itemID - + " AND search.resourcetype:" + IndexableItem.TYPE); + query.setQuery("*:*"); + query.addFilterQueries( + RESOURCE_ID_FIELD + ":" + itemID, + RESOURCE_TYPE_FIELD + ":" + IndexableItem.TYPE); query.setMaxResults(1); DiscoverResult resp = null; try { diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrSearchCore.java b/dspace-api/src/main/java/org/dspace/discovery/SolrSearchCore.java index f31feab6123..0d5dfad3b8b 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrSearchCore.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrSearchCore.java @@ -88,9 +88,11 @@ protected void initSolr() { solrServer.setBaseURL(solrService); solrServer.setUseMultiPartPost(true); // Dummy/test query to search for Item (type=2) of ID=1 - SolrQuery solrQuery = new SolrQuery() - .setQuery(SearchUtils.RESOURCE_TYPE_FIELD + ":" + IndexableItem.TYPE + - " AND " + SearchUtils.RESOURCE_ID_FIELD + ":1"); + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setQuery("*:*"); + solrQuery.addFilterQuery( + SearchUtils.RESOURCE_TYPE_FIELD + ":" + IndexableItem.TYPE, + SearchUtils.RESOURCE_ID_FIELD + ":1"); // Only return obj identifier fields in result doc solrQuery.setFields(SearchUtils.RESOURCE_TYPE_FIELD, SearchUtils.RESOURCE_ID_FIELD); solrServer.query(solrQuery, REQUEST_METHOD); diff --git a/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java b/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java index 6c3ccdc082e..35f86e3a1c0 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java @@ -1093,7 +1093,6 @@ public QueryResponse query(String query, String filterQuery, String facetField, return null; } - // System.out.println("QUERY"); SolrQuery solrQuery = new SolrQuery().setRows(rows).setQuery(query) .setFacetMinCount(facetMinCount); addAdditionalSolrYearCores(solrQuery); From 2abbe67ea6de8093f2223b8a44991618ec2fc934 Mon Sep 17 00:00:00 2001 From: DSpace Bot <68393067+dspace-bot@users.noreply.github.com> Date: Wed, 11 Jun 2025 07:26:49 -0500 Subject: [PATCH 585/939] [Port dspace-7_x] improve robustness of search in index field submit (use filter query) (#10890) * improve robustness of search in index field submit (use filter query) (cherry picked from commit a65ef008b74cac3a81c17bfd6ca44e095f6a3771) * fix checkstyle warnings (cherry picked from commit 183d5ca67113ea750a9de1a13413d34eac946169) * fix checkstyle warning (cherry picked from commit fe251f39e38dcdbba6ab3183a12fc5385c320b12) --------- Co-authored-by: Sascha Szott --- .../dspace/content/EntityTypeServiceImpl.java | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/EntityTypeServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/EntityTypeServiceImpl.java index b5b066d9c36..7df892cd56f 100644 --- a/dspace-api/src/main/java/org/dspace/content/EntityTypeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/EntityTypeServiceImpl.java @@ -30,6 +30,7 @@ import org.dspace.core.Context; import org.dspace.discovery.SolrSearchCore; import org.dspace.discovery.indexobject.IndexableCollection; +import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.eperson.service.GroupService; import org.springframework.beans.factory.annotation.Autowired; @@ -124,24 +125,33 @@ public void delete(Context context,EntityType entityType) throws SQLException, A public List getSubmitAuthorizedTypes(Context context) throws SQLException, SolrServerException, IOException { List types = new ArrayList<>(); - StringBuilder query = new StringBuilder(); - org.dspace.eperson.EPerson currentUser = context.getCurrentUser(); + StringBuilder query = null; + EPerson currentUser = context.getCurrentUser(); if (!authorizeService.isAdmin(context)) { String userId = ""; if (currentUser != null) { userId = currentUser.getID().toString(); + query = new StringBuilder(); + query.append("submit:(e").append(userId); } - query.append("submit:(e").append(userId); + Set groups = groupService.allMemberGroupsSet(context, currentUser); for (Group group : groups) { - query.append(" OR g").append(group.getID()); + if (query == null) { + query = new StringBuilder(); + query.append("submit:(g"); + } else { + query.append(" OR g"); + } + query.append(group.getID()); } query.append(")"); - } else { - query.append("*:*"); } - SolrQuery sQuery = new SolrQuery(query.toString()); + SolrQuery sQuery = new SolrQuery("*:*"); + if (query != null) { + sQuery.addFilterQuery(query.toString()); + } sQuery.addFilterQuery("search.resourcetype:" + IndexableCollection.TYPE); sQuery.setRows(0); sQuery.addFacetField("search.entitytype"); From d9cc564acec4a4d9add57ed52655745abaa4737c Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Sun, 13 Apr 2025 17:34:14 +0300 Subject: [PATCH 586/939] dspace-api: use static variable RESOURCE_TYPE_FIELD --- .../authorize/AuthorizeServiceImpl.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java index a99d83764a2..6bd4ed798dd 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java @@ -9,6 +9,7 @@ import static org.dspace.app.util.AuthorizeUtil.canCollectionAdminManageAccounts; import static org.dspace.app.util.AuthorizeUtil.canCommunityAdminManageAccounts; +import static org.dspace.discovery.SearchUtils.RESOURCE_TYPE_FIELD; import java.sql.SQLException; import java.util.ArrayList; @@ -736,7 +737,7 @@ public List getPoliciesActionFilterExceptRpType(Context c, DSpac */ @Override public boolean isCommunityAdmin(Context context) throws SQLException { - return performCheck(context, "search.resourcetype:" + IndexableCommunity.TYPE); + return performCheck(context, RESOURCE_TYPE_FIELD + ":" + IndexableCommunity.TYPE); } /** @@ -749,7 +750,7 @@ public boolean isCommunityAdmin(Context context) throws SQLException { */ @Override public boolean isCollectionAdmin(Context context) throws SQLException { - return performCheck(context, "search.resourcetype:" + IndexableCollection.TYPE); + return performCheck(context, RESOURCE_TYPE_FIELD + ":" + IndexableCollection.TYPE); } /** @@ -762,7 +763,7 @@ public boolean isCollectionAdmin(Context context) throws SQLException { */ @Override public boolean isItemAdmin(Context context) throws SQLException { - return performCheck(context, "search.resourcetype:" + IndexableItem.TYPE); + return performCheck(context, RESOURCE_TYPE_FIELD + ":" + IndexableItem.TYPE); } /** @@ -776,8 +777,8 @@ public boolean isItemAdmin(Context context) throws SQLException { @Override public boolean isComColAdmin(Context context) throws SQLException { return performCheck(context, - "(search.resourcetype:" + IndexableCommunity.TYPE + " OR search.resourcetype:" + - IndexableCollection.TYPE + ")"); + "(" + RESOURCE_TYPE_FIELD + ":" + IndexableCommunity.TYPE + " OR " + + RESOURCE_TYPE_FIELD + ":" + IndexableCollection.TYPE + ")"); } /** @@ -795,7 +796,7 @@ public List findAdminAuthorizedCommunity(Context context, String quer throws SearchServiceException, SQLException { List communities = new ArrayList<>(); query = formatCustomQuery(query); - DiscoverResult discoverResult = getDiscoverResult(context, query + "search.resourcetype:" + + DiscoverResult discoverResult = getDiscoverResult(context, query + RESOURCE_TYPE_FIELD + ":" + IndexableCommunity.TYPE, offset, limit, null, null); for (IndexableObject solrCollections : discoverResult.getIndexableObjects()) { @@ -817,7 +818,7 @@ public List findAdminAuthorizedCommunity(Context context, String quer public long countAdminAuthorizedCommunity(Context context, String query) throws SearchServiceException, SQLException { query = formatCustomQuery(query); - DiscoverResult discoverResult = getDiscoverResult(context, query + "search.resourcetype:" + + DiscoverResult discoverResult = getDiscoverResult(context, query + RESOURCE_TYPE_FIELD + ":" + IndexableCommunity.TYPE, null, null, null, null); return discoverResult.getTotalSearchResults(); @@ -842,7 +843,7 @@ public List findAdminAuthorizedCollection(Context context, String qu } query = formatCustomQuery(query); - DiscoverResult discoverResult = getDiscoverResult(context, query + "search.resourcetype:" + + DiscoverResult discoverResult = getDiscoverResult(context, query + RESOURCE_TYPE_FIELD + ":" + IndexableCollection.TYPE, offset, limit, CollectionService.SOLR_SORT_FIELD, SORT_ORDER.asc); for (IndexableObject solrCollections : discoverResult.getIndexableObjects()) { @@ -864,7 +865,7 @@ public List findAdminAuthorizedCollection(Context context, String qu public long countAdminAuthorizedCollection(Context context, String query) throws SearchServiceException, SQLException { query = formatCustomQuery(query); - DiscoverResult discoverResult = getDiscoverResult(context, query + "search.resourcetype:" + + DiscoverResult discoverResult = getDiscoverResult(context, query + RESOURCE_TYPE_FIELD + ":" + IndexableCollection.TYPE, null, null, null, null); return discoverResult.getTotalSearchResults(); From bd753005e68dd3c3da0533a4a8aae9cc553e1adb Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Sun, 13 Apr 2025 17:35:55 +0300 Subject: [PATCH 587/939] dspace-api: do not request actual search hits in count-only query --- .../main/java/org/dspace/authorize/AuthorizeServiceImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java index 6bd4ed798dd..7198e28d03b 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java @@ -820,7 +820,7 @@ public long countAdminAuthorizedCommunity(Context context, String query) query = formatCustomQuery(query); DiscoverResult discoverResult = getDiscoverResult(context, query + RESOURCE_TYPE_FIELD + ":" + IndexableCommunity.TYPE, - null, null, null, null); + null, 0, null, null); return discoverResult.getTotalSearchResults(); } @@ -867,7 +867,7 @@ public long countAdminAuthorizedCollection(Context context, String query) query = formatCustomQuery(query); DiscoverResult discoverResult = getDiscoverResult(context, query + RESOURCE_TYPE_FIELD + ":" + IndexableCollection.TYPE, - null, null, null, null); + null, 0, null, null); return discoverResult.getTotalSearchResults(); } From 9eef166b7e90c7206221ad76d6e116c7da41b716 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Sun, 13 Apr 2025 17:36:35 +0300 Subject: [PATCH 588/939] dspace-api: set search fields in Solr query only if we are interested in the actual search results --- .../org/dspace/discovery/SolrServiceImpl.java | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java index 8cf1ec85888..06edf4408c0 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java @@ -861,16 +861,20 @@ protected SolrQuery resolveToSolrQuery(Context context, DiscoverQuery discoveryQ solrQuery.setQuery(query); - // Add any search fields to our query. This is the limited list - // of fields that will be returned in the solr result - for (String fieldName : discoveryQuery.getSearchFields()) { - solrQuery.addField(fieldName); - } - // Also ensure a few key obj identifier fields are returned with every query - solrQuery.addField(SearchUtils.RESOURCE_TYPE_FIELD); - solrQuery.addField(SearchUtils.RESOURCE_ID_FIELD); - solrQuery.addField(SearchUtils.RESOURCE_UNIQUE_ID); - solrQuery.addField(STATUS_FIELD); + if (discoveryQuery.getMaxResults() != 0) { + // set search fields in Solr query only if we are interested in the actual search results + + // Add any search fields to our query. This is the limited list + // of fields that will be returned in the solr result + for (String fieldName : discoveryQuery.getSearchFields()) { + solrQuery.addField(fieldName); + } + // Also ensure a few key obj identifier fields are returned with every query + solrQuery.addField(SearchUtils.RESOURCE_TYPE_FIELD); + solrQuery.addField(SearchUtils.RESOURCE_ID_FIELD); + solrQuery.addField(SearchUtils.RESOURCE_UNIQUE_ID); + solrQuery.addField(STATUS_FIELD); + } if (discoveryQuery.isSpellCheck()) { solrQuery.setParam(SpellingParams.SPELLCHECK_Q, query); From 34134b3c3b99779d07c8bda9181b9eeaacd2f6a3 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Wed, 9 Apr 2025 13:39:05 +0200 Subject: [PATCH 589/939] remove inclusion of sword-client.cfg --- dspace/config/dspace.cfg | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 4621ca51dc1..3047c2ce4db 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1658,7 +1658,6 @@ include = ${module_dir}/solrauthority.cfg include = ${module_dir}/researcher-profile.cfg include = ${module_dir}/spring.cfg include = ${module_dir}/submission-curation.cfg -include = ${module_dir}/sword-client.cfg include = ${module_dir}/sword-server.cfg include = ${module_dir}/swordv2-server.cfg include = ${module_dir}/translator.cfg From 07e840b675ee933868ac53e41e1034b749f0e627 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Wed, 9 Apr 2025 13:40:02 +0200 Subject: [PATCH 590/939] removal of configuration file sword-client.cfg --- dspace/config/modules/sword-client.cfg | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 dspace/config/modules/sword-client.cfg diff --git a/dspace/config/modules/sword-client.cfg b/dspace/config/modules/sword-client.cfg deleted file mode 100644 index 9e8a029ae68..00000000000 --- a/dspace/config/modules/sword-client.cfg +++ /dev/null @@ -1,23 +0,0 @@ -#---------------------------------------------------------------# -#--------------SWORD V.1 CLIENT CONFIGURATIONS------------------# -#---------------------------------------------------------------# -# Configuration properties used solely by the UI-based SWORD # -# Client interface (used to submit DSpace content to another # -# SWORD server). # -#---------------------------------------------------------------# -# TODO: UNSUPPORTED in DSpace 7.0 -# List of remote Sword servers. Used to build the drop-down list of selectable Sword targets. -sword-client.targets = http://localhost:8080/sword/servicedocument, \ - http://client.swordapp.org/client/servicedocument, \ - http://dspace.swordapp.org/sword/servicedocument, \ - http://sword.eprints.org/sword-app/servicedocument, \ - http://sword.intralibrary.com/IntraLibrary-Deposit/service, \ - http://fedora.swordapp.org/sword-fedora/servicedocument - -# List of file types from which the user can select. If a type is not supported by the remote server -# it will not appear in the drop-down list. -sword-client.file-types = application/zip - -# List of package formats from which the user can select. If a format is not supported by the remote server -# it will not appear in the drop-down list. -sword-client.package-formats = http://purl.org/net/sword-types/METSDSpaceSIP From 39def525922f9bba8bc0a39e521f6ce7fbaa02cb Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Fri, 24 Jan 2025 01:21:25 +0100 Subject: [PATCH 591/939] [DURACOM-318] add new ITs for ResourcePolicy (cherry picked from commit d78d4f00d94ef0d4b147031b075b8df1e8896fe4) (cherry picked from commit 98c2b9942167890d24b71f5c32e6918ac2eee7a6) --- .../rest/ResourcePolicyRestRepositoryIT.java | 548 ++++++++++++++++++ 1 file changed, 548 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java index 5d2a05ab644..08ce836f3d7 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java @@ -23,6 +23,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.text.SimpleDateFormat; +import java.io.InputStream; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; @@ -32,6 +35,9 @@ import javax.ws.rs.core.MediaType; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.ws.rs.core.MediaType; +import org.apache.commons.codec.CharEncoding; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.matcher.ResourcePolicyMatcher; import org.dspace.app.rest.model.ResourcePolicyRest; @@ -43,12 +49,14 @@ import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.AuthorizeService; import org.dspace.authorize.service.ResourcePolicyService; +import org.dspace.builder.BitstreamBuilder; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.EPersonBuilder; import org.dspace.builder.GroupBuilder; import org.dspace.builder.ItemBuilder; import org.dspace.builder.ResourcePolicyBuilder; +import org.dspace.content.Bitstream; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; @@ -1215,6 +1223,376 @@ public void createOneForbiddenTest() throws Exception { .andExpect(jsonPath("$.page.totalElements", is(0))); } + @Test + public void createPolicyByCollectionAdminTest() throws Exception { + context.turnOffAuthorisationSystem(); + EPerson colAdmin = EPersonBuilder.createEPerson(context) + .withEmail("colAdmin@mail.test") + .withPassword(password) + .build(); + + EPerson colAdmin2 = EPersonBuilder.createEPerson(context) + .withEmail("colAdmin2@mail.test") + .withPassword(password) + .build(); + + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("colSubmitter@mail.test") + .withPassword(password) + .build(); + + Community community = CommunityBuilder.createCommunity(context) + .withName("My top commynity") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("My collection") + .withAdminGroup(colAdmin) + .withSubmitterGroup(submitter) + .withEntityType("Publication") + .build(); + + CollectionBuilder.createCollection(context, community) + .withName("My Second Collection") + .withAdminGroup(colAdmin2) + .withSubmitterGroup(submitter) + .withEntityType("Publication") + .build(); + + Item publication = ItemBuilder.createItem(context, collection) + .withTitle("Public item") + .build(); + + //Add a bitstream to a publication + Bitstream bitstream = null; + try (InputStream is = IOUtils.toInputStream("ThisIsSomeDummyText", CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder.createBitstream(context, publication, is) + .withName("Bitstream") + .withDescription("description") + .withMimeType("text/plain") + .build(); + } + context.restoreAuthSystemState(); + + ResourcePolicyRest resourcePolicyRest = new ResourcePolicyRest(); + resourcePolicyRest.setPolicyType(ResourcePolicy.TYPE_CUSTOM); + resourcePolicyRest.setAction(Constants.actionText[Constants.WRITE]); + resourcePolicyRest.setName("Test for collection admin"); + + String authcolAdminToken = getAuthToken(colAdmin.getEmail(), password); + String authcolAdmin2Token = getAuthToken(colAdmin2.getEmail(), password); + String authSubmitterToken = getAuthToken(submitter.getEmail(), password); + AtomicReference idRef = new AtomicReference(); + + try { + // submitter can't create policy + getClient(authSubmitterToken).perform(post("/api/authz/resourcepolicies") + .content(new ObjectMapper().writeValueAsBytes(resourcePolicyRest)) + .param("resource", bitstream.getID().toString()) + .param("eperson", submitter.getID().toString()) + .param("projections", "full") + .contentType(contentType)) + .andExpect(status().isForbidden()); + + // other collection admin can't create policy for other collection + getClient(authcolAdmin2Token).perform(post("/api/authz/resourcepolicies") + .content(new ObjectMapper().writeValueAsBytes(resourcePolicyRest)) + .param("resource", bitstream.getID().toString()) + .param("eperson", submitter.getID().toString()) + .param("projections", "full") + .contentType(contentType)) + .andExpect(status().isForbidden()); + + // create policy for submitter by collection admin + getClient(authcolAdminToken).perform(post("/api/authz/resourcepolicies") + .content(new ObjectMapper().writeValueAsBytes(resourcePolicyRest)) + .param("resource", bitstream.getID().toString()) + .param("eperson", submitter.getID().toString()) + .param("projections", "full") + .contentType(contentType)) + .andExpect(status().isCreated()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", ResourcePolicyMatcher.matchFullEmbeds())) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.name", is(resourcePolicyRest.getName())), + hasJsonPath("$.description", is(resourcePolicyRest.getDescription())), + hasJsonPath("$.policyType", is(resourcePolicyRest.getPolicyType())), + hasJsonPath("$.action", is(resourcePolicyRest.getAction())), + hasJsonPath("$.startDate", is(resourcePolicyRest.getStartDate())), + hasJsonPath("$.endDate", is(resourcePolicyRest.getEndDate())), + hasJsonPath("$.type", is(resourcePolicyRest.getType()))))) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + + // submitter can see own policy + getClient(authSubmitterToken).perform(get("/api/authz/resourcepolicies/" + idRef.get())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/authz/resourcepolicies/" + idRef.get()))); + + // collection admin can see that policy + getClient(authcolAdminToken).perform(get("/api/authz/resourcepolicies/" + idRef.get())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/authz/resourcepolicies/" + idRef.get()))); + } finally { + ResourcePolicyBuilder.delete(idRef.get()); + } + } + + @Test + public void createPolicyBySubCommunityAdminTest() throws Exception { + context.turnOffAuthorisationSystem(); + EPerson comAdmin = EPersonBuilder.createEPerson(context) + .withEmail("comAdmin@mail.test") + .withPassword(password) + .build(); + + EPerson comAdmin2 = EPersonBuilder.createEPerson(context) + .withEmail("comAdmin2@mail.test") + .withPassword(password) + .build(); + + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("colSubmitter@mail.test") + .withPassword(password) + .build(); + + Community community = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("My First Commynity") + .withAdminGroup(comAdmin) + .build(); + + Community community2 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("My Second Commynity") + .withAdminGroup(comAdmin2) + .build(); + + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("My collection") + .withSubmitterGroup(submitter) + .withEntityType("Publication") + .build(); + + CollectionBuilder.createCollection(context, community2) + .withName("My Second Collection") + .withSubmitterGroup(submitter) + .withEntityType("Publication") + .build(); + + Item publication = ItemBuilder.createItem(context, collection) + .withTitle("Public item") + .build(); + + context.restoreAuthSystemState(); + + ResourcePolicyRest resourcePolicyRest = new ResourcePolicyRest(); + resourcePolicyRest.setPolicyType(ResourcePolicy.TYPE_CUSTOM); + resourcePolicyRest.setAction(Constants.actionText[Constants.WRITE]); + resourcePolicyRest.setName("Test for collection admin"); + + String authcomAdminToken = getAuthToken(comAdmin.getEmail(), password); + String authcomAdmin2Token = getAuthToken(comAdmin2.getEmail(), password); + String authSubmitterToken = getAuthToken(submitter.getEmail(), password); + AtomicReference idRef = new AtomicReference(); + + try { + // submitter can't create policy + getClient(authSubmitterToken).perform(post("/api/authz/resourcepolicies") + .content(new ObjectMapper().writeValueAsBytes(resourcePolicyRest)) + .param("resource", publication.getID().toString()) + .param("eperson", submitter.getID().toString()) + .param("projections", "full") + .contentType(contentType)) + .andExpect(status().isForbidden()); + + // other Community admin can't create policy for collections into other Community + getClient(authcomAdmin2Token).perform(post("/api/authz/resourcepolicies") + .content(new ObjectMapper().writeValueAsBytes(resourcePolicyRest)) + .param("resource", publication.getID().toString()) + .param("eperson", submitter.getID().toString()) + .param("projections", "full") + .contentType(contentType)) + .andExpect(status().isForbidden()); + + // create policy for submitter by Community admin + getClient(authcomAdminToken).perform(post("/api/authz/resourcepolicies") + .content(new ObjectMapper().writeValueAsBytes(resourcePolicyRest)) + .param("resource", publication.getID().toString()) + .param("eperson", submitter.getID().toString()) + .param("projections", "full") + .contentType(contentType)) + .andExpect(status().isCreated()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", ResourcePolicyMatcher.matchFullEmbeds())) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.name", is(resourcePolicyRest.getName())), + hasJsonPath("$.description", is(resourcePolicyRest.getDescription())), + hasJsonPath("$.policyType", is(resourcePolicyRest.getPolicyType())), + hasJsonPath("$.action", is(resourcePolicyRest.getAction())), + hasJsonPath("$.startDate", is(resourcePolicyRest.getStartDate())), + hasJsonPath("$.endDate", is(resourcePolicyRest.getEndDate())), + hasJsonPath("$.type", is(resourcePolicyRest.getType()))))) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + + // submitter can see own policy + getClient(authSubmitterToken).perform(get("/api/authz/resourcepolicies/" + idRef.get())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/authz/resourcepolicies/" + idRef.get()))); + + // community admin can see policies of own collections/items + getClient(authcomAdminToken).perform(get("/api/authz/resourcepolicies/" + idRef.get())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/authz/resourcepolicies/" + idRef.get()))); + + // Other community admin can't see policies of other community's collections/items + getClient(authcomAdmin2Token).perform(get("/api/authz/resourcepolicies/" + idRef.get())) + .andExpect(status().isForbidden()); + } finally { + ResourcePolicyBuilder.delete(idRef.get()); + } + } + + @Test + public void createPolicyByCommunityAdminTest() throws Exception { + context.turnOffAuthorisationSystem(); + EPerson rootComAdmin = EPersonBuilder.createEPerson(context) + .withEmail("rootComAdmin@mail.test") + .withPassword(password) + .build(); + + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("colSubmitter@mail.test") + .withPassword(password) + .build(); + + Community rootCommunity = CommunityBuilder.createCommunity(context) + .withName("Root Community") + .withAdminGroup(rootComAdmin) + .build(); + + Community community = CommunityBuilder.createSubCommunity(context, rootCommunity) + .withName("My First Commynity") + .build(); + + Community community2 = CommunityBuilder.createSubCommunity(context, rootCommunity) + .withName("My Second Commynity") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("My collection") + .withSubmitterGroup(submitter) + .withEntityType("Publication") + .build(); + + CollectionBuilder.createCollection(context, community2) + .withName("My Second Collection") + .withSubmitterGroup(submitter) + .withEntityType("Publication") + .build(); + + Item publication = ItemBuilder.createItem(context, collection) + .withTitle("Public item") + .build(); + + Collection collection2 = CollectionBuilder.createCollection(context, community) + .withName("My Second Collection") + .withSubmitterGroup(submitter) + .withEntityType("Publication") + .build(); + + Item publication2 = ItemBuilder.createItem(context, collection2) + .withTitle("Item of second collection") + .build(); + + //Add a bitstream to a publication + Bitstream bitstream = null; + try (InputStream is = IOUtils.toInputStream("ThisIsSomeDummyText", CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder.createBitstream(context, publication2, is) + .withName("Bitstream") + .withDescription("description") + .withMimeType("text/plain") + .build(); + } + + context.restoreAuthSystemState(); + + ResourcePolicyRest resourcePolicyRest = new ResourcePolicyRest(); + resourcePolicyRest.setPolicyType(ResourcePolicy.TYPE_CUSTOM); + resourcePolicyRest.setAction(Constants.actionText[Constants.WRITE]); + resourcePolicyRest.setName("Test for collection admin"); + + ResourcePolicyRest resourcePolicyRest2 = new ResourcePolicyRest(); + resourcePolicyRest2.setPolicyType(ResourcePolicy.TYPE_CUSTOM); + resourcePolicyRest2.setAction(Constants.actionText[Constants.WRITE]); + resourcePolicyRest2.setName("Test for root community admin"); + + String authSubmitterToken = getAuthToken(submitter.getEmail(), password); + String authRootAdminToken = getAuthToken(rootComAdmin.getEmail(), password); + + AtomicReference idRef = new AtomicReference(); + AtomicReference idRef2 = new AtomicReference(); + try { + // create policy for submitter by root Community admin + getClient(authRootAdminToken).perform(post("/api/authz/resourcepolicies") + .content(new ObjectMapper().writeValueAsBytes(resourcePolicyRest)) + .param("resource", publication.getID().toString()) + .param("eperson", submitter.getID().toString()) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", ResourcePolicyMatcher.matchFullEmbeds())) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.name", is(resourcePolicyRest.getName())), + hasJsonPath("$.description", is(resourcePolicyRest.getDescription())), + hasJsonPath("$.policyType", is(resourcePolicyRest.getPolicyType())), + hasJsonPath("$.action", is(resourcePolicyRest.getAction())), + hasJsonPath("$.startDate", is(resourcePolicyRest.getStartDate())), + hasJsonPath("$.endDate", is(resourcePolicyRest.getEndDate())), + hasJsonPath("$.type", is(resourcePolicyRest.getType()))))) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + + // create policy for submitter by root Community admin + getClient(authRootAdminToken).perform(post("/api/authz/resourcepolicies") + .content(new ObjectMapper().writeValueAsBytes(resourcePolicyRest)) + .param("resource", bitstream.getID().toString()) + .param("eperson", submitter.getID().toString()) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", ResourcePolicyMatcher.matchFullEmbeds())) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.name", is(resourcePolicyRest.getName())), + hasJsonPath("$.description", is(resourcePolicyRest.getDescription())), + hasJsonPath("$.policyType", is(resourcePolicyRest.getPolicyType())), + hasJsonPath("$.action", is(resourcePolicyRest.getAction())), + hasJsonPath("$.startDate", is(resourcePolicyRest.getStartDate())), + hasJsonPath("$.endDate", is(resourcePolicyRest.getEndDate())), + hasJsonPath("$.type", is(resourcePolicyRest.getType()))))) + .andDo(result -> idRef2.set(read(result.getResponse().getContentAsString(), "$.id"))); + + getClient(authSubmitterToken).perform(get("/api/authz/resourcepolicies/" + idRef.get())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/authz/resourcepolicies/" + idRef.get()))); + + getClient(authSubmitterToken).perform(get("/api/authz/resourcepolicies/" + idRef2.get())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/authz/resourcepolicies/" + idRef2.get()))); + } finally { + ResourcePolicyBuilder.delete(idRef.get()); + ResourcePolicyBuilder.delete(idRef2.get()); + } + } + @Test public void deleteOne() throws Exception { context.turnOffAuthorisationSystem(); @@ -1308,6 +1686,176 @@ public void deleteOneNotFoundTest() throws Exception { .andExpect(status().isNotFound()); } + @Test + public void deletePolicyByCollectionAdminTest() throws Exception { + context.turnOffAuthorisationSystem(); + EPerson colAdmin = EPersonBuilder.createEPerson(context) + .withEmail("colAdmin@mail.test") + .withPassword(password) + .build(); + + EPerson colAdmin2 = EPersonBuilder.createEPerson(context) + .withEmail("colAdmin2@mail.test") + .withPassword(password) + .build(); + + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("colSubmitter@mail.test") + .withPassword(password) + .build(); + + Community community = CommunityBuilder.createCommunity(context) + .withName("My top commynity") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("My collection") + .withAdminGroup(colAdmin) + .withSubmitterGroup(submitter) + .withEntityType("Publication") + .build(); + + CollectionBuilder.createCollection(context, community) + .withName("My Second Collection") + .withAdminGroup(colAdmin2) + .withSubmitterGroup(submitter) + .withEntityType("Publication") + .build(); + + Item publication = ItemBuilder.createItem(context, collection) + .withTitle("Public item") + .build(); + + //Add a bitstream to a publication + Bitstream bitstream = null; + try (InputStream is = IOUtils.toInputStream("ThisIsSomeDummyText", CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder.createBitstream(context, publication, is) + .withName("Bitstream") + .withDescription("description") + .withMimeType("text/plain") + .build(); + } + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + String authcolAdminToken = getAuthToken(colAdmin.getEmail(), password); + String authcolAdmin2Token = getAuthToken(colAdmin2.getEmail(), password); + String authSubmitterToken = getAuthToken(submitter.getEmail(), password); + + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) + .withDspaceObject(bitstream) + .withAction(Constants.READ) + .withPolicyType(ResourcePolicy.TYPE_CUSTOM) + .withUser(submitter) + .build(); + + // submitter can't delete own policy + getClient(authSubmitterToken).perform(delete("/api/authz/resourcepolicies/" + rp.getID())) + .andExpect(status().isForbidden()); + + // check that policy wasn't deleted + getClient(adminToken).perform(get("/api/authz/resourcepolicies/" + rp.getID())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/authz/resourcepolicies/" + rp.getID()))); + + // other collection admin can't delete policy that belong to items of other collections + getClient(authcolAdmin2Token).perform(delete("/api/authz/resourcepolicies/" + rp.getID())) + .andExpect(status().isForbidden()); + + // check that policy wasn't deleted + getClient(adminToken).perform(get("/api/authz/resourcepolicies/" + rp.getID())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/authz/resourcepolicies/" + rp.getID()))); + + // delete policy for submitter by collection admin + getClient(authcolAdminToken).perform(delete("/api/authz/resourcepolicies/" + rp.getID())) + .andExpect(status().isNoContent()); + + getClient(adminToken).perform(get("/api/authz/resourcepolicies/" + rp.getID())) + .andExpect(status().isNotFound()); + } + + @Test + public void deletePolicyBySubCommunityAdminTest() throws Exception { + context.turnOffAuthorisationSystem(); + EPerson comAdmin = EPersonBuilder.createEPerson(context) + .withEmail("comAdmin@mail.test") + .withPassword(password) + .build(); + + EPerson comAdmin2 = EPersonBuilder.createEPerson(context) + .withEmail("comAdmin2@mail.test") + .withPassword(password) + .build(); + + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("colSubmitter@mail.test") + .withPassword(password) + .build(); + + Community community = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("My First Commynity") + .withAdminGroup(comAdmin) + .build(); + + Community community2 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("My Second Commynity") + .withAdminGroup(comAdmin2) + .build(); + + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("My collection") + .withSubmitterGroup(submitter) + .withEntityType("Publication") + .build(); + + CollectionBuilder.createCollection(context, community2) + .withName("My Second Collection") + .withSubmitterGroup(submitter) + .withEntityType("Publication") + .build(); + + Item publication = ItemBuilder.createItem(context, collection) + .withTitle("Public item") + .build(); + + context.restoreAuthSystemState(); + + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) + .withDspaceObject(publication) + .withAction(Constants.WRITE) + .withPolicyType(ResourcePolicy.TYPE_CUSTOM) + .withUser(submitter) + .build(); + + String adminToken = getAuthToken(admin.getEmail(), password); + String authcomAdminToken = getAuthToken(comAdmin.getEmail(), password); + String authcomAdmin2Token = getAuthToken(comAdmin2.getEmail(), password); + + // other Community admin can't delete policy of other Community + getClient(authcomAdmin2Token).perform(delete("/api/authz/resourcepolicies/" + rp.getID())) + .andExpect(status().isForbidden()); + + getClient(adminToken).perform(get("/api/authz/resourcepolicies/" + rp.getID())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/authz/resourcepolicies/" + rp.getID()))); + + // Community admin can delete policy + getClient(authcomAdminToken).perform(delete("/api/authz/resourcepolicies/" + rp.getID())) + .andExpect(status().isNoContent()); + + // submitter can see own policy + getClient(adminToken).perform(get("/api/authz/resourcepolicies/" + rp.getID())) + .andExpect(status().isNotFound()); + } + @Test public void patchReplaceStartDateTest() throws Exception { context.turnOffAuthorisationSystem(); From f92e376896eabb56046951b6ddfbf12ba93b3753 Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Fri, 24 Jan 2025 02:03:23 +0100 Subject: [PATCH 592/939] [DURACOM-318] update security annotations on ResourcePolicyRepository (cherry picked from commit fabcc692db68e5232986ff062e849e4ec5c68c8b) (cherry picked from commit 95836c271cb4af41d3c29f2dda118eab4674f653) --- .../ResourcePolicyRestRepository.java | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResourcePolicyRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResourcePolicyRestRepository.java index a79a9fe4eaa..c0341f15e34 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResourcePolicyRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResourcePolicyRestRepository.java @@ -25,6 +25,7 @@ import org.dspace.app.rest.model.ResourcePolicyRest; import org.dspace.app.rest.model.patch.Patch; import org.dspace.app.rest.repository.patch.ResourcePatch; +import org.dspace.app.rest.security.DSpacePermissionEvaluator; import org.dspace.app.rest.utils.DSpaceObjectUtils; import org.dspace.app.rest.utils.Utils; import org.dspace.authorize.AuthorizeException; @@ -44,6 +45,8 @@ import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.hateoas.Link; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; /** @@ -73,6 +76,9 @@ public class ResourcePolicyRestRepository extends DSpaceRestRepository resourcePatch; + @Autowired + private DSpacePermissionEvaluator permissionEvaluator; + @Autowired DiscoverableEndpointsService discoverableEndpointsService; @@ -222,14 +228,13 @@ public Page findByGroup(@Parameter(value = "uuid", required } @Override - @PreAuthorize("hasAuthority('ADMIN')") + @PreAuthorize("isAuthenticated()") protected ResourcePolicyRest createAndReturn(Context context) throws AuthorizeException, SQLException { String resourceUuidStr = getRequestService().getCurrentRequest().getServletRequest().getParameter("resource"); String epersonUuidStr = getRequestService().getCurrentRequest().getServletRequest().getParameter("eperson"); String groupUuidStr = getRequestService().getCurrentRequest().getServletRequest().getParameter("group"); - if (resourceUuidStr == null) { throw new MissingParameterException("Missing resource (uuid) parameter"); } @@ -244,6 +249,11 @@ protected ResourcePolicyRest createAndReturn(Context context) throws AuthorizeEx UUID resourceUuid = UUID.fromString(resourceUuidStr); + if (isNotAuthorized(resourceUuid, "WRITE")) { + throw new AuthorizeException( + "User unauthorized to create a new ResourcePolicy for resource: " + resourceUuid); + } + try { resourcePolicyRest = mapper.readValue(req.getInputStream(), ResourcePolicyRest.class); } catch (IOException exIO) { @@ -298,7 +308,7 @@ protected ResourcePolicyRest createAndReturn(Context context) throws AuthorizeEx } @Override - @PreAuthorize("hasAuthority('ADMIN')") + @PreAuthorize("hasPermission(#id, 'resourcepolicy', 'ADMIN')") protected void delete(Context context, Integer id) throws AuthorizeException { ResourcePolicy resourcePolicy = null; try { @@ -332,4 +342,10 @@ public void afterPropertiesSet() throws Exception { Link.of("/api/" + ResourcePolicyRest.CATEGORY + "/" + ResourcePolicyRest.PLURAL_NAME + "/search", ResourcePolicyRest.PLURAL_NAME + "-search"))); } + + private boolean isNotAuthorized(UUID id, String permission) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + return !permissionEvaluator.hasPermission(authentication, id, "resourcepolicy", permission); + } + } From de5908725af8bc991440506d5af985d04ac5f87c Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Fri, 24 Jan 2025 02:06:16 +0100 Subject: [PATCH 593/939] [DURACOM-318] improve sucurity plugin (cherry picked from commit b1ce88925ea36e84a77e667a94ae5577b5ee05b6) (cherry picked from commit e9be8435ec9fffec790ad965c162f89e11fedf97) --- .../authorize/ResourcePolicyServiceImpl.java | 6 ++- .../src/main/java/org/dspace/core/Utils.java | 43 +++++++++++++++++++ ...PolicyAdminPermissionEvalutatorPlugin.java | 42 +++++++++++++----- ...cePolicyRestPermissionEvaluatorPlugin.java | 1 - 4 files changed, 80 insertions(+), 12 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java b/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java index 86998a2196e..08a8a1463c0 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicyServiceImpl.java @@ -19,6 +19,7 @@ import org.apache.commons.lang3.ObjectUtils; import org.apache.logging.log4j.Logger; import org.dspace.authorize.dao.ResourcePolicyDAO; +import org.dspace.authorize.service.AuthorizeService; import org.dspace.authorize.service.ResourcePolicyService; import org.dspace.content.DSpaceObject; import org.dspace.content.factory.ContentServiceFactory; @@ -51,6 +52,9 @@ public class ResourcePolicyServiceImpl implements ResourcePolicyService { @Autowired private GroupService groupService; + @Autowired + private AuthorizeService authorizeService; + protected ResourcePolicyServiceImpl() { } @@ -422,6 +426,6 @@ public boolean isMyResourcePolicy(Context context, EPerson eperson, Integer id) } else if (group != null && groupService.isMember(context, eperson, group)) { isMy = true; } - return isMy; + return isMy || authorizeService.isAdmin(context, eperson, resourcePolicy.getdSpaceObject()); } } diff --git a/dspace-api/src/main/java/org/dspace/core/Utils.java b/dspace-api/src/main/java/org/dspace/core/Utils.java index ea9ed57eca0..90df7240503 100644 --- a/dspace-api/src/main/java/org/dspace/core/Utils.java +++ b/dspace-api/src/main/java/org/dspace/core/Utils.java @@ -506,4 +506,47 @@ public static String interpolateConfigsInString(String string) { ConfigurationService config = DSpaceServicesFactory.getInstance().getConfigurationService(); return StringSubstitutor.replace(string, config.getProperties()); } + + /** + * Get the maximum timestamp that can be stored in a PostgreSQL database with hibernate, + * for our "distant future" access expiry date. + * @return the maximum timestamp that can be stored with Postgres + Hibernate + */ + public static Instant getMaxTimestamp() { + return LocalDateTime.of(294276, 12, 31, 23, 59, 59) + .toInstant(ZoneOffset.UTC); + } + + /** + * Get the minimum timestamp that can be stored in a PostgreSQL database, for date validation or any other + * purpose to ensure we don't try to store a date before the epoch. + * @return the minimum timestamp that can be stored with Postgres + Hibernate + */ + public static Instant getMinTimestamp() { + return LocalDateTime.of(-4713, 11, 12, 0, 0, 0) + .toInstant(ZoneOffset.UTC); + } + + /** + * Checks whether a given string can be converted to a valid {@code int} value. + *

    + * This method returns {@code false} if the input string is {@code null}, empty, + * or contains only whitespace. Otherwise, it attempts to parse the string as an + * integer using {@link Integer#parseInt(String)}. + * + * @param str the string to check for integer convertibility + * @return {@code true} if the string is non-blank and can be parsed as an integer; + * {@code false} otherwise + */ + public static boolean isConvertibleToInt(String str) { + if (StringUtils.isBlank(str)) { + return false; + } + try { + Integer.parseInt(str); + return true; + } catch (NumberFormatException e) { + return false; + } + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResourcePolicyAdminPermissionEvalutatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResourcePolicyAdminPermissionEvalutatorPlugin.java index 421d25f9406..69188b6cca5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResourcePolicyAdminPermissionEvalutatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResourcePolicyAdminPermissionEvalutatorPlugin.java @@ -9,15 +9,18 @@ import java.io.Serializable; import java.sql.SQLException; +import java.util.UUID; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.model.ResourcePolicyRest; import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.app.util.factory.UtilServiceFactory; import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.AuthorizeService; import org.dspace.authorize.service.ResourcePolicyService; import org.dspace.content.DSpaceObject; import org.dspace.core.Context; +import org.dspace.core.Utils; import org.dspace.services.RequestService; import org.dspace.services.model.Request; import org.slf4j.Logger; @@ -38,7 +41,7 @@ public class ResourcePolicyAdminPermissionEvalutatorPlugin extends RestObjectPer private static final Logger log = LoggerFactory.getLogger(ResourcePolicyRestPermissionEvaluatorPlugin.class); - public static final String RESOURCE_POLICY_PATCH = "resourcepolicy"; + public static final String RESOURCE_POLICY_TYPE = "resourcepolicy"; @Autowired AuthorizeService authorizeService; @@ -55,8 +58,9 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t DSpaceRestPermission restPermission = DSpaceRestPermission.convert(permission); - if (!DSpaceRestPermission.ADMIN.equals(restPermission) - || !StringUtils.equalsIgnoreCase(targetType, RESOURCE_POLICY_PATCH)) { + if (!DSpaceRestPermission.ADMIN.equals(restPermission) && + !DSpaceRestPermission.WRITE.equals(restPermission) || + !StringUtils.equalsIgnoreCase(targetType, RESOURCE_POLICY_TYPE)) { return false; } @@ -64,19 +68,37 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t Context context = ContextUtil.obtainContext(request.getHttpServletRequest()); try { - int resourcePolicyID = Integer.parseInt(targetId.toString()); - ResourcePolicy resourcePolicy = resourcePolicyService.find(context, resourcePolicyID); - if (resourcePolicy == null) { - throw new ResourceNotFoundException( - ResourcePolicyRest.CATEGORY + "." + ResourcePolicyRest.NAME + - " with id: " + resourcePolicyID + " not found"); + DSpaceObject dso = null; + if (Utils.isConvertibleToInt(targetId.toString())) { + var id = Integer.parseInt(targetId.toString()); + dso = getDSO(context, id); + } else { + var uuid = UUID.fromString(targetId.toString()); + dso = getDSO(context, uuid); } - DSpaceObject dso = resourcePolicy.getdSpaceObject(); return authorizeService.isAdmin(context, dso); + } catch (SQLException e) { log.error(e.getMessage(), e); } return false; } + private DSpaceObject getDSO(Context context, int id) throws SQLException { + ResourcePolicy resourcePolicy = resourcePolicyService.find(context, id); + if (resourcePolicy == null) { + throw new ResourceNotFoundException( + ResourcePolicyRest.CATEGORY + "." + ResourcePolicyRest.NAME + " with id: " + id + " not found"); + } + return resourcePolicy.getdSpaceObject(); + } + + private DSpaceObject getDSO(Context context, UUID uuid) throws SQLException { + DSpaceObject dso = UtilServiceFactory.getInstance().getDSpaceObjectUtils().findDSpaceObject(context, uuid); + if (dso == null) { + throw new ResourceNotFoundException("DSpaceObject with uuid: " + uuid + " not found"); + } + return dso; + } + } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResourcePolicyRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResourcePolicyRestPermissionEvaluatorPlugin.java index bf7ce3b53f1..5728fb86672 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResourcePolicyRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResourcePolicyRestPermissionEvaluatorPlugin.java @@ -55,7 +55,6 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t DSpaceRestPermission restPermission = DSpaceRestPermission.convert(permission); if (!DSpaceRestPermission.READ.equals(restPermission) - && !DSpaceRestPermission.WRITE.equals(restPermission) && !DSpaceRestPermission.DELETE.equals(restPermission) || !StringUtils.equalsIgnoreCase(targetType, ResourcePolicyRest.NAME)) { return false; From a24340a19725610a77b0fba964edccaae02e9852 Mon Sep 17 00:00:00 2001 From: Adamo Date: Fri, 2 May 2025 09:38:28 +0200 Subject: [PATCH 594/939] [DURACOM-318] IT fix (cherry picked from commit accba0738f7684e31af707bf5bd06508e5571621) --- .../dspace/app/rest/ResourcePolicyRestRepositoryIT.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java index 08ce836f3d7..2e5998b999b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java @@ -191,7 +191,7 @@ public void findOneUnAuthenticatedTest() throws Exception { public void findOneNotFoundTest() throws Exception { String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform(get("/api/authz/resourcepolicies/" + UUID.randomUUID().toString())) + getClient(authToken).perform(get("/api/authz/resourcepolicies/" + UUID.randomUUID())) .andExpect(status().isNotFound()); } @@ -1743,11 +1743,10 @@ public void deletePolicyByCollectionAdminTest() throws Exception { String authcolAdmin2Token = getAuthToken(colAdmin2.getEmail(), password); String authSubmitterToken = getAuthToken(submitter.getEmail(), password); - ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context, submitter, null) .withDspaceObject(bitstream) .withAction(Constants.READ) .withPolicyType(ResourcePolicy.TYPE_CUSTOM) - .withUser(submitter) .build(); // submitter can't delete own policy @@ -1826,11 +1825,10 @@ public void deletePolicyBySubCommunityAdminTest() throws Exception { context.restoreAuthSystemState(); - ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context, submitter, null) .withDspaceObject(publication) .withAction(Constants.WRITE) .withPolicyType(ResourcePolicy.TYPE_CUSTOM) - .withUser(submitter) .build(); String adminToken = getAuthToken(admin.getEmail(), password); From 2104d605bd82d1372bb6af468dd8ee3bf210bb77 Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Mon, 3 Feb 2025 19:30:16 +0100 Subject: [PATCH 595/939] [DURACOM-318] improve code (cherry picked from commit 8e0ca2e6f88b0251edf8a840f65135590c65f088) (cherry picked from commit 4270170d40833b7cd4c505a850c47f4bdff55a1c) --- dspace-api/src/main/java/org/dspace/core/Utils.java | 1 + .../ResourcePolicyAdminPermissionEvalutatorPlugin.java | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/core/Utils.java b/dspace-api/src/main/java/org/dspace/core/Utils.java index 90df7240503..59d37f71cf4 100644 --- a/dspace-api/src/main/java/org/dspace/core/Utils.java +++ b/dspace-api/src/main/java/org/dspace/core/Utils.java @@ -549,4 +549,5 @@ public static boolean isConvertibleToInt(String str) { return false; } } + } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResourcePolicyAdminPermissionEvalutatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResourcePolicyAdminPermissionEvalutatorPlugin.java index 69188b6cca5..280946e6476 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResourcePolicyAdminPermissionEvalutatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResourcePolicyAdminPermissionEvalutatorPlugin.java @@ -11,6 +11,7 @@ import java.sql.SQLException; import java.util.UUID; +import org.apache.commons.lang.math.NumberUtils; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.model.ResourcePolicyRest; import org.dspace.app.rest.utils.ContextUtil; @@ -69,7 +70,7 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t try { DSpaceObject dso = null; - if (Utils.isConvertibleToInt(targetId.toString())) { + if (NumberUtils.isNumber(targetId.toString())) { var id = Integer.parseInt(targetId.toString()); dso = getDSO(context, id); } else { From 03992be08d3756e400a2740597d5f7d8d04cf8ad Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Mon, 3 Feb 2025 22:54:52 +0100 Subject: [PATCH 596/939] [DURACOM-318] remove unused import (cherry picked from commit ed91462ccd7f99f5cf0dc326ed06ab42c13b80e0) (cherry picked from commit 8df4e35e76364bf20dad51073e33cda8f3c3f623) --- .../security/ResourcePolicyAdminPermissionEvalutatorPlugin.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResourcePolicyAdminPermissionEvalutatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResourcePolicyAdminPermissionEvalutatorPlugin.java index 280946e6476..e544665e434 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResourcePolicyAdminPermissionEvalutatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResourcePolicyAdminPermissionEvalutatorPlugin.java @@ -21,7 +21,6 @@ import org.dspace.authorize.service.ResourcePolicyService; import org.dspace.content.DSpaceObject; import org.dspace.core.Context; -import org.dspace.core.Utils; import org.dspace.services.RequestService; import org.dspace.services.model.Request; import org.slf4j.Logger; From 1732285d59fc143b1a9b8f112ef5292f861016fc Mon Sep 17 00:00:00 2001 From: "max.nuding" Date: Mon, 23 Jun 2025 10:12:56 +0200 Subject: [PATCH 597/939] add import for Intstant --- dspace-api/src/main/java/org/dspace/core/Utils.java | 1 + 1 file changed, 1 insertion(+) diff --git a/dspace-api/src/main/java/org/dspace/core/Utils.java b/dspace-api/src/main/java/org/dspace/core/Utils.java index 59d37f71cf4..df1e0218a1f 100644 --- a/dspace-api/src/main/java/org/dspace/core/Utils.java +++ b/dspace-api/src/main/java/org/dspace/core/Utils.java @@ -24,6 +24,7 @@ import java.security.NoSuchAlgorithmException; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.time.Instant; import java.util.Arrays; import java.util.Calendar; import java.util.Collections; From e90f792869b20eab9b59d985c4f8be0937c98c35 Mon Sep 17 00:00:00 2001 From: "max.nuding" Date: Mon, 23 Jun 2025 11:36:52 +0200 Subject: [PATCH 598/939] add missing imports --- dspace-api/src/main/java/org/dspace/core/Utils.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/core/Utils.java b/dspace-api/src/main/java/org/dspace/core/Utils.java index df1e0218a1f..b13eb3ba360 100644 --- a/dspace-api/src/main/java/org/dspace/core/Utils.java +++ b/dspace-api/src/main/java/org/dspace/core/Utils.java @@ -25,6 +25,8 @@ import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; import java.util.Arrays; import java.util.Calendar; import java.util.Collections; From 61c6e59a060c63189974949651861a64279e9ff0 Mon Sep 17 00:00:00 2001 From: "max.nuding" Date: Mon, 23 Jun 2025 11:44:22 +0200 Subject: [PATCH 599/939] remove jakarta import --- .../java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java index 2e5998b999b..61e86310c34 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java @@ -35,7 +35,6 @@ import javax.ws.rs.core.MediaType; import com.fasterxml.jackson.databind.ObjectMapper; -import jakarta.ws.rs.core.MediaType; import org.apache.commons.codec.CharEncoding; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; From 8a84cba371af1370f2b21e53b923733feac88c57 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 23 Jun 2025 15:13:51 -0500 Subject: [PATCH 600/939] Fix broken ITs by removing unnecessary registrations and managing context permissions better --- .../VersionedHandleIdentifierProviderIT.java | 14 +++++-- ...ntifierProviderWithCanonicalHandlesIT.java | 38 +++++++++---------- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderIT.java b/dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderIT.java index fd6340fc638..58ebd7866f8 100644 --- a/dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderIT.java +++ b/dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderIT.java @@ -48,15 +48,21 @@ public void setUp() throws Exception { collection = CollectionBuilder.createCollection(context, parentCommunity) .withName("Collection") .build(); + + context.restoreAuthSystemState(); } private void createVersions() throws SQLException, AuthorizeException { + context.turnOffAuthorisationSystem(); + itemV1 = ItemBuilder.createItem(context, collection) .withTitle("First version") .build(); firstHandle = itemV1.getHandle(); itemV2 = VersionBuilder.createVersion(context, itemV1, "Second version").build().getItem(); itemV3 = VersionBuilder.createVersion(context, itemV1, "Third version").build().getItem(); + + context.restoreAuthSystemState(); } @Test @@ -76,8 +82,7 @@ public void testDefaultVersionedHandleProvider() throws Exception { @Test public void testCollectionHandleMetadata() { - registerProvider(VersionedHandleIdentifierProvider.class); - + context.turnOffAuthorisationSystem(); Community testCommunity = CommunityBuilder.createCommunity(context) .withName("Test community") .build(); @@ -85,6 +90,7 @@ public void testCollectionHandleMetadata() { Collection testCollection = CollectionBuilder.createCollection(context, testCommunity) .withName("Test Collection") .build(); + context.restoreAuthSystemState(); List metadata = ContentServiceFactory.getInstance().getDSpaceObjectService(testCollection) .getMetadata(testCollection, "dc", "identifier", "uri", @@ -96,11 +102,11 @@ public void testCollectionHandleMetadata() { @Test public void testCommunityHandleMetadata() { - registerProvider(VersionedHandleIdentifierProvider.class); - + context.turnOffAuthorisationSystem(); Community testCommunity = CommunityBuilder.createCommunity(context) .withName("Test community") .build(); + context.restoreAuthSystemState(); List metadata = ContentServiceFactory.getInstance().getDSpaceObjectService(testCommunity) .getMetadata(testCommunity, "dc", "identifier", "uri", diff --git a/dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandlesIT.java b/dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandlesIT.java index b4f2bc9b207..fde52f42de4 100644 --- a/dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandlesIT.java +++ b/dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandlesIT.java @@ -14,7 +14,6 @@ import java.util.ArrayList; import java.util.List; -import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.authorize.AuthorizeException; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; @@ -27,10 +26,11 @@ import org.dspace.content.factory.ContentServiceFactory; import org.dspace.kernel.ServiceManager; import org.dspace.services.factory.DSpaceServicesFactory; +import org.junit.After; import org.junit.Before; import org.junit.Test; -public class VersionedHandleIdentifierProviderWithCanonicalHandlesIT extends AbstractIntegrationTestWithDatabase { +public class VersionedHandleIdentifierProviderWithCanonicalHandlesIT extends AbstractIdentifierProviderIT { private ServiceManager serviceManager; private IdentifierServiceImpl identifierService; @@ -62,34 +62,34 @@ public void setUp() throws Exception { .withName("Collection") .build(); - + registerProvider(VersionedHandleIdentifierProviderWithCanonicalHandles.class); } - private void registerProvider(Class type) { - // Register our new provider - List servicesByType = serviceManager.getServicesByType(type); - if (servicesByType.isEmpty()) { - serviceManager.registerServiceClass(type.getName(), type); - } - IdentifierProvider identifierProvider = - (IdentifierProvider) serviceManager.getServiceByName(type.getName(), type); - - // Overwrite the identifier-service's providers with the new one to ensure only this provider is used - identifierService.setProviders(List.of(identifierProvider)); + @After + @Override + public void destroy() throws Exception { + super.destroy(); + // Unregister this non-default provider + unregisterProvider(VersionedHandleIdentifierProviderWithCanonicalHandles.class); + // Re-register the default provider (for later tests) + registerProvider(VersionedHandleIdentifierProvider.class); } private void createVersions() throws SQLException, AuthorizeException { + context.turnOffAuthorisationSystem(); + itemV1 = ItemBuilder.createItem(context, collection) .withTitle("First version") .build(); firstHandle = itemV1.getHandle(); itemV2 = VersionBuilder.createVersion(context, itemV1, "Second version").build().getItem(); itemV3 = VersionBuilder.createVersion(context, itemV1, "Third version").build().getItem(); + + context.restoreAuthSystemState(); } @Test public void testCanonicalVersionedHandleProvider() throws Exception { - registerProvider(VersionedHandleIdentifierProviderWithCanonicalHandles.class); createVersions(); // Confirm the original item only has a version handle @@ -106,8 +106,7 @@ public void testCanonicalVersionedHandleProvider() throws Exception { @Test public void testCollectionHandleMetadata() { - registerProvider(VersionedHandleIdentifierProviderWithCanonicalHandles.class); - + context.turnOffAuthorisationSystem(); Community testCommunity = CommunityBuilder.createCommunity(context) .withName("Test community") .build(); @@ -115,6 +114,7 @@ public void testCollectionHandleMetadata() { Collection testCollection = CollectionBuilder.createCollection(context, testCommunity) .withName("Test Collection") .build(); + context.restoreAuthSystemState(); List metadata = ContentServiceFactory.getInstance().getDSpaceObjectService(testCollection) .getMetadata(testCollection, "dc", "identifier", "uri", @@ -126,11 +126,11 @@ public void testCollectionHandleMetadata() { @Test public void testCommunityHandleMetadata() { - registerProvider(VersionedHandleIdentifierProviderWithCanonicalHandles.class); - + context.turnOffAuthorisationSystem(); Community testCommunity = CommunityBuilder.createCommunity(context) .withName("Test community") .build(); + context.restoreAuthSystemState(); List metadata = ContentServiceFactory.getInstance().getDSpaceObjectService(testCommunity) .getMetadata(testCommunity, "dc", "identifier", "uri", From b8923c986fac7cb25066c5685067935b37e8a740 Mon Sep 17 00:00:00 2001 From: "max.nuding" Date: Tue, 24 Jun 2025 07:01:56 +0200 Subject: [PATCH 601/939] fix imports for checkstyle --- .../org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java index 61e86310c34..279b3b378fc 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java @@ -22,10 +22,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import java.text.SimpleDateFormat; import java.io.InputStream; -import java.time.LocalDate; -import java.time.format.DateTimeFormatter; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; From 230bf80b5fcde55a2abdf916c282c3439e49654b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Jun 2025 20:04:48 +0000 Subject: [PATCH 602/939] Bump log4j.version from 2.24.3 to 2.25.0 Bumps `log4j.version` from 2.24.3 to 2.25.0. Updates `org.apache.logging.log4j:log4j-api` from 2.24.3 to 2.25.0 Updates `org.apache.logging.log4j:log4j-core` from 2.24.3 to 2.25.0 Updates `org.apache.logging.log4j:log4j-1.2-api` from 2.24.3 to 2.25.0 --- updated-dependencies: - dependency-name: org.apache.logging.log4j:log4j-api dependency-version: 2.25.0 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.apache.logging.log4j:log4j-core dependency-version: 2.25.0 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.apache.logging.log4j:log4j-1.2-api dependency-version: 2.25.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e819160df56..b6450fe077b 100644 --- a/pom.xml +++ b/pom.xml @@ -38,7 +38,7 @@ 1.1.1 9.4.57.v20241219 - 2.24.3 + 2.25.0 2.0.34 1.19.0 1.7.36 From b594ebbf9e6ebb838dce7a2cc5b53b8c769e98d6 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Tue, 10 Jun 2025 14:32:51 +0300 Subject: [PATCH 603/939] dspace-api: improve date parsing for Solr sort Re-use DSpace date parsing from o.d.util.MultiFormatDateParser for more robust date support when creating of Solr browse/sort indexes. --- .../java/org/dspace/sort/OrderFormatDate.java | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/sort/OrderFormatDate.java b/dspace-api/src/main/java/org/dspace/sort/OrderFormatDate.java index f56a97776f6..a8a6e5b98a0 100644 --- a/dspace-api/src/main/java/org/dspace/sort/OrderFormatDate.java +++ b/dspace-api/src/main/java/org/dspace/sort/OrderFormatDate.java @@ -7,31 +7,28 @@ */ package org.dspace.sort; +import java.util.Date; + +import org.dspace.util.MultiFormatDateParser; + /** - * Standard date ordering delegate implementation. The only "special" need is - * to treat dates with less than 4-digit year. + * Standard date ordering delegate implementation using date format + * parsing from o.d.u.MultiFormatDateParser. * * @author Andrea Bollini + * @author Alan Orth */ public class OrderFormatDate implements OrderFormatDelegate { @Override public String makeSortString(String value, String language) { - int padding = 0; - int endYearIdx = value.indexOf('-'); - - if (endYearIdx >= 0 && endYearIdx < 4) { - padding = 4 - endYearIdx; - } else if (value.length() < 4) { - padding = 4 - value.length(); - } + Date result = MultiFormatDateParser.parse(value); - if (padding > 0) { - // padding the value from left with 0 so that 87 -> 0087, 687-11-24 - // -> 0687-11-24 - return String.format("%1$0" + padding + "d", 0) - + value; + // If parsing was successful we return the value as an ISO instant, + // otherwise we return null so Solr does not index this date value. + if (result != null) { + return result.toInstant().toString(); } else { - return value; + return null; } } } From 8839eefbe7c4d67179b9fb7ea4912e4a9abcffb2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Jun 2025 20:09:59 +0000 Subject: [PATCH 604/939] Bump org.postgresql:postgresql from 42.7.6 to 42.7.7 Bumps [org.postgresql:postgresql](https://github.com/pgjdbc/pgjdbc) from 42.7.6 to 42.7.7. - [Release notes](https://github.com/pgjdbc/pgjdbc/releases) - [Changelog](https://github.com/pgjdbc/pgjdbc/blob/master/CHANGELOG.md) - [Commits](https://github.com/pgjdbc/pgjdbc/compare/REL42.7.6...REL42.7.7) --- updated-dependencies: - dependency-name: org.postgresql:postgresql dependency-version: 42.7.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e819160df56..b13dd09b3f7 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ 5.7.14 5.6.15.Final 6.2.5.Final - 42.7.6 + 42.7.7 8.11.4 3.10.8 From 312416a716ee622efae75586112bac9a956fae08 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Jun 2025 20:24:13 +0000 Subject: [PATCH 605/939] Bump jersey.version from 2.46 to 2.47 Bumps `jersey.version` from 2.46 to 2.47. Updates `org.glassfish.jersey.core:jersey-client` from 2.46 to 2.47 Updates `org.glassfish.jersey.inject:jersey-hk2` from 2.46 to 2.47 Updates `org.glassfish.jersey.core:jersey-server` from 2.46 to 2.47 Updates `org.glassfish.jersey.containers:jersey-container-servlet` from 2.46 to 2.47 Updates `org.glassfish.jersey.media:jersey-media-json-jackson` from 2.46 to 2.47 Updates `org.glassfish.jersey.media:jersey-media-jaxb` from 2.46 to 2.47 Updates `org.glassfish.jersey.ext:jersey-spring5` from 2.46 to 2.47 --- updated-dependencies: - dependency-name: org.glassfish.jersey.core:jersey-client dependency-version: '2.47' dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.glassfish.jersey.inject:jersey-hk2 dependency-version: '2.47' dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.glassfish.jersey.core:jersey-server dependency-version: '2.47' dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.glassfish.jersey.containers:jersey-container-servlet dependency-version: '2.47' dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.glassfish.jersey.media:jersey-media-json-jackson dependency-version: '2.47' dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.glassfish.jersey.media:jersey-media-jaxb dependency-version: '2.47' dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.glassfish.jersey.ext:jersey-spring5 dependency-version: '2.47' dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e819160df56..0baab9fdf1f 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,7 @@ https://jena.apache.org/documentation/migrate_jena2_jena3.html --> 2.13.0 - 2.46 + 2.47 UTF-8 From e3b917948218754ef3a02d370c4efe0dc708b849 Mon Sep 17 00:00:00 2001 From: "max.nuding" Date: Wed, 25 Jun 2025 10:36:10 +0200 Subject: [PATCH 606/939] remove unnecessary code --- .../src/main/java/org/dspace/core/Utils.java | 46 ------------------- 1 file changed, 46 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/core/Utils.java b/dspace-api/src/main/java/org/dspace/core/Utils.java index b13eb3ba360..a1294c3317c 100644 --- a/dspace-api/src/main/java/org/dspace/core/Utils.java +++ b/dspace-api/src/main/java/org/dspace/core/Utils.java @@ -24,9 +24,6 @@ import java.security.NoSuchAlgorithmException; import java.text.ParseException; import java.text.SimpleDateFormat; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneOffset; import java.util.Arrays; import java.util.Calendar; import java.util.Collections; @@ -510,47 +507,4 @@ public static String interpolateConfigsInString(String string) { return StringSubstitutor.replace(string, config.getProperties()); } - /** - * Get the maximum timestamp that can be stored in a PostgreSQL database with hibernate, - * for our "distant future" access expiry date. - * @return the maximum timestamp that can be stored with Postgres + Hibernate - */ - public static Instant getMaxTimestamp() { - return LocalDateTime.of(294276, 12, 31, 23, 59, 59) - .toInstant(ZoneOffset.UTC); - } - - /** - * Get the minimum timestamp that can be stored in a PostgreSQL database, for date validation or any other - * purpose to ensure we don't try to store a date before the epoch. - * @return the minimum timestamp that can be stored with Postgres + Hibernate - */ - public static Instant getMinTimestamp() { - return LocalDateTime.of(-4713, 11, 12, 0, 0, 0) - .toInstant(ZoneOffset.UTC); - } - - /** - * Checks whether a given string can be converted to a valid {@code int} value. - *

    - * This method returns {@code false} if the input string is {@code null}, empty, - * or contains only whitespace. Otherwise, it attempts to parse the string as an - * integer using {@link Integer#parseInt(String)}. - * - * @param str the string to check for integer convertibility - * @return {@code true} if the string is non-blank and can be parsed as an integer; - * {@code false} otherwise - */ - public static boolean isConvertibleToInt(String str) { - if (StringUtils.isBlank(str)) { - return false; - } - try { - Integer.parseInt(str); - return true; - } catch (NumberFormatException e) { - return false; - } - } - } From 57a1de5ecdb1a1bec180bf73a05839b8e8b1340a Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 25 Jun 2025 16:41:48 -0500 Subject: [PATCH 607/939] Update deploy demo.dspace.org branch to 9.x to ensure older branches never trigger a redeploy. --- .github/workflows/reusable-docker-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml index aec03603cef..528f5779ca9 100644 --- a/.github/workflows/reusable-docker-build.yml +++ b/.github/workflows/reusable-docker-build.yml @@ -73,7 +73,7 @@ env: REDEPLOY_DEMO_URL: ${{ secrets.REDEPLOY_DEMO_URL }} # Current DSpace maintenance branch (and architecture) which is deployed to demo.dspace.org / sandbox.dspace.org # (NOTE: No deployment branch specified for sandbox.dspace.org as it uses the default_branch) - DEPLOY_DEMO_BRANCH: 'dspace-8_x' + DEPLOY_DEMO_BRANCH: 'dspace-9_x' DEPLOY_SANDBOX_BRANCH: 'main' DEPLOY_ARCH: 'linux/amd64' # Registry used during building of Docker images. (All images are later copied to docker.io registry) From d0b5911cf644cb27b33887e7e69d71696cf18ce6 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 11 Jun 2025 15:43:10 -0400 Subject: [PATCH 608/939] Make POI record buffer size adjustable. --- .../mediafilter/TikaTextExtractionFilter.java | 18 ++++++++++++++---- dspace/config/dspace.cfg | 7 +++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/TikaTextExtractionFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/TikaTextExtractionFilter.java index b7a6063165b..5728f4f42f4 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/TikaTextExtractionFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/TikaTextExtractionFilter.java @@ -18,6 +18,7 @@ import org.apache.commons.lang.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.poi.util.IOUtils; import org.apache.tika.Tika; import org.apache.tika.exception.TikaException; import org.apache.tika.metadata.Metadata; @@ -37,6 +38,8 @@ public class TikaTextExtractionFilter extends MediaFilter { private final static Logger log = LogManager.getLogger(); + private static final int DEFAULT_MAX_CHARS = 100_000; + private static final int DEFAULT_MAX_ARRAY = 100_000_000; @Override public String getFilteredName(String oldFilename) { @@ -70,9 +73,12 @@ public InputStream getDestinationStream(Item currentItem, InputStream source, bo } // Not using temporary file. We'll use Tika's default in-memory parsing. - // Get maximum characters to extract. Default is 100,000 chars, which is also Tika's default setting. String extractedText; - int maxChars = configurationService.getIntProperty("textextractor.max-chars", 100000); + // Get maximum characters to extract. Default is 100,000 chars, which is also Tika's default setting. + int maxChars = configurationService.getIntProperty("textextractor.max-chars", DEFAULT_MAX_CHARS); + // Get maximum size of structure that Tika will try to buffer. + int maxArray = configurationService.getIntProperty("textextractor.max-array", DEFAULT_MAX_ARRAY); + IOUtils.setByteArrayMaxOverride(maxArray); try { // Use Tika to extract text from input. Tika will automatically detect the file type. Tika tika = new Tika(); @@ -80,13 +86,13 @@ public InputStream getDestinationStream(Item currentItem, InputStream source, bo extractedText = tika.parseToString(source); } catch (IOException e) { System.err.format("Unable to extract text from bitstream in Item %s%n", currentItem.getID().toString()); - e.printStackTrace(); + e.printStackTrace(System.err); log.error("Unable to extract text from bitstream in Item {}", currentItem.getID().toString(), e); throw e; } catch (OutOfMemoryError oe) { System.err.format("OutOfMemoryError occurred when extracting text from bitstream in Item %s. " + "You may wish to enable 'textextractor.use-temp-file'.%n", currentItem.getID().toString()); - oe.printStackTrace(); + oe.printStackTrace(System.err); log.error("OutOfMemoryError occurred when extracting text from bitstream in Item {}. " + "You may wish to enable 'textextractor.use-temp-file'.", currentItem.getID().toString(), oe); throw oe; @@ -167,6 +173,10 @@ public void ignorableWhitespace(char[] ch, int start, int length) throws SAXExce } }); + ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + int maxArray = configurationService.getIntProperty("textextractor.max-array", DEFAULT_MAX_ARRAY); + IOUtils.setByteArrayMaxOverride(maxArray); + AutoDetectParser parser = new AutoDetectParser(); Metadata metadata = new Metadata(); // parse our source InputStream using the above custom handler diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 4621ca51dc1..c0600dbcebd 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -523,6 +523,13 @@ filter.org.dspace.app.mediafilter.PDFBoxThumbnail.inputFormats = Adobe PDF # text ("filter-media -f" ) and then reindex your site ("index-discovery -b"). #textextractor.use-temp-file = false +# Maximum size of a record buffer for text extraction. Set this if you are +# seeing RecordFormatException calling out excessive array length from +# 'dspace filter-media'. It is likely that you will need to increase the +# size of the Java heap if you greatly increase this value -- see JAVA_OPTS +# in 'bin/dspace' or 'bin/dspace/bat'. +#textextractor.max-array = 1000000 + # Custom settigns for ImageMagick Thumbnail Filters # ImageMagick and GhostScript must be installed on the server, set the path to ImageMagick and GhostScript executable # http://www.imagemagick.org/ From 172783691b29ecd21bb75fb5ddbda80ab0b9f77d Mon Sep 17 00:00:00 2001 From: abhinav Date: Wed, 25 Jun 2025 16:01:22 +0200 Subject: [PATCH 609/939] fix metadata getting cleared on patch request with invalid field (cherry picked from commit e559af1841ba52890279fdc5efdcc441e7f9eb79) --- .../DSpaceObjectMetadataReplaceOperation.java | 3 +-- .../org/dspace/app/rest/PatchMetadataIT.java | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/DSpaceObjectMetadataReplaceOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/DSpaceObjectMetadataReplaceOperation.java index 1cf15684587..a3de8d3c9ea 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/DSpaceObjectMetadataReplaceOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/DSpaceObjectMetadataReplaceOperation.java @@ -80,8 +80,7 @@ private void replace(Context context, DSpaceObject dso, DSpaceObjectService dsoS MetadataValueRest metadataValue, String index, String propertyOfMd, String valueMdProperty) { // replace entire set of metadata if (metadataField == null) { - this.replaceAllMetadata(context, dso, dsoService); - return; + throw new UnprocessableEntityException("Metadata field does not exist"); } // replace all metadata for existing key diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java index 840792f31b9..95ebb480bcf 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java @@ -1426,6 +1426,27 @@ public void moveMetadataAuthorFourToOneMultiOpTest() throws Exception { moveMetadataAuthorTest(moves, expectedOrder); } + @Test + public void replaceInvalidMetadataShouldFailTest() throws Exception { + initSimplePublicationItem(); + assertEquals(12, publicationItem.getMetadata().size()); + + String patchBody = getPatchContent(List.of( + new ReplaceOperation("/metadata/dc.contributor.invalid/0", "some value") + )); + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(patch("/api/core/items/" + publicationItem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isUnprocessableEntity()); + + publicationItem = context.reloadEntity(publicationItem); + + assertEquals(12, publicationItem.getMetadata().size()); + assertEquals(0, + itemService.getMetadata(publicationItem, "dc", "contributor", "invalid", Item.ANY, false).size()); + } + /** * This method moves an author (dc.contributor.author) within a workspace publication's "traditionalpageone" * section from position "from" to "path" using a PATCH request and verifies the order of the authors within the From 934f73f412c6deb3e3c17e9c97d96493f4aa4029 Mon Sep 17 00:00:00 2001 From: abhinav Date: Wed, 25 Jun 2025 16:19:56 +0200 Subject: [PATCH 610/939] Update PatchMetadataIT (cherry picked from commit 27d59085dbcdc702bf779a309b4ed11148ebb5dc) --- .../src/test/java/org/dspace/app/rest/PatchMetadataIT.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java index 95ebb480bcf..368128fd6e4 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java @@ -1429,7 +1429,7 @@ public void moveMetadataAuthorFourToOneMultiOpTest() throws Exception { @Test public void replaceInvalidMetadataShouldFailTest() throws Exception { initSimplePublicationItem(); - assertEquals(12, publicationItem.getMetadata().size()); + assertEquals(11, publicationItem.getMetadata().size()); String patchBody = getPatchContent(List.of( new ReplaceOperation("/metadata/dc.contributor.invalid/0", "some value") @@ -1442,7 +1442,7 @@ public void replaceInvalidMetadataShouldFailTest() throws Exception { publicationItem = context.reloadEntity(publicationItem); - assertEquals(12, publicationItem.getMetadata().size()); + assertEquals(11, publicationItem.getMetadata().size()); assertEquals(0, itemService.getMetadata(publicationItem, "dc", "contributor", "invalid", Item.ANY, false).size()); } From 130442746a96819fc8ca2eac9aad7a1f541bc885 Mon Sep 17 00:00:00 2001 From: abhinav Date: Thu, 26 Jun 2025 09:25:40 +0200 Subject: [PATCH 611/939] move the exception to patchUtils (cherry picked from commit a2dc6fbdf89e15db0d51d261dd1708e00a526d27) --- .../operation/DSpaceObjectMetadataPatchUtils.java | 15 +++++++++++++-- .../DSpaceObjectMetadataReplaceOperation.java | 3 ++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/DSpaceObjectMetadataPatchUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/DSpaceObjectMetadataPatchUtils.java index 954cc844f23..5bbd5c013c9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/DSpaceObjectMetadataPatchUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/DSpaceObjectMetadataPatchUtils.java @@ -14,6 +14,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.MetadataValueRest; import org.dspace.app.rest.model.patch.JsonValueEvaluator; import org.dspace.app.rest.model.patch.Operation; @@ -135,10 +136,20 @@ protected String extractNewValueOfMd(Operation operation) { * @param context Context the retrieve metadataField from service with string * @param operation Operation of the patch * @return The metadataField corresponding to the md element string of the operation + * Null if no metadata field is passed in the operation + * @throws UnprocessableEntityException if an invalid metadata field is passed in the operation */ - protected MetadataField getMetadataField(Context context, Operation operation) throws SQLException { + protected MetadataField getMetadataField(Context context, Operation operation) + throws SQLException, UnprocessableEntityException { String mdElement = this.extractMdFieldStringFromOperation(operation); - return metadataFieldService.findByString(context, mdElement, '.'); + if (StringUtils.isBlank(mdElement)) { + return null; + } + MetadataField metadataField = metadataFieldService.findByString(context, mdElement, '.'); + if (metadataField == null) { + throw new UnprocessableEntityException("Metadata field does not exist"); + } + return metadataField; } /** diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/DSpaceObjectMetadataReplaceOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/DSpaceObjectMetadataReplaceOperation.java index a3de8d3c9ea..1cf15684587 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/DSpaceObjectMetadataReplaceOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/DSpaceObjectMetadataReplaceOperation.java @@ -80,7 +80,8 @@ private void replace(Context context, DSpaceObject dso, DSpaceObjectService dsoS MetadataValueRest metadataValue, String index, String propertyOfMd, String valueMdProperty) { // replace entire set of metadata if (metadataField == null) { - throw new UnprocessableEntityException("Metadata field does not exist"); + this.replaceAllMetadata(context, dso, dsoService); + return; } // replace all metadata for existing key From aac45284d239a54a036eefe455c6f75bb0cd1655 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 27 Jun 2025 13:05:51 -0500 Subject: [PATCH 612/939] Correct metadata value count for dspace-7_x --- .../src/test/java/org/dspace/app/rest/PatchMetadataIT.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java index 368128fd6e4..95ebb480bcf 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java @@ -1429,7 +1429,7 @@ public void moveMetadataAuthorFourToOneMultiOpTest() throws Exception { @Test public void replaceInvalidMetadataShouldFailTest() throws Exception { initSimplePublicationItem(); - assertEquals(11, publicationItem.getMetadata().size()); + assertEquals(12, publicationItem.getMetadata().size()); String patchBody = getPatchContent(List.of( new ReplaceOperation("/metadata/dc.contributor.invalid/0", "some value") @@ -1442,7 +1442,7 @@ public void replaceInvalidMetadataShouldFailTest() throws Exception { publicationItem = context.reloadEntity(publicationItem); - assertEquals(11, publicationItem.getMetadata().size()); + assertEquals(12, publicationItem.getMetadata().size()); assertEquals(0, itemService.getMetadata(publicationItem, "dc", "contributor", "invalid", Item.ANY, false).size()); } From 66a75f522faeba45dc3081460d33a43aa9a864ba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Jun 2025 02:37:25 +0000 Subject: [PATCH 613/939] Bump the fasterxml group with 3 updates Bumps the fasterxml group with 3 updates: [com.fasterxml.jackson.core:jackson-annotations](https://github.com/FasterXML/jackson), [com.fasterxml.jackson.core:jackson-core](https://github.com/FasterXML/jackson-core) and [com.fasterxml.jackson.core:jackson-databind](https://github.com/FasterXML/jackson). Updates `com.fasterxml.jackson.core:jackson-annotations` from 2.19.0 to 2.19.1 - [Commits](https://github.com/FasterXML/jackson/commits) Updates `com.fasterxml.jackson.core:jackson-core` from 2.19.0 to 2.19.1 - [Commits](https://github.com/FasterXML/jackson-core/compare/jackson-core-2.19.0...jackson-core-2.19.1) Updates `com.fasterxml.jackson.core:jackson-core` from 2.19.0 to 2.19.1 - [Commits](https://github.com/FasterXML/jackson-core/compare/jackson-core-2.19.0...jackson-core-2.19.1) Updates `com.fasterxml.jackson.core:jackson-databind` from 2.19.0 to 2.19.1 - [Commits](https://github.com/FasterXML/jackson/commits) --- updated-dependencies: - dependency-name: com.fasterxml.jackson.core:jackson-annotations dependency-version: 2.19.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: fasterxml - dependency-name: com.fasterxml.jackson.core:jackson-core dependency-version: 2.19.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: fasterxml - dependency-name: com.fasterxml.jackson.core:jackson-core dependency-version: 2.19.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: fasterxml - dependency-name: com.fasterxml.jackson.core:jackson-databind dependency-version: 2.19.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: fasterxml ... Signed-off-by: dependabot[bot] --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 6524cf95c09..928d60d1a77 100644 --- a/pom.xml +++ b/pom.xml @@ -30,8 +30,8 @@ 3.10.8 2.31.0 - 2.19.0 - 2.19.0 + 2.19.1 + 2.19.1 1.3.2 2.3.1 2.3.9 From 76c50ac43e929a9ce702b674bb6fdf4335e471ce Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 30 Jun 2025 16:26:48 -0500 Subject: [PATCH 614/939] Update POM to use central-publishing-maven-plugin and Sonatype's Central Portal --- pom.xml | 77 +++++++++++++++++++++++++-------------------------------- 1 file changed, 33 insertions(+), 44 deletions(-) diff --git a/pom.xml b/pom.xml index 928d60d1a77..85fb3ce6ccd 100644 --- a/pom.xml +++ b/pom.xml @@ -355,9 +355,9 @@ - org.sonatype.plugins - nexus-staging-maven-plugin - 1.7.0 + org.sonatype.central + central-publishing-maven-plugin + 0.8.0 @@ -938,7 +938,7 @@ + - org.sonatype.plugins - nexus-staging-maven-plugin + org.sonatype.central + central-publishing-maven-plugin true - - - ossrh - https://oss.sonatype.org/ - - true - - false - - 10 - - + org.apache.maven.plugins maven-source-plugin @@ -987,7 +985,7 @@ - + org.apache.maven.plugins maven-javadoc-plugin @@ -1000,8 +998,8 @@ - + org.apache.maven.plugins maven-gpg-plugin @@ -1940,35 +1938,23 @@ scm:git:git@github.com:DSpace/DSpace.git scm:git:git@github.com:DSpace/DSpace.git - git@github.com:DSpace/DSpace.git + https://github.com/DSpace/DSpace dspace-7_x - - - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - - - maven-central + central https://repo.maven.apache.org/maven2 + + false + - + - maven-snapshots - https://oss.sonatype.org/content/repositories/snapshots - default + central-portal-snapshots + https://central.sonatype.com/repository/maven-snapshots/ false @@ -1980,6 +1966,9 @@ handle.net https://handle.net/maven + + false + From 8f9a7f1f92ca320476b110f595d70bcc208bbe44 Mon Sep 17 00:00:00 2001 From: Yury Bondarenko Date: Wed, 2 Jul 2025 10:24:18 +0200 Subject: [PATCH 615/939] Point directly to HTTPS address for ArXiv (cherry picked from commit cf0d6635f2683b1c8a9c116d33a3e82779e70f77) --- .../resources/spring/spring-dspace-addon-import-services.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml b/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml index 6b0ef3e9b9e..c758966f449 100644 --- a/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml +++ b/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml @@ -45,7 +45,7 @@ - + From f7dcbf1b44fb2d973000cedadb4d1f5a48bd70a1 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 2 Jul 2025 10:39:50 +0200 Subject: [PATCH 616/939] Safe and consistent XML entity handling in parsers --- .../dspace/administer/RegistryImporter.java | 7 +- .../org/dspace/administer/RegistryLoader.java | 9 +- .../org/dspace/administer/StructBuilder.java | 6 +- .../app/itemimport/ItemImportServiceImpl.java | 8 +- .../dspace/app/itemupdate/ItemArchive.java | 18 +-- .../dspace/app/launcher/ScriptLauncher.java | 3 +- .../app/sfx/SFXFileReaderServiceImpl.java | 5 +- .../org/dspace/app/util/DCInputsReader.java | 6 +- .../dspace/app/util/InitializeEntities.java | 6 +- .../app/util/SubmissionConfigReader.java | 9 +- .../java/org/dspace/app/util/XMLUtils.java | 123 ++++++++++++++++++ .../crosswalk/METSDisseminationCrosswalk.java | 3 +- .../crosswalk/MODSDisseminationCrosswalk.java | 3 +- .../content/crosswalk/QDCCrosswalk.java | 3 +- .../content/crosswalk/RoleCrosswalk.java | 3 +- .../crosswalk/XSLTIngestionCrosswalk.java | 3 +- .../dspace/content/packager/METSManifest.java | 8 +- .../dspace/content/packager/RoleIngester.java | 4 +- .../ctask/general/MetadataWebService.java | 11 +- .../provider/orcid/xml/Converter.java | 4 +- .../identifier/doi/DataCiteConnector.java | 3 +- .../ArXivImportMetadataSourceServiceImpl.java | 5 +- .../CiniiImportMetadataSourceServiceImpl.java | 15 +-- .../crossref/CrossRefAbstractProcessor.java | 5 +- .../EpoImportMetadataSourceServiceImpl.java | 13 +- ...PubmedImportMetadataSourceServiceImpl.java | 20 ++- ...PubmedEuropeMetadataSourceServiceImpl.java | 11 +- ...ScopusImportMetadataSourceServiceImpl.java | 9 +- .../WOSImportMetadataSourceServiceImpl.java | 11 +- .../CCLicenseConnectorServiceImpl.java | 3 +- .../dspace/orcid/client/OrcidClientImpl.java | 4 +- .../vocabulary/ControlledVocabulary.java | 4 +- 32 files changed, 224 insertions(+), 121 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/administer/RegistryImporter.java b/dspace-api/src/main/java/org/dspace/administer/RegistryImporter.java index 27a65342131..c74e56bce89 100644 --- a/dspace-api/src/main/java/org/dspace/administer/RegistryImporter.java +++ b/dspace-api/src/main/java/org/dspace/administer/RegistryImporter.java @@ -10,7 +10,6 @@ import java.io.File; import java.io.IOException; import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.xpath.XPath; @@ -18,6 +17,7 @@ import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; +import org.dspace.app.util.XMLUtils; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; @@ -49,8 +49,9 @@ private RegistryImporter() { } */ public static Document loadXML(String filename) throws IOException, ParserConfigurationException, SAXException { - DocumentBuilder builder = DocumentBuilderFactory.newInstance() - .newDocumentBuilder(); + // This XML builder will *not* disable external entities as XML + // registries are considered trusted content + DocumentBuilder builder = XMLUtils.getTrustedDocumentBuilder(); Document document = builder.parse(new File(filename)); diff --git a/dspace-api/src/main/java/org/dspace/administer/RegistryLoader.java b/dspace-api/src/main/java/org/dspace/administer/RegistryLoader.java index d503bfc00b7..8bb72e18521 100644 --- a/dspace-api/src/main/java/org/dspace/administer/RegistryLoader.java +++ b/dspace-api/src/main/java/org/dspace/administer/RegistryLoader.java @@ -13,7 +13,6 @@ import java.util.ArrayList; import java.util.Arrays; import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.xpath.XPath; @@ -29,6 +28,7 @@ import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; +import org.dspace.app.util.XMLUtils; import org.dspace.authorize.AuthorizeException; import org.dspace.content.BitstreamFormat; import org.dspace.content.factory.ContentServiceFactory; @@ -266,8 +266,9 @@ private static void loadFormat(Context context, Node node) */ private static Document loadXML(String filename) throws IOException, ParserConfigurationException, SAXException { - DocumentBuilder builder = DocumentBuilderFactory.newInstance() - .newDocumentBuilder(); + // This XML builder will *not* disable external entities as XML + // registries are considered trusted content + DocumentBuilder builder = XMLUtils.getTrustedDocumentBuilder(); return builder.parse(new File(filename)); } @@ -351,4 +352,4 @@ private static String[] getRepeatedElementData(Node parentElement, return data; } -} \ No newline at end of file +} diff --git a/dspace-api/src/main/java/org/dspace/administer/StructBuilder.java b/dspace-api/src/main/java/org/dspace/administer/StructBuilder.java index 8bbcfe0ff75..f2577a37b17 100644 --- a/dspace-api/src/main/java/org/dspace/administer/StructBuilder.java +++ b/dspace-api/src/main/java/org/dspace/administer/StructBuilder.java @@ -27,7 +27,6 @@ import java.util.List; import java.util.Map; import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.xpath.XPath; @@ -43,6 +42,7 @@ import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.StringUtils; +import org.dspace.app.util.XMLUtils; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Collection; import org.dspace.content.Community; @@ -613,8 +613,8 @@ private static String validateCollections(NodeList collections, int level) */ private static org.w3c.dom.Document loadXML(InputStream input) throws IOException, ParserConfigurationException, SAXException { - DocumentBuilder builder = DocumentBuilderFactory.newInstance() - .newDocumentBuilder(); + // This builder factory does not disable external DTD, entities, etc. + DocumentBuilder builder = XMLUtils.getTrustedDocumentBuilder(); org.w3c.dom.Document document = builder.parse(input); 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 1e219ee6314..3af383b04ca 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 @@ -48,7 +48,6 @@ import java.util.zip.ZipFile; import javax.mail.MessagingException; import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.xpath.XPath; @@ -67,6 +66,7 @@ import org.dspace.app.itemimport.service.ItemImportService; import org.dspace.app.util.LocalSchemaFilenameFilter; import org.dspace.app.util.RelationshipUtils; +import org.dspace.app.util.XMLUtils; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.AuthorizeService; @@ -179,6 +179,8 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea @Autowired(required = true) protected MetadataValueService metadataValueService; + protected DocumentBuilder builder; + protected String tempWorkDir; protected boolean isTest = false; @@ -1888,9 +1890,7 @@ protected String getStringValue(Node node) { */ protected Document loadXML(String filename) throws IOException, ParserConfigurationException, SAXException { - DocumentBuilder builder = DocumentBuilderFactory.newInstance() - .newDocumentBuilder(); - + DocumentBuilder builder = XMLUtils.getDocumentBuilder(); return builder.parse(new File(filename)); } diff --git a/dspace-api/src/main/java/org/dspace/app/itemupdate/ItemArchive.java b/dspace-api/src/main/java/org/dspace/app/itemupdate/ItemArchive.java index 26de45caf77..7dda65a0a75 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemupdate/ItemArchive.java +++ b/dspace-api/src/main/java/org/dspace/app/itemupdate/ItemArchive.java @@ -23,8 +23,6 @@ import java.util.Iterator; import java.util.List; import java.util.UUID; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; @@ -33,6 +31,7 @@ import org.apache.logging.log4j.Logger; import org.dspace.app.util.LocalSchemaFilenameFilter; +import org.dspace.app.util.XMLUtils; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; @@ -52,7 +51,6 @@ public class ItemArchive { public static final String DUBLIN_CORE_XML = "dublin_core.xml"; - protected static DocumentBuilder builder = null; protected Transformer transformer = null; protected List dtomList = null; @@ -95,14 +93,14 @@ public static ItemArchive create(Context context, File dir, String itemField) InputStream is = null; try { is = new FileInputStream(new File(dir, DUBLIN_CORE_XML)); - itarch.dtomList = MetadataUtilities.loadDublinCore(getDocumentBuilder(), is); + itarch.dtomList = MetadataUtilities.loadDublinCore(XMLUtils.getDocumentBuilder(), is); //The code to search for local schema files was copied from org.dspace.app.itemimport // .ItemImportServiceImpl.java File file[] = dir.listFiles(new LocalSchemaFilenameFilter()); for (int i = 0; i < file.length; i++) { is = new FileInputStream(file[i]); - itarch.dtomList.addAll(MetadataUtilities.loadDublinCore(getDocumentBuilder(), is)); + itarch.dtomList.addAll(MetadataUtilities.loadDublinCore(XMLUtils.getDocumentBuilder(), is)); } } finally { if (is != null) { @@ -126,14 +124,6 @@ public static ItemArchive create(Context context, File dir, String itemField) return itarch; } - protected static DocumentBuilder getDocumentBuilder() - throws ParserConfigurationException { - if (builder == null) { - builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); - } - return builder; - } - /** * Getter for Transformer * @@ -318,7 +308,7 @@ public void writeUndo(File undoDir) try { out = new FileOutputStream(new File(dir, "dublin_core.xml")); - Document doc = MetadataUtilities.writeDublinCore(getDocumentBuilder(), undoDtomList); + Document doc = MetadataUtilities.writeDublinCore(XMLUtils.getDocumentBuilder(), undoDtomList); MetadataUtilities.writeDocument(doc, getTransformer(), out); // if undo has delete bitstream diff --git a/dspace-api/src/main/java/org/dspace/app/launcher/ScriptLauncher.java b/dspace-api/src/main/java/org/dspace/app/launcher/ScriptLauncher.java index 89a416bfa88..ab8807c2cae 100644 --- a/dspace-api/src/main/java/org/dspace/app/launcher/ScriptLauncher.java +++ b/dspace-api/src/main/java/org/dspace/app/launcher/ScriptLauncher.java @@ -19,6 +19,7 @@ import org.apache.commons.cli.ParseException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dspace.app.util.XMLUtils; import org.dspace.core.Context; import org.dspace.scripts.DSpaceRunnable; import org.dspace.scripts.DSpaceRunnable.StepResult; @@ -314,7 +315,7 @@ public static Document getConfig(DSpaceKernelImpl kernelImpl) { String config = kernelImpl.getConfigurationService().getProperty("dspace.dir") + System.getProperty("file.separator") + "config" + System.getProperty("file.separator") + "launcher.xml"; - SAXBuilder saxBuilder = new SAXBuilder(); + SAXBuilder saxBuilder = XMLUtils.getSAXBuilder(); Document doc = null; try { doc = saxBuilder.build(config); diff --git a/dspace-api/src/main/java/org/dspace/app/sfx/SFXFileReaderServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/sfx/SFXFileReaderServiceImpl.java index 184f00a53e5..d3b447374a2 100644 --- a/dspace-api/src/main/java/org/dspace/app/sfx/SFXFileReaderServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/sfx/SFXFileReaderServiceImpl.java @@ -18,6 +18,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.app.sfx.service.SFXFileReaderService; +import org.dspace.app.util.XMLUtils; import org.dspace.content.DCPersonName; import org.dspace.content.Item; import org.dspace.content.MetadataValue; @@ -79,9 +80,9 @@ public Document parseFile(String fileName) { log.info("Parsing XML file... " + fileName); DocumentBuilder docBuilder; Document doc = null; - DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); - docBuilderFactory.setIgnoringElementContentWhitespace(true); try { + DocumentBuilderFactory docBuilderFactory = XMLUtils.getDocumentBuilderFactory(); + docBuilderFactory.setIgnoringElementContentWhitespace(true); docBuilder = docBuilderFactory.newDocumentBuilder(); } catch (ParserConfigurationException e) { log.error("Wrong parser configuration: " + e.getMessage()); diff --git a/dspace-api/src/main/java/org/dspace/app/util/DCInputsReader.java b/dspace-api/src/main/java/org/dspace/app/util/DCInputsReader.java index c77c3bf10e0..d2f3ac0dc5d 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/DCInputsReader.java +++ b/dspace-api/src/main/java/org/dspace/app/util/DCInputsReader.java @@ -121,7 +121,11 @@ private void buildInputs(String fileName) String uri = "file:" + new File(fileName).getAbsolutePath(); try { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + // This document builder factory will *not* disable external + // entities as they can be useful in managing large forms, but + // it is up to site administrators to validate the XML they are + // storing + DocumentBuilderFactory factory = XMLUtils.getTrustedDocumentBuilderFactory(); factory.setValidating(false); factory.setIgnoringComments(true); factory.setIgnoringElementContentWhitespace(true); diff --git a/dspace-api/src/main/java/org/dspace/app/util/InitializeEntities.java b/dspace-api/src/main/java/org/dspace/app/util/InitializeEntities.java index 8d3964a3e3c..4e64c18fced 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/InitializeEntities.java +++ b/dspace-api/src/main/java/org/dspace/app/util/InitializeEntities.java @@ -11,7 +11,6 @@ import java.io.IOException; import java.sql.SQLException; import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.apache.commons.cli.CommandLine; @@ -139,8 +138,9 @@ private void run(String fileLocation) throws SQLException, AuthorizeException { private void parseXMLToRelations(Context context, String fileLocation) throws AuthorizeException { try { File fXmlFile = new File(fileLocation); - DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); - DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); + // This XML builder will allow external entities, so the relationship types XML should + // be considered trusted by administrators + DocumentBuilder dBuilder = XMLUtils.getTrustedDocumentBuilder(); Document doc = dBuilder.parse(fXmlFile); doc.getDocumentElement().normalize(); diff --git a/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java b/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java index 70c5092602a..1914ded86b6 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java +++ b/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java @@ -160,8 +160,11 @@ private void buildInputs(String fileName) throws SubmissionConfigReaderException String uri = "file:" + new File(fileName).getAbsolutePath(); try { - DocumentBuilderFactory factory = DocumentBuilderFactory - .newInstance(); + // This document builder factory will *not* disable external + // entities as they can be useful in managing large forms, but + // it is up to site administrators to validate the XML they are + // storing + DocumentBuilderFactory factory = XMLUtils.getTrustedDocumentBuilderFactory(); factory.setValidating(false); factory.setIgnoringComments(true); factory.setIgnoringElementContentWhitespace(true); @@ -681,4 +684,4 @@ public List getCollectionsBySubmissionConfig(Context context, String } return results; } -} \ No newline at end of file +} diff --git a/dspace-api/src/main/java/org/dspace/app/util/XMLUtils.java b/dspace-api/src/main/java/org/dspace/app/util/XMLUtils.java index c39d0d26fd5..389d53fe6da 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/XMLUtils.java +++ b/dspace-api/src/main/java/org/dspace/app/util/XMLUtils.java @@ -9,8 +9,13 @@ import java.util.ArrayList; import java.util.List; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.stream.XMLInputFactory; import org.apache.commons.lang3.StringUtils; +import org.jdom2.input.SAXBuilder; import org.w3c.dom.Element; import org.w3c.dom.NodeList; @@ -161,4 +166,122 @@ public static List getElementValueArrayList(Element rootElement, } return result; } + + /** + * Initialize and return a javax DocumentBuilderFactory with NO security + * applied. This is intended only for internal, administrative/configuration + * use where external entities and other dangerous features are actually + * purposefully included. + * The method here is tiny, but may be expanded with other features like + * whitespace handling, and calling this method name helps to document + * the fact that the caller knows it is trusting the XML source / factory. + * + * @return document builder factory to generate new builders + * @throws ParserConfigurationException + */ + public static DocumentBuilderFactory getTrustedDocumentBuilderFactory() + throws ParserConfigurationException { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + return factory; + } + + /** + * Initialize and return the javax DocumentBuilderFactory with some basic security + * applied to avoid XXE attacks and other unwanted content inclusion + * @return document builder factory to generate new builders + * @throws ParserConfigurationException + */ + public static DocumentBuilderFactory getDocumentBuilderFactory() + throws ParserConfigurationException { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + // No DOCTYPE / DTDs + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + // No external general entities + factory.setFeature("http://xml.org/sax/features/external-general-entities", false); + // No external parameter entities + factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + // No external DTDs + factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + // Even if entities somehow get defined, they will not be expanded + factory.setExpandEntityReferences(false); + // Disable "XInclude" markup processing + factory.setXIncludeAware(false); + + return factory; + } + + /** + * Initialize and return a javax DocumentBuilder with NO security + * applied. This is intended only for internal, administrative/configuration + * use where external entities and other dangerous features are actually + * purposefully included. + * The method here is tiny, but may be expanded with other features like + * whitespace handling, and calling this method name helps to document + * the fact that the caller knows it is trusting the XML source / builder + * + * @return document builder with no security features set + * @throws ParserConfigurationException + */ + public static DocumentBuilder getTrustedDocumentBuilder() + throws ParserConfigurationException { + return getTrustedDocumentBuilderFactory().newDocumentBuilder(); + } + + /** + * Initialize and return the javax DocumentBuilder with some basic security applied + * to avoid XXE attacks and other unwanted content inclusion + * @return document builder for use in XML parsing + * @throws ParserConfigurationException + */ + public static DocumentBuilder getDocumentBuilder() + throws ParserConfigurationException { + return getDocumentBuilderFactory().newDocumentBuilder(); + } + + /** + * Initialize and return the SAX document builder with some basic security applied + * to avoid XXE attacks and other unwanted content inclusion + * @return SAX document builder for use in XML parsing + */ + public static SAXBuilder getSAXBuilder() { + return getSAXBuilder(false); + } + + /** + * Initialize and return the SAX document builder with some basic security applied + * to avoid XXE attacks and other unwanted content inclusion + * @param validate whether to use JDOM XSD validation + * @return SAX document builder for use in XML parsing + */ + public static SAXBuilder getSAXBuilder(boolean validate) { + SAXBuilder saxBuilder = new SAXBuilder(); + if (validate) { + saxBuilder.setValidation(true); + } + // No DOCTYPE / DTDs + saxBuilder.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + // No external general entities + saxBuilder.setFeature("http://xml.org/sax/features/external-general-entities", false); + // No external parameter entities + saxBuilder.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + // No external DTDs + saxBuilder.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + // Don't expand entities + saxBuilder.setExpandEntities(false); + + return saxBuilder; + } + + /** + * Initialize and return the Java XML Input Factory with some basic security applied + * to avoid XXE attacks and other unwanted content inclusion + * @return XML input factory for use in XML parsing + */ + public static XMLInputFactory getXMLInputFactory() { + XMLInputFactory xmlInputFactory = XMLInputFactory.newFactory(); + xmlInputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false); + + return xmlInputFactory; + } + } diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/METSDisseminationCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/METSDisseminationCrosswalk.java index b8a4a8aef39..5ceacc933e4 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/METSDisseminationCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/METSDisseminationCrosswalk.java @@ -14,6 +14,7 @@ import java.util.List; import org.apache.commons.lang3.ArrayUtils; +import org.dspace.app.util.XMLUtils; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; import org.dspace.content.packager.PackageDisseminator; @@ -129,7 +130,7 @@ public Element disseminateElement(Context context, DSpaceObject dso) try { //Return just the root Element of the METS file - SAXBuilder builder = new SAXBuilder(); + SAXBuilder builder = XMLUtils.getSAXBuilder(); Document metsDocument = builder.build(tempFile); return metsDocument.getRootElement(); } catch (JDOMException je) { diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/MODSDisseminationCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/MODSDisseminationCrosswalk.java index 1e63be5ba1b..205b3ef5b34 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/MODSDisseminationCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/MODSDisseminationCrosswalk.java @@ -22,6 +22,7 @@ import org.apache.commons.lang3.ArrayUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dspace.app.util.XMLUtils; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Collection; import org.dspace.content.Community; @@ -144,7 +145,7 @@ public static String[] getPluginNames() { MODS_NS.getURI() + " " + MODS_XSD; private static final XMLOutputter outputUgly = new XMLOutputter(); - private static final SAXBuilder builder = new SAXBuilder(); + private static final SAXBuilder builder = XMLUtils.getSAXBuilder(); private Map modsMap = null; diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/QDCCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/QDCCrosswalk.java index 2fdbaaad003..51e6357d93e 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/QDCCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/QDCCrosswalk.java @@ -22,6 +22,7 @@ import org.apache.commons.lang3.ArrayUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dspace.app.util.XMLUtils; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; @@ -125,7 +126,7 @@ public class QDCCrosswalk extends SelfNamedPlugin // XML schemaLocation fragment for this crosswalk, from config. private String schemaLocation = null; - private static final SAXBuilder builder = new SAXBuilder(); + private static final SAXBuilder builder = XMLUtils.getSAXBuilder(); protected ItemService itemService = ContentServiceFactory.getInstance().getItemService(); diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/RoleCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/RoleCrosswalk.java index 2c763036ce3..8d5bf49902c 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/RoleCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/RoleCrosswalk.java @@ -13,6 +13,7 @@ import java.sql.SQLException; import java.util.List; +import org.dspace.app.util.XMLUtils; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; import org.dspace.content.packager.PackageDisseminator; @@ -208,7 +209,7 @@ public Element disseminateElement(Context context, DSpaceObject dso) try { //Try to parse our XML results (which were disseminated by the Packager) - SAXBuilder builder = new SAXBuilder(); + SAXBuilder builder = XMLUtils.getSAXBuilder(); Document xmlDocument = builder.build(tempFile); //If XML parsed successfully, return root element of doc if (xmlDocument != null && xmlDocument.hasRootElement()) { diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/XSLTIngestionCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/XSLTIngestionCrosswalk.java index 63ef5f7336c..b07b2b2228e 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/XSLTIngestionCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/XSLTIngestionCrosswalk.java @@ -18,6 +18,7 @@ import org.apache.commons.lang3.ArrayUtils; import org.apache.logging.log4j.Logger; +import org.dspace.app.util.XMLUtils; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Collection; import org.dspace.content.Community; @@ -297,7 +298,7 @@ public static void main(String[] argv) throws Exception { "Failed to initialize transformer, probably error loading stylesheet."); } - SAXBuilder builder = new SAXBuilder(); + SAXBuilder builder = XMLUtils.getSAXBuilder(); Document inDoc = builder.build(new FileInputStream(argv[i + 1])); XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat()); List dimList; diff --git a/dspace-api/src/main/java/org/dspace/content/packager/METSManifest.java b/dspace-api/src/main/java/org/dspace/content/packager/METSManifest.java index 3399bdf0f07..a1ed3c12437 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/METSManifest.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/METSManifest.java @@ -20,6 +20,7 @@ import org.apache.commons.codec.binary.Base64; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dspace.app.util.XMLUtils; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; @@ -265,12 +266,13 @@ protected METSManifest(SAXBuilder builder, Element mets, String configName) { public static METSManifest create(InputStream is, boolean validate, String configName) throws IOException, MetadataValidationException { - SAXBuilder builder = new SAXBuilder(validate); + SAXBuilder builder = XMLUtils.getSAXBuilder(); builder.setIgnoringElementContentWhitespace(true); // Set validation feature if (validate) { + builder.setValidation(true); builder.setFeature("http://apache.org/xml/features/validation/schema", true); // Tell the parser where local copies of schemas are, to speed up @@ -278,10 +280,6 @@ public static METSManifest create(InputStream is, boolean validate, String confi if (localSchemas.length() > 0) { builder.setProperty("http://apache.org/xml/properties/schema/external-schemaLocation", localSchemas); } - } else { - // disallow DTD parsing to ensure no XXE attacks can occur. - // See https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html - builder.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); } // Parse the METS file diff --git a/dspace-api/src/main/java/org/dspace/content/packager/RoleIngester.java b/dspace-api/src/main/java/org/dspace/content/packager/RoleIngester.java index 2ce3f50a3cb..d71012ff835 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/RoleIngester.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/RoleIngester.java @@ -386,7 +386,7 @@ public void ingestStream(Context context, DSpaceObject parent, Document document; try { - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilderFactory dbf = XMLUtils.getDocumentBuilderFactory(); dbf.setIgnoringComments(true); dbf.setCoalescing(true); DocumentBuilder db = dbf.newDocumentBuilder(); @@ -420,7 +420,7 @@ public DSpaceObject ingest(Context context, DSpaceObject parent, Document document; try { - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilderFactory dbf = XMLUtils.getDocumentBuilderFactory(); dbf.setIgnoringComments(true); dbf.setCoalescing(true); DocumentBuilder db = dbf.newDocumentBuilder(); diff --git a/dspace-api/src/main/java/org/dspace/ctask/general/MetadataWebService.java b/dspace-api/src/main/java/org/dspace/ctask/general/MetadataWebService.java index fc62d7a4b23..1b618ad31fb 100644 --- a/dspace-api/src/main/java/org/dspace/ctask/general/MetadataWebService.java +++ b/dspace-api/src/main/java/org/dspace/ctask/general/MetadataWebService.java @@ -37,6 +37,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.app.client.DSpaceHttpClientFactory; +import org.dspace.app.util.XMLUtils; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; @@ -176,7 +177,7 @@ public void init(Curator curator, String taskId) throws IOException { fieldSeparator = (fldSep != null) ? fldSep : " "; urlTemplate = taskProperty("template"); templateParam = urlTemplate.substring(urlTemplate.indexOf("{") + 1, - urlTemplate.indexOf("}")); + urlTemplate.indexOf("}")); String[] parsed = parseTransform(templateParam); lookupField = parsed[0]; lookupTransform = parsed[1]; @@ -204,13 +205,9 @@ public void init(Curator curator, String taskId) throws IOException { } } // initialize response document parser - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - factory.setNamespaceAware(true); try { - // disallow DTD parsing to ensure no XXE attacks can occur - // See https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html - factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); - factory.setXIncludeAware(false); + DocumentBuilderFactory factory = XMLUtils.getDocumentBuilderFactory(); + factory.setNamespaceAware(true); docBuilder = factory.newDocumentBuilder(); } catch (ParserConfigurationException pcE) { log.error("caught exception: " + pcE); diff --git a/dspace-api/src/main/java/org/dspace/external/provider/orcid/xml/Converter.java b/dspace-api/src/main/java/org/dspace/external/provider/orcid/xml/Converter.java index 756b8654f28..af4e31e7fae 100644 --- a/dspace-api/src/main/java/org/dspace/external/provider/orcid/xml/Converter.java +++ b/dspace-api/src/main/java/org/dspace/external/provider/orcid/xml/Converter.java @@ -31,9 +31,7 @@ public abstract class Converter { protected Object unmarshall(InputStream input, Class type) throws SAXException, URISyntaxException { try { - XMLInputFactory xmlInputFactory = XMLInputFactory.newFactory(); - // disallow DTD parsing to ensure no XXE attacks can occur - xmlInputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false); + XMLInputFactory xmlInputFactory = XMLUtils.getXMLInputFactory(); XMLStreamReader xmlStreamReader = xmlInputFactory.createXMLStreamReader(input); JAXBContext context = JAXBContext.newInstance(type); diff --git a/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java b/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java index 86c7b8322e8..c8e316eaf66 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java +++ b/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java @@ -38,6 +38,7 @@ import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.util.EntityUtils; import org.dspace.app.client.DSpaceHttpClientFactory; +import org.dspace.app.util.XMLUtils; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; import org.dspace.content.crosswalk.CrosswalkException; @@ -832,7 +833,7 @@ protected String extractAlternateIdentifier(Context context, String content) } // parse the XML - SAXBuilder saxBuilder = new SAXBuilder(); + SAXBuilder saxBuilder = XMLUtils.getSAXBuilder(); Document doc = null; try { doc = saxBuilder.build(new ByteArrayInputStream(content.getBytes("UTF-8"))); diff --git a/dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java index 4369b0d48b4..a8d01fc3fd2 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java @@ -22,6 +22,7 @@ import javax.ws.rs.core.Response; import org.apache.commons.lang3.StringUtils; +import org.dspace.app.util.XMLUtils; import org.dspace.content.Item; import org.dspace.importer.external.datamodel.ImportRecord; import org.dspace.importer.external.datamodel.Query; @@ -218,7 +219,7 @@ public Integer call() throws Exception { if (response.getStatus() == 200) { String responseString = response.readEntity(String.class); - SAXBuilder saxBuilder = new SAXBuilder(); + SAXBuilder saxBuilder = XMLUtils.getSAXBuilder(); Document document = saxBuilder.build(new StringReader(responseString)); Element root = document.getRootElement(); @@ -399,7 +400,7 @@ private String getQuery(Query query) { private List splitToRecords(String recordsSrc) { try { - SAXBuilder saxBuilder = new SAXBuilder(); + SAXBuilder saxBuilder = XMLUtils.getSAXBuilder(); Document document = saxBuilder.build(new StringReader(recordsSrc)); Element root = document.getRootElement(); diff --git a/dspace-api/src/main/java/org/dspace/importer/external/cinii/CiniiImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/cinii/CiniiImportMetadataSourceServiceImpl.java index 82a4b2d7796..41c80ab7fe6 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/cinii/CiniiImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/cinii/CiniiImportMetadataSourceServiceImpl.java @@ -26,6 +26,7 @@ import org.apache.http.client.utils.URIBuilder; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dspace.app.util.XMLUtils; import org.dspace.content.Item; import org.dspace.importer.external.datamodel.ImportRecord; import org.dspace.importer.external.datamodel.Query; @@ -301,9 +302,7 @@ protected List search(String id, String appId) private List splitToRecords(String recordsSrc) { try { - SAXBuilder saxBuilder = new SAXBuilder(); - // disallow DTD parsing to ensure no XXE attacks can occur - saxBuilder.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true); + SAXBuilder saxBuilder = XMLUtils.getSAXBuilder(); Document document = saxBuilder.build(new StringReader(recordsSrc)); Element root = document.getRootElement(); return root.getChildren(); @@ -356,9 +355,7 @@ private List getCiniiIds(String appId, Integer maxResult, String author, Map> params = new HashMap>(); String response = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); int url_len = this.url.length() - 1; - SAXBuilder saxBuilder = new SAXBuilder(); - // disallow DTD parsing to ensure no XXE attacks can occur - saxBuilder.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true); + SAXBuilder saxBuilder = XMLUtils.getSAXBuilder(); Document document = saxBuilder.build(new StringReader(response)); Element root = document.getRootElement(); List namespaces = Arrays.asList( @@ -420,9 +417,7 @@ private Integer countCiniiElement(String appId, Integer maxResult, String author Map> params = new HashMap>(); String response = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); - SAXBuilder saxBuilder = new SAXBuilder(); - // disallow DTD parsing to ensure no XXE attacks can occur - saxBuilder.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true); + SAXBuilder saxBuilder = XMLUtils.getSAXBuilder(); Document document = saxBuilder.build(new StringReader(response)); Element root = document.getRootElement(); List namespaces = Arrays @@ -449,4 +444,4 @@ private MetadatumDTO createIdentifier(String id) { return metadatumDTO; } -} \ No newline at end of file +} diff --git a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAbstractProcessor.java b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAbstractProcessor.java index 1b6da9d37b1..99f1ee37a54 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAbstractProcessor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAbstractProcessor.java @@ -12,7 +12,6 @@ import java.util.ArrayList; import java.util.Collection; import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import com.fasterxml.jackson.core.JsonProcessingException; @@ -21,6 +20,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dspace.app.util.XMLUtils; import org.dspace.importer.external.metadatamapping.contributor.JsonPathMetadataProcessor; import org.w3c.dom.Document; import org.w3c.dom.Node; @@ -64,10 +64,9 @@ private String prettifyAbstract(String abstractValue) { } String xmlString = "" + abstractValue + ""; - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); Document xmlDoc; try { - DocumentBuilder builder = factory.newDocumentBuilder(); + DocumentBuilder builder = XMLUtils.getDocumentBuilder(); InputSource is = new InputSource(new StringReader(xmlString)); xmlDoc = builder.parse(is); } catch (SAXException | IOException | ParserConfigurationException e) { diff --git a/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoImportMetadataSourceServiceImpl.java index fbae302bca6..7edd3f9d01c 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoImportMetadataSourceServiceImpl.java @@ -32,6 +32,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.xerces.impl.dv.util.Base64; +import org.dspace.app.util.XMLUtils; import org.dspace.content.Item; import org.dspace.importer.external.datamodel.ImportRecord; import org.dspace.importer.external.datamodel.Query; @@ -397,9 +398,7 @@ private Integer countDocument(String bearer, String query) { String response = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); - SAXBuilder saxBuilder = new SAXBuilder(); - // disallow DTD parsing to ensure no XXE attacks can occur - saxBuilder.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true); + SAXBuilder saxBuilder = XMLUtils.getSAXBuilder(); Document document = saxBuilder.build(new StringReader(response)); Element root = document.getRootElement(); @@ -436,9 +435,7 @@ private List searchDocumentIds(String bearer, String query, int s String response = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); - SAXBuilder saxBuilder = new SAXBuilder(); - // disallow DTD parsing to ensure no XXE attacks can occur - saxBuilder.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true); + SAXBuilder saxBuilder = XMLUtils.getSAXBuilder(); Document document = saxBuilder.build(new StringReader(response)); Element root = document.getRootElement(); @@ -489,9 +486,7 @@ private List searchDocument(String bearer, String id, String docTy private List splitToRecords(String recordsSrc) { try { - SAXBuilder saxBuilder = new SAXBuilder(); - // disallow DTD parsing to ensure no XXE attacks can occur - saxBuilder.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true); + SAXBuilder saxBuilder = XMLUtils.getSAXBuilder(); Document document = saxBuilder.build(new StringReader(recordsSrc)); Element root = document.getRootElement(); List namespaces = Arrays.asList(Namespace.getNamespace("ns", "http://www.epo.org/exchange")); diff --git a/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java index 000ef19eaec..7718e59e483 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java @@ -24,6 +24,7 @@ import com.google.common.io.CharStreams; import org.apache.commons.lang3.StringUtils; import org.apache.http.client.utils.URIBuilder; +import org.dspace.app.util.XMLUtils; import org.dspace.content.Item; import org.dspace.importer.external.datamodel.ImportRecord; import org.dspace.importer.external.datamodel.Query; @@ -233,11 +234,13 @@ private String getSingleElementValue(String src, String elementName) { String value = null; try { - SAXBuilder saxBuilder = new SAXBuilder(); - // Disallow external entities & entity expansion to protect against XXE attacks - // (NOTE: We receive errors if we disable all DTDs for PubMed, so this is the best we can do) - saxBuilder.setFeature("http://xml.org/sax/features/external-general-entities", false); - saxBuilder.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + SAXBuilder saxBuilder = XMLUtils.getSAXBuilder(); + // To properly parse PubMed responses, we must allow DOCTYPE/DTDs overall but + // we can still take advantage of entities themselves being disabled, and not + // expanded. + saxBuilder.setFeature("http://apache.org/xml/features/disallow-doctype-decl", false); + saxBuilder.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", + true); Document document = saxBuilder.build(new StringReader(src)); Element root = document.getRootElement(); @@ -354,12 +357,7 @@ public Collection call() throws Exception { private List splitToRecords(String recordsSrc) { try { - SAXBuilder saxBuilder = new SAXBuilder(); - // Disallow external entities & entity expansion to protect against XXE attacks - // (NOTE: We receive errors if we disable all DTDs for PubMed, so this is the best we can do) - saxBuilder.setFeature("http://xml.org/sax/features/external-general-entities", false); - saxBuilder.setFeature("http://xml.org/sax/features/external-parameter-entities", false); - saxBuilder.setExpandEntities(false); + SAXBuilder saxBuilder = XMLUtils.getSAXBuilder(); Document document = saxBuilder.build(new StringReader(recordsSrc)); Element root = document.getRootElement(); diff --git a/dspace-api/src/main/java/org/dspace/importer/external/pubmedeurope/PubmedEuropeMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/pubmedeurope/PubmedEuropeMetadataSourceServiceImpl.java index 7cd297eb281..24f40339dde 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/pubmedeurope/PubmedEuropeMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/pubmedeurope/PubmedEuropeMetadataSourceServiceImpl.java @@ -24,6 +24,7 @@ import org.apache.http.client.utils.URIBuilder; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dspace.app.util.XMLUtils; import org.dspace.content.Item; import org.dspace.importer.external.datamodel.ImportRecord; import org.dspace.importer.external.datamodel.Query; @@ -292,9 +293,7 @@ public Integer count(String query) throws URISyntaxException, ClientProtocolExce Map> params = new HashMap>(); String response = liveImportClient.executeHttpGetRequest(1000, buildURI(1, query), params); - SAXBuilder saxBuilder = new SAXBuilder(); - // disallow DTD parsing to ensure no XXE attacks can occur - saxBuilder.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true); + SAXBuilder saxBuilder = XMLUtils.getSAXBuilder(); Document document = saxBuilder.build(new StringReader(response)); Element root = document.getRootElement(); Element element = root.getChild("hitCount"); @@ -365,9 +364,7 @@ public List search(String query, Integer size, Integer start) thro String response = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); String cursorMark = StringUtils.EMPTY; if (StringUtils.isNotBlank(response)) { - SAXBuilder saxBuilder = new SAXBuilder(); - // disallow DTD parsing to ensure no XXE attacks can occur - saxBuilder.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true); + SAXBuilder saxBuilder = XMLUtils.getSAXBuilder(); Document document = saxBuilder.build(new StringReader(response)); XPathFactory xpfac = XPathFactory.instance(); XPathExpression xPath = xpfac.compile("//responseWrapper/resultList/result", @@ -419,4 +416,4 @@ public void setUrl(String url) { this.url = url; } -} \ No newline at end of file +} diff --git a/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/ScopusImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/ScopusImportMetadataSourceServiceImpl.java index a3f74694bec..22e3534ca89 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/ScopusImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/ScopusImportMetadataSourceServiceImpl.java @@ -26,6 +26,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dspace.app.util.XMLUtils; import org.dspace.content.Item; import org.dspace.importer.external.datamodel.ImportRecord; import org.dspace.importer.external.datamodel.Query; @@ -208,9 +209,7 @@ public Integer call() throws Exception { return 0; } - SAXBuilder saxBuilder = new SAXBuilder(); - // disallow DTD parsing to ensure no XXE attacks can occur - saxBuilder.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true); + SAXBuilder saxBuilder = XMLUtils.getSAXBuilder(); Document document = saxBuilder.build(new StringReader(response)); Element root = document.getRootElement(); @@ -397,9 +396,7 @@ private Map getRequestParameters(String query, String viewMode, private List splitToRecords(String recordsSrc) { try { - SAXBuilder saxBuilder = new SAXBuilder(); - // disallow DTD parsing to ensure no XXE attacks can occur - saxBuilder.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true); + SAXBuilder saxBuilder = XMLUtils.getSAXBuilder(); Document document = saxBuilder.build(new StringReader(recordsSrc)); Element root = document.getRootElement(); String totalResults = root.getChildText("totalResults", Namespace.getNamespace("http://a9.com/-/spec/opensearch/1.1/")); diff --git a/dspace-api/src/main/java/org/dspace/importer/external/wos/service/WOSImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/wos/service/WOSImportMetadataSourceServiceImpl.java index 9bffa2a84a1..2ac63d50513 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/wos/service/WOSImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/wos/service/WOSImportMetadataSourceServiceImpl.java @@ -26,6 +26,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dspace.app.util.XMLUtils; import org.dspace.content.Item; import org.dspace.importer.external.datamodel.ImportRecord; import org.dspace.importer.external.datamodel.Query; @@ -145,9 +146,7 @@ public Integer call() throws Exception { params.put(HEADER_PARAMETERS, getRequestParameters()); String response = liveImportClient.executeHttpGetRequest(timeout, url, params); - SAXBuilder saxBuilder = new SAXBuilder(); - // disallow DTD parsing to ensure no XXE attacks can occur - saxBuilder.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true); + SAXBuilder saxBuilder = XMLUtils.getSAXBuilder(); Document document = saxBuilder.build(new StringReader(response)); Element root = document.getRootElement(); XPathExpression xpath = XPathFactory.instance().compile("//*[@name=\"RecordsFound\"]", @@ -288,9 +287,7 @@ private boolean isIsi(String query) { private List splitToRecords(String recordsSrc) { try { - SAXBuilder saxBuilder = new SAXBuilder(); - // disallow DTD parsing to ensure no XXE attacks can occur - saxBuilder.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true); + SAXBuilder saxBuilder = XMLUtils.getSAXBuilder(); Document document = saxBuilder.build(new StringReader(recordsSrc)); Element root = document.getRootElement(); String cData = XPathFactory.instance().compile("//*[@name=\"Records\"]", @@ -332,4 +329,4 @@ public void setApiKey(String apiKey) { this.apiKey = apiKey; } -} \ No newline at end of file +} diff --git a/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java b/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java index 1d777a2e13c..3c9088bda5d 100644 --- a/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java @@ -28,6 +28,7 @@ import org.apache.http.util.EntityUtils; import org.apache.logging.log4j.Logger; import org.dspace.app.client.DSpaceHttpClientFactory; +import org.dspace.app.util.XMLUtils; import org.dspace.services.ConfigurationService; import org.jdom2.Attribute; import org.jdom2.Document; @@ -50,7 +51,7 @@ public class CCLicenseConnectorServiceImpl implements CCLicenseConnectorService, private Logger log = org.apache.logging.log4j.LogManager.getLogger(CCLicenseConnectorServiceImpl.class); private CloseableHttpClient client; - protected SAXBuilder parser = new SAXBuilder(); + protected SAXBuilder parser = XMLUtils.getSAXBuilder(); private String postArgument = "answers"; private String postAnswerFormat = diff --git a/dspace-api/src/main/java/org/dspace/orcid/client/OrcidClientImpl.java b/dspace-api/src/main/java/org/dspace/orcid/client/OrcidClientImpl.java index 954336da257..682e90828e3 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/client/OrcidClientImpl.java +++ b/dspace-api/src/main/java/org/dspace/orcid/client/OrcidClientImpl.java @@ -43,6 +43,7 @@ import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.message.BasicNameValuePair; import org.dspace.app.client.DSpaceHttpClientFactory; +import org.dspace.app.util.XMLUtils; import org.dspace.orcid.OrcidToken; import org.dspace.orcid.exception.OrcidClientException; import org.dspace.orcid.model.OrcidEntityType; @@ -351,8 +352,7 @@ private String marshall(Object object) throws JAXBException { @SuppressWarnings("unchecked") private T unmarshall(HttpEntity entity, Class clazz) throws Exception { JAXBContext jaxbContext = JAXBContext.newInstance(clazz); - XMLInputFactory xmlInputFactory = XMLInputFactory.newFactory(); - xmlInputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false); + XMLInputFactory xmlInputFactory = XMLUtils.getXMLInputFactory(); XMLStreamReader xmlStreamReader = xmlInputFactory.createXMLStreamReader(entity.getContent()); Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); return (T) unmarshaller.unmarshal(xmlStreamReader); diff --git a/dspace-api/src/main/java/org/dspace/vocabulary/ControlledVocabulary.java b/dspace-api/src/main/java/org/dspace/vocabulary/ControlledVocabulary.java index 7f2bdc6ef77..bd19a1254fe 100644 --- a/dspace-api/src/main/java/org/dspace/vocabulary/ControlledVocabulary.java +++ b/dspace-api/src/main/java/org/dspace/vocabulary/ControlledVocabulary.java @@ -12,7 +12,6 @@ import java.util.ArrayList; import java.util.List; import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.xpath.XPath; @@ -20,6 +19,7 @@ import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; +import org.dspace.app.util.XMLUtils; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.w3c.dom.Document; @@ -71,7 +71,7 @@ public static ControlledVocabulary loadVocabulary(String fileName) File controlledVocFile = new File(filePath.toString()); if (controlledVocFile.exists()) { - DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + DocumentBuilder builder = XMLUtils.getDocumentBuilder(); Document document = builder.parse(controlledVocFile); XPath xPath = XPathFactory.newInstance().newXPath(); Node node = (Node) xPath.compile("node").evaluate(document, XPathConstants.NODE); From 8c80b67b04028a747515c84f0fbd1ef8ecd1ee40 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 2 Jul 2025 15:30:04 -0500 Subject: [PATCH 617/939] EPO and PubMed only need to allow for DOCTYPEs. All other XML security changes can be used. --- .../service/EpoImportMetadataSourceServiceImpl.java | 4 ++++ .../service/PubmedImportMetadataSourceServiceImpl.java | 10 +++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoImportMetadataSourceServiceImpl.java index 7edd3f9d01c..552f607827a 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoImportMetadataSourceServiceImpl.java @@ -399,6 +399,10 @@ private Integer countDocument(String bearer, String query) { String response = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); SAXBuilder saxBuilder = XMLUtils.getSAXBuilder(); + // To properly parse EPO responses, we must allow DOCTYPEs overall. But, we can still apply all the + // other default XXE protections, including disabling external entities and entity expansion. + // NOTE: we only need to allow DOCTYPEs for this initial API call. All other calls have them disabled. + saxBuilder.setFeature("http://apache.org/xml/features/disallow-doctype-decl", false); Document document = saxBuilder.build(new StringReader(response)); Element root = document.getRootElement(); diff --git a/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java index 7718e59e483..c870161bf9b 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java @@ -235,12 +235,9 @@ private String getSingleElementValue(String src, String elementName) { try { SAXBuilder saxBuilder = XMLUtils.getSAXBuilder(); - // To properly parse PubMed responses, we must allow DOCTYPE/DTDs overall but - // we can still take advantage of entities themselves being disabled, and not - // expanded. + // To properly parse PubMed responses, we must allow DOCTYPEs overall. But, we can still apply all the + // other default XXE protections, including disabling external entities and entity expansion. saxBuilder.setFeature("http://apache.org/xml/features/disallow-doctype-decl", false); - saxBuilder.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", - true); Document document = saxBuilder.build(new StringReader(src)); Element root = document.getRootElement(); @@ -358,6 +355,9 @@ public Collection call() throws Exception { private List splitToRecords(String recordsSrc) { try { SAXBuilder saxBuilder = XMLUtils.getSAXBuilder(); + // To properly parse PubMed responses, we must allow DOCTYPEs overall. But, we can still apply all the + // other default XXE protections, including disabling external entities and entity expansion. + saxBuilder.setFeature("http://apache.org/xml/features/disallow-doctype-decl", false); Document document = saxBuilder.build(new StringReader(recordsSrc)); Element root = document.getRootElement(); From 99b2a630a75db4eafbd7f44bb75a503f48e6170f Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Thu, 10 Jul 2025 16:00:34 +0200 Subject: [PATCH 618/939] Allow trusted XML builder to enforce base path for entities --- .../org/dspace/app/util/DCInputsReader.java | 19 ++-- .../app/util/SubmissionConfigReader.java | 10 +- .../java/org/dspace/app/util/XMLUtils.java | 94 +++++++++++++++++-- 3 files changed, 99 insertions(+), 24 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/util/DCInputsReader.java b/dspace-api/src/main/java/org/dspace/app/util/DCInputsReader.java index d2f3ac0dc5d..293ceaac47d 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/DCInputsReader.java +++ b/dspace-api/src/main/java/org/dspace/app/util/DCInputsReader.java @@ -8,6 +8,7 @@ package org.dspace.app.util; import java.io.File; +import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; @@ -118,19 +119,17 @@ private void buildInputs(String fileName) formDefns = new HashMap>>>(); valuePairs = new HashMap>(); - String uri = "file:" + new File(fileName).getAbsolutePath(); + File inputFile = new File(fileName); + String inputFileDir = inputFile.toPath().normalize().getParent().toString(); + + String uri = "file:" + inputFile.getAbsolutePath(); try { - // This document builder factory will *not* disable external + // This document builder will *not* disable external // entities as they can be useful in managing large forms, but - // it is up to site administrators to validate the XML they are - // storing - DocumentBuilderFactory factory = XMLUtils.getTrustedDocumentBuilderFactory(); - factory.setValidating(false); - factory.setIgnoringComments(true); - factory.setIgnoringElementContentWhitespace(true); - - DocumentBuilder db = factory.newDocumentBuilder(); + // it will restrict them to be within the directory that the + // current input form XML file exists (or a sub-directory) + DocumentBuilder db = XMLUtils.getTrustedDocumentBuilder(inputFileDir); Document doc = db.parse(uri); doNodes(doc); checkValues(); diff --git a/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java b/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java index 1914ded86b6..27138d1ba1e 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java +++ b/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java @@ -162,14 +162,8 @@ private void buildInputs(String fileName) throws SubmissionConfigReaderException try { // This document builder factory will *not* disable external // entities as they can be useful in managing large forms, but - // it is up to site administrators to validate the XML they are - // storing - DocumentBuilderFactory factory = XMLUtils.getTrustedDocumentBuilderFactory(); - factory.setValidating(false); - factory.setIgnoringComments(true); - factory.setIgnoringElementContentWhitespace(true); - - DocumentBuilder db = factory.newDocumentBuilder(); + // it will restrict them to the config dir containing submission definitions + DocumentBuilder db = XMLUtils.getTrustedDocumentBuilder(configDir); Document doc = db.parse(uri); doNodes(doc); } catch (FactoryConfigurationError fe) { diff --git a/dspace-api/src/main/java/org/dspace/app/util/XMLUtils.java b/dspace-api/src/main/java/org/dspace/app/util/XMLUtils.java index 389d53fe6da..6b419a0485e 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/XMLUtils.java +++ b/dspace-api/src/main/java/org/dspace/app/util/XMLUtils.java @@ -7,7 +7,13 @@ */ package org.dspace.app.util; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -18,6 +24,9 @@ import org.jdom2.input.SAXBuilder; import org.w3c.dom.Element; import org.w3c.dom.NodeList; +import org.xml.sax.EntityResolver; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; /** * Simple class to read information from small XML using DOM manipulation @@ -211,27 +220,36 @@ public static DocumentBuilderFactory getDocumentBuilderFactory() } /** - * Initialize and return a javax DocumentBuilder with NO security + * Initialize and return a javax DocumentBuilder with less security * applied. This is intended only for internal, administrative/configuration * use where external entities and other dangerous features are actually - * purposefully included. + * purposefully included, but are only allowed from specified paths, e.g. + * dspace.dir or some other path specified by the java caller. * The method here is tiny, but may be expanded with other features like * whitespace handling, and calling this method name helps to document * the fact that the caller knows it is trusting the XML source / builder + *

    + * If no allowedPaths are passed, then all external entities are rejected * * @return document builder with no security features set - * @throws ParserConfigurationException + * @throws ParserConfigurationException if the builder can not be configured */ - public static DocumentBuilder getTrustedDocumentBuilder() + public static DocumentBuilder getTrustedDocumentBuilder(String... allowedPaths) throws ParserConfigurationException { - return getTrustedDocumentBuilderFactory().newDocumentBuilder(); + DocumentBuilderFactory factory = getTrustedDocumentBuilderFactory(); + factory.setValidating(false); + factory.setIgnoringComments(true); + factory.setIgnoringElementContentWhitespace(true); + DocumentBuilder builder = factory.newDocumentBuilder(); + builder.setEntityResolver(new PathRestrictedEntityResolver(allowedPaths)); + return factory.newDocumentBuilder(); } /** * Initialize and return the javax DocumentBuilder with some basic security applied * to avoid XXE attacks and other unwanted content inclusion * @return document builder for use in XML parsing - * @throws ParserConfigurationException + * @throws ParserConfigurationException if the builder can not be configured */ public static DocumentBuilder getDocumentBuilder() throws ParserConfigurationException { @@ -284,4 +302,68 @@ public static XMLInputFactory getXMLInputFactory() { return xmlInputFactory; } + /** + * This entity resolver accepts one or more path strings in its + * constructor and throws a SAXException if the entity systemID + * is not within the allowed path (or a subdirectory). + * If no parameters are passed, then this effectively disallows + * any external entity resolution. + */ + public static class PathRestrictedEntityResolver implements EntityResolver { + private final List allowedBasePaths; + + public PathRestrictedEntityResolver(String... allowedBasePaths) { + this.allowedBasePaths = Arrays.asList(allowedBasePaths); + } + + @Override + public InputSource resolveEntity(String publicId, String systemId) + throws SAXException, IOException { + + if (systemId == null) { + return null; + } + + String filePath; + if (systemId.startsWith("file://")) { + filePath = systemId.substring(7); + } else if (systemId.startsWith("file:")) { + filePath = systemId.substring(5); + } else if (!systemId.contains("://")) { + filePath = systemId; + } else { + throw new SAXException("External resources not allowed: " + systemId + + ". Only local file paths are permitted."); + } + + Path resolvedPath; + try { + resolvedPath = Paths.get(filePath).toAbsolutePath().normalize(); + } catch (Exception e) { + throw new SAXException("Invalid path: " + systemId, e); + } + + boolean isAllowed = false; + for (String basePath : allowedBasePaths) { + Path allowedPath = Paths.get(basePath).toAbsolutePath().normalize(); + if (resolvedPath.startsWith(allowedPath)) { + isAllowed = true; + break; + } + } + + if (!isAllowed) { + throw new SAXException("Access denied to path: " + resolvedPath); + } + + File file = resolvedPath.toFile(); + if (!file.exists() || !file.canRead()) { + throw new SAXException("File not found or not readable: " + resolvedPath); + } + + return new InputSource(new FileInputStream(file)); + } + } + + } From d48e22aff5a72f539cbf2a4a29981a9ee4a6cdc8 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 11 Jul 2025 14:01:37 -0500 Subject: [PATCH 619/939] Update LICENSES_THIRD_PARTY to prepare for 7.6.4 release. --- LICENSES_THIRD_PARTY | 245 +++++++++++++++++++++++-------------------- pom.xml | 4 +- 2 files changed, 133 insertions(+), 116 deletions(-) diff --git a/LICENSES_THIRD_PARTY b/LICENSES_THIRD_PARTY index e12c429121a..304ee4e3430 100644 --- a/LICENSES_THIRD_PARTY +++ b/LICENSES_THIRD_PARTY @@ -21,18 +21,18 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines Apache Software License, Version 2.0: * Ant-Contrib Tasks (ant-contrib:ant-contrib:1.0b3 - http://ant-contrib.sourceforge.net) - * AWS SDK for Java - Core (com.amazonaws:aws-java-sdk-core:1.12.780 - https://aws.amazon.com/sdkforjava) - * AWS Java SDK for AWS KMS (com.amazonaws:aws-java-sdk-kms:1.12.780 - https://aws.amazon.com/sdkforjava) - * AWS Java SDK for Amazon S3 (com.amazonaws:aws-java-sdk-s3:1.12.780 - https://aws.amazon.com/sdkforjava) - * JMES Path Query library (com.amazonaws:jmespath-java:1.12.780 - https://aws.amazon.com/sdkforjava) + * AWS SDK for Java - Core (com.amazonaws:aws-java-sdk-core:1.12.785 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK for AWS KMS (com.amazonaws:aws-java-sdk-kms:1.12.785 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK for Amazon S3 (com.amazonaws:aws-java-sdk-s3:1.12.785 - https://aws.amazon.com/sdkforjava) + * JMES Path Query library (com.amazonaws:jmespath-java:1.12.785 - https://aws.amazon.com/sdkforjava) * HPPC Collections (com.carrotsearch:hppc:0.8.1 - http://labs.carrotsearch.com/hppc.html/hppc) * com.drewnoakes:metadata-extractor (com.drewnoakes:metadata-extractor:2.19.0 - https://drewnoakes.com/code/exif/) * parso (com.epam:parso:2.0.14 - https://github.com/epam/parso) * Internet Time Utility (com.ethlo.time:itu:1.7.0 - https://github.com/ethlo/itu) * ClassMate (com.fasterxml:classmate:1.7.0 - https://github.com/FasterXML/java-classmate) - * Jackson-annotations (com.fasterxml.jackson.core:jackson-annotations:2.18.2 - https://github.com/FasterXML/jackson) - * Jackson-core (com.fasterxml.jackson.core:jackson-core:2.18.2 - https://github.com/FasterXML/jackson-core) - * jackson-databind (com.fasterxml.jackson.core:jackson-databind:2.18.2 - https://github.com/FasterXML/jackson) + * Jackson-annotations (com.fasterxml.jackson.core:jackson-annotations:2.19.1 - https://github.com/FasterXML/jackson) + * Jackson-core (com.fasterxml.jackson.core:jackson-core:2.19.1 - https://github.com/FasterXML/jackson-core) + * jackson-databind (com.fasterxml.jackson.core:jackson-databind:2.19.1 - https://github.com/FasterXML/jackson) * Jackson dataformat: CBOR (com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.17.2 - https://github.com/FasterXML/jackson-dataformats-binary) * Jackson dataformat: Smile (com.fasterxml.jackson.dataformat:jackson-dataformat-smile:2.15.2 - https://github.com/FasterXML/jackson-dataformats-binary) * Jackson-dataformat-YAML (com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.14.0 - https://github.com/FasterXML/jackson-dataformats-text) @@ -57,22 +57,22 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Google APIs Client Library for Java (com.google.api-client:google-api-client:1.35.2 - https://github.com/googleapis/google-api-java-client/google-api-client) * Google Analytics API v3-rev145-1.23.0 (com.google.apis:google-api-services-analytics:v3-rev145-1.23.0 - http://nexus.sonatype.org/oss-repository-hosting.html/google-api-services-analytics) * FindBugs-jsr305 (com.google.code.findbugs:jsr305:3.0.2 - http://findbugs.sourceforge.net/) - * Gson (com.google.code.gson:gson:2.10.1 - https://github.com/google/gson/gson) + * Gson (com.google.code.gson:gson:2.11.0 - https://github.com/google/gson) * error-prone annotations (com.google.errorprone:error_prone_annotations:2.21.1 - https://errorprone.info/error_prone_annotations) * Guava InternalFutureFailureAccess and InternalFutures (com.google.guava:failureaccess:1.0.1 - https://github.com/google/guava/failureaccess) * Guava: Google Core Libraries for Java (com.google.guava:guava:32.1.3-jre - https://github.com/google/guava) * Guava ListenableFuture only (com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava - https://github.com/google/guava/listenablefuture) - * Google HTTP Client Library for Java (com.google.http-client:google-http-client:1.45.3 - https://github.com/googleapis/google-http-java-client/google-http-client) + * Google HTTP Client Library for Java (com.google.http-client:google-http-client:1.47.0 - https://github.com/googleapis/google-http-java-client/google-http-client) * Apache HTTP transport v2 for the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-apache-v2:1.42.0 - https://github.com/googleapis/google-http-java-client/google-http-client-apache-v2) - * GSON extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-gson:1.43.3 - https://github.com/googleapis/google-http-java-client/google-http-client-gson) - * Jackson 2 extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-jackson2:1.45.3 - https://github.com/googleapis/google-http-java-client/google-http-client-jackson2) + * GSON extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-gson:1.47.0 - https://github.com/googleapis/google-http-java-client/google-http-client-gson) + * Jackson 2 extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-jackson2:1.47.0 - https://github.com/googleapis/google-http-java-client/google-http-client-jackson2) * J2ObjC Annotations (com.google.j2objc:j2objc-annotations:1.3 - https://github.com/google/j2objc/) * J2ObjC Annotations (com.google.j2objc:j2objc-annotations:2.8 - https://github.com/google/j2objc/) - * Google OAuth Client Library for Java (com.google.oauth-client:google-oauth-client:1.37.0 - https://github.com/googleapis/google-oauth-java-client/google-oauth-client) + * Google OAuth Client Library for Java (com.google.oauth-client:google-oauth-client:1.39.0 - https://github.com/googleapis/google-oauth-java-client/google-oauth-client) * ConcurrentLinkedHashMap (com.googlecode.concurrentlinkedhashmap:concurrentlinkedhashmap-lru:1.4.2 - http://code.google.com/p/concurrentlinkedhashmap) * libphonenumber (com.googlecode.libphonenumber:libphonenumber:8.11.1 - https://github.com/google/libphonenumber/) - * Jackcess (com.healthmarketscience.jackcess:jackcess:4.0.5 - https://jackcess.sourceforge.io) - * Jackcess Encrypt (com.healthmarketscience.jackcess:jackcess-encrypt:4.0.2 - http://jackcessencrypt.sf.net) + * Jackcess (com.healthmarketscience.jackcess:jackcess:4.0.8 - https://jackcess.sourceforge.io) + * Jackcess Encrypt (com.healthmarketscience.jackcess:jackcess-encrypt:4.0.3 - http://jackcessencrypt.sf.net) * json-path (com.jayway.jsonpath:json-path:2.9.0 - https://github.com/jayway/JsonPath) * json-path-assert (com.jayway.jsonpath:json-path-assert:2.9.0 - https://github.com/jayway/JsonPath) * Disruptor Framework (com.lmax:disruptor:3.4.2 - http://lmax-exchange.github.com/disruptor) @@ -81,11 +81,15 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * JsonSchemaValidator (com.networknt:json-schema-validator:1.0.76 - https://github.com/networknt/json-schema-validator) * Nimbus JOSE+JWT (com.nimbusds:nimbus-jose-jwt:7.9 - https://bitbucket.org/connect2id/nimbus-jose-jwt) * Nimbus JOSE+JWT (com.nimbusds:nimbus-jose-jwt:9.28 - https://bitbucket.org/connect2id/nimbus-jose-jwt) - * opencsv (com.opencsv:opencsv:5.10 - http://opencsv.sf.net) + * opencsv (com.opencsv:opencsv:5.11.1 - http://opencsv.sf.net) * java-libpst (com.pff:java-libpst:0.9.3 - https://github.com/rjohnsondev/java-libpst) * rome (com.rometools:rome:1.19.0 - http://rometools.com/rome) * rome-modules (com.rometools:rome-modules:1.19.0 - http://rometools.com/rome-modules) * rome-utils (com.rometools:rome-utils:1.19.0 - http://rometools.com/rome-utils) + * mockwebserver (com.squareup.okhttp3:mockwebserver:4.12.0 - https://square.github.io/okhttp/) + * okhttp (com.squareup.okhttp3:okhttp:4.12.0 - https://square.github.io/okhttp/) + * okio (com.squareup.okio:okio:3.6.0 - https://github.com/square/okio/) + * okio (com.squareup.okio:okio-jvm:3.6.0 - https://github.com/square/okio/) * T-Digest (com.tdunning:t-digest:3.1 - https://github.com/tdunning/t-digest) * config (com.typesafe:config:1.3.3 - https://github.com/lightbend/config) * ssl-config-core (com.typesafe:ssl-config-core_2.13:0.3.8 - https://github.com/lightbend/ssl-config) @@ -98,15 +102,15 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * scala-logging (com.typesafe.scala-logging:scala-logging_2.13:3.9.2 - https://github.com/lightbend/scala-logging) * JSON library from Android SDK (com.vaadin.external.google:android-json:0.0.20131108.vaadin1 - http://developer.android.com/sdk) * SparseBitSet (com.zaxxer:SparseBitSet:1.3 - https://github.com/brettwooldridge/SparseBitSet) - * Apache Commons BeanUtils (commons-beanutils:commons-beanutils:1.10.0 - https://commons.apache.org/proper/commons-beanutils) + * Apache Commons BeanUtils (commons-beanutils:commons-beanutils:1.11.0 - https://commons.apache.org/proper/commons-beanutils) * Apache Commons CLI (commons-cli:commons-cli:1.9.0 - https://commons.apache.org/proper/commons-cli/) - * Apache Commons Codec (commons-codec:commons-codec:1.17.2 - https://commons.apache.org/proper/commons-codec/) + * Apache Commons Codec (commons-codec:commons-codec:1.18.0 - https://commons.apache.org/proper/commons-codec/) * Apache Commons Collections (commons-collections:commons-collections:3.2.2 - http://commons.apache.org/collections/) * Commons Digester (commons-digester:commons-digester:2.1 - http://commons.apache.org/digester/) - * Apache Commons FileUpload (commons-fileupload:commons-fileupload:1.5 - https://commons.apache.org/proper/commons-fileupload/) - * Apache Commons IO (commons-io:commons-io:2.18.0 - https://commons.apache.org/proper/commons-io/) + * Commons FileUpload (commons-fileupload:commons-fileupload:1.2.1 - http://commons.apache.org/fileupload/) + * Apache Commons IO (commons-io:commons-io:2.19.0 - https://commons.apache.org/proper/commons-io/) * Commons Lang (commons-lang:commons-lang:2.6 - http://commons.apache.org/lang/) - * Apache Commons Logging (commons-logging:commons-logging:1.3.4 - https://commons.apache.org/proper/commons-logging/) + * Apache Commons Logging (commons-logging:commons-logging:1.3.5 - https://commons.apache.org/proper/commons-logging/) * Apache Commons Validator (commons-validator:commons-validator:1.9.0 - http://commons.apache.org/proper/commons-validator/) * GeoJson POJOs for Jackson (de.grundid.opendatalab:geojson-jackson:1.14 - https://github.com/opendatalab-de/geojson-jackson) * OpenAIRE Funders Model (eu.openaire:funders-model:2.0.0 - https://api.openaire.eu) @@ -115,30 +119,34 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Metrics Integration for Jetty 9.3 and higher (io.dropwizard.metrics:metrics-jetty9:4.1.5 - https://metrics.dropwizard.io/metrics-jetty9) * Metrics Integration with JMX (io.dropwizard.metrics:metrics-jmx:4.1.5 - https://metrics.dropwizard.io/metrics-jmx) * JVM Integration for Metrics (io.dropwizard.metrics:metrics-jvm:4.1.5 - https://metrics.dropwizard.io/metrics-jvm) - * io.grpc:grpc-api (io.grpc:grpc-api:1.69.0 - https://github.com/grpc/grpc-java) - * io.grpc:grpc-context (io.grpc:grpc-context:1.69.0 - https://github.com/grpc/grpc-java) + * io.grpc:grpc-api (io.grpc:grpc-api:1.73.0 - https://github.com/grpc/grpc-java) + * io.grpc:grpc-context (io.grpc:grpc-context:1.73.0 - https://github.com/grpc/grpc-java) * micrometer-core (io.micrometer:micrometer-core:1.9.17 - https://github.com/micrometer-metrics/micrometer) - * Netty/Buffer (io.netty:netty-buffer:4.1.117.Final - https://netty.io/netty-buffer/) * Netty/Buffer (io.netty:netty-buffer:4.1.99.Final - https://netty.io/netty-buffer/) - * Netty/Codec (io.netty:netty-codec:4.1.117.Final - https://netty.io/netty-codec/) + * Netty/Buffer (io.netty:netty-buffer:4.2.2.Final - https://netty.io/netty-buffer/) * Netty/Codec (io.netty:netty-codec:4.1.99.Final - https://netty.io/netty-codec/) + * Netty/Codec (io.netty:netty-codec:4.2.2.Final - https://netty.io/netty-codec/) + * Netty/Codec/Base (io.netty:netty-codec-base:4.2.2.Final - https://netty.io/netty-codec-base/) + * Netty/Codec/Compression (io.netty:netty-codec-compression:4.2.2.Final - https://netty.io/netty-codec-compression/) * Netty/Codec/HTTP (io.netty:netty-codec-http:4.1.86.Final - https://netty.io/netty-codec-http/) * Netty/Codec/HTTP2 (io.netty:netty-codec-http2:4.1.86.Final - https://netty.io/netty-codec-http2/) + * Netty/Codec/Marshalling (io.netty:netty-codec-marshalling:4.2.2.Final - https://netty.io/netty-codec-marshalling/) + * Netty/Codec/Protobuf (io.netty:netty-codec-protobuf:4.2.2.Final - https://netty.io/netty-codec-protobuf/) * Netty/Codec/Socks (io.netty:netty-codec-socks:4.1.86.Final - https://netty.io/netty-codec-socks/) - * Netty/Common (io.netty:netty-common:4.1.117.Final - https://netty.io/netty-common/) * Netty/Common (io.netty:netty-common:4.1.99.Final - https://netty.io/netty-common/) - * Netty/Handler (io.netty:netty-handler:4.1.117.Final - https://netty.io/netty-handler/) + * Netty/Common (io.netty:netty-common:4.2.2.Final - https://netty.io/netty-common/) * Netty/Handler (io.netty:netty-handler:4.1.99.Final - https://netty.io/netty-handler/) + * Netty/Handler (io.netty:netty-handler:4.2.2.Final - https://netty.io/netty-handler/) * Netty/Handler/Proxy (io.netty:netty-handler-proxy:4.1.86.Final - https://netty.io/netty-handler-proxy/) * Netty/Resolver (io.netty:netty-resolver:4.1.99.Final - https://netty.io/netty-resolver/) * Netty/TomcatNative [BoringSSL - Static] (io.netty:netty-tcnative-boringssl-static:2.0.56.Final - https://github.com/netty/netty-tcnative/netty-tcnative-boringssl-static/) * Netty/TomcatNative [OpenSSL - Classes] (io.netty:netty-tcnative-classes:2.0.56.Final - https://github.com/netty/netty-tcnative/netty-tcnative-classes/) - * Netty/Transport (io.netty:netty-transport:4.1.117.Final - https://netty.io/netty-transport/) * Netty/Transport (io.netty:netty-transport:4.1.99.Final - https://netty.io/netty-transport/) + * Netty/Transport (io.netty:netty-transport:4.2.2.Final - https://netty.io/netty-transport/) * Netty/Transport/Classes/Epoll (io.netty:netty-transport-classes-epoll:4.1.99.Final - https://netty.io/netty-transport-classes-epoll/) * Netty/Transport/Native/Epoll (io.netty:netty-transport-native-epoll:4.1.99.Final - https://netty.io/netty-transport-native-epoll/) - * Netty/Transport/Native/Unix/Common (io.netty:netty-transport-native-unix-common:4.1.117.Final - https://netty.io/netty-transport-native-unix-common/) * Netty/Transport/Native/Unix/Common (io.netty:netty-transport-native-unix-common:4.1.99.Final - https://netty.io/netty-transport-native-unix-common/) + * Netty/Transport/Native/Unix/Common (io.netty:netty-transport-native-unix-common:4.2.2.Final - https://netty.io/netty-transport-native-unix-common/) * OpenCensus (io.opencensus:opencensus-api:0.31.1 - https://github.com/census-instrumentation/opencensus-java) * OpenCensus (io.opencensus:opencensus-contrib-http-util:0.31.1 - https://github.com/census-instrumentation/opencensus-java) * OpenTracing API (io.opentracing:opentracing-api:0.33.0 - https://github.com/opentracing/opentracing-java/opentracing-api) @@ -167,16 +175,17 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * JSR107 API and SPI (javax.cache:cache-api:1.1.1 - https://github.com/jsr107/jsr107spec) * javax.inject (javax.inject:javax.inject:1 - http://code.google.com/p/atinject/) * jdbm (jdbm:jdbm:1.0 - no url defined) - * Joda-Time (joda-time:joda-time:2.13.0 - https://www.joda.org/joda-time/) - * Byte Buddy (without dependencies) (net.bytebuddy:byte-buddy:1.16.1 - https://bytebuddy.net/byte-buddy) + * Joda-Time (joda-time:joda-time:2.14.0 - https://www.joda.org/joda-time/) + * Byte Buddy (without dependencies) (net.bytebuddy:byte-buddy:1.11.13 - https://bytebuddy.net/byte-buddy) + * Byte Buddy (without dependencies) (net.bytebuddy:byte-buddy:1.12.18 - https://bytebuddy.net/byte-buddy) * Byte Buddy agent (net.bytebuddy:byte-buddy-agent:1.11.13 - https://bytebuddy.net/byte-buddy-agent) * eigenbase-properties (net.hydromatic:eigenbase-properties:1.1.5 - http://github.com/julianhyde/eigenbase-properties) * json-unit-core (net.javacrumbs.json-unit:json-unit-core:2.36.0 - https://github.com/lukas-krecan/JsonUnit/json-unit-core) * "Java Concurrency in Practice" book annotations (net.jcip:jcip-annotations:1.0 - http://jcip.net/) * ASM based accessors helper used by json-smart (net.minidev:accessors-smart:2.5.0 - https://urielch.github.io/) - * ASM based accessors helper used by json-smart (net.minidev:accessors-smart:2.5.1 - https://urielch.github.io/) + * ASM based accessors helper used by json-smart (net.minidev:accessors-smart:2.5.2 - https://urielch.github.io/) * JSON Small and Fast Parser (net.minidev:json-smart:2.5.0 - https://urielch.github.io/) - * JSON Small and Fast Parser (net.minidev:json-smart:2.5.1 - https://urielch.github.io/) + * JSON Small and Fast Parser (net.minidev:json-smart:2.5.2 - https://urielch.github.io/) * Abdera Core (org.apache.abdera:abdera-core:1.1.3 - http://abdera.apache.org/abdera-core) * I18N Libraries (org.apache.abdera:abdera-i18n:1.1.3 - http://abdera.apache.org) * Apache Ant Core (org.apache.ant:ant:1.10.15 - https://ant.apache.org/) @@ -186,18 +195,18 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Calcite Linq4j (org.apache.calcite:calcite-linq4j:1.35.0 - https://calcite.apache.org) * Apache Calcite Avatica (org.apache.calcite.avatica:avatica-core:1.23.0 - https://calcite.apache.org/avatica) * Apache Calcite Avatica Metrics (org.apache.calcite.avatica:avatica-metrics:1.23.0 - https://calcite.apache.org/avatica) - * Apache Commons Collections (org.apache.commons:commons-collections4:4.4 - https://commons.apache.org/proper/commons-collections/) + * Apache Commons Collections (org.apache.commons:commons-collections4:4.5.0 - https://commons.apache.org/proper/commons-collections/) * Apache Commons Compress (org.apache.commons:commons-compress:1.27.1 - https://commons.apache.org/proper/commons-compress/) - * Apache Commons Configuration (org.apache.commons:commons-configuration2:2.11.0 - https://commons.apache.org/proper/commons-configuration/) - * Apache Commons CSV (org.apache.commons:commons-csv:1.10.0 - https://commons.apache.org/proper/commons-csv/) + * Apache Commons Configuration (org.apache.commons:commons-configuration2:2.12.0 - https://commons.apache.org/proper/commons-configuration/) + * Apache Commons CSV (org.apache.commons:commons-csv:1.14.0 - https://commons.apache.org/proper/commons-csv/) * Apache Commons DBCP (org.apache.commons:commons-dbcp2:2.13.0 - https://commons.apache.org/proper/commons-dbcp/) * Apache Commons Digester (org.apache.commons:commons-digester3:3.2 - http://commons.apache.org/digester/) * Apache Commons Exec (org.apache.commons:commons-exec:1.3 - http://commons.apache.org/proper/commons-exec/) * Apache Commons Exec (org.apache.commons:commons-exec:1.4.0 - https://commons.apache.org/proper/commons-exec/) * Apache Commons Lang (org.apache.commons:commons-lang3:3.17.0 - https://commons.apache.org/proper/commons-lang/) * Apache Commons Math (org.apache.commons:commons-math3:3.6.1 - http://commons.apache.org/proper/commons-math/) - * Apache Commons Pool (org.apache.commons:commons-pool2:2.12.0 - https://commons.apache.org/proper/commons-pool/) - * Apache Commons Text (org.apache.commons:commons-text:1.13.0 - https://commons.apache.org/proper/commons-text) + * Apache Commons Pool (org.apache.commons:commons-pool2:2.12.1 - https://commons.apache.org/proper/commons-pool/) + * Apache Commons Text (org.apache.commons:commons-text:1.13.1 - https://commons.apache.org/proper/commons-text) * Curator Client (org.apache.curator:curator-client:2.13.0 - http://curator.apache.org/curator-client) * Curator Framework (org.apache.curator:curator-framework:2.13.0 - http://curator.apache.org/curator-framework) * Curator Recipes (org.apache.curator:curator-recipes:2.13.0 - http://curator.apache.org/curator-recipes) @@ -214,7 +223,7 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Apache HttpComponents Core HTTP/1.1 (org.apache.httpcomponents.core5:httpcore5:5.1.3 - https://hc.apache.org/httpcomponents-core-5.1.x/5.1.3/httpcore5/) * Apache HttpComponents Core HTTP/2 (org.apache.httpcomponents.core5:httpcore5-h2:5.1.3 - https://hc.apache.org/httpcomponents-core-5.1.x/5.1.3/httpcore5-h2/) * Apache James :: Mime4j :: Core (org.apache.james:apache-mime4j-core:0.8.12 - http://james.apache.org/mime4j/apache-mime4j-core) - * Apache James :: Mime4j :: DOM (org.apache.james:apache-mime4j-dom:0.8.11 - http://james.apache.org/mime4j/apache-mime4j-dom) + * Apache James :: Mime4j :: DOM (org.apache.james:apache-mime4j-dom:0.8.12 - http://james.apache.org/mime4j/apache-mime4j-dom) * Apache Jena - Libraries POM (org.apache.jena:apache-jena-libs:2.13.0 - http://jena.apache.org/apache-jena-libs/) * Apache Jena - ARQ (SPARQL 1.1 Query Engine) (org.apache.jena:jena-arq:2.13.0 - http://jena.apache.org/jena-arq/) * Apache Jena - Core (org.apache.jena:jena-core:2.13.0 - http://jena.apache.org/jena-core/) @@ -224,9 +233,9 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Kerby-kerb Util (org.apache.kerby:kerb-util:1.0.1 - http://directory.apache.org/kerby/kerby-kerb/kerb-util) * Kerby ASN1 Project (org.apache.kerby:kerby-asn1:1.0.1 - http://directory.apache.org/kerby/kerby-common/kerby-asn1) * Kerby PKIX Project (org.apache.kerby:kerby-pkix:1.0.1 - http://directory.apache.org/kerby/kerby-pkix) - * Apache Log4j 1.x Compatibility API (org.apache.logging.log4j:log4j-1.2-api:2.24.3 - https://logging.apache.org/log4j/2.x/log4j/log4j-1.2-api/) - * Apache Log4j API (org.apache.logging.log4j:log4j-api:2.24.3 - https://logging.apache.org/log4j/2.x/log4j/log4j-api/) - * Apache Log4j Core (org.apache.logging.log4j:log4j-core:2.24.3 - https://logging.apache.org/log4j/2.x/log4j/log4j-core/) + * Apache Log4j 1.x Compatibility API (org.apache.logging.log4j:log4j-1.2-api:2.25.0 - https://logging.apache.org/log4j/2.x/) + * Apache Log4j API (org.apache.logging.log4j:log4j-api:2.25.0 - https://logging.apache.org/log4j/2.x/) + * Apache Log4j Core (org.apache.logging.log4j:log4j-core:2.25.0 - https://logging.apache.org/log4j/2.x/) * Apache Log4j JUL Adapter (org.apache.logging.log4j:log4j-jul:2.17.2 - https://logging.apache.org/log4j/2.x/log4j-jul/) * Apache Log4j Layout for JSON template (org.apache.logging.log4j:log4j-layout-template-json:2.17.2 - https://logging.apache.org/log4j/2.x/log4j-layout-template-json/) * Apache Log4j SLF4J Binding (org.apache.logging.log4j:log4j-slf4j-impl:2.17.2 - https://logging.apache.org/log4j/2.x/log4j-slf4j-impl/) @@ -254,45 +263,45 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Lucene Spatial Extras (org.apache.lucene:lucene-spatial-extras:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-spatial-extras) * Lucene Spatial 3D (org.apache.lucene:lucene-spatial3d:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-spatial3d) * Lucene Suggest (org.apache.lucene:lucene-suggest:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-suggest) - * Apache FontBox (org.apache.pdfbox:fontbox:2.0.33 - http://pdfbox.apache.org/) + * Apache FontBox (org.apache.pdfbox:fontbox:2.0.34 - http://pdfbox.apache.org/) * PDFBox JBIG2 ImageIO plugin (org.apache.pdfbox:jbig2-imageio:3.0.4 - https://www.apache.org/jbig2-imageio/) * Apache JempBox (org.apache.pdfbox:jempbox:1.8.17 - http://www.apache.org/pdfbox-parent/jempbox/) - * Apache PDFBox (org.apache.pdfbox:pdfbox:2.0.33 - https://www.apache.org/pdfbox-parent/pdfbox/) - * Apache PDFBox tools (org.apache.pdfbox:pdfbox-tools:2.0.31 - https://www.apache.org/pdfbox-parent/pdfbox-tools/) - * Apache XmpBox (org.apache.pdfbox:xmpbox:2.0.31 - https://www.apache.org/pdfbox-parent/xmpbox/) - * Apache POI - Common (org.apache.poi:poi:5.2.5 - https://poi.apache.org/) - * Apache POI - API based on OPC and OOXML schemas (org.apache.poi:poi-ooxml:5.2.5 - https://poi.apache.org/) - * Apache POI (org.apache.poi:poi-ooxml-lite:5.2.5 - https://poi.apache.org/) - * Apache POI (org.apache.poi:poi-scratchpad:5.2.5 - https://poi.apache.org/) + * Apache PDFBox (org.apache.pdfbox:pdfbox:2.0.34 - https://www.apache.org/pdfbox-parent/pdfbox/) + * Apache PDFBox tools (org.apache.pdfbox:pdfbox-tools:2.0.34 - https://www.apache.org/pdfbox-parent/pdfbox-tools/) + * Apache XmpBox (org.apache.pdfbox:xmpbox:2.0.34 - https://www.apache.org/pdfbox-parent/xmpbox/) + * Apache POI - Common (org.apache.poi:poi:5.4.1 - https://poi.apache.org/) + * Apache POI - API based on OPC and OOXML schemas (org.apache.poi:poi-ooxml:5.4.1 - https://poi.apache.org/) + * Apache POI (org.apache.poi:poi-ooxml-lite:5.4.1 - https://poi.apache.org/) + * Apache POI (org.apache.poi:poi-scratchpad:5.4.1 - https://poi.apache.org/) * Apache Solr Core (org.apache.solr:solr-core:8.11.4 - https://lucene.apache.org/solr-parent/solr-core) * Apache Solr Solrj (org.apache.solr:solr-solrj:8.11.4 - https://lucene.apache.org/solr-parent/solr-solrj) * Apache Standard Taglib Implementation (org.apache.taglibs:taglibs-standard-impl:1.2.5 - http://tomcat.apache.org/taglibs/standard-1.2.5/taglibs-standard-impl) * Apache Standard Taglib Specification API (org.apache.taglibs:taglibs-standard-spec:1.2.5 - http://tomcat.apache.org/taglibs/standard-1.2.5/taglibs-standard-spec) * Apache Thrift (org.apache.thrift:libthrift:0.9.2 - http://thrift.apache.org) - * Apache Tika core (org.apache.tika:tika-core:2.9.2 - https://tika.apache.org/) - * Apache Tika Apple parser module (org.apache.tika:tika-parser-apple-module:2.9.2 - https://tika.apache.org/tika-parser-apple-module/) - * Apache Tika audiovideo parser module (org.apache.tika:tika-parser-audiovideo-module:2.9.2 - https://tika.apache.org/tika-parser-audiovideo-module/) - * Apache Tika cad parser module (org.apache.tika:tika-parser-cad-module:2.9.2 - https://tika.apache.org/tika-parser-cad-module/) - * Apache Tika code parser module (org.apache.tika:tika-parser-code-module:2.9.2 - https://tika.apache.org/tika-parser-code-module/) - * Apache Tika crypto parser module (org.apache.tika:tika-parser-crypto-module:2.9.2 - https://tika.apache.org/tika-parser-crypto-module/) - * Apache Tika digest commons (org.apache.tika:tika-parser-digest-commons:2.9.2 - https://tika.apache.org/tika-parser-digest-commons/) - * Apache Tika font parser module (org.apache.tika:tika-parser-font-module:2.9.2 - https://tika.apache.org/tika-parser-font-module/) - * Apache Tika html parser module (org.apache.tika:tika-parser-html-module:2.9.2 - https://tika.apache.org/tika-parser-html-module/) - * Apache Tika image parser module (org.apache.tika:tika-parser-image-module:2.9.2 - https://tika.apache.org/tika-parser-image-module/) - * Apache Tika mail commons (org.apache.tika:tika-parser-mail-commons:2.9.2 - https://tika.apache.org/tika-parser-mail-commons/) - * Apache Tika mail parser module (org.apache.tika:tika-parser-mail-module:2.9.2 - https://tika.apache.org/tika-parser-mail-module/) - * Apache Tika Microsoft parser module (org.apache.tika:tika-parser-microsoft-module:2.9.2 - https://tika.apache.org/tika-parser-microsoft-module/) - * Apache Tika miscellaneous office format parser module (org.apache.tika:tika-parser-miscoffice-module:2.9.2 - https://tika.apache.org/tika-parser-miscoffice-module/) - * Apache Tika news parser module (org.apache.tika:tika-parser-news-module:2.9.2 - https://tika.apache.org/tika-parser-news-module/) - * Apache Tika OCR parser module (org.apache.tika:tika-parser-ocr-module:2.9.2 - https://tika.apache.org/tika-parser-ocr-module/) - * Apache Tika PDF parser module (org.apache.tika:tika-parser-pdf-module:2.9.2 - https://tika.apache.org/tika-parser-pdf-module/) - * Apache Tika package parser module (org.apache.tika:tika-parser-pkg-module:2.9.2 - https://tika.apache.org/tika-parser-pkg-module/) - * Apache Tika text parser module (org.apache.tika:tika-parser-text-module:2.9.2 - https://tika.apache.org/tika-parser-text-module/) - * Apache Tika WARC parser module (org.apache.tika:tika-parser-webarchive-module:2.9.2 - https://tika.apache.org/tika-parser-webarchive-module/) - * Apache Tika XML parser module (org.apache.tika:tika-parser-xml-module:2.9.2 - https://tika.apache.org/tika-parser-xml-module/) - * Apache Tika XMP commons (org.apache.tika:tika-parser-xmp-commons:2.9.2 - https://tika.apache.org/tika-parser-xmp-commons/) - * Apache Tika ZIP commons (org.apache.tika:tika-parser-zip-commons:2.9.2 - https://tika.apache.org/tika-parser-zip-commons/) - * Apache Tika standard parser package (org.apache.tika:tika-parsers-standard-package:2.9.2 - https://tika.apache.org/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/) + * Apache Tika core (org.apache.tika:tika-core:2.9.4 - https://tika.apache.org/) + * Apache Tika Apple parser module (org.apache.tika:tika-parser-apple-module:2.9.4 - https://tika.apache.org/tika-parser-apple-module/) + * Apache Tika audiovideo parser module (org.apache.tika:tika-parser-audiovideo-module:2.9.4 - https://tika.apache.org/tika-parser-audiovideo-module/) + * Apache Tika cad parser module (org.apache.tika:tika-parser-cad-module:2.9.4 - https://tika.apache.org/tika-parser-cad-module/) + * Apache Tika code parser module (org.apache.tika:tika-parser-code-module:2.9.4 - https://tika.apache.org/tika-parser-code-module/) + * Apache Tika crypto parser module (org.apache.tika:tika-parser-crypto-module:2.9.4 - https://tika.apache.org/tika-parser-crypto-module/) + * Apache Tika digest commons (org.apache.tika:tika-parser-digest-commons:2.9.4 - https://tika.apache.org/tika-parser-digest-commons/) + * Apache Tika font parser module (org.apache.tika:tika-parser-font-module:2.9.4 - https://tika.apache.org/tika-parser-font-module/) + * Apache Tika html parser module (org.apache.tika:tika-parser-html-module:2.9.4 - https://tika.apache.org/tika-parser-html-module/) + * Apache Tika image parser module (org.apache.tika:tika-parser-image-module:2.9.4 - https://tika.apache.org/tika-parser-image-module/) + * Apache Tika mail commons (org.apache.tika:tika-parser-mail-commons:2.9.4 - https://tika.apache.org/tika-parser-mail-commons/) + * Apache Tika mail parser module (org.apache.tika:tika-parser-mail-module:2.9.4 - https://tika.apache.org/tika-parser-mail-module/) + * Apache Tika Microsoft parser module (org.apache.tika:tika-parser-microsoft-module:2.9.4 - https://tika.apache.org/tika-parser-microsoft-module/) + * Apache Tika miscellaneous office format parser module (org.apache.tika:tika-parser-miscoffice-module:2.9.4 - https://tika.apache.org/tika-parser-miscoffice-module/) + * Apache Tika news parser module (org.apache.tika:tika-parser-news-module:2.9.4 - https://tika.apache.org/tika-parser-news-module/) + * Apache Tika OCR parser module (org.apache.tika:tika-parser-ocr-module:2.9.4 - https://tika.apache.org/tika-parser-ocr-module/) + * Apache Tika PDF parser module (org.apache.tika:tika-parser-pdf-module:2.9.4 - https://tika.apache.org/tika-parser-pdf-module/) + * Apache Tika package parser module (org.apache.tika:tika-parser-pkg-module:2.9.4 - https://tika.apache.org/tika-parser-pkg-module/) + * Apache Tika text parser module (org.apache.tika:tika-parser-text-module:2.9.4 - https://tika.apache.org/tika-parser-text-module/) + * Apache Tika WARC parser module (org.apache.tika:tika-parser-webarchive-module:2.9.4 - https://tika.apache.org/tika-parser-webarchive-module/) + * Apache Tika XML parser module (org.apache.tika:tika-parser-xml-module:2.9.4 - https://tika.apache.org/tika-parser-xml-module/) + * Apache Tika XMP commons (org.apache.tika:tika-parser-xmp-commons:2.9.4 - https://tika.apache.org/tika-parser-xmp-commons/) + * Apache Tika ZIP commons (org.apache.tika:tika-parser-zip-commons:2.9.4 - https://tika.apache.org/tika-parser-zip-commons/) + * Apache Tika standard parser package (org.apache.tika:tika-parsers-standard-package:2.9.4 - https://tika.apache.org/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/) * tomcat-embed-core (org.apache.tomcat.embed:tomcat-embed-core:9.0.83 - https://tomcat.apache.org/) * tomcat-embed-el (org.apache.tomcat.embed:tomcat-embed-el:9.0.83 - https://tomcat.apache.org/) * tomcat-embed-websocket (org.apache.tomcat.embed:tomcat-embed-websocket:9.0.83 - https://tomcat.apache.org/) @@ -301,7 +310,7 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Apache Velocity Tools - Generic tools (org.apache.velocity.tools:velocity-tools-generic:3.1 - https://velocity.apache.org/tools/devel/velocity-tools-generic/) * Axiom API (org.apache.ws.commons.axiom:axiom-api:1.2.22 - http://ws.apache.org/axiom/) * Abdera Model (FOM) Implementation (org.apache.ws.commons.axiom:fom-impl:1.2.22 - http://ws.apache.org/axiom/implementations/fom-impl/) - * XmlBeans (org.apache.xmlbeans:xmlbeans:5.2.0 - https://xmlbeans.apache.org/) + * XmlBeans (org.apache.xmlbeans:xmlbeans:5.3.0 - https://xmlbeans.apache.org/) * Apache ZooKeeper - Server (org.apache.zookeeper:zookeeper:3.6.2 - http://zookeeper.apache.org/zookeeper) * Apache ZooKeeper - Jute (org.apache.zookeeper:zookeeper-jute:3.6.2 - http://zookeeper.apache.org/zookeeper-jute) * org.apiguardian:apiguardian-api (org.apiguardian:apiguardian-api:1.1.2 - https://github.com/apiguardian-team/apiguardian) @@ -348,9 +357,9 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * flyway-core (org.flywaydb:flyway-core:8.5.13 - https://flywaydb.org/flyway-core) * Ogg and Vorbis for Java, Core (org.gagravarr:vorbis-java-core:0.8 - https://github.com/Gagravarr/VorbisJava) * Apache Tika plugin for Ogg, Vorbis and FLAC (org.gagravarr:vorbis-java-tika:0.8 - https://github.com/Gagravarr/VorbisJava) - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * Hibernate Validator Engine (org.hibernate.validator:hibernate-validator:6.2.5.Final - http://hibernate.org/validator/hibernate-validator) * Hibernate Validator Portable Extension (org.hibernate.validator:hibernate-validator-cdi:6.2.5.Final - http://hibernate.org/validator/hibernate-validator-cdi) * org.immutables.value-annotations (org.immutables:value-annotations:2.9.2 - http://immutables.org/value-annotations) @@ -360,6 +369,11 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Java Annotation Indexer (org.jboss:jandex:2.4.2.Final - http://www.jboss.org/jandex) * JBoss Logging 3 (org.jboss.logging:jboss-logging:3.6.1.Final - http://www.jboss.org) * JDOM (org.jdom:jdom2:2.0.6.1 - http://www.jdom.org) + * IntelliJ IDEA Annotations (org.jetbrains:annotations:13.0 - http://www.jetbrains.org) + * Kotlin Stdlib (org.jetbrains.kotlin:kotlin-stdlib:1.8.21 - https://kotlinlang.org/) + * Kotlin Stdlib Common (org.jetbrains.kotlin:kotlin-stdlib-common:1.8.21 - https://kotlinlang.org/) + * Kotlin Stdlib Jdk7 (org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.21 - https://kotlinlang.org/) + * Kotlin Stdlib Jdk8 (org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.21 - https://kotlinlang.org/) * jtwig-core (org.jtwig:jtwig-core:5.87.0.RELEASE - http://jtwig.org) * jtwig-reflection (org.jtwig:jtwig-reflection:5.87.0.RELEASE - http://jtwig.org) * jtwig-spring (org.jtwig:jtwig-spring:5.87.0.RELEASE - http://jtwig.org) @@ -377,8 +391,9 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Jetty Servlet Tester (org.mortbay.jetty:jetty-servlet-tester:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/jetty-servlet-tester) * Jetty Utilities (org.mortbay.jetty:jetty-util:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/jetty-util) * Servlet Specification API (org.mortbay.jetty:servlet-api:2.5-20081211 - http://jetty.mortbay.org/servlet-api) - * jwarc (org.netpreserve:jwarc:0.29.0 - https://github.com/iipc/jwarc) + * jwarc (org.netpreserve:jwarc:0.31.1 - https://github.com/iipc/jwarc) * Objenesis (org.objenesis:objenesis:3.2 - http://objenesis.org/objenesis) + * org.opentest4j:opentest4j (org.opentest4j:opentest4j:1.3.0 - https://github.com/ota4j-team/opentest4j) * parboiled-core (org.parboiled:parboiled-core:1.1.7 - http://parboiled.org) * parboiled-java (org.parboiled:parboiled-java:1.1.7 - http://parboiled.org) * RRD4J (org.rrd4j:rrd4j:3.5 - https://github.com/rrd4j/rrd4j/) @@ -438,7 +453,7 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * SWORD v2 :: Common Server Library (org.swordapp:sword2-server:1.0 - http://www.swordapp.org/) * snappy-java (org.xerial.snappy:snappy-java:1.1.10.1 - https://github.com/xerial/snappy-java) * xml-matchers (org.xmlmatchers:xml-matchers:0.10 - http://code.google.com/p/xml-matchers/) - * org.xmlunit:xmlunit-core (org.xmlunit:xmlunit-core:2.10.0 - https://www.xmlunit.org/) + * org.xmlunit:xmlunit-core (org.xmlunit:xmlunit-core:2.10.2 - https://www.xmlunit.org/) * org.xmlunit:xmlunit-core (org.xmlunit:xmlunit-core:2.9.1 - https://www.xmlunit.org/) * org.xmlunit:xmlunit-placeholders (org.xmlunit:xmlunit-placeholders:2.9.1 - https://www.xmlunit.org/xmlunit-placeholders/) * SnakeYAML (org.yaml:snakeyaml:1.30 - https://bitbucket.org/snakeyaml/snakeyaml) @@ -456,15 +471,15 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Protocol Buffers [Core] (com.google.protobuf:protobuf-java:3.15.0 - https://developers.google.com/protocol-buffers/protobuf-java/) * JZlib (com.jcraft:jzlib:1.1.3 - http://www.jcraft.com/jzlib/) * jmustache (com.samskivert:jmustache:1.15 - http://github.com/samskivert/jmustache) - * dnsjava (dnsjava:dnsjava:3.6.2 - https://github.com/dnsjava/dnsjava) + * dnsjava (dnsjava:dnsjava:3.6.3 - https://github.com/dnsjava/dnsjava) * jaxen (jaxen:jaxen:2.0.0 - http://www.cafeconleche.org/jaxen/jaxen) * ANTLR 4 Runtime (org.antlr:antlr4-runtime:4.5.1-1 - http://www.antlr.org/antlr4-runtime) * commons-compiler (org.codehaus.janino:commons-compiler:3.1.8 - http://janino-compiler.github.io/commons-compiler/) * janino (org.codehaus.janino:janino:3.1.8 - http://janino-compiler.github.io/janino/) * Stax2 API (org.codehaus.woodstox:stax2-api:4.2.1 - http://github.com/FasterXML/stax2-api) * Hamcrest Date (org.exparity:hamcrest-date:2.0.8 - https://github.com/exparity/hamcrest-date) - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * Hamcrest (org.hamcrest:hamcrest:2.2 - http://hamcrest.org/JavaHamcrest/) * Hamcrest Core (org.hamcrest:hamcrest-core:2.2 - http://hamcrest.org/JavaHamcrest/) * HdrHistogram (org.hdrhistogram:HdrHistogram:2.1.12 - http://hdrhistogram.github.io/HdrHistogram/) @@ -474,9 +489,10 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * asm-commons (org.ow2.asm:asm-commons:9.3 - http://asm.ow2.io/) * ASM Tree (org.ow2.asm:asm-tree:5.0.3 - http://asm.objectweb.org/asm-tree/) * ASM Util (org.ow2.asm:asm-util:5.0.3 - http://asm.objectweb.org/asm-util/) - * PostgreSQL JDBC Driver (org.postgresql:postgresql:42.7.5 - https://jdbc.postgresql.org) + * PostgreSQL JDBC Driver (org.postgresql:postgresql:42.7.7 - https://jdbc.postgresql.org) * Reflections (org.reflections:reflections:0.9.12 - http://github.com/ronmamo/reflections) * JMatIO (org.tallison:jmatio:1.5 - https://github.com/tballison/jmatio) + * XZ for Java (org.tukaani:xz:1.10 - https://tukaani.org/xz/java.html) * XMLUnit for Java (xmlunit:xmlunit:1.3 - http://xmlunit.sourceforge.net/) CC0: @@ -495,7 +511,6 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * JavaBeans Activation Framework (JAF) (javax.activation:activation:1.1 - http://java.sun.com/products/javabeans/jaf/index.jsp) * JavaBeans Activation Framework API jar (javax.activation:javax.activation-api:1.2.0 - http://java.net/all/javax.activation-api/) * javax.annotation API (javax.annotation:javax.annotation-api:1.3.2 - http://jcp.org/en/jsr/detail?id=250) - * Expression Language 3.0 API (javax.el:javax.el-api:3.0.0 - http://uel-spec.java.net) * Java Servlet API (javax.servlet:javax.servlet-api:3.1.0 - http://servlet-spec.java.net) * javax.transaction API (javax.transaction:javax.transaction-api:1.3 - http://jta-spec.java.net) * jaxb-api (javax.xml.bind:jaxb-api:2.3.1 - https://github.com/javaee/jaxb-spec/jaxb-api) @@ -506,8 +521,8 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * OSGi resource locator (org.glassfish.hk2:osgi-resource-locator:1.0.3 - https://projects.eclipse.org/projects/ee4j/osgi-resource-locator) * aopalliance version 1.0 repackaged as a module (org.glassfish.hk2.external:aopalliance-repackaged:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/external/aopalliance-repackaged) * javax.inject:1 as OSGi bundle (org.glassfish.hk2.external:jakarta.inject:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/external/jakarta.inject) - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * Java Transaction API (org.jboss.spec.javax.transaction:jboss-transaction-api_1.2_spec:1.1.1.Final - http://www.jboss.org/jboss-transaction-api_1.2_spec) Cordra (Version 2) License Agreement: @@ -528,8 +543,8 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * javax.persistence-api (javax.persistence:javax.persistence-api:2.2 - https://github.com/javaee/jpa-spec) * JAXB Runtime (org.glassfish.jaxb:jaxb-runtime:2.3.9 - https://eclipse-ee4j.github.io/jaxb-ri/) * TXW2 Runtime (org.glassfish.jaxb:txw2:2.3.9 - https://eclipse-ee4j.github.io/jaxb-ri/) - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * Java Persistence API, Version 2.1 (org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.2.Final - http://hibernate.org) * org.locationtech.jts:jts-core (org.locationtech.jts:jts-core:1.19.0 - https://www.locationtech.org/projects/technology.jts/jts-modules/jts-core) * org.locationtech.jts.io:jts-io-common (org.locationtech.jts.io:jts-io-common:1.19.0 - https://www.locationtech.org/projects/technology.jts/jts-modules/jts-io/jts-io-common) @@ -584,10 +599,13 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * OSGi resource locator (org.glassfish.hk2:osgi-resource-locator:1.0.3 - https://projects.eclipse.org/projects/ee4j/osgi-resource-locator) * aopalliance version 1.0 repackaged as a module (org.glassfish.hk2.external:aopalliance-repackaged:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/external/aopalliance-repackaged) * javax.inject:1 as OSGi bundle (org.glassfish.hk2.external:jakarta.inject:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/external/jakarta.inject) - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * Java Persistence API, Version 2.1 (org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.2.Final - http://hibernate.org) + * JUnit Platform Commons (org.junit.platform:junit-platform-commons:1.11.4 - https://junit.org/junit5/) + * JUnit Platform Engine API (org.junit.platform:junit-platform-engine:1.11.4 - https://junit.org/junit5/) + * JUnit Vintage Engine (org.junit.vintage:junit-vintage-engine:5.11.4 - https://junit.org/junit5/) * org.locationtech.jts:jts-core (org.locationtech.jts:jts-core:1.19.0 - https://www.locationtech.org/projects/technology.jts/jts-modules/jts-core) * org.locationtech.jts.io:jts-io-common (org.locationtech.jts.io:jts-io-common:1.19.0 - https://www.locationtech.org/projects/technology.jts/jts-modules/jts-io/jts-io-common) * Jetty Server (org.mortbay.jetty:jetty:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/modules/jetty) @@ -596,11 +614,11 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines GENERAL PUBLIC LICENSE, version 3 (GPL-3.0): - * juniversalchardet (com.github.albfernandez:juniversalchardet:2.4.0 - https://github.com/albfernandez/juniversalchardet) + * juniversalchardet (com.github.albfernandez:juniversalchardet:2.5.0 - https://github.com/albfernandez/juniversalchardet) GNU LESSER GENERAL PUBLIC LICENSE, version 3 (LGPL-3.0): - * juniversalchardet (com.github.albfernandez:juniversalchardet:2.4.0 - https://github.com/albfernandez/juniversalchardet) + * juniversalchardet (com.github.albfernandez:juniversalchardet:2.5.0 - https://github.com/albfernandez/juniversalchardet) GNU Lesser General Public License (LGPL): @@ -626,9 +644,9 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * RE2/J (com.google.re2j:re2j:1.2 - http://github.com/google/re2j) - Handle.Net Public License Agreement (Ver.2): + Handle.Net Public License Agreement (Ver.3): - * Handle Server (net.handle:handle:9.3.1 - https://www.handle.net) + * Handle Server (net.handle:handle:9.3.2 - https://www.handle.net) ISC License: @@ -643,15 +661,15 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * s3mock (io.findify:s3mock_2.13:0.2.6 - https://github.com/findify/s3mock) * ClassGraph (io.github.classgraph:classgraph:4.8.154 - https://github.com/classgraph/classgraph) * JOpt Simple (net.sf.jopt-simple:jopt-simple:5.0.4 - http://jopt-simple.github.io/jopt-simple) - * Bouncy Castle S/MIME API (org.bouncycastle:bcmail-jdk18on:1.77 - https://www.bouncycastle.org/java.html) - * Bouncy Castle PKIX, CMS, EAC, TSP, PKCS, OCSP, CMP, and CRMF APIs (org.bouncycastle:bcpkix-jdk18on:1.80 - https://www.bouncycastle.org/download/bouncy-castle-java/) - * Bouncy Castle Provider (org.bouncycastle:bcprov-jdk18on:1.80 - https://www.bouncycastle.org/download/bouncy-castle-java/) - * Bouncy Castle ASN.1 Extension and Utility APIs (org.bouncycastle:bcutil-jdk18on:1.80 - https://www.bouncycastle.org/download/bouncy-castle-java/) + * Bouncy Castle JavaMail S/MIME APIs (org.bouncycastle:bcmail-jdk18on:1.80 - https://www.bouncycastle.org/download/bouncy-castle-java/) + * Bouncy Castle PKIX, CMS, EAC, TSP, PKCS, OCSP, CMP, and CRMF APIs (org.bouncycastle:bcpkix-jdk18on:1.81 - https://www.bouncycastle.org/download/bouncy-castle-java/) + * Bouncy Castle Provider (org.bouncycastle:bcprov-jdk18on:1.81 - https://www.bouncycastle.org/download/bouncy-castle-java/) + * Bouncy Castle ASN.1 Extension and Utility APIs (org.bouncycastle:bcutil-jdk18on:1.81 - https://www.bouncycastle.org/download/bouncy-castle-java/) * org.brotli:dec (org.brotli:dec:0.1.2 - http://brotli.org/dec) * Checker Qual (org.checkerframework:checker-qual:3.23.0 - https://checkerframework.org) - * Checker Qual (org.checkerframework:checker-qual:3.48.3 - https://checkerframework.org/) - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * Checker Qual (org.checkerframework:checker-qual:3.49.3 - https://checkerframework.org/) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * mockito-core (org.mockito:mockito-core:3.12.4 - https://github.com/mockito/mockito) * mockito-inline (org.mockito:mockito-inline:3.12.4 - https://github.com/mockito/mockito) * ORCID - Model (org.orcid:orcid-model:3.0.2 - http://github.com/ORCID/orcid-model) @@ -664,27 +682,26 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * jquery (org.webjars.bowergithub.jquery:jquery-dist:3.7.1 - https://www.webjars.org) * urijs (org.webjars.bowergithub.medialize:uri.js:1.19.11 - https://www.webjars.org) * bootstrap (org.webjars.bowergithub.twbs:bootstrap:4.6.2 - https://www.webjars.org) - * core-js (org.webjars.npm:core-js:3.40.0 - https://www.webjars.org) - * @json-editor/json-editor (org.webjars.npm:json-editor__json-editor:2.15.1 - https://www.webjars.org) + * core-js (org.webjars.npm:core-js:3.42.0 - https://www.webjars.org) + * @json-editor/json-editor (org.webjars.npm:json-editor__json-editor:2.15.2 - https://www.webjars.org) Mozilla Public License: - * juniversalchardet (com.github.albfernandez:juniversalchardet:2.4.0 - https://github.com/albfernandez/juniversalchardet) + * juniversalchardet (com.github.albfernandez:juniversalchardet:2.5.0 - https://github.com/albfernandez/juniversalchardet) * H2 Database Engine (com.h2database:h2:2.3.232 - https://h2database.com) - * Saxon-HE (net.sf.saxon:Saxon-HE:9.8.0-14 - http://www.saxonica.com/) + * Saxon-HE (net.sf.saxon:Saxon-HE:9.9.1-8 - http://www.saxonica.com/) * Javassist (org.javassist:javassist:3.30.2-GA - https://www.javassist.org/) * Mozilla Rhino (org.mozilla:rhino:1.7.7.2 - https://developer.mozilla.org/en/Rhino) Public Domain: - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * HdrHistogram (org.hdrhistogram:HdrHistogram:2.1.12 - http://hdrhistogram.github.io/HdrHistogram/) * JSON in Java (org.json:json:20231013 - https://github.com/douglascrockford/JSON-java) * LatencyUtils (org.latencyutils:LatencyUtils:2.0.3 - http://latencyutils.github.io/LatencyUtils/) * Reflections (org.reflections:reflections:0.9.12 - http://github.com/ronmamo/reflections) - * XZ for Java (org.tukaani:xz:1.9 - https://tukaani.org/xz/java.html) UnRar License: @@ -696,10 +713,10 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines W3C license: - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) jQuery license: - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.46 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) diff --git a/pom.xml b/pom.xml index 85fb3ce6ccd..8a5c266b84b 100644 --- a/pom.xml +++ b/pom.xml @@ -706,7 +706,7 @@ Apache Software License, Version 2.0|The SAX License|The W3C License Apache Software License, Version 2.0|Similar to Apache License but with the acknowledgment clause removed - BSD License|The BSD License|BSD licence|BSD license|BSD|BSD-style license|New BSD License|New BSD license|Revised BSD License|BSD 2-Clause license|3-Clause BSD License|BSD 2-Clause|BSD 3-clause New License|BSD Licence 3|BSD-2-Clause|BSD-3-Clause|Modified BSD|The New BSD License|The BSD 3-Clause License (BSD3)|BSD License 3|BSD License 2.0|The (New) BSD License + BSD License|The BSD License|BSD licence|BSD license|BSD|BSD-style license|New BSD License|New BSD license|Revised BSD License|BSD 2-Clause license|3-Clause BSD License|BSD 2-Clause|BSD 3-clause New License|BSD Licence 3|BSD-2-Clause|BSD-3-Clause|Modified BSD|The New BSD License|The BSD 3-Clause License (BSD3)|BSD License 3|BSD License 2.0|The (New) BSD License|0BSD BSD License|DSpace BSD License|DSpace Sourcecode License @@ -727,7 +727,7 @@ Common Development and Distribution License (CDDL)|GNU General Public License, Version 2 with the Classpath Exception Eclipse Distribution License, Version 1.0|Eclipse Distribution License (EDL), Version 1.0|Eclipse Distribution License - v 1.0|Eclipse Distribution License v. 1.0|EDL 1.0 - Eclipse Public License|Eclipse Public License - Version 1.0|Eclipse Public License - v 1.0|EPL 1.0 license|Eclipse Public License (EPL), Version 1.0|Eclipse Public License 1.0|Eclipse Public License v1.0|Eclipse Public License, Version 1.0|EPL 1.0|EPL 2.0|Eclipse Public License - v 2.0|EPL-2.0|Eclipse Public License 2.0|Eclipse Public License v. 2.0|Eclipse Public License, Version 2.0 + Eclipse Public License|Eclipse Public License - Version 1.0|Eclipse Public License - v 1.0|EPL 1.0 license|Eclipse Public License (EPL), Version 1.0|Eclipse Public License 1.0|Eclipse Public License v1.0|Eclipse Public License, Version 1.0|EPL 1.0|EPL 2.0|Eclipse Public License - v 2.0|EPL-2.0|Eclipse Public License 2.0|Eclipse Public License v. 2.0|Eclipse Public License, Version 2.0|Eclipse Public License v2.0 Eclipse Public License|Common Public License Version 1.0 From 259c3ddd370521bca4c5ebe77d02c1801c12b7dd Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Sun, 13 Jul 2025 10:06:33 +0200 Subject: [PATCH 620/939] Enforce path traversal check on import subdir (pre-processing) --- .../app/itemimport/ItemImportServiceImpl.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 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 3af383b04ca..4c53dc6ee59 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 @@ -29,6 +29,7 @@ import java.io.OutputStream; import java.io.PrintWriter; import java.net.URL; +import java.nio.file.Path; import java.sql.SQLException; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -744,15 +745,20 @@ protected Item addItem(Context c, List mycollections, String path, myitem = wi.getItem(); } + // normalize and validate path to make sure itemname doesn't contain path traversal + Path itemPath = new File(path + File.separatorChar + itemname + File.separatorChar) + .toPath().normalize(); + if (!itemPath.startsWith(path)) { + throw new IOException("Illegal item metadata path: '" + itemPath); + } + // now fill out dublin core for item - loadMetadata(c, myitem, path + File.separatorChar + itemname - + File.separatorChar); + loadMetadata(c, myitem, itemPath.toString()); // and the bitstreams from the contents file // process contents file, add bistreams and bundles, return any // non-standard permissions - List options = processContentsFile(c, myitem, path - + File.separatorChar + itemname, "contents"); + List options = processContentsFile(c, myitem, itemPath.toString(), "contents"); if (useWorkflow) { // don't process handle file @@ -770,8 +776,7 @@ protected Item addItem(Context c, List mycollections, String path, } } else { // only process handle file if not using workflow system - String myhandle = processHandleFile(c, myitem, path - + File.separatorChar + itemname, "handle"); + String myhandle = processHandleFile(c, myitem, itemPath.toString(), "handle"); // put item in system if (!isTest) { From a381611ebd040dc4f88bda6cb66efdfd66366f7c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 03:45:03 +0000 Subject: [PATCH 621/939] Bump log4j.version from 2.25.0 to 2.25.1 Bumps `log4j.version` from 2.25.0 to 2.25.1. Updates `org.apache.logging.log4j:log4j-api` from 2.25.0 to 2.25.1 Updates `org.apache.logging.log4j:log4j-core` from 2.25.0 to 2.25.1 Updates `org.apache.logging.log4j:log4j-1.2-api` from 2.25.0 to 2.25.1 --- updated-dependencies: - dependency-name: org.apache.logging.log4j:log4j-api dependency-version: 2.25.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.logging.log4j:log4j-core dependency-version: 2.25.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.logging.log4j:log4j-1.2-api dependency-version: 2.25.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8a5c266b84b..d5d8b26225c 100644 --- a/pom.xml +++ b/pom.xml @@ -38,7 +38,7 @@ 1.1.1 9.4.57.v20241219 - 2.25.0 + 2.25.1 2.0.34 1.19.0 1.7.36 From 45a9f8b530d69a29de5c6e071ce76ebcb2837812 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Mon, 14 Jul 2025 12:48:55 +0200 Subject: [PATCH 622/939] Re-add file separator to normalized SAF item path --- .../org/dspace/app/itemimport/ItemImportServiceImpl.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 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 4c53dc6ee59..6536af7139d 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 @@ -751,14 +751,16 @@ protected Item addItem(Context c, List mycollections, String path, if (!itemPath.startsWith(path)) { throw new IOException("Illegal item metadata path: '" + itemPath); } + // Normalization chops off the last separator, and we need to put it back + String itemPathDir = itemPath.toString() + File.separatorChar; // now fill out dublin core for item - loadMetadata(c, myitem, itemPath.toString()); + loadMetadata(c, myitem, itemPathDir); // and the bitstreams from the contents file // process contents file, add bistreams and bundles, return any // non-standard permissions - List options = processContentsFile(c, myitem, itemPath.toString(), "contents"); + List options = processContentsFile(c, myitem, itemPathDir, "contents"); if (useWorkflow) { // don't process handle file @@ -776,7 +778,7 @@ protected Item addItem(Context c, List mycollections, String path, } } else { // only process handle file if not using workflow system - String myhandle = processHandleFile(c, myitem, itemPath.toString(), "handle"); + String myhandle = processHandleFile(c, myitem, itemPathDir, "handle"); // put item in system if (!isTest) { From dda6d9ec9ddf0697c3e51fa0bf70068eabb626e0 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Mon, 14 Jul 2025 13:06:08 +0200 Subject: [PATCH 623/939] Remove unused imports --- .../src/main/java/org/dspace/app/util/DCInputsReader.java | 2 -- .../main/java/org/dspace/app/util/SubmissionConfigReader.java | 1 - 2 files changed, 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/util/DCInputsReader.java b/dspace-api/src/main/java/org/dspace/app/util/DCInputsReader.java index 293ceaac47d..7c0ad4830f1 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/DCInputsReader.java +++ b/dspace-api/src/main/java/org/dspace/app/util/DCInputsReader.java @@ -8,7 +8,6 @@ package org.dspace.app.util; import java.io.File; -import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; @@ -16,7 +15,6 @@ import java.util.List; import java.util.Map; import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.FactoryConfigurationError; import org.apache.commons.lang3.StringUtils; diff --git a/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java b/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java index 27138d1ba1e..899702757ad 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java +++ b/dspace-api/src/main/java/org/dspace/app/util/SubmissionConfigReader.java @@ -16,7 +16,6 @@ import java.util.List; import java.util.Map; import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.FactoryConfigurationError; import org.apache.commons.lang3.StringUtils; From e9bc74cf6df686965d988c1737c6803fe308bd9d Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Mon, 14 Jul 2025 13:09:43 +0200 Subject: [PATCH 624/939] Fix missing XMLUtils imports --- .../src/main/java/org/dspace/content/packager/RoleIngester.java | 1 + .../java/org/dspace/external/provider/orcid/xml/Converter.java | 1 + 2 files changed, 2 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/content/packager/RoleIngester.java b/dspace-api/src/main/java/org/dspace/content/packager/RoleIngester.java index d71012ff835..5c4cf214445 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/RoleIngester.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/RoleIngester.java @@ -19,6 +19,7 @@ import javax.xml.parsers.ParserConfigurationException; import org.apache.commons.codec.DecoderException; +import org.dspace.app.util.XMLUtils; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Collection; import org.dspace.content.Community; diff --git a/dspace-api/src/main/java/org/dspace/external/provider/orcid/xml/Converter.java b/dspace-api/src/main/java/org/dspace/external/provider/orcid/xml/Converter.java index af4e31e7fae..0e156f7ab5f 100644 --- a/dspace-api/src/main/java/org/dspace/external/provider/orcid/xml/Converter.java +++ b/dspace-api/src/main/java/org/dspace/external/provider/orcid/xml/Converter.java @@ -16,6 +16,7 @@ import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; +import org.dspace.app.util.XMLUtils; import org.xml.sax.SAXException; /** From 84e308c8f549b204eae89fbb36d6c2a3ef44a221 Mon Sep 17 00:00:00 2001 From: MMilosz Date: Fri, 4 Jul 2025 17:13:02 +0200 Subject: [PATCH 625/939] fix: prevent path traversal in SAF import (cherry picked from commit 596d8666f4b91c6af92f72f45da262d761b96607) --- .../app/itemimport/ItemImportServiceImpl.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) 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 6536af7139d..8f8e7438acb 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 @@ -1010,6 +1010,34 @@ protected void addDCValue(Context c, Item i, String schema, Node n) } } + /** + * Ensures a file path does not attempt to access files outside the designated parent directory. + * + * @param parentDir The absolute path to the parent directory that should contain the file + * @param fileName The name or path of the file to validate + * @throws IOException If an error occurs while resolving canonical paths, or the file path attempts + * to access a location outside the parent directory + */ + private void validateFilePath(String parentDir, String fileName) throws IOException { + File parent = new File(parentDir); + File file = new File(fileName); + + // If the fileName is not an absolute path, we resolve it against the parentDir + if (!file.isAbsolute()) { + file = new File(parent, fileName); + } + + String parentCanonicalPath = parent.getCanonicalPath(); + String fileCanonicalPath = file.getCanonicalPath(); + + if (!fileCanonicalPath.startsWith(parentCanonicalPath)) { + log.error("File path outside of canonical root requested: fileCanonicalPath={} does not begin " + + "with parentCanonicalPath={}", fileCanonicalPath, parentCanonicalPath); + throw new IOException("Illegal file path '" + fileName + "' encountered. This references a path " + + "outside of the import package. Please see the system logs for more details."); + } + } + /** * Read the collections file inside the item directory. If there * is one and it is not empty return a list of collections in @@ -1210,6 +1238,7 @@ protected List processContentsFile(Context c, Item i, String path, sDescription = sDescription.replaceFirst("description:", ""); } + validateFilePath(path, sFilePath); registerBitstream(c, i, iAssetstore, sFilePath, sBundle, sDescription); logInfo("\tRegistering Bitstream: " + sFilePath + "\tAssetstore: " + iAssetstore @@ -1423,6 +1452,7 @@ protected void processContentFileEntry(Context c, Item i, String path, return; } + validateFilePath(path, fileName); String fullpath = path + File.separatorChar + fileName; // get an input stream From b0a4a3400f3285da033cc7f71b6ac80ec2e07a85 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 9 Jul 2025 13:59:37 +0200 Subject: [PATCH 626/939] Enforce bitstream path to be within (fs) bitstore base on get (cherry picked from commit 6799660a903bfea985c818654cca3891527780de) --- .../org/dspace/storage/bitstore/DSBitStoreService.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/DSBitStoreService.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/DSBitStoreService.java index 6fef7365e48..3f92ccaac5f 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/DSBitStoreService.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/DSBitStoreService.java @@ -12,6 +12,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.file.Path; import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -249,6 +250,12 @@ protected File getFile(Bitstream bitstream) throws IOException { log.debug("Local filename for " + sInternalId + " is " + bufFilename.toString()); } + File bitstreamFile = new File(bufFilename.toString()); + Path normalizedPath = bitstreamFile.toPath().normalize(); + if (!normalizedPath.startsWith(baseDir.getAbsolutePath())) { + log.error("Bitstream path outside of assetstore root requested: bitstream={}, path={}, assetstore={}", bitstream.getID(), normalizedPath, baseDir.getAbsolutePath()); + throw new IOException("Illegal bitstream path constructed"); + } return new File(bufFilename.toString()); } From 907b42c2a913ce748e71fbd15819e9b798909068 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 9 Jul 2025 19:07:32 +0200 Subject: [PATCH 627/939] return existing File constructed and validated for bitstream (cherry picked from commit 31b1c922b2cba42857679f291cb9320cf820db46) --- .../java/org/dspace/storage/bitstore/DSBitStoreService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/DSBitStoreService.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/DSBitStoreService.java index 3f92ccaac5f..6589f99bc74 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/DSBitStoreService.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/DSBitStoreService.java @@ -256,7 +256,7 @@ protected File getFile(Bitstream bitstream) throws IOException { log.error("Bitstream path outside of assetstore root requested: bitstream={}, path={}, assetstore={}", bitstream.getID(), normalizedPath, baseDir.getAbsolutePath()); throw new IOException("Illegal bitstream path constructed"); } - return new File(bufFilename.toString()); + return bitstreamFile; } public boolean isRegisteredBitstream(String internalId) { From bc17559162125252c4c759ac540dffa982748c05 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Mon, 14 Jul 2025 12:42:30 +0200 Subject: [PATCH 628/939] Fix line length in DSBitstore log (cherry picked from commit dbf524c1120261f407cc77574642e3ee0e4ffe33) --- .../java/org/dspace/storage/bitstore/DSBitStoreService.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/DSBitStoreService.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/DSBitStoreService.java index 6589f99bc74..7743b93ca4b 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/DSBitStoreService.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/DSBitStoreService.java @@ -253,7 +253,9 @@ protected File getFile(Bitstream bitstream) throws IOException { File bitstreamFile = new File(bufFilename.toString()); Path normalizedPath = bitstreamFile.toPath().normalize(); if (!normalizedPath.startsWith(baseDir.getAbsolutePath())) { - log.error("Bitstream path outside of assetstore root requested: bitstream={}, path={}, assetstore={}", bitstream.getID(), normalizedPath, baseDir.getAbsolutePath()); + log.error("Bitstream path outside of assetstore root requested:" + + "bitstream={}, path={}, assetstore={}", + bitstream.getID(), normalizedPath, baseDir.getAbsolutePath()); throw new IOException("Illegal bitstream path constructed"); } return bitstreamFile; From a5f04f9c7731dbf088a00e28012367f24d76842b Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 14 Jul 2025 12:03:12 -0500 Subject: [PATCH 629/939] [maven-release-plugin] prepare release dspace-7.6.4 --- dspace-api/pom.xml | 2 +- dspace-iiif/pom.xml | 2 +- dspace-oai/pom.xml | 2 +- dspace-rdf/pom.xml | 2 +- dspace-rest/pom.xml | 4 ++-- dspace-server-webapp/pom.xml | 2 +- dspace-services/pom.xml | 2 +- dspace-sword/pom.xml | 2 +- dspace-swordv2/pom.xml | 2 +- dspace/modules/additions/pom.xml | 2 +- dspace/modules/pom.xml | 2 +- dspace/modules/rest/pom.xml | 2 +- dspace/modules/server/pom.xml | 2 +- dspace/pom.xml | 2 +- pom.xml | 32 ++++++++++++++++---------------- 15 files changed, 31 insertions(+), 31 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 7b3ab4a315e..b6028cdb424 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6.4-SNAPSHOT + 7.6.4 .. diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index 24c3d5f1642..080962f6776 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.4-SNAPSHOT + 7.6.4 .. diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index b6c419702d0..9060c6a02f1 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - 7.6.4-SNAPSHOT + 7.6.4 .. diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index ee06135cdce..951793b5f7f 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.4-SNAPSHOT + 7.6.4 .. diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index e7f2c20ec8e..663ed04c4d3 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -3,7 +3,7 @@ org.dspace dspace-rest war - 7.6.4-SNAPSHOT + 7.6.4 DSpace (Deprecated) REST Webapp DSpace RESTful Web Services API. NOTE: this REST API is DEPRECATED. Please consider using the REST API in the dspace-server-webapp instead! @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6.4-SNAPSHOT + 7.6.4 .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 15ee58e6e1b..ce71d1cad2e 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.4-SNAPSHOT + 7.6.4 .. diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index 6d783349f16..3d55556936b 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.4-SNAPSHOT + 7.6.4 diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index fc436f66892..5e502e2b3fc 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.4-SNAPSHOT + 7.6.4 .. diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index 755dc30d696..97cf8e235f3 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - 7.6.4-SNAPSHOT + 7.6.4 .. diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index dd38555808a..7687c54b921 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - 7.6.4-SNAPSHOT + 7.6.4 .. diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index bc902913e96..798fb1045d1 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - 7.6.4-SNAPSHOT + 7.6.4 ../../pom.xml diff --git a/dspace/modules/rest/pom.xml b/dspace/modules/rest/pom.xml index c424668c898..bd2b9a2128f 100644 --- a/dspace/modules/rest/pom.xml +++ b/dspace/modules/rest/pom.xml @@ -13,7 +13,7 @@ org.dspace modules - 7.6.4-SNAPSHOT + 7.6.4 .. diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index 318c01edd21..cbc2aee61ee 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -13,7 +13,7 @@ just adding new jar in the classloader modules org.dspace - 7.6.4-SNAPSHOT + 7.6.4 .. diff --git a/dspace/pom.xml b/dspace/pom.xml index af6881303b9..d5ad247b3e2 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - 7.6.4-SNAPSHOT + 7.6.4 ../pom.xml diff --git a/pom.xml b/pom.xml index 8a5c266b84b..a87d28e8f6e 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - 7.6.4-SNAPSHOT + 7.6.4 DSpace Parent Project DSpace open source software is a turnkey institutional repository application. @@ -875,14 +875,14 @@ org.dspace dspace-rest - 7.6.4-SNAPSHOT + 7.6.4 jar classes org.dspace dspace-rest - 7.6.4-SNAPSHOT + 7.6.4 war @@ -1031,69 +1031,69 @@ org.dspace dspace-api - 7.6.4-SNAPSHOT + 7.6.4 org.dspace dspace-api test-jar - 7.6.4-SNAPSHOT + 7.6.4 test org.dspace.modules additions - 7.6.4-SNAPSHOT + 7.6.4 org.dspace dspace-sword - 7.6.4-SNAPSHOT + 7.6.4 org.dspace dspace-swordv2 - 7.6.4-SNAPSHOT + 7.6.4 org.dspace dspace-oai - 7.6.4-SNAPSHOT + 7.6.4 org.dspace dspace-services - 7.6.4-SNAPSHOT + 7.6.4 org.dspace dspace-server-webapp test-jar - 7.6.4-SNAPSHOT + 7.6.4 test org.dspace dspace-rdf - 7.6.4-SNAPSHOT + 7.6.4 org.dspace dspace-iiif - 7.6.4-SNAPSHOT + 7.6.4 org.dspace dspace-server-webapp - 7.6.4-SNAPSHOT + 7.6.4 jar classes org.dspace dspace-server-webapp - 7.6.4-SNAPSHOT + 7.6.4 war @@ -1939,7 +1939,7 @@ scm:git:git@github.com:DSpace/DSpace.git scm:git:git@github.com:DSpace/DSpace.git https://github.com/DSpace/DSpace - dspace-7_x + dspace-7.6.4 From 9424ccf4aa6d81072391018063500c8b0f0714c0 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 14 Jul 2025 12:03:15 -0500 Subject: [PATCH 630/939] [maven-release-plugin] prepare for next development iteration --- dspace-api/pom.xml | 2 +- dspace-iiif/pom.xml | 2 +- dspace-oai/pom.xml | 2 +- dspace-rdf/pom.xml | 2 +- dspace-rest/pom.xml | 4 ++-- dspace-server-webapp/pom.xml | 2 +- dspace-services/pom.xml | 2 +- dspace-sword/pom.xml | 2 +- dspace-swordv2/pom.xml | 2 +- dspace/modules/additions/pom.xml | 2 +- dspace/modules/pom.xml | 2 +- dspace/modules/rest/pom.xml | 2 +- dspace/modules/server/pom.xml | 2 +- dspace/pom.xml | 2 +- pom.xml | 32 ++++++++++++++++---------------- 15 files changed, 31 insertions(+), 31 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index b6028cdb424..78acb3d23ae 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6.4 + 7.6.5-SNAPSHOT .. diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index 080962f6776..ff76244905d 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.4 + 7.6.5-SNAPSHOT .. diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index 9060c6a02f1..af0ffdf2c77 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - 7.6.4 + 7.6.5-SNAPSHOT .. diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index 951793b5f7f..8a72bf049f3 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.4 + 7.6.5-SNAPSHOT .. diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index 663ed04c4d3..bec3f96ee33 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -3,7 +3,7 @@ org.dspace dspace-rest war - 7.6.4 + 7.6.5-SNAPSHOT DSpace (Deprecated) REST Webapp DSpace RESTful Web Services API. NOTE: this REST API is DEPRECATED. Please consider using the REST API in the dspace-server-webapp instead! @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6.4 + 7.6.5-SNAPSHOT .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index ce71d1cad2e..4b7ba616a65 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.4 + 7.6.5-SNAPSHOT .. diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index 3d55556936b..2f4c4a5d391 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.4 + 7.6.5-SNAPSHOT diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index 5e502e2b3fc..c93949e1de5 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.4 + 7.6.5-SNAPSHOT .. diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index 97cf8e235f3..46a9c25890d 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - 7.6.4 + 7.6.5-SNAPSHOT .. diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index 7687c54b921..008c4f15a9a 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - 7.6.4 + 7.6.5-SNAPSHOT .. diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index 798fb1045d1..9da5e9d1059 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - 7.6.4 + 7.6.5-SNAPSHOT ../../pom.xml diff --git a/dspace/modules/rest/pom.xml b/dspace/modules/rest/pom.xml index bd2b9a2128f..a8bf8e6438c 100644 --- a/dspace/modules/rest/pom.xml +++ b/dspace/modules/rest/pom.xml @@ -13,7 +13,7 @@ org.dspace modules - 7.6.4 + 7.6.5-SNAPSHOT .. diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index cbc2aee61ee..18115c72974 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -13,7 +13,7 @@ just adding new jar in the classloader modules org.dspace - 7.6.4 + 7.6.5-SNAPSHOT .. diff --git a/dspace/pom.xml b/dspace/pom.xml index d5ad247b3e2..5be9b100ffc 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - 7.6.4 + 7.6.5-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index a87d28e8f6e..90703f43b1a 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - 7.6.4 + 7.6.5-SNAPSHOT DSpace Parent Project DSpace open source software is a turnkey institutional repository application. @@ -875,14 +875,14 @@ org.dspace dspace-rest - 7.6.4 + 7.6.5-SNAPSHOT jar classes org.dspace dspace-rest - 7.6.4 + 7.6.5-SNAPSHOT war @@ -1031,69 +1031,69 @@ org.dspace dspace-api - 7.6.4 + 7.6.5-SNAPSHOT org.dspace dspace-api test-jar - 7.6.4 + 7.6.5-SNAPSHOT test org.dspace.modules additions - 7.6.4 + 7.6.5-SNAPSHOT org.dspace dspace-sword - 7.6.4 + 7.6.5-SNAPSHOT org.dspace dspace-swordv2 - 7.6.4 + 7.6.5-SNAPSHOT org.dspace dspace-oai - 7.6.4 + 7.6.5-SNAPSHOT org.dspace dspace-services - 7.6.4 + 7.6.5-SNAPSHOT org.dspace dspace-server-webapp test-jar - 7.6.4 + 7.6.5-SNAPSHOT test org.dspace dspace-rdf - 7.6.4 + 7.6.5-SNAPSHOT org.dspace dspace-iiif - 7.6.4 + 7.6.5-SNAPSHOT org.dspace dspace-server-webapp - 7.6.4 + 7.6.5-SNAPSHOT jar classes org.dspace dspace-server-webapp - 7.6.4 + 7.6.5-SNAPSHOT war @@ -1939,7 +1939,7 @@ scm:git:git@github.com:DSpace/DSpace.git scm:git:git@github.com:DSpace/DSpace.git https://github.com/DSpace/DSpace - dspace-7.6.4 + dspace-7_x From 5cd3daa379b250b884c9093a358d18cf3b383588 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 02:49:30 +0000 Subject: [PATCH 631/939] Bump com.amazonaws:aws-java-sdk-s3 from 1.12.785 to 1.12.788 Bumps [com.amazonaws:aws-java-sdk-s3](https://github.com/aws/aws-sdk-java) from 1.12.785 to 1.12.788. - [Changelog](https://github.com/aws/aws-sdk-java/blob/master/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-java/compare/1.12.785...1.12.788) --- updated-dependencies: - dependency-name: com.amazonaws:aws-java-sdk-s3 dependency-version: 1.12.788 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 78acb3d23ae..e4a86a0676f 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -752,7 +752,7 @@ com.amazonaws aws-java-sdk-s3 - 1.12.785 + 1.12.788 From 39fb2afba19b2b9b580dbac394a983958e8c5f27 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 16 Jul 2025 16:27:05 -0500 Subject: [PATCH 632/939] Update test to no longer assume 127.0.0.1 will always respond with "localhost" as the hostname. On my machine it does not. --- .../client/DSpaceHttpClientFactoryTest.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/app/client/DSpaceHttpClientFactoryTest.java b/dspace-api/src/test/java/org/dspace/app/client/DSpaceHttpClientFactoryTest.java index b518f19ff4d..ca91bb5dc9c 100644 --- a/dspace-api/src/test/java/org/dspace/app/client/DSpaceHttpClientFactoryTest.java +++ b/dspace-api/src/test/java/org/dspace/app/client/DSpaceHttpClientFactoryTest.java @@ -17,6 +17,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import java.net.InetAddress; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; @@ -107,7 +108,14 @@ public void testBuildWithProxyConfiguredAndHostToIgnoreSet() throws Exception { @Test public void testBuildWithProxyConfiguredAndHostPrefixToIgnoreSet() throws Exception { - setHttpProxyOnConfigurationService("local*", "www.test.com"); + // Get hostname assigned to 127.0.0.1 (usually is "localhost", but not always) + InetAddress address = InetAddress.getByAddress(new byte[]{127, 0, 0, 1}); + String hostname = address.getHostName(); + // Take first 4 characters hostname as the prefix (e.g. "loca" in "localhost") + String hostnamePrefix = hostname.substring(0, 4); + // Save hostname prefix to our list of hosts to ignore, followed by an asterisk. + // (This should result in our Proxy ignoring our localhost) + setHttpProxyOnConfigurationService(hostnamePrefix + "*", "www.test.com"); CloseableHttpClient httpClient = httpClientFactory.build(); assertThat(mockProxy.getRequestCount(), is(0)); assertThat(mockServer.getRequestCount(), is(0)); @@ -122,7 +130,14 @@ public void testBuildWithProxyConfiguredAndHostPrefixToIgnoreSet() throws Except @Test public void testBuildWithProxyConfiguredAndHostSuffixToIgnoreSet() throws Exception { - setHttpProxyOnConfigurationService("www.test.com", "*host"); + // Get hostname assigned to 127.0.0.1 (usually is "localhost", but not always) + InetAddress address = InetAddress.getByAddress(new byte[]{127, 0, 0, 1}); + String hostname = address.getHostName(); + // Take last 4 characters hostname as the suffix (e.g. "host" in "localhost") + String hostnameSuffix = hostname.substring(hostname.length() - 4); + // Save hostname suffix to our list of hosts to ignore, preceded by an asterisk. + // (This should result in our Proxy ignoring our localhost) + setHttpProxyOnConfigurationService("www.test.com", "*" + hostnameSuffix); CloseableHttpClient httpClient = httpClientFactory.build(); assertThat(mockProxy.getRequestCount(), is(0)); assertThat(mockServer.getRequestCount(), is(0)); From d2c120bd8b0dd111281c70ab3dba7b51d2395330 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 18 Jul 2025 11:33:41 -0500 Subject: [PATCH 633/939] Fix broken tests on Windows by using Paths to split file path instead of regex. Also switch to in-memory s3mock because Windows cannot cleanup created files successfully. --- .../storage/bitstore/S3BitStoreServiceIT.java | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java index 6ea21eac8d6..63d88e95011 100644 --- a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java +++ b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java @@ -26,9 +26,11 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.nio.file.Paths; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.sql.SQLException; +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -41,7 +43,6 @@ import com.amazonaws.services.s3.model.Bucket; import com.amazonaws.services.s3.model.ObjectMetadata; import io.findify.s3mock.S3Mock; -import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.BooleanUtils; import org.dspace.AbstractIntegrationTestWithDatabase; @@ -80,8 +81,6 @@ public class S3BitStoreServiceIT extends AbstractIntegrationTestWithDatabase { private Collection collection; - private File s3Directory; - private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); @@ -89,9 +88,8 @@ public class S3BitStoreServiceIT extends AbstractIntegrationTestWithDatabase { public void setup() throws Exception { configurationService.setProperty("assetstore.s3.enabled", "true"); - s3Directory = new File(System.getProperty("java.io.tmpdir"), "s3"); - s3Mock = S3Mock.create(8001, s3Directory.getAbsolutePath()); + s3Mock = new S3Mock.Builder().withPort(8001).withInMemoryBackend().build(); s3Mock.start(); amazonS3Client = createAmazonS3Client(); @@ -112,8 +110,7 @@ public void setup() throws Exception { } @After - public void cleanUp() throws IOException { - FileUtils.deleteDirectory(s3Directory); + public void cleanUp() { s3Mock.shutdown(); } @@ -337,7 +334,7 @@ public void givenBitStreamIdentifierWhenIntermediatePathIsComputedThenNotEndingD String computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); int slashes = computeSlashes(path.toString()); assertThat(computedPath, Matchers.endsWith(File.separator)); - assertThat(computedPath.split(File.separator).length, Matchers.equalTo(slashes)); + assertThat(countPathElements(computedPath), Matchers.equalTo(slashes)); path.append("2"); computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); @@ -362,31 +359,31 @@ public void givenBitStreamIdentidierWhenIntermediatePathIsComputedThenMustBeSpli String computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); int slashes = computeSlashes(path.toString()); assertThat(computedPath, Matchers.endsWith(File.separator)); - assertThat(computedPath.split(File.separator).length, Matchers.equalTo(slashes)); + assertThat(countPathElements(computedPath), Matchers.equalTo(slashes)); path.append("2"); computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); slashes = computeSlashes(path.toString()); assertThat(computedPath, Matchers.endsWith(File.separator)); - assertThat(computedPath.split(File.separator).length, Matchers.equalTo(slashes)); + assertThat(countPathElements(computedPath), Matchers.equalTo(slashes)); path.append("3"); computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); slashes = computeSlashes(path.toString()); assertThat(computedPath, Matchers.endsWith(File.separator)); - assertThat(computedPath.split(File.separator).length, Matchers.equalTo(slashes)); + assertThat(countPathElements(computedPath), Matchers.equalTo(slashes)); path.append("4"); computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); slashes = computeSlashes(path.toString()); assertThat(computedPath, Matchers.endsWith(File.separator)); - assertThat(computedPath.split(File.separator).length, Matchers.equalTo(slashes)); + assertThat(countPathElements(computedPath), Matchers.equalTo(slashes)); path.append("56789"); computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); slashes = computeSlashes(path.toString()); assertThat(computedPath, Matchers.endsWith(File.separator)); - assertThat(computedPath.split(File.separator).length, Matchers.equalTo(slashes)); + assertThat(countPathElements(computedPath), Matchers.equalTo(slashes)); } @Test @@ -465,4 +462,12 @@ private int computeSlashes(String internalId) { return Math.min(slashes, S3BitStoreService.directoryLevels); } + // Count the number of elements in a Unix or Windows path. + // We use 'Paths' instead of splitting on slashes because these OSes use different path separators. + private int countPathElements(String stringPath) { + List pathElements = new ArrayList<>(); + Paths.get(stringPath).forEach(p -> pathElements.add(p.toString())); + return pathElements.size(); + } + } From 28686fc48adc8f6f188231c6531c27e58134548e Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 21 Jul 2025 15:57:34 -0500 Subject: [PATCH 634/939] Improve logging in AbstractLiveImportIntegrationTest (to make tests easier to debug). Replace obscure StringInputStream (from Ant) with IOUtils.toInputStream --- .../importer/external/metadatamapping/MetadatumDTO.java | 9 +++++++++ .../app/rest/AbstractLiveImportIntegrationTest.java | 7 ++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/MetadatumDTO.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/MetadatumDTO.java index 265dd55eb93..b8a7507eaa6 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/MetadatumDTO.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/MetadatumDTO.java @@ -105,4 +105,13 @@ public String getValue() { public void setValue(String value) { this.value = value; } + + /** + * Return string representation of MetadatumDTO + * @return string representation of format "[schema].[element].[qualifier]=[value]" + */ + @Override + public String toString() { + return schema + "." + element + "." + qualifier + "=" + value; + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AbstractLiveImportIntegrationTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AbstractLiveImportIntegrationTest.java index cf3e125cc53..94fbce4a511 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AbstractLiveImportIntegrationTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AbstractLiveImportIntegrationTest.java @@ -16,12 +16,12 @@ import java.util.ArrayList; import java.util.List; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.http.ProtocolVersion; import org.apache.http.StatusLine; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.entity.BasicHttpEntity; -import org.apache.tools.ant.filters.StringInputStream; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.importer.external.datamodel.ImportRecord; import org.dspace.importer.external.metadatamapping.MetadatumDTO; @@ -43,7 +43,8 @@ protected void matchRecords(ArrayList recordsImported, ArrayList list, List list2) { assertEquals(list.size(), list2.size()); for (int i = 0; i < list.size(); i++) { - assertTrue(sameMetadatum(list.get(i), list2.get(i))); + assertTrue("'" + list.get(i).toString() + "' should be equal to '" + list2.get(i).toString() + "'", + sameMetadatum(list.get(i), list2.get(i))); } } @@ -70,7 +71,7 @@ protected CloseableHttpResponse mockResponse(String xmlExample, int statusCode, throws UnsupportedEncodingException { BasicHttpEntity basicHttpEntity = new BasicHttpEntity(); basicHttpEntity.setChunked(true); - basicHttpEntity.setContent(new StringInputStream(xmlExample)); + basicHttpEntity.setContent(IOUtils.toInputStream(xmlExample)); CloseableHttpResponse response = mock(CloseableHttpResponse.class); when(response.getStatusLine()).thenReturn(statusLine(statusCode, reason)); From 15b3f314a3bd19cd47d8964f24385e8a08bca4c2 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 21 Jul 2025 15:58:37 -0500 Subject: [PATCH 635/939] Force UTF-8 encoding in all tests. This fixes several test failures when running tests from Windows commandline because Windows doesn't default to using UTF-8. --- pom.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 90703f43b1a..3df4347fe92 100644 --- a/pom.xml +++ b/pom.xml @@ -515,10 +515,10 @@ - + test-argLine @@ -527,7 +527,7 @@ - -Xmx1024m + -Xmx1024m -Dfile.encoding=UTF-8 From 588c4ef4d259bccb927440058639a00ace67fb11 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 22 Jul 2025 17:27:13 +0200 Subject: [PATCH 636/939] Add simple log4j WebappLoggingIT test --- .../dspace/app/rest/test/WebappLoggingIT.java | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/test/WebappLoggingIT.java diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/WebappLoggingIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/WebappLoggingIT.java new file mode 100644 index 00000000000..fe746452c79 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/WebappLoggingIT.java @@ -0,0 +1,101 @@ +/** + * 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.app.rest.test; + +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.LoggerConfig; +import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.junit.After; +import org.junit.Test; + +/** + * Test basic log4j logging functionality, extending AbstractControllerIntegrationTest + * purely to make sure we are testing the *web application* and not just the kernel + * as that is where logging has broken in the past. + * + * @author Kim Shepherd + */ +public class WebappLoggingIT extends AbstractControllerIntegrationTest { + + private static final Logger logger = LogManager.getLogger(WebappLoggingIT.class); + private static final String APPENDER_NAME = "DSpaceTestAppender"; + + static class InMemoryAppender extends AbstractAppender { + private final List messages = new ArrayList<>(); + + protected InMemoryAppender(String name) { + super( + name, + null, + PatternLayout.newBuilder().withPattern("%m").build(), + false, + Property.EMPTY_ARRAY + ); + start(); + } + + @Override + public void append(LogEvent event) { + messages.add(event.getMessage().getFormattedMessage()); + } + + public List getMessages() { + return messages; + } + } + + @Test + public void testLogging() throws Exception { + LoggerContext context = (LoggerContext) LogManager.getContext(false); + Configuration config = context.getConfiguration(); + + InMemoryAppender appender = new InMemoryAppender(APPENDER_NAME); + config.addAppender(appender); + + LoggerConfig testLoggerConfig = new LoggerConfig(logger.getName(), Level.INFO, false); + testLoggerConfig.addAppender(appender, null, null); + config.addLogger(logger.getName(), testLoggerConfig); + context.updateLoggers(); + + logger.info("DSPACE TEST LOG ENTRY"); + + List messages = appender.getMessages(); + assertTrue(messages.stream().anyMatch(msg -> msg.contains("DSPACE TEST LOG ENTRY"))); + } + + @After + public void cleanupAppender() { + LoggerContext context = (LoggerContext) LogManager.getContext(false); + Configuration config = context.getConfiguration(); + + config.removeLogger(logger.getName()); + + Appender appender = config.getAppender(APPENDER_NAME); + if (appender != null) { + appender.stop(); + config.getAppenders().remove(APPENDER_NAME); + } + + context.updateLoggers(); +} + +} + From 479cb7688527eea88a87423540ec1c11f81f5848 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 22 Jul 2025 14:42:47 -0500 Subject: [PATCH 637/939] Minor updates to LICENSES_THIRD_PARTY for 7.6.5 release --- LICENSES_THIRD_PARTY | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/LICENSES_THIRD_PARTY b/LICENSES_THIRD_PARTY index 304ee4e3430..92868467c98 100644 --- a/LICENSES_THIRD_PARTY +++ b/LICENSES_THIRD_PARTY @@ -233,9 +233,9 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Kerby-kerb Util (org.apache.kerby:kerb-util:1.0.1 - http://directory.apache.org/kerby/kerby-kerb/kerb-util) * Kerby ASN1 Project (org.apache.kerby:kerby-asn1:1.0.1 - http://directory.apache.org/kerby/kerby-common/kerby-asn1) * Kerby PKIX Project (org.apache.kerby:kerby-pkix:1.0.1 - http://directory.apache.org/kerby/kerby-pkix) - * Apache Log4j 1.x Compatibility API (org.apache.logging.log4j:log4j-1.2-api:2.25.0 - https://logging.apache.org/log4j/2.x/) - * Apache Log4j API (org.apache.logging.log4j:log4j-api:2.25.0 - https://logging.apache.org/log4j/2.x/) - * Apache Log4j Core (org.apache.logging.log4j:log4j-core:2.25.0 - https://logging.apache.org/log4j/2.x/) + * Apache Log4j 1.x Compatibility API (org.apache.logging.log4j:log4j-1.2-api:2.25.1 - https://logging.apache.org/log4j/2.x/) + * Apache Log4j API (org.apache.logging.log4j:log4j-api:2.25.1 - https://logging.apache.org/log4j/2.x/) + * Apache Log4j Core (org.apache.logging.log4j:log4j-core:2.25.1 - https://logging.apache.org/log4j/2.x/) * Apache Log4j JUL Adapter (org.apache.logging.log4j:log4j-jul:2.17.2 - https://logging.apache.org/log4j/2.x/log4j-jul/) * Apache Log4j Layout for JSON template (org.apache.logging.log4j:log4j-layout-template-json:2.17.2 - https://logging.apache.org/log4j/2.x/log4j-layout-template-json/) * Apache Log4j SLF4J Binding (org.apache.logging.log4j:log4j-slf4j-impl:2.17.2 - https://logging.apache.org/log4j/2.x/log4j-slf4j-impl/) From 6e8b6cc33acd3db77687995acbe3753d6ffdfc3a Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 22 Jul 2025 15:03:04 -0500 Subject: [PATCH 638/939] [maven-release-plugin] prepare release dspace-7.6.5 --- dspace-api/pom.xml | 2 +- dspace-iiif/pom.xml | 2 +- dspace-oai/pom.xml | 2 +- dspace-rdf/pom.xml | 2 +- dspace-rest/pom.xml | 4 ++-- dspace-server-webapp/pom.xml | 2 +- dspace-services/pom.xml | 2 +- dspace-sword/pom.xml | 2 +- dspace-swordv2/pom.xml | 2 +- dspace/modules/additions/pom.xml | 2 +- dspace/modules/pom.xml | 2 +- dspace/modules/rest/pom.xml | 2 +- dspace/modules/server/pom.xml | 2 +- dspace/pom.xml | 2 +- pom.xml | 32 ++++++++++++++++---------------- 15 files changed, 31 insertions(+), 31 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 78acb3d23ae..6071f64c6ea 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6.5-SNAPSHOT + 7.6.5 .. diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index ff76244905d..79d8412c17b 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.5-SNAPSHOT + 7.6.5 .. diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index af0ffdf2c77..a46ee7ba511 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - 7.6.5-SNAPSHOT + 7.6.5 .. diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index 8a72bf049f3..d32a00bf626 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.5-SNAPSHOT + 7.6.5 .. diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index bec3f96ee33..64b798c58a1 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -3,7 +3,7 @@ org.dspace dspace-rest war - 7.6.5-SNAPSHOT + 7.6.5 DSpace (Deprecated) REST Webapp DSpace RESTful Web Services API. NOTE: this REST API is DEPRECATED. Please consider using the REST API in the dspace-server-webapp instead! @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6.5-SNAPSHOT + 7.6.5 .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 4b7ba616a65..193a9809e0d 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.5-SNAPSHOT + 7.6.5 .. diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index 2f4c4a5d391..7849381edfc 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.5-SNAPSHOT + 7.6.5 diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index c93949e1de5..cd5b8a6af30 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.5-SNAPSHOT + 7.6.5 .. diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index 46a9c25890d..34a22026828 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - 7.6.5-SNAPSHOT + 7.6.5 .. diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index 008c4f15a9a..125f0e16cce 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - 7.6.5-SNAPSHOT + 7.6.5 .. diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index 9da5e9d1059..04ecf6c5efa 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - 7.6.5-SNAPSHOT + 7.6.5 ../../pom.xml diff --git a/dspace/modules/rest/pom.xml b/dspace/modules/rest/pom.xml index a8bf8e6438c..c182a7ab3b0 100644 --- a/dspace/modules/rest/pom.xml +++ b/dspace/modules/rest/pom.xml @@ -13,7 +13,7 @@ org.dspace modules - 7.6.5-SNAPSHOT + 7.6.5 .. diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index 18115c72974..56a2ecdfdaf 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -13,7 +13,7 @@ just adding new jar in the classloader modules org.dspace - 7.6.5-SNAPSHOT + 7.6.5 .. diff --git a/dspace/pom.xml b/dspace/pom.xml index 5be9b100ffc..8e1060a432c 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - 7.6.5-SNAPSHOT + 7.6.5 ../pom.xml diff --git a/pom.xml b/pom.xml index 9f6c2ed42fb..3526d2cf2e7 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - 7.6.5-SNAPSHOT + 7.6.5 DSpace Parent Project DSpace open source software is a turnkey institutional repository application. @@ -875,14 +875,14 @@ org.dspace dspace-rest - 7.6.5-SNAPSHOT + 7.6.5 jar classes org.dspace dspace-rest - 7.6.5-SNAPSHOT + 7.6.5 war @@ -1031,69 +1031,69 @@ org.dspace dspace-api - 7.6.5-SNAPSHOT + 7.6.5 org.dspace dspace-api test-jar - 7.6.5-SNAPSHOT + 7.6.5 test org.dspace.modules additions - 7.6.5-SNAPSHOT + 7.6.5 org.dspace dspace-sword - 7.6.5-SNAPSHOT + 7.6.5 org.dspace dspace-swordv2 - 7.6.5-SNAPSHOT + 7.6.5 org.dspace dspace-oai - 7.6.5-SNAPSHOT + 7.6.5 org.dspace dspace-services - 7.6.5-SNAPSHOT + 7.6.5 org.dspace dspace-server-webapp test-jar - 7.6.5-SNAPSHOT + 7.6.5 test org.dspace dspace-rdf - 7.6.5-SNAPSHOT + 7.6.5 org.dspace dspace-iiif - 7.6.5-SNAPSHOT + 7.6.5 org.dspace dspace-server-webapp - 7.6.5-SNAPSHOT + 7.6.5 jar classes org.dspace dspace-server-webapp - 7.6.5-SNAPSHOT + 7.6.5 war @@ -1939,7 +1939,7 @@ scm:git:git@github.com:DSpace/DSpace.git scm:git:git@github.com:DSpace/DSpace.git https://github.com/DSpace/DSpace - dspace-7_x + dspace-7.6.5 From c061015ecfbaa11fbb2681680f481e18e806ae2d Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 22 Jul 2025 15:03:08 -0500 Subject: [PATCH 639/939] [maven-release-plugin] prepare for next development iteration --- dspace-api/pom.xml | 2 +- dspace-iiif/pom.xml | 2 +- dspace-oai/pom.xml | 2 +- dspace-rdf/pom.xml | 2 +- dspace-rest/pom.xml | 4 ++-- dspace-server-webapp/pom.xml | 2 +- dspace-services/pom.xml | 2 +- dspace-sword/pom.xml | 2 +- dspace-swordv2/pom.xml | 2 +- dspace/modules/additions/pom.xml | 2 +- dspace/modules/pom.xml | 2 +- dspace/modules/rest/pom.xml | 2 +- dspace/modules/server/pom.xml | 2 +- dspace/pom.xml | 2 +- pom.xml | 32 ++++++++++++++++---------------- 15 files changed, 31 insertions(+), 31 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 6071f64c6ea..248a5727cbb 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6.5 + 7.6.6-SNAPSHOT .. diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index 79d8412c17b..9cc05dc5ceb 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.5 + 7.6.6-SNAPSHOT .. diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index a46ee7ba511..c9211d5e592 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - 7.6.5 + 7.6.6-SNAPSHOT .. diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index d32a00bf626..fc40a39d2a2 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.5 + 7.6.6-SNAPSHOT .. diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index 64b798c58a1..fa5c56003bf 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -3,7 +3,7 @@ org.dspace dspace-rest war - 7.6.5 + 7.6.6-SNAPSHOT DSpace (Deprecated) REST Webapp DSpace RESTful Web Services API. NOTE: this REST API is DEPRECATED. Please consider using the REST API in the dspace-server-webapp instead! @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6.5 + 7.6.6-SNAPSHOT .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 193a9809e0d..4cfe93f2ee4 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.5 + 7.6.6-SNAPSHOT .. diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index 7849381edfc..26f6ecff3ef 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.5 + 7.6.6-SNAPSHOT diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index cd5b8a6af30..15d918c4d10 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.5 + 7.6.6-SNAPSHOT .. diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index 34a22026828..4972cd4e98d 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - 7.6.5 + 7.6.6-SNAPSHOT .. diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index 125f0e16cce..41274052cd7 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - 7.6.5 + 7.6.6-SNAPSHOT .. diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index 04ecf6c5efa..ecc5b3fec7a 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - 7.6.5 + 7.6.6-SNAPSHOT ../../pom.xml diff --git a/dspace/modules/rest/pom.xml b/dspace/modules/rest/pom.xml index c182a7ab3b0..4e7adb7b922 100644 --- a/dspace/modules/rest/pom.xml +++ b/dspace/modules/rest/pom.xml @@ -13,7 +13,7 @@ org.dspace modules - 7.6.5 + 7.6.6-SNAPSHOT .. diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index 56a2ecdfdaf..0e33182c698 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -13,7 +13,7 @@ just adding new jar in the classloader modules org.dspace - 7.6.5 + 7.6.6-SNAPSHOT .. diff --git a/dspace/pom.xml b/dspace/pom.xml index 8e1060a432c..6338d764b51 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - 7.6.5 + 7.6.6-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index 3526d2cf2e7..5c7b514fc58 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - 7.6.5 + 7.6.6-SNAPSHOT DSpace Parent Project DSpace open source software is a turnkey institutional repository application. @@ -875,14 +875,14 @@ org.dspace dspace-rest - 7.6.5 + 7.6.6-SNAPSHOT jar classes org.dspace dspace-rest - 7.6.5 + 7.6.6-SNAPSHOT war @@ -1031,69 +1031,69 @@ org.dspace dspace-api - 7.6.5 + 7.6.6-SNAPSHOT org.dspace dspace-api test-jar - 7.6.5 + 7.6.6-SNAPSHOT test org.dspace.modules additions - 7.6.5 + 7.6.6-SNAPSHOT org.dspace dspace-sword - 7.6.5 + 7.6.6-SNAPSHOT org.dspace dspace-swordv2 - 7.6.5 + 7.6.6-SNAPSHOT org.dspace dspace-oai - 7.6.5 + 7.6.6-SNAPSHOT org.dspace dspace-services - 7.6.5 + 7.6.6-SNAPSHOT org.dspace dspace-server-webapp test-jar - 7.6.5 + 7.6.6-SNAPSHOT test org.dspace dspace-rdf - 7.6.5 + 7.6.6-SNAPSHOT org.dspace dspace-iiif - 7.6.5 + 7.6.6-SNAPSHOT org.dspace dspace-server-webapp - 7.6.5 + 7.6.6-SNAPSHOT jar classes org.dspace dspace-server-webapp - 7.6.5 + 7.6.6-SNAPSHOT war @@ -1939,7 +1939,7 @@ scm:git:git@github.com:DSpace/DSpace.git scm:git:git@github.com:DSpace/DSpace.git https://github.com/DSpace/DSpace - dspace-7.6.5 + dspace-7_x From 2f66457abe0a4a410e2dff67db2d376aec959a25 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Thu, 28 Nov 2024 17:38:20 +0100 Subject: [PATCH 640/939] 109807: Live Import - PubMed - support optional apiKey config --- ...PubmedImportMetadataSourceServiceImpl.java | 23 +++++++++++++++++++ .../spring-dspace-addon-import-services.xml | 1 + dspace/config/modules/external-providers.cfg | 3 +++ 3 files changed, 27 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java index c870161bf9b..dc995496939 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java @@ -55,6 +55,7 @@ public class PubmedImportMetadataSourceServiceImpl extends AbstractImportMetadat private String urlFetch; private String urlSearch; + private String apiKey; private int attempt = 3; @@ -210,6 +211,9 @@ public GetNbRecords(Query query) { @Override public Integer call() throws Exception { URIBuilder uriBuilder = new URIBuilder(urlSearch); + if (StringUtils.isNotBlank(apiKey)) { + uriBuilder.addParameter("api_key", apiKey); + } uriBuilder.addParameter("db", "pubmed"); uriBuilder.addParameter("term", query.getParameterAsClass("query", String.class)); Map> params = new HashMap>(); @@ -286,6 +290,9 @@ public Collection call() throws Exception { List records = new LinkedList(); URIBuilder uriBuilder = new URIBuilder(urlSearch); + if (StringUtils.isNotBlank(apiKey)) { + uriBuilder.addParameter("api_key", apiKey); + } uriBuilder.addParameter("db", "pubmed"); uriBuilder.addParameter("retstart", start.toString()); uriBuilder.addParameter("retmax", count.toString()); @@ -316,6 +323,9 @@ public Collection call() throws Exception { String webEnv = getSingleElementValue(response, "WebEnv"); URIBuilder uriBuilder2 = new URIBuilder(urlFetch); + if (StringUtils.isNotBlank(apiKey)) { + uriBuilder2.addParameter("api_key", apiKey); + } uriBuilder2.addParameter("db", "pubmed"); uriBuilder2.addParameter("retstart", start.toString()); uriBuilder2.addParameter("retmax", count.toString()); @@ -388,6 +398,9 @@ public GetRecord(Query q) { public ImportRecord call() throws Exception { URIBuilder uriBuilder = new URIBuilder(urlFetch); + if (StringUtils.isNotBlank(apiKey)) { + uriBuilder.addParameter("api_key", apiKey); + } uriBuilder.addParameter("db", "pubmed"); uriBuilder.addParameter("retmode", "xml"); uriBuilder.addParameter("id", query.getParameterAsClass("id", String.class)); @@ -428,6 +441,9 @@ public FindMatchingRecords(Query q) { public Collection call() throws Exception { URIBuilder uriBuilder = new URIBuilder(urlSearch); + if (StringUtils.isNotBlank(apiKey)) { + uriBuilder.addParameter("api_key", apiKey); + } uriBuilder.addParameter("db", "pubmed"); uriBuilder.addParameter("usehistory", "y"); uriBuilder.addParameter("term", query.getParameterAsClass("term", String.class)); @@ -457,6 +473,9 @@ public Collection call() throws Exception { String queryKey = getSingleElementValue(response, "QueryKey"); URIBuilder uriBuilder2 = new URIBuilder(urlFetch); + if (StringUtils.isNotBlank(apiKey)) { + uriBuilder.addParameter("api_key", apiKey); + } uriBuilder2.addParameter("db", "pubmed"); uriBuilder2.addParameter("retmode", "xml"); uriBuilder2.addParameter("WebEnv", webEnv); @@ -532,4 +551,8 @@ public void setUrlSearch(String urlSearch) { this.urlSearch = urlSearch; } + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + } + } diff --git a/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml b/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml index c758966f449..c3f1036382e 100644 --- a/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml +++ b/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml @@ -56,6 +56,7 @@ + diff --git a/dspace/config/modules/external-providers.cfg b/dspace/config/modules/external-providers.cfg index b7c0e120dbc..4f6de6e080e 100644 --- a/dspace/config/modules/external-providers.cfg +++ b/dspace/config/modules/external-providers.cfg @@ -45,6 +45,9 @@ epo.searchUrl = https://ops.epo.org/rest-services/published-data/search ################################################################# #---------------------- PubMed -----------------------------# #---------------------------------------------------------------# +# If apiKey is set then it's used, if not set or blank then it's not +# Max amount of requests per ip per second with apiKey is 10; without 3 +pubmed.apiKey = pubmed.url.search = https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi pubmed.url.fetch = https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi ################################################################# From ffd60bf8115b6f6ce35167cbd52d9ea7040ba5be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Jul 2025 02:55:08 +0000 Subject: [PATCH 641/939] Bump the fasterxml group with 3 updates Bumps the fasterxml group with 3 updates: [com.fasterxml.jackson.core:jackson-annotations](https://github.com/FasterXML/jackson), [com.fasterxml.jackson.core:jackson-core](https://github.com/FasterXML/jackson-core) and [com.fasterxml.jackson.core:jackson-databind](https://github.com/FasterXML/jackson). Updates `com.fasterxml.jackson.core:jackson-annotations` from 2.19.1 to 2.19.2 - [Commits](https://github.com/FasterXML/jackson/commits) Updates `com.fasterxml.jackson.core:jackson-core` from 2.19.1 to 2.19.2 - [Commits](https://github.com/FasterXML/jackson-core/compare/jackson-core-2.19.1...jackson-core-2.19.2) Updates `com.fasterxml.jackson.core:jackson-core` from 2.19.1 to 2.19.2 - [Commits](https://github.com/FasterXML/jackson-core/compare/jackson-core-2.19.1...jackson-core-2.19.2) Updates `com.fasterxml.jackson.core:jackson-databind` from 2.19.1 to 2.19.2 - [Commits](https://github.com/FasterXML/jackson/commits) --- updated-dependencies: - dependency-name: com.fasterxml.jackson.core:jackson-annotations dependency-version: 2.19.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: fasterxml - dependency-name: com.fasterxml.jackson.core:jackson-core dependency-version: 2.19.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: fasterxml - dependency-name: com.fasterxml.jackson.core:jackson-core dependency-version: 2.19.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: fasterxml - dependency-name: com.fasterxml.jackson.core:jackson-databind dependency-version: 2.19.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: fasterxml ... Signed-off-by: dependabot[bot] --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 5c7b514fc58..b8f9dd44ee5 100644 --- a/pom.xml +++ b/pom.xml @@ -30,8 +30,8 @@ 3.10.8 2.31.0 - 2.19.1 - 2.19.1 + 2.19.2 + 2.19.2 1.3.2 2.3.1 2.3.9 From 609062befbd00027724561409cccb389f7e4019f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Jul 2025 02:55:24 +0000 Subject: [PATCH 642/939] Bump the google-apis group with 3 updates Bumps the google-apis group with 3 updates: [com.google.http-client:google-http-client](https://github.com/googleapis/google-http-java-client), [com.google.http-client:google-http-client-jackson2](https://github.com/googleapis/google-http-java-client) and [com.google.http-client:google-http-client-gson](https://github.com/googleapis/google-http-java-client). Updates `com.google.http-client:google-http-client` from 1.47.0 to 1.47.1 - [Release notes](https://github.com/googleapis/google-http-java-client/releases) - [Changelog](https://github.com/googleapis/google-http-java-client/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/google-http-java-client/compare/v1.47.0...v1.47.1) Updates `com.google.http-client:google-http-client-jackson2` from 1.47.0 to 1.47.1 - [Release notes](https://github.com/googleapis/google-http-java-client/releases) - [Changelog](https://github.com/googleapis/google-http-java-client/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/google-http-java-client/compare/v1.47.0...v1.47.1) Updates `com.google.http-client:google-http-client-gson` from 1.47.0 to 1.47.1 - [Release notes](https://github.com/googleapis/google-http-java-client/releases) - [Changelog](https://github.com/googleapis/google-http-java-client/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/google-http-java-client/compare/v1.47.0...v1.47.1) --- updated-dependencies: - dependency-name: com.google.http-client:google-http-client dependency-version: 1.47.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: google-apis - dependency-name: com.google.http-client:google-http-client-jackson2 dependency-version: 1.47.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: google-apis - dependency-name: com.google.http-client:google-http-client-gson dependency-version: 1.47.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: google-apis ... Signed-off-by: dependabot[bot] --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 5c7b514fc58..df92203466d 100644 --- a/pom.xml +++ b/pom.xml @@ -1701,7 +1701,7 @@ com.google.http-client google-http-client - 1.47.0 + 1.47.1 com.google.errorprone @@ -1723,7 +1723,7 @@ com.google.http-client google-http-client-jackson2 - 1.47.0 + 1.47.1 jackson-core @@ -1745,7 +1745,7 @@ com.google.http-client google-http-client-gson - 1.47.0 + 1.47.1 com.squareup.okhttp3 From 92120211da77d6c73cd948de274d813de66825b0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Jul 2025 02:55:44 +0000 Subject: [PATCH 643/939] Bump the test-tools group across 1 directory with 7 updates --- updated-dependencies: - dependency-name: org.xmlunit:xmlunit-core dependency-version: 2.10.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-buffer dependency-version: 4.2.3.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-transport dependency-version: 4.2.3.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-transport-native-unix-common dependency-version: 4.2.3.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-common dependency-version: 4.2.3.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-handler dependency-version: 4.2.3.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-codec dependency-version: 4.2.3.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 248a5727cbb..18be290ad01 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -884,32 +884,32 @@ io.netty netty-buffer - 4.2.2.Final + 4.2.3.Final io.netty netty-transport - 4.2.2.Final + 4.2.3.Final io.netty netty-transport-native-unix-common - 4.2.2.Final + 4.2.3.Final io.netty netty-common - 4.2.2.Final + 4.2.3.Final io.netty netty-handler - 4.2.2.Final + 4.2.3.Final io.netty netty-codec - 4.2.2.Final + 4.2.3.Final org.apache.velocity @@ -919,7 +919,7 @@ org.xmlunit xmlunit-core - 2.10.2 + 2.10.3 test From ac81a3d36f7e3e0a24a9077ba0d5c03e9de23ac6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Jul 2025 03:36:02 +0000 Subject: [PATCH 644/939] Bump the apache-commons group across 1 directory with 5 updates Bumps the apache-commons group with 5 updates in the / directory: | Package | From | To | | --- | --- | --- | | [commons-codec:commons-codec](https://github.com/apache/commons-codec) | `1.18.0` | `1.19.0` | | [commons-io:commons-io](https://github.com/apache/commons-io) | `2.19.0` | `2.20.0` | | org.apache.commons:commons-lang3 | `3.17.0` | `3.18.0` | | [org.apache.commons:commons-text](https://github.com/apache/commons-text) | `1.13.1` | `1.14.0` | | commons-validator:commons-validator | `1.9.0` | `1.10.0` | Updates `commons-codec:commons-codec` from 1.18.0 to 1.19.0 - [Changelog](https://github.com/apache/commons-codec/blob/master/RELEASE-NOTES.txt) - [Commits](https://github.com/apache/commons-codec/compare/rel/commons-codec-1.18.0...rel/commons-codec-1.19.0) Updates `commons-io:commons-io` from 2.19.0 to 2.20.0 - [Changelog](https://github.com/apache/commons-io/blob/master/RELEASE-NOTES.txt) - [Commits](https://github.com/apache/commons-io/compare/rel/commons-io-2.19.0...rel/commons-io-2.20.0) Updates `org.apache.commons:commons-lang3` from 3.17.0 to 3.18.0 Updates `org.apache.commons:commons-text` from 1.13.1 to 1.14.0 - [Changelog](https://github.com/apache/commons-text/blob/master/RELEASE-NOTES.txt) - [Commits](https://github.com/apache/commons-text/compare/rel/commons-text-1.13.1...rel/commons-text-1.14.0) Updates `commons-validator:commons-validator` from 1.9.0 to 1.10.0 --- updated-dependencies: - dependency-name: commons-codec:commons-codec dependency-version: 1.19.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: apache-commons - dependency-name: commons-io:commons-io dependency-version: 2.20.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: apache-commons - dependency-name: org.apache.commons:commons-lang3 dependency-version: 3.18.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: apache-commons - dependency-name: org.apache.commons:commons-text dependency-version: 1.14.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: apache-commons - dependency-name: commons-validator:commons-validator dependency-version: 1.10.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: apache-commons ... Signed-off-by: dependabot[bot] --- pom.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 5c7b514fc58..9f94b8aab42 100644 --- a/pom.xml +++ b/pom.xml @@ -1478,7 +1478,7 @@ commons-codec commons-codec - 1.18.0 + 1.19.0 org.apache.commons @@ -1498,12 +1498,12 @@ commons-io commons-io - 2.19.0 + 2.20.0 org.apache.commons commons-lang3 - 3.17.0 + 3.18.0 @@ -1525,12 +1525,12 @@ org.apache.commons commons-text - 1.13.1 + 1.14.0 commons-validator commons-validator - 1.9.0 + 1.10.0 joda-time From de220d55acb6b8f8b5f5f77a26089369e1ddd701 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Jul 2025 03:37:58 +0000 Subject: [PATCH 645/939] Bump org.apache.james:apache-mime4j-core from 0.8.12 to 0.8.13 Bumps org.apache.james:apache-mime4j-core from 0.8.12 to 0.8.13. --- updated-dependencies: - dependency-name: org.apache.james:apache-mime4j-core dependency-version: 0.8.13 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5c7b514fc58..54591d3a5f7 100644 --- a/pom.xml +++ b/pom.xml @@ -1324,7 +1324,7 @@ org.apache.james apache-mime4j-core - 0.8.12 + 0.8.13 From dc65c7a3046d89f6960ec7843cd704b801dd3aa1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Jul 2025 03:39:24 +0000 Subject: [PATCH 646/939] Bump com.opencsv:opencsv from 5.11.1 to 5.12.0 Bumps com.opencsv:opencsv from 5.11.1 to 5.12.0. --- updated-dependencies: - dependency-name: com.opencsv:opencsv dependency-version: 5.12.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 248a5727cbb..ab15840d20b 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -801,7 +801,7 @@ com.opencsv opencsv - 5.11.1 + 5.12.0 From ceab9526c414d1ead05f4c18d69b70b07d991ae6 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Mon, 28 Jul 2025 13:42:28 -0400 Subject: [PATCH 647/939] Port #11075 to 7_x --- .../authority/DSpaceControlledVocabulary.java | 89 +++++++++++-------- .../DSpaceControlledVocabularyTest.java | 2 + 2 files changed, 55 insertions(+), 36 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java index 444332df97d..3f7f926c07a 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java @@ -34,35 +34,37 @@ * from {@code ${dspace.dir}/config/controlled-vocabularies/*.xml} and turns * them into autocompleting authorities. * - * Configuration: This MUST be configured as a self-named plugin, e.g.: {@code - * plugin.selfnamed.org.dspace.content.authority.ChoiceAuthority = \ + *

    Configuration: This MUST be configured as a self-named plugin, e.g.: {@code + * plugin.selfnamed.org.dspace.content.authority.ChoiceAuthority = * org.dspace.content.authority.DSpaceControlledVocabulary * } * - * It AUTOMATICALLY configures a plugin instance for each XML file in the + *

    It AUTOMATICALLY configures a plugin instance for each XML file in the * controlled vocabularies directory. The name of the plugin is the basename of * the file; e.g., {@code ${dspace.dir}/config/controlled-vocabularies/nsi.xml} * would generate a plugin called "nsi". * - * Each configured plugin comes with three configuration options: {@code - * vocabulary.plugin._plugin_.hierarchy.store = - * # Store entire hierarchy along with selected value. Default: TRUE - * vocabulary.plugin._plugin_.hierarchy.suggest = - * # Display entire hierarchy in the suggestion list. Default: TRUE - * vocabulary.plugin._plugin_.delimiter = "" - * # Delimiter to use when building hierarchy strings. Default: "::" - * } + *

    Each configured plugin comes with three configuration options: + *

      + *
    • {@code vocabulary.plugin._plugin_.hierarchy.store = } + * # Store entire hierarchy along with selected value. Default: TRUE
    • + *
    • {@code vocabulary.plugin._plugin_.hierarchy.suggest = + * # Display entire hierarchy in the suggestion list. Default: TRUE}
    • + *
    • {@code vocabulary.plugin._plugin_.delimiter = "" + * # Delimiter to use when building hierarchy strings. Default: "::"}
    • + *
    * * @author Michael B. Klein */ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements HierarchicalAuthority { - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(DSpaceControlledVocabulary.class); + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(); protected static String xpathTemplate = "//node[contains(translate(@label,'ABCDEFGHIJKLMNOPQRSTUVWXYZ'," + - "'abcdefghijklmnopqrstuvwxyz'),'%s')]"; - protected static String idTemplate = "//node[@id = '%s']"; - protected static String labelTemplate = "//node[@label = '%s']"; + "'abcdefghijklmnopqrstuvwxyz'),%s)]"; + protected static String idTemplate = "//node[@id = %s]"; + protected static String idTemplateQuoted = "//node[@id = '%s']"; + protected static String labelTemplate = "//node[@label = %s]"; protected static String idParentTemplate = "//node[@id = '%s']/parent::isComposedBy/parent::node"; protected static String rootTemplate = "/node"; protected static String pluginNames[] = null; @@ -106,7 +108,7 @@ public boolean accept(File dir, String name) { File.separator + "config" + File.separator + "controlled-vocabularies"; String[] xmlFiles = (new File(vocabulariesPath)).list(new xmlFilter()); - List names = new ArrayList(); + List names = new ArrayList<>(); for (String filename : xmlFiles) { names.add((new File(filename)).getName().replace(".xml", "")); } @@ -162,14 +164,23 @@ protected String buildString(Node node) { public Choices getMatches(String text, int start, int limit, String locale) { init(); log.debug("Getting matches for '" + text + "'"); - String xpathExpression = ""; String[] textHierarchy = text.split(hierarchyDelimiter, -1); + StringBuilder xpathExpressionBuilder = new StringBuilder(); for (int i = 0; i < textHierarchy.length; i++) { - xpathExpression += String.format(xpathTemplate, textHierarchy[i].replaceAll("'", "'").toLowerCase()); + xpathExpressionBuilder.append(String.format(xpathTemplate, "$var" + i)); } + String xpathExpression = xpathExpressionBuilder.toString(); XPath xpath = XPathFactory.newInstance().newXPath(); - int total = 0; - List choices = new ArrayList(); + xpath.setXPathVariableResolver(variableName -> { + String varName = variableName.getLocalPart(); + if (varName.startsWith("var")) { + int index = Integer.parseInt(varName.substring(3)); + return textHierarchy[index].toLowerCase(); + } + throw new IllegalArgumentException("Unexpected variable: " + varName); + }); + int total; + List choices; try { NodeList results = (NodeList) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODESET); total = results.getLength(); @@ -185,14 +196,23 @@ public Choices getMatches(String text, int start, int limit, String locale) { @Override public Choices getBestMatch(String text, String locale) { init(); - log.debug("Getting best matches for '" + text + "'"); - String xpathExpression = ""; + log.debug("Getting best matches for {}'", text); String[] textHierarchy = text.split(hierarchyDelimiter, -1); + StringBuilder xpathExpressionBuilder = new StringBuilder(); for (int i = 0; i < textHierarchy.length; i++) { - xpathExpression += String.format(labelTemplate, textHierarchy[i].replaceAll("'", "'")); + xpathExpressionBuilder.append(String.format(labelTemplate, "$var" + i)); } + String xpathExpression = xpathExpressionBuilder.toString(); XPath xpath = XPathFactory.newInstance().newXPath(); - List choices = new ArrayList(); + xpath.setXPathVariableResolver(variableName -> { + String varName = variableName.getLocalPart(); + if (varName.startsWith("var")) { + int index = Integer.parseInt(varName.substring(3)); + return textHierarchy[index]; + } + throw new IllegalArgumentException("Unexpected variable: " + varName); + }); + List choices; try { NodeList results = (NodeList) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODESET); choices = getChoicesFromNodeList(results, 0, 1); @@ -240,7 +260,7 @@ public Choices getTopChoices(String authorityName, int start, int limit, String @Override public Choices getChoicesByParent(String authorityName, String parentId, int start, int limit, String locale) { init(); - String xpathExpression = String.format(idTemplate, parentId); + String xpathExpression = String.format(idTemplateQuoted, parentId); return getChoicesByXpath(xpathExpression, start, limit); } @@ -264,15 +284,12 @@ public Integer getPreloadLevel() { } private boolean isRootElement(Node node) { - if (node != null && node.getOwnerDocument().getDocumentElement().equals(node)) { - return true; - } - return false; + return node != null && node.getOwnerDocument().getDocumentElement().equals(node); } private Node getNode(String key) throws XPathExpressionException { init(); - String xpathExpression = String.format(idTemplate, key); + String xpathExpression = String.format(idTemplateQuoted, key); Node node = getNodeFromXPath(xpathExpression); return node; } @@ -284,7 +301,7 @@ private Node getNodeFromXPath(String xpathExpression) throws XPathExpressionExce } private List getChoicesFromNodeList(NodeList results, int start, int limit) { - List choices = new ArrayList(); + List choices = new ArrayList<>(); for (int i = 0; i < results.getLength(); i++) { if (i < start) { continue; @@ -303,14 +320,14 @@ private List getChoicesFromNodeList(NodeList results, int start, int lim private Map addOtherInformation(String parentCurr, String noteCurr, List childrenCurr, String authorityCurr) { - Map extras = new HashMap(); + Map extras = new HashMap<>(); if (StringUtils.isNotBlank(parentCurr)) { extras.put("parent", parentCurr); } if (StringUtils.isNotBlank(noteCurr)) { extras.put("note", noteCurr); } - if (childrenCurr.size() > 0) { + if (!childrenCurr.isEmpty()) { extras.put("hasChildren", "true"); } else { extras.put("hasChildren", "false"); @@ -368,7 +385,7 @@ private String getNote(Node node) { } private List getChildren(Node node) { - List children = new ArrayList(); + List children = new ArrayList<>(); NodeList childNodes = node.getChildNodes(); for (int ci = 0; ci < childNodes.getLength(); ci++) { Node firstChild = childNodes.item(ci); @@ -391,7 +408,7 @@ private List getChildren(Node node) { private boolean isSelectable(Node node) { Node selectableAttr = node.getAttributes().getNamedItem("selectable"); if (null != selectableAttr) { - return Boolean.valueOf(selectableAttr.getNodeValue()); + return Boolean.parseBoolean(selectableAttr.getNodeValue()); } else { // Default is true return true; } @@ -418,7 +435,7 @@ private String getAuthority(Node node) { } private Choices getChoicesByXpath(String xpathExpression, int start, int limit) { - List choices = new ArrayList(); + List choices = new ArrayList<>(); XPath xpath = XPathFactory.newInstance().newXPath(); try { Node parentNode = (Node) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODE); diff --git a/dspace-api/src/test/java/org/dspace/content/authority/DSpaceControlledVocabularyTest.java b/dspace-api/src/test/java/org/dspace/content/authority/DSpaceControlledVocabularyTest.java index 255b070e5ea..524c6407b7b 100644 --- a/dspace-api/src/test/java/org/dspace/content/authority/DSpaceControlledVocabularyTest.java +++ b/dspace-api/src/test/java/org/dspace/content/authority/DSpaceControlledVocabularyTest.java @@ -8,6 +8,7 @@ package org.dspace.content.authority; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import java.io.IOException; @@ -86,6 +87,7 @@ public void testGetMatches() throws IOException, ClassNotFoundException { CoreServiceFactory.getInstance().getPluginService().getNamedPlugin(Class.forName(PLUGIN_INTERFACE), "farm"); assertNotNull(instance); Choices result = instance.getMatches(text, start, limit, locale); + assertNotEquals("At least one match expected", 0, result.values.length); assertEquals("north 40", result.values[0].value); } From af38697adee0ad4462b69805aec1ed9554e32bf0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Jul 2025 19:43:25 +0000 Subject: [PATCH 648/939] Bump the build-tools group across 1 directory with 2 updates Bumps the build-tools group with 2 updates in the / directory: [org.apache.maven.plugins:maven-enforcer-plugin](https://github.com/apache/maven-enforcer) and [org.codehaus.mojo:license-maven-plugin](https://github.com/mojohaus/license-maven-plugin). Updates `org.apache.maven.plugins:maven-enforcer-plugin` from 3.5.0 to 3.6.0 - [Release notes](https://github.com/apache/maven-enforcer/releases) - [Commits](https://github.com/apache/maven-enforcer/compare/enforcer-3.5.0...enforcer-3.6.0) Updates `org.codehaus.mojo:license-maven-plugin` from 2.5.0 to 2.6.0 - [Release notes](https://github.com/mojohaus/license-maven-plugin/releases) - [Commits](https://github.com/mojohaus/license-maven-plugin/compare/2.5.0...2.6.0) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-enforcer-plugin dependency-version: 3.6.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools - dependency-name: org.codehaus.mojo:license-maven-plugin dependency-version: 2.6.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools ... Signed-off-by: dependabot[bot] --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 78f1a388afe..9e1d52f0be6 100644 --- a/pom.xml +++ b/pom.xml @@ -89,7 +89,7 @@ org.apache.maven.plugins maven-enforcer-plugin - 3.5.0 + 3.6.1 enforce-java @@ -680,7 +680,7 @@ org.codehaus.mojo license-maven-plugin - 2.5.0 + 2.6.0 false From 5e3463bd8becba0ee105be00e9f5a117a12a02b3 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 25 Jul 2025 11:13:02 -0500 Subject: [PATCH 649/939] Add tests for backend logging to docker deployment tests --- .github/workflows/docker.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 815aec5cf6b..07990074521 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -219,6 +219,19 @@ jobs: result=$(wget -O- -q http://127.0.0.1:8080/server/api/core/collections) echo "$result" echo "$result" | grep -oE "\"Dog in Yard\"," + # Verify basic backend logging is working. + # 1. Access the top communities list. Verify that the "Before request" INFO statement is logged + # 2. Access an invalid endpoint (and ignore 404 response). Verify that a "status:404" WARN statement is logged + - name: Verify backend is logging properly + run: | + wget -O/dev/null -q http://127.0.0.1:8080/server/api/core/communities/search/top + logs=$(docker compose -f docker-compose.yml logs -n 5 dspace) + echo "$logs" + echo "$logs" | grep -o "Before request \[GET /server/api/core/communities/search/top\]" + wget -O/dev/null -q http://127.0.0.1:8080/server/api/does/not/exist || true + logs=$(docker compose -f docker-compose.yml logs -n 5 dspace) + echo "$logs" + echo "$logs" | grep -o "status:404 exception: The repository type does.not was not found" # Verify Handle Server can be stared and is working properly # 1. First generate the "[dspace]/handle-server" folder with the sitebndl.zip # 2. Start the Handle Server (and wait 20 seconds to let it start up) From 2b2d0b19b68f45311d9774de8ace443fc4988d95 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 24 Feb 2025 09:16:19 -0600 Subject: [PATCH 650/939] Ensure DSpace defaults to UTC time zone in all code / tests. This is necessary so that Spring / Hibernate don't auto-switch timezones when reading from database. --- .../app/bulkaccesscontrol/BulkAccessControl.java | 3 ++- .../src/main/java/org/dspace/util/DateMathParser.java | 3 ++- .../org/dspace/AbstractDSpaceIntegrationTest.java | 7 +++++-- .../src/test/java/org/dspace/AbstractDSpaceTest.java | 7 +++++-- .../main/java/org/dspace/app/rest/Application.java | 11 +++++++++++ 5 files changed, 25 insertions(+), 6 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControl.java b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControl.java index 30f68efaf3c..59e75059c94 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControl.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControl.java @@ -18,6 +18,7 @@ import java.sql.SQLException; import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.time.ZoneOffset; import java.util.Arrays; import java.util.Date; import java.util.Iterator; @@ -154,7 +155,7 @@ public void internalRun() throws Exception { } ObjectMapper mapper = new ObjectMapper(); - mapper.setTimeZone(TimeZone.getTimeZone("UTC")); + mapper.setTimeZone(TimeZone.getTimeZone(ZoneOffset.UTC)); BulkAccessControlInput accessControl; context = new Context(Context.Mode.BATCH_EDIT); setEPerson(context); diff --git a/dspace-api/src/main/java/org/dspace/util/DateMathParser.java b/dspace-api/src/main/java/org/dspace/util/DateMathParser.java index 9ff252e8ce3..13f9216c9bd 100644 --- a/dspace-api/src/main/java/org/dspace/util/DateMathParser.java +++ b/dspace-api/src/main/java/org/dspace/util/DateMathParser.java @@ -13,6 +13,7 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZoneId; +import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; @@ -107,7 +108,7 @@ public class DateMathParser { private static final Logger LOG = LogManager.getLogger(); - public static final TimeZone UTC = TimeZone.getTimeZone("UTC"); + public static final TimeZone UTC = TimeZone.getTimeZone(ZoneOffset.UTC); /** * Default TimeZone for DateMath rounding (UTC) diff --git a/dspace-api/src/test/java/org/dspace/AbstractDSpaceIntegrationTest.java b/dspace-api/src/test/java/org/dspace/AbstractDSpaceIntegrationTest.java index 791fdbc66ab..2822cdcf601 100644 --- a/dspace-api/src/test/java/org/dspace/AbstractDSpaceIntegrationTest.java +++ b/dspace-api/src/test/java/org/dspace/AbstractDSpaceIntegrationTest.java @@ -12,6 +12,7 @@ import java.io.IOException; import java.net.URL; import java.sql.SQLException; +import java.time.ZoneOffset; import java.util.Properties; import java.util.TimeZone; @@ -73,8 +74,10 @@ public static void initTestEnvironment() { //Stops System.exit(0) throws exception instead of exitting System.setSecurityManager(new NoExitSecurityManager()); - //set a standard time zone for the tests - TimeZone.setDefault(TimeZone.getTimeZone("Europe/Dublin")); + // All tests should assume UTC timezone by default (unless overridden in the test itself) + // This ensures that Spring doesn't attempt to change the timezone of dates that are read from the + // database (via Hibernate). We store all dates in the database as UTC. + TimeZone.setDefault(TimeZone.getTimeZone(ZoneOffset.UTC)); //load the properties of the tests testProps = new Properties(); diff --git a/dspace-api/src/test/java/org/dspace/AbstractDSpaceTest.java b/dspace-api/src/test/java/org/dspace/AbstractDSpaceTest.java index 136af83f076..4452955a3b6 100644 --- a/dspace-api/src/test/java/org/dspace/AbstractDSpaceTest.java +++ b/dspace-api/src/test/java/org/dspace/AbstractDSpaceTest.java @@ -12,6 +12,7 @@ import java.io.IOException; import java.net.URL; import java.sql.SQLException; +import java.time.ZoneOffset; import java.util.Properties; import java.util.TimeZone; @@ -82,8 +83,10 @@ protected AbstractDSpaceTest() { } @BeforeClass public static void initKernel() { try { - //set a standard time zone for the tests - TimeZone.setDefault(TimeZone.getTimeZone("Europe/Dublin")); + // All tests should assume UTC timezone by default (unless overridden in the test itself) + // This ensures that Spring doesn't attempt to change the timezone of dates that are read from the + // database (via Hibernate). We store all dates in the database as UTC. + TimeZone.setDefault(TimeZone.getTimeZone(ZoneOffset.UTC)); //load the properties of the tests testProps = new Properties(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/Application.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/Application.java index 07b802b684e..7dc878a5c7a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/Application.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/Application.java @@ -9,7 +9,10 @@ import java.io.IOException; import java.sql.SQLException; +import java.time.ZoneOffset; import java.util.List; +import java.util.TimeZone; +import javax.annotation.PostConstruct; import javax.servlet.Filter; import org.dspace.app.rest.filter.DSpaceRequestContextFilter; @@ -262,4 +265,12 @@ public void addArgumentResolvers(@NonNull List ar } }; } + + @PostConstruct + public void setDefaultTimezone() { + // Set the default timezone in Spring Boot to UTC. + // This ensures that Spring Boot doesn't attempt to change the timezone of dates that are read from the + // database (via Hibernate). We store all dates in the database as UTC. + TimeZone.setDefault(TimeZone.getTimeZone(ZoneOffset.UTC)); + } } From 6282cb9f12101f552123e8644f4d4ec0dc0fd505 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Jul 2025 18:40:38 +0000 Subject: [PATCH 651/939] Bump org.apache.commons:commons-compress in the apache-commons group Bumps the apache-commons group with 1 update: [org.apache.commons:commons-compress](https://github.com/apache/commons-compress). Updates `org.apache.commons:commons-compress` from 1.27.1 to 1.28.0 - [Changelog](https://github.com/apache/commons-compress/blob/master/RELEASE-NOTES.txt) - [Commits](https://github.com/apache/commons-compress/compare/rel/commons-compress-1.27.1...rel/commons-compress-1.28.0) --- updated-dependencies: - dependency-name: org.apache.commons:commons-compress dependency-version: 1.28.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: apache-commons ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2581f3b09ee..14292ea0ff8 100644 --- a/pom.xml +++ b/pom.xml @@ -1515,7 +1515,7 @@ org.apache.commons commons-compress - 1.27.1 + 1.28.0 org.apache.commons From 1ecb5660f8b99e0eb0a54faa489132f40d6bbebf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Jul 2025 18:41:59 +0000 Subject: [PATCH 652/939] Bump com.github.spotbugs:spotbugs-maven-plugin from 4.9.3.0 to 4.9.3.2 Bumps [com.github.spotbugs:spotbugs-maven-plugin](https://github.com/spotbugs/spotbugs-maven-plugin) from 4.9.3.0 to 4.9.3.2. - [Release notes](https://github.com/spotbugs/spotbugs-maven-plugin/releases) - [Commits](https://github.com/spotbugs/spotbugs-maven-plugin/compare/spotbugs-maven-plugin-4.9.3.0...spotbugs-maven-plugin-4.9.3.2) --- updated-dependencies: - dependency-name: com.github.spotbugs:spotbugs-maven-plugin dependency-version: 4.9.3.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2581f3b09ee..b898ac20a7a 100644 --- a/pom.xml +++ b/pom.xml @@ -295,7 +295,7 @@ com.github.spotbugs spotbugs-maven-plugin - 4.9.3.0 + 4.9.3.2 Max Low From 808135f688a8b56bf3e76260befe4361b53a60b2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Jul 2025 18:42:52 +0000 Subject: [PATCH 653/939] Bump io.grpc:grpc-context from 1.73.0 to 1.74.0 Bumps [io.grpc:grpc-context](https://github.com/grpc/grpc-java) from 1.73.0 to 1.74.0. - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.73.0...v1.74.0) --- updated-dependencies: - dependency-name: io.grpc:grpc-context dependency-version: 1.74.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2581f3b09ee..adc3809fa73 100644 --- a/pom.xml +++ b/pom.xml @@ -1718,7 +1718,7 @@ io.grpc grpc-context - 1.73.0 + 1.74.0 com.google.http-client From 2d353ea995ff7e540dc1d81c96391abc53f6dc7e Mon Sep 17 00:00:00 2001 From: "David P. Steelman" Date: Thu, 13 Mar 2025 14:21:16 -0400 Subject: [PATCH 654/939] Fix checkpointing for bitstore migration Fixes the checkpointing for bitstore migration by actually committing the changes to the database. Replacing "dispatchEvents" with "commit" as testing has shown that this is necessary to ensure that the status of bitstreams is properly updated when the bitstore migration is interrupted. The provided integration tests fail if bitstreams successfully migrated before the interruption are not properly recorded in the database as being in the destination assetstore. (cherry picked from commit 3d7c45852d8b09fb15cddc1fb72c2a389e2b4d6a) --- .../bitstore/BitstreamStorageServiceImpl.java | 2 +- .../BitstreamStorageServiceImplIT.java | 262 ++++++++++++++++++ 2 files changed, 263 insertions(+), 1 deletion(-) create mode 100644 dspace-api/src/test/java/org/dspace/storage/bitstore/BitstreamStorageServiceImplIT.java diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/BitstreamStorageServiceImpl.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/BitstreamStorageServiceImpl.java index 956ac5a7f8f..dcdc238298f 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/BitstreamStorageServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/BitstreamStorageServiceImpl.java @@ -423,7 +423,7 @@ public void migrate(Context context, Integer assetstoreSource, Integer assetstor //modulo if ((processedCounter % batchCommitSize) == 0) { log.info("Migration Commit Checkpoint: " + processedCounter); - context.dispatchEvents(); + context.commit(); } } diff --git a/dspace-api/src/test/java/org/dspace/storage/bitstore/BitstreamStorageServiceImplIT.java b/dspace-api/src/test/java/org/dspace/storage/bitstore/BitstreamStorageServiceImplIT.java new file mode 100644 index 00000000000..5b15cba1c4c --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/storage/bitstore/BitstreamStorageServiceImplIT.java @@ -0,0 +1,262 @@ +/** + * 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.storage.bitstore; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.io.InputStream; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.io.IOUtils; +import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.authorize.AuthorizeException; +import org.dspace.builder.BitstreamBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Bitstream; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.BitstreamService; +import org.dspace.core.Context; +import org.dspace.storage.bitstore.factory.StorageServiceFactory; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class BitstreamStorageServiceImplIT extends AbstractIntegrationTestWithDatabase { + private BitstreamService bitstreamService = ContentServiceFactory.getInstance().getBitstreamService(); + private BitstreamStorageServiceImpl bitstreamStorageService = + (BitstreamStorageServiceImpl) StorageServiceFactory.getInstance().getBitstreamStorageService(); + private Collection collection; + + private Map originalBitstores; + + private static final Integer SOURCE_STORE = 0; + private static final Integer DEST_STORE = 1; + + @Rule + public final TemporaryFolder tempStoreDir = new TemporaryFolder(); + + @Before + public void setup() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .build(); + + collection = CollectionBuilder.createCollection(context, parentCommunity) + .build(); + + originalBitstores = bitstreamStorageService.getStores(); + Map stores = new HashMap<>(); + DSBitStoreService sourceStore = new DSBitStoreService(); + sourceStore.setBaseDir(tempStoreDir.newFolder("src")); + + stores.put(SOURCE_STORE, sourceStore); + bitstreamStorageService.setStores(stores); + + context.restoreAuthSystemState(); + } + + @After + public void cleanUp() throws IOException { + // Restore the bitstore storage stores + bitstreamStorageService.setStores(originalBitstores); + } + + /** + * Test batch commit checkpointing, using the default batch commit size of 1 + * + * @throws Exception if an exception occurs. + */ + @Test + public void testDefaultBatchCommitSize() throws Exception { + Context context = this.context; + + // Destination assetstore fails after two bitstreams have been migrated + DSBitStoreService destinationStore = new LimitedTempDSBitStoreService(tempStoreDir, 2); + Map stores = bitstreamStorageService.getStores(); + stores.put(DEST_STORE, destinationStore); + + // Create three bitstreams in the source assetstore + createBitstreams(context, 3); + + // Three bitstreams in source assetstore at the start + assertThat(bitstreamService.countByStoreNumber(context, SOURCE_STORE).intValue(), equalTo(3)); + + // No bitstreams in destination assetstore at the start + assertThat(bitstreamService.countByStoreNumber(context, DEST_STORE).intValue(), equalTo(0)); + + /// Commit any pending transaction to database + context.commit(); + + // Migrate bitstreams + context.turnOffAuthorisationSystem(); + + boolean deleteOld = false; + Integer batchCommitSize = 1; + try { + bitstreamStorageService.migrate( + context, SOURCE_STORE, DEST_STORE, deleteOld, + batchCommitSize + ); + fail("IOException should have been thrown"); + } catch (IOException ioe) { + // Rollback any pending transaction + context.rollback(); + } + + context.restoreAuthSystemState(); + + // One bitstream should still be in the source assetstore, due to the + // interrupted migration + assertThat(bitstreamService.countByStoreNumber(context, SOURCE_STORE).intValue(), equalTo(1)); + + // Two bitstreams should have migrated to the destination assetstore + assertThat(bitstreamService.countByStoreNumber(context, DEST_STORE).intValue(), equalTo(2)); + } + + /** + * Test batch commit checkpointing, using the default batch commit size of 3 + * + * @throws Exception if an exception occurs. + */ + @Test + public void testBatchCommitSizeThree() throws Exception { + Context context = this.context; + + // Destination assetstore fails after four bitstreams have been migrated + DSBitStoreService destinationStore = new LimitedTempDSBitStoreService(tempStoreDir, 4); + Map stores = bitstreamStorageService.getStores(); + stores.put(DEST_STORE, destinationStore); + + // Create five bitstreams in the source assetstore + createBitstreams(context, 5); + + // Five bitstreams in source assetstore at the start + assertThat(bitstreamService.countByStoreNumber(context, SOURCE_STORE).intValue(), equalTo(5)); + + // No bitstreams in destination assetstore at the start + assertThat(bitstreamService.countByStoreNumber(context, DEST_STORE).intValue(), equalTo(0)); + + // Commit any pending transaction to database + context.commit(); + + // Migrate bitstreams + context.turnOffAuthorisationSystem(); + + boolean deleteOld = false; + Integer batchCommitSize = 3; + try { + bitstreamStorageService.migrate( + context, SOURCE_STORE, DEST_STORE, deleteOld, + batchCommitSize + ); + fail("IOException should have been thrown"); + } catch (IOException ioe) { + // Rollback any pending transaction + context.rollback(); + } + + context.restoreAuthSystemState(); + + // Since the batch commit size is 3, only three bitstreams should be + // marked as migrated, so there should still be two bitstreams + // in the source assetstore, due to the interrupted migration + assertThat(bitstreamService.countByStoreNumber(context, SOURCE_STORE).intValue(), equalTo(2)); + + // Three bitstreams should have migrated to the destination assetstore + assertThat(bitstreamService.countByStoreNumber(context, DEST_STORE).intValue(), equalTo(3)); + } + + private void createBitstreams(Context context, int numBitstreams) + throws SQLException { + context.turnOffAuthorisationSystem(); + for (int i = 0; i < numBitstreams; i++) { + String content = "Test bitstream " + i; + createBitstream(content); + } + context.restoreAuthSystemState(); + context.commit(); + } + + private Bitstream createBitstream(String content) { + try { + return BitstreamBuilder + .createBitstream(context, createItem(), toInputStream(content)) + .build(); + } catch (SQLException | AuthorizeException | IOException e) { + throw new RuntimeException(e); + } + } + + private Item createItem() { + return ItemBuilder.createItem(context, collection) + .withTitle("Test item") + .build(); + } + + + private InputStream toInputStream(String content) { + return IOUtils.toInputStream(content, UTF_8); + } + + + /** + * DSBitStoreService variation that only allows a limited number of puts + * to the bit store before throwing an IOException, to test the + * error handling of the BitstreamStorageService.migrate() method. + */ + class LimitedTempDSBitStoreService extends DSBitStoreService { + // The number of put calls allowed before throwing an IOException + protected int maxPuts = Integer.MAX_VALUE; + + // The number of "put" method class seen so far. + protected int putCallCount = 0; + + /** + * Constructor. + * + * @param maxPuts the number of put calls to allow before throwing an + * IOException + */ + public LimitedTempDSBitStoreService(TemporaryFolder tempStoreDir, int maxPuts) throws IOException { + super(); + setBaseDir(tempStoreDir.newFolder()); + this.maxPuts = maxPuts; + } + + /** + * Store a stream of bits. + * + * After "maxPut" number of calls, this method throws an IOException. + * @param in The stream of bits to store + * @throws java.io.IOException If a problem occurs while storing the bits + */ + @Override + public void put(Bitstream bitstream, InputStream in) throws IOException { + putCallCount = putCallCount + 1; + if (putCallCount > maxPuts) { + throw new IOException("Max 'put' method calls exceeded"); + } else { + super.put(bitstream, in); + } + } + } +} From 3bca2164be9cfd08d2e45cd766ad977aa517e397 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 28 May 2025 17:26:13 +0200 Subject: [PATCH 655/939] [TLC-1097] ORCID external identifier sync fix Handle SELF and PART_OF identifiers properly based on configuration, work type, and identifier type --- .../orcid/model/OrcidWorkFieldMapping.java | 10 ++ .../model/factory/impl/OrcidWorkFactory.java | 69 +++++++------ .../java/org/dspace/builder/ItemBuilder.java | 8 ++ .../service/OrcidEntityFactoryServiceIT.java | 99 ++++++++++++++++++- ...space-to-orcid-publication-type.properties | 3 +- dspace/config/modules/orcid.cfg | 10 +- dspace/config/spring/api/orcid-services.xml | 57 +++++++---- 7 files changed, 202 insertions(+), 54 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/orcid/model/OrcidWorkFieldMapping.java b/dspace-api/src/main/java/org/dspace/orcid/model/OrcidWorkFieldMapping.java index 781a9dcbd90..faefe798e92 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/model/OrcidWorkFieldMapping.java +++ b/dspace-api/src/main/java/org/dspace/orcid/model/OrcidWorkFieldMapping.java @@ -39,6 +39,7 @@ public class OrcidWorkFieldMapping { * The metadata fields related to the work external identifiers. */ private Map externalIdentifierFields = new HashMap<>(); + private Map> externalIdentifierPartOfMap = new HashMap<>(); /** * The metadata field related to the work publication date. @@ -129,6 +130,15 @@ public void setExternalIdentifierFields(String externalIdentifierFields) { this.externalIdentifierFields = parseConfigurations(externalIdentifierFields); } + public Map> getExternalIdentifierPartOfMap() { + return this.externalIdentifierPartOfMap; + } + + public void setExternalIdentifierPartOfMap( + HashMap> externalIdentifierPartOfMap) { + this.externalIdentifierPartOfMap = externalIdentifierPartOfMap; + } + public String getPublicationDateField() { return publicationDateField; } diff --git a/dspace-api/src/main/java/org/dspace/orcid/model/factory/impl/OrcidWorkFactory.java b/dspace-api/src/main/java/org/dspace/orcid/model/factory/impl/OrcidWorkFactory.java index 53b46d8256d..a0835d2eb7a 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/model/factory/impl/OrcidWorkFactory.java +++ b/dspace-api/src/main/java/org/dspace/orcid/model/factory/impl/OrcidWorkFactory.java @@ -9,6 +9,7 @@ import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.orcid.jaxb.model.common.Relationship.PART_OF; import static org.orcid.jaxb.model.common.Relationship.SELF; import java.util.ArrayList; @@ -73,12 +74,12 @@ public OrcidEntityType getEntityType() { @Override public Activity createOrcidObject(Context context, Item item) { Work work = new Work(); + work.setWorkType(getWorkType(context, item)); work.setJournalTitle(getJournalTitle(context, item)); work.setWorkContributors(getWorkContributors(context, item)); work.setWorkTitle(getWorkTitle(context, item)); work.setPublicationDate(getPublicationDate(context, item)); - work.setWorkExternalIdentifiers(getWorkExternalIds(context, item)); - work.setWorkType(getWorkType(context, item)); + work.setWorkExternalIdentifiers(getWorkExternalIds(context, item, work)); work.setShortDescription(getShortDescription(context, item)); work.setLanguageCode(getLanguageCode(context, item)); work.setUrl(getUrl(context, item)); @@ -148,64 +149,68 @@ private PublicationDate getPublicationDate(Context context, Item item) { .orElse(null); } - /** - * Creates an instance of ExternalIDs from the metadata values of the given - * item, using the orcid.mapping.funding.external-ids configuration. - */ - private ExternalIDs getWorkExternalIds(Context context, Item item) { - ExternalIDs externalIdentifiers = new ExternalIDs(); - externalIdentifiers.getExternalIdentifier().addAll(getWorkSelfExternalIds(context, item)); - return externalIdentifiers; + private ExternalIDs getWorkExternalIds(Context context, Item item, Work work) { + ExternalIDs externalIDs = new ExternalIDs(); + externalIDs.getExternalIdentifier().addAll(getWorkExternalIdList(context, item, work)); + return externalIDs; } /** * Creates a list of ExternalID, one for orcid.mapping.funding.external-ids * value, taking the values from the given item. */ - private List getWorkSelfExternalIds(Context context, Item item) { + private List getWorkExternalIdList(Context context, Item item, Work work) { - List selfExternalIds = new ArrayList(); + List externalIds = new ArrayList<>(); Map externalIdentifierFields = fieldMapping.getExternalIdentifierFields(); if (externalIdentifierFields.containsKey(SIMPLE_HANDLE_PLACEHOLDER)) { String handleType = externalIdentifierFields.get(SIMPLE_HANDLE_PLACEHOLDER); - selfExternalIds.add(getExternalId(handleType, item.getHandle(), SELF)); + ExternalID handle = new ExternalID(); + handle.setType(handleType); + handle.setValue(item.getHandle()); + handle.setRelationship(SELF); + externalIds.add(handle); } + // Resolve work type, used to determine identifier relationship type + // For version / funding relationships, we might want to use more complex + // business rules than just "work and id type" + final String workType = (work != null && work.getWorkType() != null) ? + work.getWorkType().value() : WorkType.OTHER.value(); getMetadataValues(context, item, externalIdentifierFields.keySet()).stream() - .map(this::getSelfExternalId) - .forEach(selfExternalIds::add); + .map(metadataValue -> this.getExternalId(metadataValue, workType)) + .forEach(externalIds::add); - return selfExternalIds; - } - - /** - * Creates an instance of ExternalID taking the value from the given - * metadataValue. The type of the ExternalID is calculated using the - * orcid.mapping.funding.external-ids configuration. The relationship of the - * ExternalID is SELF. - */ - private ExternalID getSelfExternalId(MetadataValue metadataValue) { - Map externalIdentifierFields = fieldMapping.getExternalIdentifierFields(); - String metadataField = metadataValue.getMetadataField().toString('.'); - return getExternalId(externalIdentifierFields.get(metadataField), metadataValue.getValue(), SELF); + return externalIds; } /** * Creates an instance of ExternalID with the given type, value and * relationship. */ - private ExternalID getExternalId(String type, String value, Relationship relationship) { + private ExternalID getExternalId(MetadataValue metadataValue, String workType) { + Map externalIdentifierFields = fieldMapping.getExternalIdentifierFields(); + Map> externalIdentifierPartOfMap = fieldMapping.getExternalIdentifierPartOfMap(); + String metadataField = metadataValue.getMetadataField().toString('.'); + String identifierType = externalIdentifierFields.get(metadataField); + // Default relationship type is SELF, configuration can + // override to PART_OF based on identifier and work type + Relationship relationship = SELF; + if (externalIdentifierPartOfMap.containsKey(identifierType) + && externalIdentifierPartOfMap.get(identifierType).contains(workType)) { + relationship = PART_OF; + } ExternalID externalID = new ExternalID(); - externalID.setType(type); - externalID.setValue(value); + externalID.setType(identifierType); + externalID.setValue(metadataValue.getValue()); externalID.setRelationship(relationship); return externalID; } /** - * Creates an instance of WorkType from the given item, taking the value fom the + * Creates an instance of WorkType from the given item, taking the value from the * configured metadata field (orcid.mapping.work.type). */ private WorkType getWorkType(Context context, Item item) { diff --git a/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java b/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java index 3e5ab0f38f5..9918b509685 100644 --- a/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java @@ -112,6 +112,14 @@ public ItemBuilder withScopusIdentifier(String scopus) { return addMetadataValue(item, "dc", "identifier", "scopus", scopus); } + public ItemBuilder withISSN(String issn) { + return addMetadataValue(item, "dc", "identifier", "issn", issn); + } + + public ItemBuilder withISBN(String isbn) { + return addMetadataValue(item, "dc", "identifier", "isbn", isbn); + } + public ItemBuilder withRelationFunding(String funding) { return addMetadataValue(item, "dc", "relation", "funding", funding); } diff --git a/dspace-api/src/test/java/org/dspace/orcid/service/OrcidEntityFactoryServiceIT.java b/dspace-api/src/test/java/org/dspace/orcid/service/OrcidEntityFactoryServiceIT.java index 17bc6ee531c..33f5cc3102e 100644 --- a/dspace-api/src/test/java/org/dspace/orcid/service/OrcidEntityFactoryServiceIT.java +++ b/dspace-api/src/test/java/org/dspace/orcid/service/OrcidEntityFactoryServiceIT.java @@ -73,6 +73,9 @@ public class OrcidEntityFactoryServiceIT extends AbstractIntegrationTestWithData private Collection projects; + private static final String isbn = "978-0-439-02348-1"; + private static final String issn = "1234-1234X"; + @Before public void setup() { @@ -117,6 +120,7 @@ public void testWorkCreation() { .withLanguage("en_US") .withType("Book") .withIsPartOf("Journal") + .withISBN(isbn) .withDoiIdentifier("doi-id") .withScopusIdentifier("scopus-id") .build(); @@ -149,11 +153,100 @@ public void testWorkCreation() { assertThat(work.getExternalIdentifiers(), notNullValue()); List externalIds = work.getExternalIdentifiers().getExternalIdentifier(); - assertThat(externalIds, hasSize(3)); + assertThat(externalIds, hasSize(4)); + assertThat(externalIds, has(selfExternalId("doi", "doi-id"))); + assertThat(externalIds, has(selfExternalId("eid", "scopus-id"))); + assertThat(externalIds, has(selfExternalId("handle", publication.getHandle()))); + // Book type should have SELF rel for ISBN + assertThat(externalIds, has(selfExternalId("isbn", isbn))); + + } + + @Test + public void testJournalArticleAndISSN() { + context.turnOffAuthorisationSystem(); + + Item publication = ItemBuilder.createItem(context, publications) + .withTitle("Test publication") + .withAuthor("Walter White") + .withAuthor("Jesse Pinkman") + .withEditor("Editor") + .withIssueDate("2021-04-30") + .withDescriptionAbstract("Publication description") + .withLanguage("en_US") + .withType("Article") + .withIsPartOf("Journal") + .withISSN(issn) + .withDoiIdentifier("doi-id") + .withScopusIdentifier("scopus-id") + .build(); + + context.restoreAuthSystemState(); + + Activity activity = entityFactoryService.createOrcidObject(context, publication); + assertThat(activity, instanceOf(Work.class)); + + Work work = (Work) activity; + assertThat(work.getJournalTitle(), notNullValue()); + assertThat(work.getJournalTitle().getContent(), is("Journal")); + assertThat(work.getLanguageCode(), is("en")); + assertThat(work.getPublicationDate(), matches(date("2021", "04", "30"))); + assertThat(work.getShortDescription(), is("Publication description")); + assertThat(work.getPutCode(), nullValue()); + assertThat(work.getWorkType(), is(WorkType.JOURNAL_ARTICLE)); + assertThat(work.getWorkTitle(), notNullValue()); + assertThat(work.getWorkTitle().getTitle(), notNullValue()); + assertThat(work.getWorkTitle().getTitle().getContent(), is("Test publication")); + assertThat(work.getWorkContributors(), notNullValue()); + assertThat(work.getUrl(), matches(urlEndsWith(publication.getHandle()))); + + List contributors = work.getWorkContributors().getContributor(); + assertThat(contributors, hasSize(3)); + assertThat(contributors, has(contributor("Walter White", AUTHOR, FIRST))); + assertThat(contributors, has(contributor("Editor", EDITOR, FIRST))); + assertThat(contributors, has(contributor("Jesse Pinkman", AUTHOR, ADDITIONAL))); + + assertThat(work.getExternalIdentifiers(), notNullValue()); + + List externalIds = work.getExternalIdentifiers().getExternalIdentifier(); + assertThat(externalIds, hasSize(4)); assertThat(externalIds, has(selfExternalId("doi", "doi-id"))); assertThat(externalIds, has(selfExternalId("eid", "scopus-id"))); assertThat(externalIds, has(selfExternalId("handle", publication.getHandle()))); + // journal-article should have PART_OF rel for ISSN + assertThat(externalIds, has(externalId("issn", issn, Relationship.PART_OF))); + } + @Test + public void testJournalWithISSN() { + context.turnOffAuthorisationSystem(); + + Item publication = ItemBuilder.createItem(context, publications) + .withTitle("Test journal") + .withEditor("Editor") + .withType("Journal") + .withISSN(issn) + .build(); + + context.restoreAuthSystemState(); + + Activity activity = entityFactoryService.createOrcidObject(context, publication); + assertThat(activity, instanceOf(Work.class)); + + Work work = (Work) activity; + assertThat(work.getWorkType(), is(WorkType.JOURNAL_ISSUE)); + assertThat(work.getWorkTitle(), notNullValue()); + assertThat(work.getWorkTitle().getTitle(), notNullValue()); + assertThat(work.getWorkTitle().getTitle().getContent(), is("Test journal")); + assertThat(work.getUrl(), matches(urlEndsWith(publication.getHandle()))); + + assertThat(work.getExternalIdentifiers(), notNullValue()); + + List externalIds = work.getExternalIdentifiers().getExternalIdentifier(); + assertThat(externalIds, hasSize(2)); + // journal-issue should have SELF rel for ISSN + assertThat(externalIds, has(selfExternalId("issn", issn))); + assertThat(externalIds, has(selfExternalId("handle", publication.getHandle()))); } @Test @@ -163,6 +256,7 @@ public void testEmptyWorkWithUnknownTypeCreation() { Item publication = ItemBuilder.createItem(context, publications) .withType("TYPE") + .withISSN(issn) .build(); context.restoreAuthSystemState(); @@ -183,8 +277,9 @@ public void testEmptyWorkWithUnknownTypeCreation() { assertThat(work.getExternalIdentifiers(), notNullValue()); List externalIds = work.getExternalIdentifiers().getExternalIdentifier(); - assertThat(externalIds, hasSize(1)); + assertThat(externalIds, hasSize(2)); assertThat(externalIds, has(selfExternalId("handle", publication.getHandle()))); + assertThat(externalIds, has(externalId("issn", issn, Relationship.PART_OF))); } @Test diff --git a/dspace/config/crosswalks/orcid/mapConverter-dspace-to-orcid-publication-type.properties b/dspace/config/crosswalks/orcid/mapConverter-dspace-to-orcid-publication-type.properties index 953ddc60eef..6eac57092a1 100644 --- a/dspace/config/crosswalks/orcid/mapConverter-dspace-to-orcid-publication-type.properties +++ b/dspace/config/crosswalks/orcid/mapConverter-dspace-to-orcid-publication-type.properties @@ -7,6 +7,7 @@ Dataset = data-set Learning\ Object = other Image = other Image,\ 3-D = other +Journal = journal-issue Map = other Musical\ Score = other Plan\ or\ blueprint = other @@ -20,4 +21,4 @@ Technical\ Report = other Thesis = other Video = other Working\ Paper = working-paper -Other = other \ No newline at end of file +Other = other diff --git a/dspace/config/modules/orcid.cfg b/dspace/config/modules/orcid.cfg index 93da0a5f4cb..7eb2af9b808 100644 --- a/dspace/config/modules/orcid.cfg +++ b/dspace/config/modules/orcid.cfg @@ -1,4 +1,3 @@ - #------------------------------------------------------------------# #--------------------ORCID GENERIC CONFIGURATIONS------------------# #------------------------------------------------------------------# @@ -58,12 +57,18 @@ orcid.mapping.work.contributors = dc.contributor.editor::editor ##orcid.mapping.work.external-ids syntax is :: or $simple-handle:: ##The full list of available external identifiers is available here https://pub.orcid.org/v3.0/identifiers +# The identifiers need to have a relationship of SELF, PART_OF, VERSION_OF or FUNDED_BY. +# The default for most identifiers is SELF. The default for identifiers more commonly +# associated with 'parent' publciations (ISSN, ISBN) is PART_OF. +# See the map in `orcid-services.xml` +# VERSION_OF and FUNDED_BY are not currently implemented. orcid.mapping.work.external-ids = dc.identifier.doi::doi orcid.mapping.work.external-ids = dc.identifier.scopus::eid orcid.mapping.work.external-ids = dc.identifier.pmid::pmid orcid.mapping.work.external-ids = $simple-handle::handle orcid.mapping.work.external-ids = dc.identifier.isi::wosuid orcid.mapping.work.external-ids = dc.identifier.issn::issn +orcid.mapping.work.external-ids = dc.identifier.isbn::isbn ### Funding mapping ### orcid.mapping.funding.title = dc.title @@ -141,6 +146,9 @@ orcid.bulk-synchronization.max-attempts = 5 #--------------------ORCID EXTERNAL DATA MAPPING-------------------# #------------------------------------------------------------------# +# Note - the below mapping is for ORCID->DSpace imports, not for +# DSpace->ORCID exports (see orcid.mapping.work.*) + ### Work (Publication) external-data.mapping ### orcid.external-data.mapping.publication.title = dc.title diff --git a/dspace/config/spring/api/orcid-services.xml b/dspace/config/spring/api/orcid-services.xml index eb31acb29c4..a2f914d0ad2 100644 --- a/dspace/config/spring/api/orcid-services.xml +++ b/dspace/config/spring/api/orcid-services.xml @@ -55,24 +55,45 @@ - - - - - - - - - - - - - - - - - - + + + + + journal-article + magazine-article + newspaper-article + data-set + learning-object + other + + + + + book-chapter + book-review + other + + + + + + + + + + + + + + + + + + + + + + From af654e57f2d09e019d6c890c759440f27ebc6c25 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Mon, 2 Jun 2025 18:23:34 +0200 Subject: [PATCH 656/939] [TLC-1097] Additional javadoc for ORCID sync fix --- .../dspace/orcid/model/factory/impl/OrcidWorkFactory.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/orcid/model/factory/impl/OrcidWorkFactory.java b/dspace-api/src/main/java/org/dspace/orcid/model/factory/impl/OrcidWorkFactory.java index a0835d2eb7a..890dd0c6ed9 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/model/factory/impl/OrcidWorkFactory.java +++ b/dspace-api/src/main/java/org/dspace/orcid/model/factory/impl/OrcidWorkFactory.java @@ -149,6 +149,10 @@ private PublicationDate getPublicationDate(Context context, Item item) { .orElse(null); } + /** + * Returns a list of external work IDs constructed in the org.orcid.jaxb + * ExternalIDs object + */ private ExternalIDs getWorkExternalIds(Context context, Item item, Work work) { ExternalIDs externalIDs = new ExternalIDs(); externalIDs.getExternalIdentifier().addAll(getWorkExternalIdList(context, item, work)); @@ -157,7 +161,7 @@ private ExternalIDs getWorkExternalIds(Context context, Item item, Work work) { /** * Creates a list of ExternalID, one for orcid.mapping.funding.external-ids - * value, taking the values from the given item. + * value, taking the values from the given item and work type. */ private List getWorkExternalIdList(Context context, Item item, Work work) { From b8cbaa053d5fd95ca691c920447668f9346240ab Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 6 Aug 2025 08:50:41 -0500 Subject: [PATCH 657/939] Add a deposit integration test for SWORDv1 based on the similar SWORDv2 test. (cherry picked from commit 0589011849cf4c7ac7a67d6dfc44839e11047980) --- .../java/org/dspace/app/sword/Swordv1IT.java | 98 +++++++++++++++++-- 1 file changed, 88 insertions(+), 10 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/sword/Swordv1IT.java b/dspace-server-webapp/src/test/java/org/dspace/app/sword/Swordv1IT.java index 24244e1773e..0b866659edd 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/sword/Swordv1IT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/sword/Swordv1IT.java @@ -10,16 +10,30 @@ import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; +import java.nio.file.Path; +import java.util.List; + import org.dspace.app.rest.test.AbstractWebClientIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.content.Collection; import org.dspace.services.ConfigurationService; +import org.hamcrest.MatcherAssert; import org.junit.Assume; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.FileSystemResource; +import org.springframework.http.ContentDisposition; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; import org.springframework.test.context.TestPropertySource; @@ -45,6 +59,9 @@ public class Swordv1IT extends AbstractWebClientIntegrationTest { private final String DEPOSIT_PATH = "/sword/deposit"; private final String MEDIA_LINK_PATH = "/sword/media-link"; + // ATOM Content type returned by SWORDv1 + private final String ATOM_CONTENT_TYPE = "application/atom+xml;charset=UTF-8"; + @Before public void onlyRunIfConfigExists() { // These integration tests REQUIRE that SWORDWebConfig is found/available (as this class deploys SWORD) @@ -93,10 +110,76 @@ public void depositUnauthorizedTest() throws Exception { } @Test - @Ignore public void depositTest() throws Exception { - // TODO: Actually test a full deposit via SWORD. - // Currently, we are just ensuring the /deposit endpoint exists (see above) and isn't throwing a 404 + context.turnOffAuthorisationSystem(); + // Create a top level community and one Collection + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + // Make sure our Collection allows the "eperson" user to submit into it + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test SWORDv1 Collection") + .withSubmitterGroup(eperson) + .build(); + // Above changes MUST be committed to the database for SWORDv2 to see them. + context.commit(); + context.restoreAuthSystemState(); + + // Specify zip file + // NOTE: We are using the same "example.zip" as SWORDv2IT because that same ZIP is valid for both v1 and v2 + FileSystemResource zipFile = new FileSystemResource(Path.of("src", "test", "resources", "org", + "dspace", "app", "sword2", "example.zip")); + + // Add required headers + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.valueOf("application/zip")); + headers.setContentDisposition(ContentDisposition.attachment().filename("example.zip").build()); + headers.set("X-Packaging", "http://purl.org/net/sword-types/METSDSpaceSIP"); + headers.setAccept(List.of(MediaType.APPLICATION_ATOM_XML)); + + //---- + // STEP 1: Verify upload/submit via SWORDv1 works + //---- + // Send POST to upload Zip file via SWORD + ResponseEntity response = postResponseAsString(DEPOSIT_PATH + "/" + collection.getHandle(), + eperson.getEmail(), password, + new HttpEntity<>(zipFile.getContentAsByteArray(), + headers)); + + // Expect a 201 CREATED response with ATOM content returned + assertEquals(HttpStatus.CREATED, response.getStatusCode()); + assertEquals(ATOM_CONTENT_TYPE, response.getHeaders().getContentType().toString()); + + // MUST return a "Location" header which is the "/sword/media-link/*" URI of the zip file bitstream within + // the created item (e.g. /sword/media-link/[handle-prefix]/[handle-suffix]/bitstream/[uuid]) + assertNotNull(response.getHeaders().getLocation()); + String mediaLink = response.getHeaders().getLocation().toString(); + + // Body should include the SWORD version in generator tag + MatcherAssert.assertThat(response.getBody(), + containsString("")); + // Verify Item title also is returned in the body + MatcherAssert.assertThat(response.getBody(), containsString("Attempts to detect retrotransposition")); + + //---- + // STEP 2: Verify /media-link access works + //---- + // Media-Link URI should work when requested by the EPerson who did the deposit + HttpHeaders authHeaders = new HttpHeaders(); + authHeaders.setBasicAuth(eperson.getEmail(), password); + RequestEntity request = RequestEntity.get(mediaLink) + .accept(MediaType.valueOf("application/atom+xml")) + .headers(authHeaders) + .build(); + response = responseAsString(request); + + // Expect a 200 response with ATOM feed content returned + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(ATOM_CONTENT_TYPE, response.getHeaders().getContentType().toString()); + // Body should include a link to the zip bitstream in the newly created Item + // This just verifies "example.zip" exists in the body. + MatcherAssert.assertThat(response.getBody(), containsString("example.zip")); } @Test @@ -105,13 +188,8 @@ public void mediaLinkUnauthorizedTest() throws Exception { ResponseEntity response = getResponseAsString(MEDIA_LINK_PATH); // Expect a 401 response code assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); - } - @Test - @Ignore - public void mediaLinkTest() throws Exception { - // TODO: Actually test a /media-link request. - // Currently, we are just ensuring the /media-link endpoint exists (see above) and isn't throwing a 404 + //NOTE: An authorized /media-link test is performed in depositTest() above. } } From ea6e025519ae6c2f19b4d8ac19461e4881ee298e Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 6 Aug 2025 08:51:16 -0500 Subject: [PATCH 658/939] Fix WRITE permissions error when ingesting a new Item. Do not call "updateDSpaceObject" after calling "finishCreateItem" as the latter saves the object and removes submitter privileges from it. (cherry picked from commit c2d05891ab32596b326d78d2e4b5ff2372d94ca2) --- .../content/packager/AbstractMETSIngester.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java index 77236be9d52..0ab5ac71cda 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java @@ -498,8 +498,11 @@ protected DSpaceObject ingestObject(Context context, DSpaceObject parent, // Finish creating the item. This actually assigns the handle, // and will either install item immediately or start a workflow, based on params PackageUtils.finishCreateItem(context, wsi, handle, params); + } else { + // We should have a workspace item during ingest, so this code is only here for safety. + // Update the object to make sure all changes are committed + PackageUtils.updateDSpaceObject(context, dso); } - } else if (type == Constants.COLLECTION || type == Constants.COMMUNITY) { // Add logo if one is referenced from manifest addContainerLogo(context, dso, manifest, pkgFile, params); @@ -513,6 +516,9 @@ protected DSpaceObject ingestObject(Context context, DSpaceObject parent, // (this allows subclasses to do some final validation / changes as // necessary) finishObject(context, dso, params); + + // Update the object to make sure all changes are committed + PackageUtils.updateDSpaceObject(context, dso); } else if (type == Constants.SITE) { // Do nothing by default -- Crosswalks will handle anything necessary to ingest at Site-level @@ -520,18 +526,15 @@ protected DSpaceObject ingestObject(Context context, DSpaceObject parent, // (this allows subclasses to do some final validation / changes as // necessary) finishObject(context, dso, params); + + // Update the object to make sure all changes are committed + PackageUtils.updateDSpaceObject(context, dso); } else { throw new PackageValidationException( "Unknown DSpace Object type in package, type=" + String.valueOf(type)); } - // -- Step 6 -- - // Finish things up! - - // Update the object to make sure all changes are committed - PackageUtils.updateDSpaceObject(context, dso); - return dso; } From 05af8605533c6eac920c42dde43c65f4fbc6870e Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 6 Aug 2025 12:19:14 -0500 Subject: [PATCH 659/939] getContentAsByteArray() didn't exist in Spring 5. Use readAllBytes() from InputStream instead. --- .../src/test/java/org/dspace/app/sword/Swordv1IT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/sword/Swordv1IT.java b/dspace-server-webapp/src/test/java/org/dspace/app/sword/Swordv1IT.java index 0b866659edd..29ea05dacab 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/sword/Swordv1IT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/sword/Swordv1IT.java @@ -143,7 +143,7 @@ public void depositTest() throws Exception { // Send POST to upload Zip file via SWORD ResponseEntity response = postResponseAsString(DEPOSIT_PATH + "/" + collection.getHandle(), eperson.getEmail(), password, - new HttpEntity<>(zipFile.getContentAsByteArray(), + new HttpEntity<>(zipFile.getInputStream().readAllBytes(), headers)); // Expect a 201 CREATED response with ATOM content returned From 56b38bb26d841cb20483d169080b221a6c1a56bf Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Wed, 6 Aug 2025 13:00:11 +0200 Subject: [PATCH 660/939] fix: uses dc.source for bitstream originalName tag Refs: DURACOM-391, #1130 (cherry picked from commit f5c0c17c663c494709666e64d594598b19db876b) --- 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 b3298358132..8f8b7380307 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 @@ -142,7 +142,7 @@ private static Element createBundlesElement(Context context, Item item) throws S bitstream.getField().add(createValue("name", name)); } if (oname != null) { - bitstream.getField().add(createValue("originalName", name)); + bitstream.getField().add(createValue("originalName", oname)); } if (description != null) { bitstream.getField().add(createValue("description", description)); From 558f0c7beab21360e593963e422dfc72f8cab2c0 Mon Sep 17 00:00:00 2001 From: "David P. Steelman" Date: Fri, 14 Mar 2025 13:30:22 -0400 Subject: [PATCH 661/939] Fix checkpointing for checksum checking Fixes the checkpointing for checksum checking by actually committing the changes to the database. Replacing "uncacheEntity" with "commit", as testing has shown that this is necessary to ensure that the checksum status is properly updated in the most_recent_checksum" table when checksum process is interrupted. The provided integration test fails if the checksums of bitstreams that were checked before the interruption are not properly recorded in the database. (cherry picked from commit 502b655ef6041381eb15833a82f03054894c96bb) --- .../org/dspace/checker/CheckerCommand.java | 2 +- .../org/dspace/checker/ChecksumCheckerIT.java | 192 ++++++++++++++++++ 2 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 dspace-api/src/test/java/org/dspace/checker/ChecksumCheckerIT.java diff --git a/dspace-api/src/main/java/org/dspace/checker/CheckerCommand.java b/dspace-api/src/main/java/org/dspace/checker/CheckerCommand.java index a12ac3b98a2..9ad9f553b44 100644 --- a/dspace-api/src/main/java/org/dspace/checker/CheckerCommand.java +++ b/dspace-api/src/main/java/org/dspace/checker/CheckerCommand.java @@ -131,7 +131,7 @@ public void process() throws SQLException { collector.collect(context, info); } - context.uncacheEntity(bitstream); + context.commit(); bitstream = dispatcher.next(); } } diff --git a/dspace-api/src/test/java/org/dspace/checker/ChecksumCheckerIT.java b/dspace-api/src/test/java/org/dspace/checker/ChecksumCheckerIT.java new file mode 100644 index 00000000000..7653d518b19 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/checker/ChecksumCheckerIT.java @@ -0,0 +1,192 @@ +/** + * 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.checker; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.sql.SQLException; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.io.IOUtils; +import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.builder.BitstreamBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.checker.factory.CheckerServiceFactory; +import org.dspace.checker.service.ChecksumHistoryService; +import org.dspace.checker.service.MostRecentChecksumService; +import org.dspace.content.Bitstream; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class ChecksumCheckerIT extends AbstractIntegrationTestWithDatabase { + protected List bitstreams; + protected MostRecentChecksumService checksumService = + CheckerServiceFactory.getInstance().getMostRecentChecksumService(); + + @Before + public void setup() throws Exception { + context.turnOffAuthorisationSystem(); + + Community parentCommunity = CommunityBuilder.createCommunity(context).build(); + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .build(); + Item item = ItemBuilder.createItem(context, collection).withTitle("Test item") + .build(); + + int numBitstreams = 3; + bitstreams = new ArrayList<>(); + for (int i = 0; i < numBitstreams; i++) { + String content = "Test bitstream " + i; + bitstreams.add( + BitstreamBuilder.createBitstream( + context, item, IOUtils.toInputStream(content, UTF_8) + ).build() + ); + } + + context.restoreAuthSystemState(); + + // Call the "updateMissingBitstreams" method so that the test bitstreams + // already have checksums in the past when CheckerCommand runs. + // Otherwise, the CheckerCommand will simply update the test + // bitstreams without going through the BitstreamDispatcher. + checksumService = CheckerServiceFactory.getInstance().getMostRecentChecksumService(); + checksumService.updateMissingBitstreams(context); + + // The "updateMissingBitstreams" method updates the test bitstreams in + // a random order. To verify that the expected bitstreams were + // processed, reset the timestamps so that the bitstreams are + // checked in a specific order (oldest first). + Instant checksumInstant = Instant.ofEpochMilli(0); + for (Bitstream bitstream: bitstreams) { + MostRecentChecksum mrc = checksumService.findByBitstream(context, bitstream); + mrc.setProcessStartDate(checksumInstant); + mrc.setProcessEndDate(checksumInstant); + checksumInstant = checksumInstant.plusSeconds(10); + } + context.commit(); + } + + @After + public void cleanUp() throws SQLException { + // Need to clean up ChecksumHistory because of a referential integrity + // constraint violation between the most_recent_checksum table and + // bitstream tables + ChecksumHistoryService checksumHistoryService = CheckerServiceFactory.getInstance().getChecksumHistoryService(); + + for (Bitstream bitstream: bitstreams) { + checksumHistoryService.deleteByBitstream(context, bitstream); + } + } + + @Test + public void testChecksumsRecordedWhenProcesingIsInterrupted() throws SQLException { + CheckerCommand checker = new CheckerCommand(context); + + // The start date to use for the checker process + Instant checkerStartDate = Instant.now(); + + // Verify that all checksums are before the checker start date + for (Bitstream bitstream: bitstreams) { + MostRecentChecksum checksum = checksumService.findByBitstream(context, bitstream); + Instant lastChecksumDate = checksum.getProcessStartDate(); + assertTrue("lastChecksumDate (" + lastChecksumDate + ") <= checkerStartDate (" + checkerStartDate + ")", + lastChecksumDate.isBefore(checkerStartDate)); + } + + // Dispatcher that throws an exception when a third bitstream is + // retrieved. + BitstreamDispatcher dispatcher = new ExpectionThrowingDispatcher( + context, checkerStartDate, false, 2); + checker.setDispatcher(dispatcher); + + + // Run the checksum checker + checker.setProcessStartDate(checkerStartDate); + try { + checker.process(); + fail("SQLException should have been thrown"); + } catch (SQLException sqle) { + // Rollback any pending transaction + context.rollback(); + } + + // Verify that the checksums of the first two bitstreams (that were + // processed before the exception) have been successfully recorded in + // the database, while the third bitstream was not updated. + int bitstreamCount = 0; + for (Bitstream bitstream: bitstreams) { + MostRecentChecksum checksum = checksumService.findByBitstream(context, bitstream); + Instant lastChecksumDate = checksum.getProcessStartDate(); + + bitstreamCount = bitstreamCount + 1; + if (bitstreamCount <= 2) { + assertTrue("lastChecksumDate (" + lastChecksumDate + ") <= checkerStartDate (" + checkerStartDate + ")", + lastChecksumDate.isAfter(checkerStartDate)); + } else { + assertTrue("lastChecksumDate (" + lastChecksumDate + ") >= checkerStartDate (" + checkerStartDate + ")", + lastChecksumDate.isBefore(checkerStartDate)); + } + } + } + + /** + * Subclass of SimpleDispatcher that only allows a limited number of "next" + * class before throwing a SQLException. + */ + class ExpectionThrowingDispatcher extends SimpleDispatcher { + // The number of "next" calls to allow before throwing a SQLException + protected int maxNextCalls; + + // The number of "next" method calls seen so far. + protected int numNextCalls = 0; + + /** + * Constructor. + * + * @param context Context + * @param startTime timestamp for beginning of checker process + * @param looping indicates whether checker should loop infinitely + * through most_recent_checksum table + * @param maxNextCalls the number of "next" method calls to allow before + * throwing a SQLException. + */ + public ExpectionThrowingDispatcher(Context context, Instant startTime, boolean looping, int maxNextCalls) { + super(context, startTime, looping); + this.maxNextCalls = maxNextCalls; + } + + /** + * Selects the next candidate bitstream. + * + * After "maxNextClass" number of calls, this method throws a + * SQLException. + * + * @throws SQLException if database error + */ + @Override + public synchronized Bitstream next() throws SQLException { + numNextCalls = numNextCalls + 1; + if (numNextCalls > maxNextCalls) { + throw new SQLException("Max 'next' method calls exceeded"); + } + return super.next(); + } + } +} From bce865f299d69f46cc1a84938b4feb73577bdd61 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 6 Aug 2025 15:09:41 -0500 Subject: [PATCH 662/939] Revert to using java.util.Date instead of Instant to support earlier versions of DSpace. --- .../org/dspace/checker/ChecksumCheckerIT.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/checker/ChecksumCheckerIT.java b/dspace-api/src/test/java/org/dspace/checker/ChecksumCheckerIT.java index 7653d518b19..34198ff1ebf 100644 --- a/dspace-api/src/test/java/org/dspace/checker/ChecksumCheckerIT.java +++ b/dspace-api/src/test/java/org/dspace/checker/ChecksumCheckerIT.java @@ -14,6 +14,7 @@ import java.sql.SQLException; import java.time.Instant; import java.util.ArrayList; +import java.util.Date; import java.util.List; import org.apache.commons.io.IOUtils; @@ -76,8 +77,8 @@ public void setup() throws Exception { Instant checksumInstant = Instant.ofEpochMilli(0); for (Bitstream bitstream: bitstreams) { MostRecentChecksum mrc = checksumService.findByBitstream(context, bitstream); - mrc.setProcessStartDate(checksumInstant); - mrc.setProcessEndDate(checksumInstant); + mrc.setProcessStartDate(Date.from(checksumInstant)); + mrc.setProcessEndDate(Date.from(checksumInstant)); checksumInstant = checksumInstant.plusSeconds(10); } context.commit(); @@ -100,14 +101,14 @@ public void testChecksumsRecordedWhenProcesingIsInterrupted() throws SQLExceptio CheckerCommand checker = new CheckerCommand(context); // The start date to use for the checker process - Instant checkerStartDate = Instant.now(); + Date checkerStartDate = Date.from(Instant.now()); // Verify that all checksums are before the checker start date for (Bitstream bitstream: bitstreams) { MostRecentChecksum checksum = checksumService.findByBitstream(context, bitstream); - Instant lastChecksumDate = checksum.getProcessStartDate(); + Date lastChecksumDate = checksum.getProcessStartDate(); assertTrue("lastChecksumDate (" + lastChecksumDate + ") <= checkerStartDate (" + checkerStartDate + ")", - lastChecksumDate.isBefore(checkerStartDate)); + lastChecksumDate.before(checkerStartDate)); } // Dispatcher that throws an exception when a third bitstream is @@ -133,15 +134,15 @@ public void testChecksumsRecordedWhenProcesingIsInterrupted() throws SQLExceptio int bitstreamCount = 0; for (Bitstream bitstream: bitstreams) { MostRecentChecksum checksum = checksumService.findByBitstream(context, bitstream); - Instant lastChecksumDate = checksum.getProcessStartDate(); + Date lastChecksumDate = checksum.getProcessStartDate(); bitstreamCount = bitstreamCount + 1; if (bitstreamCount <= 2) { assertTrue("lastChecksumDate (" + lastChecksumDate + ") <= checkerStartDate (" + checkerStartDate + ")", - lastChecksumDate.isAfter(checkerStartDate)); + lastChecksumDate.after(checkerStartDate)); } else { assertTrue("lastChecksumDate (" + lastChecksumDate + ") >= checkerStartDate (" + checkerStartDate + ")", - lastChecksumDate.isBefore(checkerStartDate)); + lastChecksumDate.before(checkerStartDate)); } } } @@ -167,7 +168,7 @@ class ExpectionThrowingDispatcher extends SimpleDispatcher { * @param maxNextCalls the number of "next" method calls to allow before * throwing a SQLException. */ - public ExpectionThrowingDispatcher(Context context, Instant startTime, boolean looping, int maxNextCalls) { + public ExpectionThrowingDispatcher(Context context, Date startTime, boolean looping, int maxNextCalls) { super(context, startTime, looping); this.maxNextCalls = maxNextCalls; } From aa0fe083a60fb4a77485bd2e6416ef20d2cb5385 Mon Sep 17 00:00:00 2001 From: im-shubham-vish Date: Tue, 12 Aug 2025 16:55:53 +0530 Subject: [PATCH 663/939] Make parse protected to make it override in MetadataExportSearch Add Test for Double Quoted Search CSV Export (cherry picked from commit 39a45f7f3465df36fbf8db3f5a9ed541eab794d8) --- .../app/bulkedit/MetadataExportSearch.java | 12 +++++++ .../org/dspace/scripts/DSpaceRunnable.java | 2 +- .../app/bulkedit/MetadataExportSearchIT.java | 31 +++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportSearch.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportSearch.java index e4bbe335d63..689df4701a9 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportSearch.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportSearch.java @@ -14,6 +14,8 @@ import java.util.List; import java.util.UUID; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.DefaultParser.Builder; import org.apache.commons.cli.ParseException; import org.dspace.content.Item; import org.dspace.content.MetadataDSpaceCsvExportServiceImpl; @@ -167,4 +169,14 @@ public IndexableObject resolveScope(Context context, String id) throws SQLExcept } return scopeObj; } + + @Override + protected StepResult parse(String[] args) throws ParseException { + commandLine = new DefaultParser().parse(getScriptConfiguration().getOptions(), args); + Builder builder = new DefaultParser().builder(); + builder.setStripLeadingAndTrailingQuotes(false); + commandLine = builder.build().parse(getScriptConfiguration().getOptions(), args); + setup(); + return StepResult.Continue; + } } diff --git a/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java b/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java index 2ea0a52d6e3..8f905e01511 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java +++ b/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java @@ -117,7 +117,7 @@ private void handleHelpCommandLine() { * @param args The primitive array of Strings representing the parameters * @throws ParseException If something goes wrong */ - private StepResult parse(String[] args) throws ParseException { + protected StepResult parse(String[] args) throws ParseException { commandLine = new DefaultParser().parse(getScriptConfiguration().getOptions(), args); setup(); return StepResult.Continue; diff --git a/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataExportSearchIT.java b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataExportSearchIT.java index 63a87a48f55..15a6371e920 100644 --- a/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataExportSearchIT.java +++ b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataExportSearchIT.java @@ -251,4 +251,35 @@ public void exportMetadataSearchNonExistinFacetsTest() throws Exception { assertNotNull(exception); assertEquals("nonExisting is not a valid search filter", exception.getMessage()); } + + @Test + public void exportMetadataSearchDoubleQuotedArgumentTest() throws Exception { + context.turnOffAuthorisationSystem(); + Item quotedItem1 = ItemBuilder.createItem(context, collection) + .withTitle("The Special Runnable Item") + .withSubject("quoted-subject") + .build(); + Item quotedItem2 = ItemBuilder.createItem(context, collection) + .withTitle("The Special Item") + .withSubject("quoted-subject") + .build(); + context.restoreAuthSystemState(); + + int result = runDSpaceScript( + "metadata-export-search", + "-q", "title:\"Special Runnable\"", + "-n", filename); + + assertEquals(0, result); + + Item[] expectedResult = new Item[] {quotedItem1}; + checkItemsPresentInFile(filename, expectedResult); + + File file = new File(filename); + try (Reader reader = Files.newReader(file, Charset.defaultCharset()); + CSVReader csvReader = new CSVReader(reader)) { + List lines = csvReader.readAll(); + assertEquals("Unexpected extra items in export", 2, lines.size()); + } + } } From d063f1ef66ee853f23d2a286fecac7f9cbecb077 Mon Sep 17 00:00:00 2001 From: Joran De Braekeleer Date: Wed, 13 Aug 2025 13:32:33 +0200 Subject: [PATCH 664/939] 133268: Metadata import empty collection col check (cherry picked from commit b9513228d78ccb3aa8bcb8a94e2d0a8e1a37c30b) --- .../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 ad46cb95c35..7873f1b12ca 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 @@ -494,7 +494,7 @@ public List runImport(Context c, boolean change, // Check it has an owning collection List collections = line.get("collection"); - if (collections == null) { + if (collections == null || collections.isEmpty()) { throw new MetadataImportException( "New items must have a 'collection' assigned in the form of a handle"); } From 94a1a0b2e0eaea14f87ed8bd2c64187f9df6e2df Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Sun, 17 Aug 2025 11:56:13 +0300 Subject: [PATCH 665/939] .github/workflows/codescan.yml: use codeql-action v3 Version 2 was deprecated in January, 2024 after the release of v3. See: https://github.blog/changelog/2025-01-10-code-scanning-codeql-action-v2-is-now-deprecated/ --- .github/workflows/codescan.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codescan.yml b/.github/workflows/codescan.yml index 1e3d835e271..88931d478e6 100644 --- a/.github/workflows/codescan.yml +++ b/.github/workflows/codescan.yml @@ -47,7 +47,7 @@ jobs: # Initializes the CodeQL tools for scanning. # https://github.com/github/codeql-action - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: # Codescan Javascript as well since a few JS files exist in REST API's interface languages: java, javascript @@ -56,8 +56,8 @@ jobs: # NOTE: Based on testing, this autobuild process works well for DSpace. A custom # DSpace build w/caching (like in build.yml) was about the same speed as autobuild. - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 # Perform GitHub Code Scanning. - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 From e51a2537aca68982cfeea39c1a753e479b561711 Mon Sep 17 00:00:00 2001 From: Miika Nurminen Date: Tue, 12 Aug 2025 19:57:57 +0300 Subject: [PATCH 666/939] Fix bean initialization error on Tomcat startup if citation-page.enabled_communities is set Arrays.asList produces non-mutable list but with communities, sub-collections are added dynamically on initializaion. --- .../org/dspace/disseminate/CitationDocumentServiceImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/disseminate/CitationDocumentServiceImpl.java b/dspace-api/src/main/java/org/dspace/disseminate/CitationDocumentServiceImpl.java index c20961db754..b1441867772 100644 --- a/dspace-api/src/main/java/org/dspace/disseminate/CitationDocumentServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/disseminate/CitationDocumentServiceImpl.java @@ -139,8 +139,8 @@ public void afterPropertiesSet() throws Exception { //Load enabled collections String[] citationEnabledCollections = configurationService - .getArrayProperty("citation-page.enabled_collections"); - citationEnabledCollectionsList = Arrays.asList(citationEnabledCollections); + .getArrayProperty("citation-page.enabled_collections"); + citationEnabledCollectionsList = new ArrayList(Arrays.asList(citationEnabledCollections)); //Load enabled communities, and add to collection-list String[] citationEnabledCommunities = configurationService From 922109b10ace76c6c7d956ac21edb0c01d46f5df Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Fri, 15 Aug 2025 09:57:51 +0300 Subject: [PATCH 667/939] dspace-api/pom.xml: remove build-helper-maven-plugin Remove the org.codehaus.mojo:build-helper-maven-plugin because the `maven-version` property has been included by maven itself since version 3.0.4. This fixes the following warning during build: [INFO] --- build-helper:3.6.1:maven-version (default) @ dspace-api --- [WARNING] Goal 'maven-version' is deprecated: Maven since version 3.0.4 has such property build in: MNG-4112 . So goal can be removed. (cherry picked from commit de3170d4ebbcc2168e639d681f0433b9dec293b5) --- dspace-api/pom.xml | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 6f084d235c7..3488fda26bf 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -99,20 +99,6 @@ - - org.codehaus.mojo - build-helper-maven-plugin - 3.6.1 - - - validate - - maven-version - - - - - org.codehaus.mojo buildnumber-maven-plugin From f831acf4d703ee13f0703ed5b2ed237b09efe991 Mon Sep 17 00:00:00 2001 From: JohnnyMendesC <177888064+JohnnyMendesC@users.noreply.github.com> Date: Thu, 14 Aug 2025 14:05:45 -0300 Subject: [PATCH 668/939] fix(#8852): Statistics event processor now uses oaiPrefix instead of getHost (cherry picked from commit c8fe80c22bdc67d8b4eae0575fb8a2cab31d2010) --- .../statistics/export/processor/ExportEventProcessor.java | 3 ++- .../statistics/export/ITIrusExportUsageEventListener.java | 1 + .../statistics/export/processor/ExportEventProcessorIT.java | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/processor/ExportEventProcessor.java b/dspace-api/src/main/java/org/dspace/statistics/export/processor/ExportEventProcessor.java index 609298779d3..562b8547ce9 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/processor/ExportEventProcessor.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/processor/ExportEventProcessor.java @@ -136,9 +136,10 @@ protected String getBaseParameters(Item item) .append(URLEncoder.encode(clientUA, UTF_8)); String hostName = Utils.getHostName(configurationService.getProperty("dspace.ui.url")); + String oaiPrefix = configurationService.getProperty("oai.identifier.prefix"); data.append("&").append(URLEncoder.encode("rft.artnum", UTF_8)).append("="). - append(URLEncoder.encode("oai:" + hostName + ":" + item + append(URLEncoder.encode("oai:" + oaiPrefix + ":" + item .getHandle(), UTF_8)); data.append("&").append(URLEncoder.encode("rfr_dat", UTF_8)).append("=") .append(URLEncoder.encode(referer, UTF_8)); diff --git a/dspace-api/src/test/java/org/dspace/statistics/export/ITIrusExportUsageEventListener.java b/dspace-api/src/test/java/org/dspace/statistics/export/ITIrusExportUsageEventListener.java index e28e8284a21..77a51a92f35 100644 --- a/dspace-api/src/test/java/org/dspace/statistics/export/ITIrusExportUsageEventListener.java +++ b/dspace-api/src/test/java/org/dspace/statistics/export/ITIrusExportUsageEventListener.java @@ -116,6 +116,7 @@ public void setUp() throws Exception { configurationService.setProperty("irus.statistics.tracker.enabled", true); configurationService.setProperty("irus.statistics.tracker.type-field", "dc.type"); configurationService.setProperty("irus.statistics.tracker.type-value", "Excluded type"); + configurationService.setProperty("oai.identifier.prefix", "localhost"); context.turnOffAuthorisationSystem(); diff --git a/dspace-api/src/test/java/org/dspace/statistics/export/processor/ExportEventProcessorIT.java b/dspace-api/src/test/java/org/dspace/statistics/export/processor/ExportEventProcessorIT.java index e42003e4fc8..1ae7d2e4905 100644 --- a/dspace-api/src/test/java/org/dspace/statistics/export/processor/ExportEventProcessorIT.java +++ b/dspace-api/src/test/java/org/dspace/statistics/export/processor/ExportEventProcessorIT.java @@ -62,6 +62,7 @@ public void setUp() throws Exception { configurationService.setProperty("irus.statistics.tracker.enabled", true); configurationService.setProperty("irus.statistics.tracker.type-field", "dc.type"); configurationService.setProperty("irus.statistics.tracker.type-value", "Excluded type"); + configurationService.setProperty("oai.identifier.prefix", "localhost"); context.turnOffAuthorisationSystem(); publication = EntityTypeBuilder.createEntityTypeBuilder(context, "Publication").build(); From 103d7f754a8d0cf138478c87aaa7d4a726d4e734 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 05:44:10 +0000 Subject: [PATCH 669/939] Bump the test-tools group with 6 updates Bumps the test-tools group with 6 updates: | Package | From | To | | --- | --- | --- | | [io.netty:netty-buffer](https://github.com/netty/netty) | `4.2.3.Final` | `4.2.4.Final` | | [io.netty:netty-transport](https://github.com/netty/netty) | `4.2.3.Final` | `4.2.4.Final` | | [io.netty:netty-transport-native-unix-common](https://github.com/netty/netty) | `4.2.3.Final` | `4.2.4.Final` | | [io.netty:netty-common](https://github.com/netty/netty) | `4.2.3.Final` | `4.2.4.Final` | | [io.netty:netty-handler](https://github.com/netty/netty) | `4.2.3.Final` | `4.2.4.Final` | | [io.netty:netty-codec](https://github.com/netty/netty) | `4.2.3.Final` | `4.2.4.Final` | Updates `io.netty:netty-buffer` from 4.2.3.Final to 4.2.4.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.3.Final...netty-4.2.4.Final) Updates `io.netty:netty-transport` from 4.2.3.Final to 4.2.4.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.3.Final...netty-4.2.4.Final) Updates `io.netty:netty-transport-native-unix-common` from 4.2.3.Final to 4.2.4.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.3.Final...netty-4.2.4.Final) Updates `io.netty:netty-common` from 4.2.3.Final to 4.2.4.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.3.Final...netty-4.2.4.Final) Updates `io.netty:netty-handler` from 4.2.3.Final to 4.2.4.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.3.Final...netty-4.2.4.Final) Updates `io.netty:netty-codec` from 4.2.3.Final to 4.2.4.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.3.Final...netty-4.2.4.Final) --- updated-dependencies: - dependency-name: io.netty:netty-buffer dependency-version: 4.2.4.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-transport dependency-version: 4.2.4.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-transport-native-unix-common dependency-version: 4.2.4.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-common dependency-version: 4.2.4.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-handler dependency-version: 4.2.4.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-codec dependency-version: 4.2.4.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 3488fda26bf..b8dbb4a6a7f 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -870,32 +870,32 @@ io.netty netty-buffer - 4.2.3.Final + 4.2.4.Final io.netty netty-transport - 4.2.3.Final + 4.2.4.Final io.netty netty-transport-native-unix-common - 4.2.3.Final + 4.2.4.Final io.netty netty-common - 4.2.3.Final + 4.2.4.Final io.netty netty-handler - 4.2.3.Final + 4.2.4.Final io.netty netty-codec - 4.2.3.Final + 4.2.4.Final org.apache.velocity From b78f570dbfa6a1303d644e39857037f8d99d4df2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 05:44:48 +0000 Subject: [PATCH 670/939] Bump the build-tools group with 3 updates Bumps the build-tools group with 3 updates: [com.github.spotbugs:spotbugs](https://github.com/spotbugs/spotbugs), [com.github.spotbugs:spotbugs-maven-plugin](https://github.com/spotbugs/spotbugs-maven-plugin) and [org.apache.maven.plugins:maven-javadoc-plugin](https://github.com/apache/maven-javadoc-plugin). Updates `com.github.spotbugs:spotbugs` from 4.9.3 to 4.9.4 - [Release notes](https://github.com/spotbugs/spotbugs/releases) - [Changelog](https://github.com/spotbugs/spotbugs/blob/master/CHANGELOG.md) - [Commits](https://github.com/spotbugs/spotbugs/compare/4.9.3...4.9.4) Updates `com.github.spotbugs:spotbugs-maven-plugin` from 4.9.3.2 to 4.9.4.0 - [Release notes](https://github.com/spotbugs/spotbugs-maven-plugin/releases) - [Commits](https://github.com/spotbugs/spotbugs-maven-plugin/compare/spotbugs-maven-plugin-4.9.3.2...spotbugs-maven-plugin-4.9.4.0) Updates `org.apache.maven.plugins:maven-javadoc-plugin` from 3.11.2 to 3.11.3 - [Release notes](https://github.com/apache/maven-javadoc-plugin/releases) - [Commits](https://github.com/apache/maven-javadoc-plugin/compare/maven-javadoc-plugin-3.11.2...maven-javadoc-plugin-3.11.3) --- updated-dependencies: - dependency-name: com.github.spotbugs:spotbugs dependency-version: 4.9.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: build-tools - dependency-name: com.github.spotbugs:spotbugs-maven-plugin dependency-version: 4.9.4.0 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: build-tools - dependency-name: org.apache.maven.plugins:maven-javadoc-plugin dependency-version: 3.11.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: build-tools ... Signed-off-by: dependabot[bot] --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 9a246b2b7ac..69daf0c7528 100644 --- a/pom.xml +++ b/pom.xml @@ -295,7 +295,7 @@ com.github.spotbugs spotbugs-maven-plugin - 4.9.3.2 + 4.9.4.0 Max Low @@ -305,7 +305,7 @@ com.github.spotbugs spotbugs - 4.9.3 + 4.9.4 @@ -363,7 +363,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.11.2 + 3.11.3 false From 9bef7b58d694f6a83b094c846ad4d740bb4c99fc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 05:45:53 +0000 Subject: [PATCH 671/939] Bump io.grpc:grpc-context from 1.74.0 to 1.75.0 Bumps [io.grpc:grpc-context](https://github.com/grpc/grpc-java) from 1.74.0 to 1.75.0. - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.74.0...v1.75.0) --- updated-dependencies: - dependency-name: io.grpc:grpc-context dependency-version: 1.75.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9a246b2b7ac..4325535d412 100644 --- a/pom.xml +++ b/pom.xml @@ -1718,7 +1718,7 @@ io.grpc grpc-context - 1.74.0 + 1.75.0 com.google.http-client From cf3f8e9bb24d7498266219b44c68af3a1178eade Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 05:51:23 +0000 Subject: [PATCH 672/939] Bump commons-cli:commons-cli in the apache-commons group Bumps the apache-commons group with 1 update: [commons-cli:commons-cli](https://github.com/apache/commons-cli). Updates `commons-cli:commons-cli` from 1.9.0 to 1.10.0 - [Changelog](https://github.com/apache/commons-cli/blob/master/RELEASE-NOTES.txt) - [Commits](https://github.com/apache/commons-cli/compare/rel/commons-cli-1.9.0...rel/commons-cli-1.10.0) --- updated-dependencies: - dependency-name: commons-cli:commons-cli dependency-version: 1.10.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: apache-commons ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9a246b2b7ac..bc588e2fb68 100644 --- a/pom.xml +++ b/pom.xml @@ -1473,7 +1473,7 @@ commons-cli commons-cli - 1.9.0 + 1.10.0 commons-codec From 13982618df3e9b7eaf88426bd3405bbb845e164d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 05:53:13 +0000 Subject: [PATCH 673/939] Bump jetty.version from 9.4.57.v20241219 to 9.4.58.v20250814 Bumps `jetty.version` from 9.4.57.v20241219 to 9.4.58.v20250814. Updates `org.eclipse.jetty:jetty-server` from 9.4.57.v20241219 to 9.4.58.v20250814 Updates `org.eclipse.jetty:jetty-alpn-java-server` from 9.4.57.v20241219 to 9.4.58.v20250814 Updates `org.eclipse.jetty:jetty-deploy` from 9.4.57.v20241219 to 9.4.58.v20250814 Updates `org.eclipse.jetty:jetty-http` from 9.4.57.v20241219 to 9.4.58.v20250814 Updates `org.eclipse.jetty:jetty-io` from 9.4.57.v20241219 to 9.4.58.v20250814 Updates `org.eclipse.jetty:jetty-servlet` from 9.4.57.v20241219 to 9.4.58.v20250814 Updates `org.eclipse.jetty:jetty-servlets` from 9.4.57.v20241219 to 9.4.58.v20250814 Updates `org.eclipse.jetty:jetty-util` from 9.4.57.v20241219 to 9.4.58.v20250814 Updates `org.eclipse.jetty:jetty-webapp` from 9.4.57.v20241219 to 9.4.58.v20250814 Updates `org.eclipse.jetty:jetty-xml` from 9.4.57.v20241219 to 9.4.58.v20250814 Updates `org.eclipse.jetty.http2:http2-common` from 9.4.57.v20241219 to 9.4.58.v20250814 Updates `org.eclipse.jetty.http2:http2-server` from 9.4.57.v20241219 to 9.4.58.v20250814 --- updated-dependencies: - dependency-name: org.eclipse.jetty:jetty-server dependency-version: 9.4.58.v20250814 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.jetty:jetty-alpn-java-server dependency-version: 9.4.58.v20250814 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.jetty:jetty-deploy dependency-version: 9.4.58.v20250814 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.jetty:jetty-http dependency-version: 9.4.58.v20250814 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.jetty:jetty-io dependency-version: 9.4.58.v20250814 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.jetty:jetty-servlet dependency-version: 9.4.58.v20250814 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.jetty:jetty-servlets dependency-version: 9.4.58.v20250814 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.jetty:jetty-util dependency-version: 9.4.58.v20250814 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.jetty:jetty-webapp dependency-version: 9.4.58.v20250814 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.jetty:jetty-xml dependency-version: 9.4.58.v20250814 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.jetty.http2:http2-common dependency-version: 9.4.58.v20250814 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.jetty.http2:http2-server dependency-version: 9.4.58.v20250814 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9a246b2b7ac..50bf7ed1fbe 100644 --- a/pom.xml +++ b/pom.xml @@ -37,7 +37,7 @@ 2.3.9 1.1.1 - 9.4.57.v20241219 + 9.4.58.v20250814 2.25.1 2.0.34 1.19.0 From 2c34dde3a84e260b4de61e65d68c01c04049bd97 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 06:00:09 +0000 Subject: [PATCH 674/939] Bump org.ehcache:ehcache from 3.10.8 to 3.11.1 Bumps [org.ehcache:ehcache](https://github.com/ehcache/ehcache3) from 3.10.8 to 3.11.1. - [Release notes](https://github.com/ehcache/ehcache3/releases) - [Commits](https://github.com/ehcache/ehcache3/compare/v3.10.8...v3.11.1) --- updated-dependencies: - dependency-name: org.ehcache:ehcache dependency-version: 3.11.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9a246b2b7ac..7b10e4cef0e 100644 --- a/pom.xml +++ b/pom.xml @@ -27,7 +27,7 @@ 42.7.7 8.11.4 - 3.10.8 + 3.11.1 2.31.0 2.19.2 From ccf253ddda16a26da13f0a957641dc971c9c4f06 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 06:01:09 +0000 Subject: [PATCH 675/939] Bump net.minidev:json-smart from 2.5.2 to 2.6.0 Bumps [net.minidev:json-smart](https://github.com/netplex/json-smart-v2) from 2.5.2 to 2.6.0. - [Release notes](https://github.com/netplex/json-smart-v2/releases) - [Commits](https://github.com/netplex/json-smart-v2/compare/2.5.2...v2.6.0) --- updated-dependencies: - dependency-name: net.minidev:json-smart dependency-version: 2.6.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dspace-server-webapp/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 4cfe93f2ee4..84227b4a12f 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -514,7 +514,7 @@ net.minidev json-smart - 2.5.2 + 2.6.0 From 95dc45362af68cb88852365b3b9eccd81b285d48 Mon Sep 17 00:00:00 2001 From: wout Date: Fri, 14 Mar 2025 16:27:53 +0100 Subject: [PATCH 676/939] 127822: update researcherid -> webofscience --- dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl b/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl index 905187494dd..d496b9c1421 100644 --- a/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl +++ b/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl @@ -369,7 +369,7 @@ + schemeURI="https://www.webofscience.com"> From 8f4a3f963af8d9a12bca9f0980a5645053b07aec Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Mon, 25 Aug 2025 14:16:56 +0200 Subject: [PATCH 677/939] Implement logging for index out-of-range Add logging for out-of-range index when removing metadata values. (cherry picked from commit 7692b02b12ddcdc50ec22e3ccd060240734202ed) --- .../impl/MetadataValueRemovePatchOperation.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/MetadataValueRemovePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/MetadataValueRemovePatchOperation.java index 1660a5455ae..18bc1df66c1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/MetadataValueRemovePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/MetadataValueRemovePatchOperation.java @@ -11,6 +11,8 @@ import java.util.Arrays; import java.util.List; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.model.MetadataValueRest; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; @@ -27,6 +29,8 @@ public abstract class MetadataValueRemovePatchOperation extends RemovePatchOperation { + private static final Logger log = LogManager.getLogger(); + @Override protected Class getArrayClassForEvaluation() { return MetadataValueRest[].class; @@ -42,7 +46,12 @@ protected void deleteValue(Context context, DSO source, String target, int index List mm = getDSpaceObjectService().getMetadata(source, metadata[0], metadata[1], metadata[2], Item.ANY); if (index != -1) { - getDSpaceObjectService().removeMetadataValues(context, source, Arrays.asList(mm.get(index))); + if (index < mm.size()) { + getDSpaceObjectService().removeMetadataValues(context, source, Arrays.asList(mm.get(index))); + } else { + log.warn("value of index ({}) is out of range of the metadata value list of size {} (target: {})", + index, mm.size(), target); + } } else { getDSpaceObjectService().clearMetadata(context, source, metadata[0], metadata[1], metadata[2], Item.ANY); } From 376f49e99b54b89e03ceba7808d153db639f6e03 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Wed, 27 Aug 2025 15:01:35 +0200 Subject: [PATCH 678/939] 133421: Removed database connection leak on unsuccessful login (cherry picked from commit 0b0c9fc84c1cf3f51d008ace95ad0d94c26e9dbc) --- .../app/rest/security/OrcidLoginFilter.java | 1 + .../rest/security/StatelessLoginFilter.java | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidLoginFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidLoginFilter.java index 9fdef6b050f..49dd7b87282 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidLoginFilter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidLoginFilter.java @@ -87,6 +87,7 @@ protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServle String baseRediredirectUrl = configurationService.getProperty("dspace.ui.url"); String redirectUrl = baseRediredirectUrl + "/error?status=401&code=orcid.generic-error"; response.sendRedirect(redirectUrl); // lgtm [java/unvalidated-url-redirection] + this.closeOpenContext(request); } else { super.unsuccessfulAuthentication(request, response, failed); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessLoginFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessLoginFilter.java index c95fce71c42..31f3d5d460b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessLoginFilter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessLoginFilter.java @@ -8,11 +8,14 @@ package org.dspace.app.rest.security; import java.io.IOException; +import java.sql.SQLException; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.core.Context; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.authentication.AuthenticationManager; @@ -122,6 +125,27 @@ protected void unsuccessfulAuthentication(HttpServletRequest request, response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication failed!"); log.error("Authentication failed (status:{})", HttpServletResponse.SC_UNAUTHORIZED, failed); + this.closeOpenContext(request); + } + + /** + * Manually closes the open {@link Context} if one exists. We need to do this manually because + * {@link #continueChainBeforeSuccessfulAuthentication} is {@code false} by default, which prevents the + * {@link org.dspace.app.rest.filter.DSpaceRequestContextFilter} from being called. Without this call, the request + * would leave an open database connection. + * + * @param request The current request. + */ + protected void closeOpenContext(HttpServletRequest request) { + if (ContextUtil.isContextAvailable(request)) { + try (Context context = ContextUtil.obtainContext(request)) { + if (context != null && context.isValid()) { + context.complete(); + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + } } } From 66fc49800db5eaacdc6de9116c66701350bc7d37 Mon Sep 17 00:00:00 2001 From: Martin Walk Date: Wed, 20 Aug 2025 15:45:14 +0200 Subject: [PATCH 679/939] Fix #11074 export simple archive format with no collection (cherry picked from commit d282f92427f340a7f8ca68ca8316114ce0bdd524) --- .../app/itemexport/ItemExportServiceImpl.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExportServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExportServiceImpl.java index 7c80e1ea7dc..e5ca8fe688c 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExportServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExportServiceImpl.java @@ -352,7 +352,7 @@ protected void writeHandle(Context c, Item i, File destDir) /** * Create the 'collections' file. List handles of all Collections which - * contain this Item. The "owning" Collection is listed first. + * contain this Item. The "owning" Collection is listed first. * * @param item list collections holding this Item. * @param destDir write the file here. @@ -363,12 +363,14 @@ protected void writeCollections(Item item, File destDir) File outFile = new File(destDir, "collections"); if (outFile.createNewFile()) { try (PrintWriter out = new PrintWriter(new FileWriter(outFile))) { - String ownerHandle = item.getOwningCollection().getHandle(); - out.println(ownerHandle); + Collection owningCollection = item.getOwningCollection(); + // The owning collection is null for workspace and workflow items + if (owningCollection != null) { + out.println(owningCollection.getHandle()); + } for (Collection collection : item.getCollections()) { - String collectionHandle = collection.getHandle(); - if (!collectionHandle.equals(ownerHandle)) { - out.println(collectionHandle); + if (!collection.equals(owningCollection)) { + out.println(collection.getHandle()); } } } From be9d2bd2bfbad319533fb46e7018863825856703 Mon Sep 17 00:00:00 2001 From: nwoodward Date: Wed, 18 Dec 2024 14:25:41 -0600 Subject: [PATCH 680/939] make several usage statistics parameters configurable (cherry picked from commit cd5798593417e0501ae8fe881f9c82666dc02830) --- .../app/rest/utils/UsageReportUtils.java | 38 +++++++++++++------ dspace/config/modules/usage-statistics.cfg | 19 +++++++++- 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/UsageReportUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/UsageReportUtils.java index 4603569da84..53f4317808a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/UsageReportUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/UsageReportUtils.java @@ -27,6 +27,7 @@ import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.handle.service.HandleService; +import org.dspace.services.ConfigurationService; import org.dspace.statistics.Dataset; import org.dspace.statistics.content.DatasetDSpaceObjectGenerator; import org.dspace.statistics.content.DatasetTimeGenerator; @@ -46,6 +47,9 @@ @Component public class UsageReportUtils { + @Autowired + private ConfigurationService configurationService; + @Autowired private HandleService handleService; @@ -135,13 +139,14 @@ public UsageReportRest createUsageReport(Context context, DSpaceObject dso, Stri */ private UsageReportRest resolveGlobalUsageReport(Context context) throws SQLException, IOException, ParseException, SolrServerException { + int topItemsLimit = configurationService.getIntProperty("usage-statistics.topItemsLimit", 10); + StatisticsListing statListing = new StatisticsListing( new StatisticsDataVisits()); - // Adding a new generator for our top 10 items without a name length delimiter + // Adding a new generator for our top n items without a name length delimiter DatasetDSpaceObjectGenerator dsoAxis = new DatasetDSpaceObjectGenerator(); - // TODO make max nr of top items (views wise)? Must be set - dsoAxis.addDsoChild(Constants.ITEM, 10, false, -1); + dsoAxis.addDsoChild(Constants.ITEM, topItemsLimit, false, -1); statListing.addDatasetGenerator(dsoAxis); Dataset dataset = statListing.getDataset(context, 1); @@ -182,7 +187,7 @@ private UsageReportRest resolveTotalVisits(Context context, DSpaceObject dso) UsageReportPointDsoTotalVisitsRest totalVisitPoint = new UsageReportPointDsoTotalVisitsRest(); totalVisitPoint.setType(StringUtils.substringAfterLast(dso.getClass().getName().toLowerCase(), ".")); totalVisitPoint.setId(dso.getID().toString()); - if (dataset.getColLabels().size() > 0) { + if (!dataset.getColLabels().isEmpty()) { totalVisitPoint.setLabel(dso.getName()); totalVisitPoint.addValue("views", Integer.valueOf(dataset.getMatrix()[0][0])); } else { @@ -205,10 +210,14 @@ private UsageReportRest resolveTotalVisits(Context context, DSpaceObject dso) */ private UsageReportRest resolveTotalVisitsPerMonth(Context context, DSpaceObject dso) throws SQLException, IOException, ParseException, SolrServerException { + String startDateInterval = + configurationService.getProperty("usage-statistics.startDateInterval", "-6"); + String endDateInterval = + configurationService.getProperty("usage-statistics.endDateInterval", "+1"); + StatisticsTable statisticsTable = new StatisticsTable(new StatisticsDataVisits(dso)); DatasetTimeGenerator timeAxis = new DatasetTimeGenerator(); - // TODO month start and end as request para? - timeAxis.setDateInterval("month", "-6", "+1"); + timeAxis.setDateInterval("month", startDateInterval, endDateInterval); statisticsTable.addDatasetGenerator(timeAxis); DatasetDSpaceObjectGenerator dsoAxis = new DatasetDSpaceObjectGenerator(); dsoAxis.addDsoChild(dso.getType(), 10, false, -1); @@ -275,7 +284,10 @@ private UsageReportRest resolveTotalDownloads(Context context, DSpaceObject dso) */ private UsageReportRest resolveTopCountries(Context context, DSpaceObject dso) throws SQLException, IOException, ParseException, SolrServerException { - Dataset dataset = this.getTypeStatsDataset(context, dso, "countryCode", 1); + int topCountriesLimit = + configurationService.getIntProperty("usage-statistics.topCountriesLimit", 100); + + Dataset dataset = this.getTypeStatsDataset(context, dso, "countryCode", topCountriesLimit, 1); UsageReportRest usageReportRest = new UsageReportRest(); for (int i = 0; i < dataset.getColLabels().size(); i++) { @@ -299,7 +311,10 @@ private UsageReportRest resolveTopCountries(Context context, DSpaceObject dso) */ private UsageReportRest resolveTopCities(Context context, DSpaceObject dso) throws SQLException, IOException, ParseException, SolrServerException { - Dataset dataset = this.getTypeStatsDataset(context, dso, "city", 1); + int topCitiesLimit = + configurationService.getIntProperty("usage-statistics.topCitiesLimit", 100); + + Dataset dataset = this.getTypeStatsDataset(context, dso, "city", topCitiesLimit, 1); UsageReportRest usageReportRest = new UsageReportRest(); for (int i = 0; i < dataset.getColLabels().size(); i++) { @@ -339,16 +354,17 @@ private Dataset getDSOStatsDataset(Context context, DSpaceObject dso, int facetM * @param dso DSO we want the stats dataset of * @param typeAxisString String of the type we want on the axis of the dataset (corresponds to solr field), * examples: countryCode, city + * @param typeAxisMax Maximum amount of results to return in the dataset * @param facetMinCount Minimum amount of results on a facet data point for it to be added to dataset * @return Stats dataset with the given type on the axis, of the given DSO and with given facetMinCount */ - private Dataset getTypeStatsDataset(Context context, DSpaceObject dso, String typeAxisString, int facetMinCount) + private Dataset getTypeStatsDataset(Context context, DSpaceObject dso, String typeAxisString, int typeAxisMax, + int facetMinCount) throws SQLException, IOException, ParseException, SolrServerException { StatisticsListing statListing = new StatisticsListing(new StatisticsDataVisits(dso)); DatasetTypeGenerator typeAxis = new DatasetTypeGenerator(); typeAxis.setType(typeAxisString); - // TODO make max nr of top countries/cities a request para? Must be set - typeAxis.setMax(100); + typeAxis.setMax(typeAxisMax); statListing.addDatasetGenerator(typeAxis); return statListing.getDataset(context, facetMinCount); } diff --git a/dspace/config/modules/usage-statistics.cfg b/dspace/config/modules/usage-statistics.cfg index c77bb1ca78a..6d47a13dcfc 100644 --- a/dspace/config/modules/usage-statistics.cfg +++ b/dspace/config/modules/usage-statistics.cfg @@ -60,4 +60,21 @@ usage-statistics.shardedByYear = false #anonymize_statistics.dns_mask = anonymized # Only anonymize statistics records older than this threshold (expressed in days) -#anonymize_statistics.time_threshold = 90 \ No newline at end of file +#anonymize_statistics.time_threshold = 90 + +# Maximum number of items to display in the usage statistics report for an entire repository +usage-statistics.topItemsLimit = 10 + +# Number of months to begin retrieving usage statistics for total visits per month of a DSpace object +# For example, -6 means include the previous six months +usage-statistics.startDateInterval = -6 + +# Number of months to end retrieving usage statistics for total visits per month of a DSpace object +# For example, +1 means include the current month +usage-statistics.endDateInterval = +1 + +# Maximum number of countries to display in the usage statistics reports +usage-statistics.topCountriesLimit = 100 + +# Maximum number of cities to display in the usage statistics reports +usage-statistics.topCitiesLimit = 100 From 5ed0b28a364cc20e063a3e2866a2d77796cbb905 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 3 Sep 2025 11:35:05 +0200 Subject: [PATCH 681/939] 133552: AIP packager bitstream url fix (cherry picked from commit 11da562ea40cf61790ee755a0069955a7a26586e) --- .../content/crosswalk/PREMISCrosswalk.java | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/PREMISCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/PREMISCrosswalk.java index 39b6c8f29c8..1d38249e6b5 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/PREMISCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/PREMISCrosswalk.java @@ -224,29 +224,17 @@ public Element disseminateElement(Context context, DSpaceObject dso) // c. made-up name based on sequence ID and extension. String sid = String.valueOf(bitstream.getSequenceID()); String baseUrl = configurationService.getProperty("dspace.ui.url"); - String handle = null; - // get handle of parent Item of this bitstream, if there is one: - List bn = bitstream.getBundles(); - if (bn.size() > 0) { - List bi = bn.get(0).getItems(); - if (bi.size() > 0) { - handle = bi.get(0).getHandle(); - } - } // get or make up name for bitstream: String bsName = bitstream.getName(); if (bsName == null) { List ext = bitstream.getFormat(context).getExtensions(); bsName = "bitstream_" + sid + (ext.size() > 0 ? ext.get(0) : ""); } - if (handle != null && baseUrl != null) { + if (baseUrl != null) { oiv.setText(baseUrl - + "/bitstream/" - + URLEncoder.encode(handle, "UTF-8") - + "/" - + sid - + "/" - + URLEncoder.encode(bsName, "UTF-8")); + + "/bitstreams/" + + bitstream.getID() + + "/download"); } else { oiv.setText(URLEncoder.encode(bsName, "UTF-8")); } From cbecc64a5a5928f4f4885db17b42899f05c17714 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 3 Sep 2025 11:56:42 +0200 Subject: [PATCH 682/939] 133552: unused import (cherry picked from commit 93240941d32b7a28533a666907d50387a7a1148a) --- .../main/java/org/dspace/content/crosswalk/PREMISCrosswalk.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/PREMISCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/PREMISCrosswalk.java index 1d38249e6b5..6b6c0fd7c5a 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/PREMISCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/PREMISCrosswalk.java @@ -20,9 +20,7 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.content.Bitstream; import org.dspace.content.BitstreamFormat; -import org.dspace.content.Bundle; import org.dspace.content.DSpaceObject; -import org.dspace.content.Item; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.BitstreamFormatService; import org.dspace.content.service.BitstreamService; From fe4077acee30fac8fb5607821a7d6e1ee3bd9d88 Mon Sep 17 00:00:00 2001 From: JohnnyMendesC <177888064+JohnnyMendesC@users.noreply.github.com> Date: Thu, 4 Sep 2025 11:10:46 -0300 Subject: [PATCH 683/939] fix(#11191): Align Content-Disposition with RFC 5987/6266 --- .../rest/utils/HttpHeadersInitializer.java | 54 +++++++++++++++++-- .../app/rest/BitstreamRestControllerIT.java | 11 ++-- 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java index d68c710a3c7..67ad7202b5a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java @@ -9,9 +9,11 @@ import static java.util.Objects.isNull; import static java.util.Objects.nonNull; -import static javax.mail.internet.MimeUtility.encodeText; import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.text.Normalizer; import java.util.Arrays; import java.util.Collections; import java.util.Objects; @@ -171,9 +173,16 @@ public HttpHeaders initialiseHeaders() throws IOException { // distposition may be null here if contentType is null if (!isNullOrEmpty(disposition)) { - httpHeaders.put(CONTENT_DISPOSITION, Collections.singletonList(String.format(CONTENT_DISPOSITION_FORMAT, - disposition, - encodeText(fileName)))); + String fallbackAsciiName = createFallbackAsciiName(this.fileName); + String encodedUtf8Name = createEncodedUtf8Name(this.fileName); + + String headerValue = String.format( + "%s; filename=\"%s\"; filename*=UTF-8''%s", + disposition, + fallbackAsciiName, + encodedUtf8Name + ); + httpHeaders.put(CONTENT_DISPOSITION, Collections.singletonList(headerValue)); } log.debug("Content-Disposition : {}", disposition); @@ -261,4 +270,41 @@ private static boolean matches(String matchHeader, String toMatch) { return Arrays.binarySearch(matchValues, toMatch) > -1 || Arrays.binarySearch(matchValues, "*") > -1; } + /** + * Creates a safe ASCII-only fallback filename by removing diacritics (accents) + * and replacing any remaining non-ASCII characters. + * E.g., "ä-ö-é.pdf" becomes "a-o-e.pdf". + * @param originalFilename The original filename. + * @return A string containing only ASCII characters. + */ + private String createFallbackAsciiName(String originalFilename) { + if (originalFilename == null) { + return ""; + } + String normalized = Normalizer.normalize(originalFilename, Normalizer.Form.NFD); + String withoutAccents = normalized.replaceAll("\\p{InCombiningDiacriticalMarks}+", ""); + return withoutAccents.replaceAll("[^\\x00-\\x7F]", ""); + } + + /** + * Creates a percent-encoded UTF-8 filename according to RFC 5987. + * This is for the `filename*` parameter. + * E.g., "ä ö é.pdf" becomes "%C3%A4%20%C3%B6%20%C3%A9.pdf". + * @param originalFilename The original filename. + * @return A percent-encoded string. + */ + private String createEncodedUtf8Name(String originalFilename) { + if (originalFilename == null) { + return ""; + } + try { + String encoded = URLEncoder.encode(originalFilename, StandardCharsets.UTF_8.toString()); + return encoded.replace("+", "%20"); + } catch (java.io.UnsupportedEncodingException e) { + // Fallback to a simple ASCII name if encoding fails. + log.error("UTF-8 encoding not supported, which should not happen.", e); + return createFallbackAsciiName(originalFilename); + } + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java index 45f8565fbaf..095d4d89fb1 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java @@ -8,7 +8,6 @@ package org.dspace.app.rest; import static java.util.UUID.randomUUID; -import static javax.mail.internet.MimeUtility.encodeText; import static org.apache.commons.codec.CharEncoding.UTF_8; import static org.apache.commons.collections.CollectionUtils.isEmpty; import static org.apache.commons.io.IOUtils.toInputStream; @@ -347,7 +346,11 @@ public void testBitstreamName() throws Exception { //2. A public item with a bitstream String bitstreamContent = "0123456789"; - String bitstreamName = "ภาษาไทย"; + String bitstreamName = "ภาษาไทย-com-acentuação.pdf"; + String expectedAscii = "-com-acentuacao.pdf"; + String expectedUtf8Encoded = + "%E0%B8%A0%E0%B8%B2%E0%B8%A9%E0%B8%B2%E0%B9%84%E0%B8%97%E0%B8%A2-" + + "com-acentua%C3%A7%C3%A3o.pdf"; try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { @@ -371,7 +374,9 @@ public void testBitstreamName() throws Exception { //We expect the content disposition to have the encoded bitstream name .andExpect(header().string( "Content-Disposition", - "attachment;filename=\"" + encodeText(bitstreamName) + "\"" + String.format("attachment; filename=\"%s\"; filename*=UTF-8''%s", + expectedAscii, + expectedUtf8Encoded) )); } From 7dd2b5318b4f00e8dc17d6f82bfb4ccacc4bcc77 Mon Sep 17 00:00:00 2001 From: Stefano Maffei Date: Thu, 14 Aug 2025 12:02:08 +0200 Subject: [PATCH 684/939] [CST-21947] fix security fix --- .../org/dspace/storage/bitstore/DSBitStoreService.java | 7 ++++++- dspace/config/modules/assetstore.cfg | 9 ++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/DSBitStoreService.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/DSBitStoreService.java index 7743b93ca4b..fd4e23b8258 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/DSBitStoreService.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/DSBitStoreService.java @@ -19,9 +19,11 @@ import java.util.List; import java.util.Map; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.content.Bitstream; import org.dspace.core.Utils; +import org.dspace.services.factory.DSpaceServicesFactory; /** * Native DSpace (or "Directory Scatter" if you prefer) asset store. @@ -252,7 +254,10 @@ protected File getFile(Bitstream bitstream) throws IOException { } File bitstreamFile = new File(bufFilename.toString()); Path normalizedPath = bitstreamFile.toPath().normalize(); - if (!normalizedPath.startsWith(baseDir.getAbsolutePath())) { + String[] allowedAssetstoreRoots = DSpaceServicesFactory.getInstance().getConfigurationService() + .getArrayProperty("assetstore.allowed.roots", new String[]{}); + if (!normalizedPath.startsWith(baseDir.getAbsolutePath()) + && !StringUtils.startsWithAny(normalizedPath.toString(), allowedAssetstoreRoots)) { log.error("Bitstream path outside of assetstore root requested:" + "bitstream={}, path={}, assetstore={}", bitstream.getID(), normalizedPath, baseDir.getAbsolutePath()); diff --git a/dspace/config/modules/assetstore.cfg b/dspace/config/modules/assetstore.cfg index cbee6bd2c3a..5e625e0c8a1 100644 --- a/dspace/config/modules/assetstore.cfg +++ b/dspace/config/modules/assetstore.cfg @@ -12,12 +12,15 @@ assetstore.dir = ${dspace.dir}/assetstore # This value will be used as `incoming` default store inside the `bitstore.xml` # Possible values are: # - 0: to use the `localStore`; -# - 1: to use the `s3Store`. +# - 1: to use the `s3Store`. # If you want to add additional assetstores, they must be added to that bitstore.xml # and new values should be provided as key-value pairs in the `stores` map of the -# `bitstore.xml` configuration. +# `bitstore.xml` configuration. assetstore.index.primary = 0 +#if the assetstore path is symbolic link, use this configuration to allow that path. +#assetstore.allowed.roots = /data/assetstore + #---------------------------------------------------------------# #-------------- Amazon S3 Specific Configurations --------------# #---------------------------------------------------------------# @@ -54,4 +57,4 @@ assetstore.s3.awsSecretKey = # If the credentials are left empty, # then this setting is ignored and the default AWS region will be used. -assetstore.s3.awsRegionName = \ No newline at end of file +assetstore.s3.awsRegionName = From 98e89caa3586cec75e9fb5ddfb0d3ef295c126a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 20:33:03 +0000 Subject: [PATCH 685/939] Bump the test-tools group with 6 updates Bumps the test-tools group with 6 updates: | Package | From | To | | --- | --- | --- | | [io.netty:netty-buffer](https://github.com/netty/netty) | `4.2.4.Final` | `4.2.6.Final` | | [io.netty:netty-transport](https://github.com/netty/netty) | `4.2.4.Final` | `4.2.6.Final` | | [io.netty:netty-transport-native-unix-common](https://github.com/netty/netty) | `4.2.4.Final` | `4.2.6.Final` | | [io.netty:netty-common](https://github.com/netty/netty) | `4.2.4.Final` | `4.2.6.Final` | | [io.netty:netty-handler](https://github.com/netty/netty) | `4.2.4.Final` | `4.2.6.Final` | | [io.netty:netty-codec](https://github.com/netty/netty) | `4.2.4.Final` | `4.2.6.Final` | Updates `io.netty:netty-buffer` from 4.2.4.Final to 4.2.6.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.4.Final...netty-4.2.6.Final) Updates `io.netty:netty-transport` from 4.2.4.Final to 4.2.6.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.4.Final...netty-4.2.6.Final) Updates `io.netty:netty-transport-native-unix-common` from 4.2.4.Final to 4.2.6.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.4.Final...netty-4.2.6.Final) Updates `io.netty:netty-common` from 4.2.4.Final to 4.2.6.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.4.Final...netty-4.2.6.Final) Updates `io.netty:netty-handler` from 4.2.4.Final to 4.2.6.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.4.Final...netty-4.2.6.Final) Updates `io.netty:netty-codec` from 4.2.4.Final to 4.2.6.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.4.Final...netty-4.2.6.Final) --- updated-dependencies: - dependency-name: io.netty:netty-buffer dependency-version: 4.2.6.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-transport dependency-version: 4.2.6.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-transport-native-unix-common dependency-version: 4.2.6.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-common dependency-version: 4.2.6.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-handler dependency-version: 4.2.6.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-codec dependency-version: 4.2.6.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index b8dbb4a6a7f..261a0e94b99 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -870,32 +870,32 @@ io.netty netty-buffer - 4.2.4.Final + 4.2.6.Final io.netty netty-transport - 4.2.4.Final + 4.2.6.Final io.netty netty-transport-native-unix-common - 4.2.4.Final + 4.2.6.Final io.netty netty-common - 4.2.4.Final + 4.2.6.Final io.netty netty-handler - 4.2.4.Final + 4.2.6.Final io.netty netty-codec - 4.2.4.Final + 4.2.6.Final org.apache.velocity From 127770219b401d36d5d9e2a67556a93578342bd9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 20:34:23 +0000 Subject: [PATCH 686/939] Bump com.github.spotbugs:spotbugs-maven-plugin from 4.9.4.0 to 4.9.4.2 Bumps [com.github.spotbugs:spotbugs-maven-plugin](https://github.com/spotbugs/spotbugs-maven-plugin) from 4.9.4.0 to 4.9.4.2. - [Release notes](https://github.com/spotbugs/spotbugs-maven-plugin/releases) - [Commits](https://github.com/spotbugs/spotbugs-maven-plugin/compare/spotbugs-maven-plugin-4.9.4.0...spotbugs-maven-plugin-4.9.4.2) --- updated-dependencies: - dependency-name: com.github.spotbugs:spotbugs-maven-plugin dependency-version: 4.9.4.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a2ed63f2df6..96dc80d20f9 100644 --- a/pom.xml +++ b/pom.xml @@ -295,7 +295,7 @@ com.github.spotbugs spotbugs-maven-plugin - 4.9.4.0 + 4.9.4.2 Max Low From 847904cadbf731cfbb6c8811680d6a5953f36a2e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 20:35:21 +0000 Subject: [PATCH 687/939] Bump com.amazonaws:aws-java-sdk-s3 from 1.12.788 to 1.12.791 Bumps [com.amazonaws:aws-java-sdk-s3](https://github.com/aws/aws-sdk-java) from 1.12.788 to 1.12.791. - [Changelog](https://github.com/aws/aws-sdk-java/blob/master/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-java/compare/1.12.788...1.12.791) --- updated-dependencies: - dependency-name: com.amazonaws:aws-java-sdk-s3 dependency-version: 1.12.791 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index b8dbb4a6a7f..1a1cc4466f9 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -738,7 +738,7 @@ com.amazonaws aws-java-sdk-s3 - 1.12.788 + 1.12.791 From 0834fb9515445b25ea71cdcf1496023d53296869 Mon Sep 17 00:00:00 2001 From: milanmajchrak Date: Tue, 9 Sep 2025 10:01:29 +0200 Subject: [PATCH 688/939] Set eperson attributes with fallback email from netid_email_domain when ldap email does not exist --- .../authenticate/LDAPAuthentication.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java index da6a7092481..6a616749d68 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java @@ -322,7 +322,7 @@ public int authenticate(Context context, log.info(LogHelper.getHeader(context, "type=ldap-login", "type=ldap_but_already_email")); context.turnOffAuthorisationSystem(); - setEpersonAttributes(context, eperson, ldap, Optional.of(netid)); + setEpersonAttributes(context, eperson, ldap, Optional.of(netid), email); ePersonService.update(context, eperson); context.dispatchEvents(); context.restoreAuthSystemState(); @@ -339,7 +339,7 @@ public int authenticate(Context context, try { context.turnOffAuthorisationSystem(); eperson = ePersonService.create(context); - setEpersonAttributes(context, eperson, ldap, Optional.of(netid)); + setEpersonAttributes(context, eperson, ldap, Optional.of(netid), email); eperson.setCanLogIn(true); authenticationService.initEPerson(context, request, eperson); ePersonService.update(context, eperson); @@ -381,11 +381,24 @@ public int authenticate(Context context, * Update eperson's attributes */ private void setEpersonAttributes(Context context, EPerson eperson, SpeakerToLDAP ldap, Optional netid) - throws SQLException { + throws SQLException { + setEpersonAttributes(context, eperson, ldap, netid, null); + } + /** + * Update eperson's attributes + */ + private void setEpersonAttributes(Context context, EPerson eperson, SpeakerToLDAP ldap, Optional netid, + String email) + throws SQLException { + + // Set email address: first try LDAP email, then fallback to provided email parameter if (StringUtils.isNotEmpty(ldap.ldapEmail)) { eperson.setEmail(ldap.ldapEmail); + } else if (StringUtils.isNotEmpty(email)) { + eperson.setEmail(email); } + if (StringUtils.isNotEmpty(ldap.ldapGivenName)) { eperson.setFirstName(context, ldap.ldapGivenName); } From cd62cac431bddc5c672e2ea036492455e08e2405 Mon Sep 17 00:00:00 2001 From: Miika Nurminen Date: Mon, 18 Aug 2025 11:06:34 +0300 Subject: [PATCH 689/939] DS-3796 Fix dates for Not to be processed checker-emailer report Based on 86de8c74a7812e9b57a5fd75bac161c4cc7985f0 (ResultTypeInDateRange dates have already been fixed in upstream). (cherry picked from commit fac888793c8c1aed3533d5b61bbb8a2697801892) --- .../dspace/checker/dao/impl/MostRecentChecksumDAOImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/checker/dao/impl/MostRecentChecksumDAOImpl.java b/dspace-api/src/main/java/org/dspace/checker/dao/impl/MostRecentChecksumDAOImpl.java index a31e02cbab4..6de60bd4fdb 100644 --- a/dspace-api/src/main/java/org/dspace/checker/dao/impl/MostRecentChecksumDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/checker/dao/impl/MostRecentChecksumDAOImpl.java @@ -56,8 +56,8 @@ public List findByNotProcessedInDateRange(Context context, D criteriaQuery.where(criteriaBuilder.and( criteriaBuilder.equal(mostRecentChecksumRoot.get(MostRecentChecksum_.toBeProcessed), false), criteriaBuilder - .lessThanOrEqualTo(mostRecentChecksumRoot.get(MostRecentChecksum_.processStartDate), startDate), - criteriaBuilder.greaterThan(mostRecentChecksumRoot.get(MostRecentChecksum_.processStartDate), endDate) + .lessThanOrEqualTo(mostRecentChecksumRoot.get(MostRecentChecksum_.processStartDate), endDate), + criteriaBuilder.greaterThan(mostRecentChecksumRoot.get(MostRecentChecksum_.processStartDate), startDate) ) ); List orderList = new LinkedList<>(); From 11ba34b1fd0581f7951db9956a0dc07b4e7c8541 Mon Sep 17 00:00:00 2001 From: Jesiel Viana Date: Fri, 5 Sep 2025 15:33:15 -0300 Subject: [PATCH 690/939] Add familyName and givenName when creating profile author and fix EPerson#getName (cherry picked from commit df0dd01776b6e9365b9a621cc093424317c53025) --- dspace-api/src/main/java/org/dspace/eperson/EPerson.java | 2 +- .../org/dspace/profile/ResearcherProfileServiceImpl.java | 2 ++ .../app/rest/ResearcherProfileRestRepositoryIT.java | 9 +++++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/eperson/EPerson.java b/dspace-api/src/main/java/org/dspace/eperson/EPerson.java index da83a1cafd3..03f546315b2 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/EPerson.java +++ b/dspace-api/src/main/java/org/dspace/eperson/EPerson.java @@ -373,7 +373,7 @@ public int getType() { @Override public String getName() { - return getEmail(); + return this.getFullName(); } String getDigestAlgorithm() { diff --git a/dspace-api/src/main/java/org/dspace/profile/ResearcherProfileServiceImpl.java b/dspace-api/src/main/java/org/dspace/profile/ResearcherProfileServiceImpl.java index 80bbd68fd19..cb298bc177f 100644 --- a/dspace-api/src/main/java/org/dspace/profile/ResearcherProfileServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/profile/ResearcherProfileServiceImpl.java @@ -283,6 +283,8 @@ private Item createProfileItem(Context context, EPerson ePerson, Collection coll itemService.addMetadata(context, item, "dc", "title", null, null, fullName); itemService.addMetadata(context, item, "person", "email", null, null, ePerson.getEmail()); itemService.addMetadata(context, item, "dspace", "object", "owner", null, fullName, id, CF_ACCEPTED); + itemService.addMetadata(context, item, "person", "familyName", null, null, ePerson.getLastName()); + itemService.addMetadata(context, item, "person", "givenName", null, null, ePerson.getFirstName()); item = installItemService.installItem(context, workspaceItem); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResearcherProfileRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResearcherProfileRestRepositoryIT.java index 0d46f4268ca..7e1b0055bbf 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResearcherProfileRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResearcherProfileRestRepositoryIT.java @@ -143,6 +143,7 @@ public void setUp() throws Exception { user = EPersonBuilder.createEPerson(context) .withEmail("user@example.com") + .withNameInMetadata("Example", "User") .withPassword(password) .build(); @@ -322,7 +323,7 @@ public void testFindByIdWithoutOwnerUser() throws Exception { public void testCreateAndReturn() throws Exception { String id = user.getID().toString(); - String name = user.getName(); + String name = user.getFullName(); String authToken = getAuthToken(user.getEmail(), password); @@ -341,6 +342,8 @@ public void testCreateAndReturn() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$.type", is("item"))) .andExpect(jsonPath("$.metadata", matchMetadata("dspace.object.owner", name, id, 0))) + .andExpect(jsonPath("$.metadata", matchMetadata("person.givenName", user.getFirstName(), 0))) + .andExpect(jsonPath("$.metadata", matchMetadata("person.familyName", user.getLastName(), 0))) .andExpect(jsonPath("$.metadata", matchMetadata("dspace.entity.type", "Person", 0))); getClient(authToken).perform(get("/api/eperson/profiles/{id}/eperson", id)) @@ -390,7 +393,7 @@ public void testCreateAndReturnWithPublicProfile() throws Exception { public void testCreateAndReturnWithAdmin() throws Exception { String id = user.getID().toString(); - String name = user.getName(); + String name = user.getFullName(); configurationService.setProperty("researcher-profile.collection.uuid", null); @@ -411,6 +414,8 @@ public void testCreateAndReturnWithAdmin() throws Exception { getClient(authToken).perform(get("/api/eperson/profiles/{id}/item", id)) .andExpect(status().isOk()) .andExpect(jsonPath("$.type", is("item"))) + .andExpect(jsonPath("$.metadata", matchMetadata("person.givenName", user.getFirstName(), 0))) + .andExpect(jsonPath("$.metadata", matchMetadata("person.familyName", user.getLastName(), 0))) .andExpect(jsonPath("$.metadata", matchMetadata("dspace.object.owner", name, id, 0))) .andExpect(jsonPath("$.metadata", matchMetadata("dspace.entity.type", "Person", 0))); From 0902db21eb48554c4e3e08e8233151938f032946 Mon Sep 17 00:00:00 2001 From: Nathan Buckingham Date: Fri, 20 Jun 2025 15:50:38 -0400 Subject: [PATCH 691/939] 132206: Saf Imports close connections (cherry picked from commit 35135c80aeabfaef0a5f5bf73ac510ef57a7408a) --- .../org/dspace/app/itemimport/ItemImport.java | 50 +++++++++++-------- .../dspace/app/itemimport/ItemImportCLI.java | 43 ++++++++++------ 2 files changed, 55 insertions(+), 38 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java index b32de11f7a7..33487bc8e35 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java @@ -22,6 +22,7 @@ import org.apache.commons.cli.ParseException; import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.tika.Tika; import org.dspace.app.itemimport.factory.ItemImportServiceFactory; @@ -333,33 +334,38 @@ protected void process(Context context, ItemImportService itemImportService, protected void readZip(Context context, ItemImportService itemImportService) throws Exception { Optional optionalFileStream = Optional.empty(); Optional validationFileStream = Optional.empty(); - if (!remoteUrl) { - // manage zip via upload - optionalFileStream = handler.getFileStream(context, zipfilename); - validationFileStream = handler.getFileStream(context, zipfilename); - } else { - // manage zip via remote url - optionalFileStream = Optional.ofNullable(new URL(zipfilename).openStream()); - validationFileStream = Optional.ofNullable(new URL(zipfilename).openStream()); - } + try { + if (!remoteUrl) { + // manage zip via upload + optionalFileStream = handler.getFileStream(context, zipfilename); + validationFileStream = handler.getFileStream(context, zipfilename); + } else { + // manage zip via remote url + optionalFileStream = Optional.ofNullable(new URL(zipfilename).openStream()); + validationFileStream = Optional.ofNullable(new URL(zipfilename).openStream()); + } - if (validationFileStream.isPresent()) { - // validate zip file if (validationFileStream.isPresent()) { - validateZip(validationFileStream.get()); + // validate zip file + if (validationFileStream.isPresent()) { + validateZip(validationFileStream.get()); + } + + workFile = new File(itemImportService.getTempWorkDir() + File.separator + + zipfilename + "-" + context.getCurrentUser().getID()); + FileUtils.copyInputStreamToFile(optionalFileStream.get(), workFile); + } else { + throw new IllegalArgumentException( + "Error reading file, the file couldn't be found for filename: " + zipfilename); } - workFile = new File(itemImportService.getTempWorkDir() + File.separator - + zipfilename + "-" + context.getCurrentUser().getID()); - FileUtils.copyInputStreamToFile(optionalFileStream.get(), workFile); - } else { - throw new IllegalArgumentException( - "Error reading file, the file couldn't be found for filename: " + zipfilename); + workDir = new File(itemImportService.getTempWorkDir() + File.separator + TEMP_DIR + + File.separator + context.getCurrentUser().getID()); + sourcedir = itemImportService.unzip(workFile, workDir.getAbsolutePath()); + } finally { + optionalFileStream.ifPresent(IOUtils::closeQuietly); + validationFileStream.ifPresent(IOUtils::closeQuietly); } - - workDir = new File(itemImportService.getTempWorkDir() + File.separator + TEMP_DIR - + File.separator + context.getCurrentUser().getID()); - sourcedir = itemImportService.unzip(workFile, workDir.getAbsolutePath()); } /** diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLI.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLI.java index 98d2469b715..bd29aa97fe4 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLI.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLI.java @@ -17,6 +17,7 @@ import java.util.UUID; import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.dspace.app.itemimport.service.ItemImportService; import org.dspace.content.Collection; @@ -111,7 +112,11 @@ protected void readZip(Context context, ItemImportService itemImportService) thr // validate zip file InputStream validationFileStream = new FileInputStream(myZipFile); - validateZip(validationFileStream); + try { + validateZip(validationFileStream); + } finally { + IOUtils.closeQuietly(validationFileStream); + } workDir = new File(itemImportService.getTempWorkDir() + File.separator + TEMP_DIR + File.separator + context.getCurrentUser().getID()); @@ -120,22 +125,28 @@ protected void readZip(Context context, ItemImportService itemImportService) thr } else { // manage zip via remote url Optional optionalFileStream = Optional.ofNullable(new URL(zipfilename).openStream()); - if (optionalFileStream.isPresent()) { - // validate zip file via url - Optional validationFileStream = Optional.ofNullable(new URL(zipfilename).openStream()); - if (validationFileStream.isPresent()) { - validateZip(validationFileStream.get()); + Optional validationFileStream = Optional.ofNullable(new URL(zipfilename).openStream()); + try { + if (optionalFileStream.isPresent()) { + // validate zip file via url + + if (validationFileStream.isPresent()) { + validateZip(validationFileStream.get()); + } + + workFile = new File(itemImportService.getTempWorkDir() + File.separator + + zipfilename + "-" + context.getCurrentUser().getID()); + FileUtils.copyInputStreamToFile(optionalFileStream.get(), workFile); + workDir = new File(itemImportService.getTempWorkDir() + File.separator + TEMP_DIR + + File.separator + context.getCurrentUser().getID()); + sourcedir = itemImportService.unzip(workFile, workDir.getAbsolutePath()); + } else { + throw new IllegalArgumentException( + "Error reading file, the file couldn't be found for filename: " + zipfilename); } - - workFile = new File(itemImportService.getTempWorkDir() + File.separator - + zipfilename + "-" + context.getCurrentUser().getID()); - FileUtils.copyInputStreamToFile(optionalFileStream.get(), workFile); - workDir = new File(itemImportService.getTempWorkDir() + File.separator + TEMP_DIR - + File.separator + context.getCurrentUser().getID()); - sourcedir = itemImportService.unzip(workFile, workDir.getAbsolutePath()); - } else { - throw new IllegalArgumentException( - "Error reading file, the file couldn't be found for filename: " + zipfilename); + } finally { + optionalFileStream.ifPresent(IOUtils::closeQuietly); + validationFileStream.ifPresent(IOUtils::closeQuietly); } } } From 467e42ca27bb4a5f158e175a9fd6e924327be77b Mon Sep 17 00:00:00 2001 From: Pierre Lasou Date: Wed, 3 Sep 2025 15:50:45 -0400 Subject: [PATCH 692/939] Add ORCID document type Addiing new mappings between ORCID document type and DSpace default type. (cherry picked from commit e213f5b305d44e3a392a759307861fb54c08afce) --- ...space-to-orcid-publication-type.properties | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/dspace/config/crosswalks/orcid/mapConverter-dspace-to-orcid-publication-type.properties b/dspace/config/crosswalks/orcid/mapConverter-dspace-to-orcid-publication-type.properties index 6eac57092a1..a45465b0839 100644 --- a/dspace/config/crosswalks/orcid/mapConverter-dspace-to-orcid-publication-type.properties +++ b/dspace/config/crosswalks/orcid/mapConverter-dspace-to-orcid-publication-type.properties @@ -4,21 +4,21 @@ Article = journal-article Book = book Book\ chapter = book-chapter Dataset = data-set -Learning\ Object = other -Image = other -Image,\ 3-D = other +Learning\ Object = learning-object +Image = image +Image,\ 3-D = image Journal = journal-issue -Map = other -Musical\ Score = other +Map = cartographic-material +Musical\ Score = musical-composition Plan\ or\ blueprint = other Preprint = preprint Presentation = other -Recording,\ acoustical = other -Recording,\ musical = other -Recording,\ oral = other +Recording,\ acoustical = sound +Recording,\ musical = sound +Recording,\ oral = sound Software = software -Technical\ Report = other -Thesis = other -Video = other +Technical\ Report = report +Thesis = dissertation-thesis +Video = moving-image Working\ Paper = working-paper Other = other From 4402847f38bb4d8bf9f0c6284913326e87e91eb2 Mon Sep 17 00:00:00 2001 From: abhinav Date: Wed, 19 Mar 2025 17:00:38 +0100 Subject: [PATCH 693/939] 129616: Added order by (cherry picked from commit cc6520bfbb7e23c8db3e75315905e920e806b87e) --- .../src/main/java/org/dspace/core/AbstractHibernateDAO.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDAO.java b/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDAO.java index 32ad747d765..2101f521854 100644 --- a/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDAO.java +++ b/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDAO.java @@ -458,6 +458,9 @@ public List findByX(Context context, Class clazz, Map equals, for (Map.Entry entry : equals.entrySet()) { criteria.where(criteriaBuilder.equal(root.get(entry.getKey()), entry.getValue())); } + + criteria.orderBy(criteriaBuilder.asc(root.get("id"))); + return executeCriteriaQuery(context, criteria, cacheable, maxResults, offset); } From fcde7489d35342098c67dd02e389eb9fb7fa44c8 Mon Sep 17 00:00:00 2001 From: abhinav Date: Thu, 3 Apr 2025 12:32:00 +0200 Subject: [PATCH 694/939] Added a test case to verify the sort (cherry picked from commit 989d93041786cd8e67b5631cfc3b884f4ae41220) --- .../org/dspace/content/BitstreamTest.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/dspace-api/src/test/java/org/dspace/content/BitstreamTest.java b/dspace-api/src/test/java/org/dspace/content/BitstreamTest.java index e85a0fc7b78..abf19f32821 100644 --- a/dspace-api/src/test/java/org/dspace/content/BitstreamTest.java +++ b/dspace-api/src/test/java/org/dspace/content/BitstreamTest.java @@ -12,6 +12,7 @@ import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -25,6 +26,9 @@ import java.io.FileInputStream; import java.io.IOException; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Iterator; import java.util.List; import java.util.UUID; @@ -148,6 +152,44 @@ public void testFindAll() throws SQLException { assertTrue("testFindAll 2", added); } + @Test + public void testFindAllBatches() throws Exception { + //Adding some data for processing and cleaning this up at the end + context.turnOffAuthorisationSystem(); + File f = new File(testProps.get("test.bitstream").toString()); + List inserted = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + Bitstream bs = bitstreamService.create(context, new FileInputStream(f)); + inserted.add(bs); + } + context.restoreAuthSystemState(); + + // sorted list of all bitstreams + List all = bitstreamService.findAll(context); + List expected = new ArrayList<>(all); + expected.sort(Comparator.comparing(bs -> bs.getID().toString())); + + int total = bitstreamService.countTotal(context); + int batchSize = 2; + int numberOfBatches = (int) Math.ceil((double) total / batchSize); + + //collect in batches + List collected = new ArrayList<>(); + for (int i = 0; i < numberOfBatches; i++) { + Iterator it = bitstreamService.findAll(context, batchSize, i * batchSize); + it.forEachRemaining(collected::add); + } + + assertEquals("Batched results should match sorted findAll", expected, collected); + + // Cleanup + context.turnOffAuthorisationSystem(); + for (Bitstream b : inserted) { + bitstreamService.delete(context, b); + } + context.restoreAuthSystemState(); + } + /** * Test of create method, of class Bitstream. */ From ee5093b6a698501a2e5f330425bf451592f4cd7b Mon Sep 17 00:00:00 2001 From: jm Date: Fri, 16 May 2025 15:56:55 +0200 Subject: [PATCH 695/939] Fixed https://github.com/DSpace/DSpace/issues/10754 by setting the context user to the current subscriber (cherry picked from commit 6275d3b756d4b1c634a7ef5f687778894380d1c3) --- .../SubscriptionEmailNotificationServiceImpl.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java index 8fb01cd36e9..c803f1407e0 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java @@ -67,6 +67,7 @@ public SubscriptionEmailNotificationServiceImpl(Map public void perform(Context context, DSpaceRunnableHandler handler, String subscriptionType, String frequency) { List communityItems = new ArrayList<>(); List collectionsItems = new ArrayList<>(); + EPerson currentEperson = context.getCurrentUser(); try { List subscriptions = findAllSubscriptionsBySubscriptionTypeAndFrequency(context, subscriptionType, frequency); @@ -77,7 +78,10 @@ public void perform(Context context, DSpaceRunnableHandler handler, String subsc for (Subscription subscription : subscriptions) { DSpaceObject dSpaceObject = subscription.getDSpaceObject(); EPerson ePerson = subscription.getEPerson(); - + // Set the current user to the subscribed eperson because the Solr query checks + // the permissions of the current user in the ANONYMOUS group. + // If there is no user (i.e., `current user = null`), it will send an email with no new items. + context.setCurrentUser(ePerson); if (!authorizeService.authorizeActionBoolean(context, ePerson, dSpaceObject, READ, true)) { iterator++; continue; @@ -126,6 +130,8 @@ public void perform(Context context, DSpaceRunnableHandler handler, String subsc handler.handleException(e); context.abort(); } + // Reset the current user because it was changed to subscriber eperson + context.setCurrentUser(currentEperson); } @SuppressWarnings("rawtypes") From d4c826d871f5bfec80d27f00be220e9a8a657556 Mon Sep 17 00:00:00 2001 From: JohnnyMendesC <177888064+JohnnyMendesC@users.noreply.github.com> Date: Fri, 5 Sep 2025 14:05:47 -0300 Subject: [PATCH 696/939] fix(#11033): Prevent logging of ignored bundles in Solr stats --- .../statistics/SolrLoggerServiceImpl.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java b/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java index 35f86e3a1c0..58a1d93b4f5 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java @@ -28,6 +28,7 @@ import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.EnumSet; @@ -232,6 +233,10 @@ public void postView(DSpaceObject dspaceObject, HttpServletRequest request, throw new RuntimeException(e); } + if (dspaceObject instanceof Bitstream && !isBitstreamLoggable((Bitstream) dspaceObject)) { + return; + } + if (solr == null) { return; } @@ -279,6 +284,10 @@ public void postView(DSpaceObject dspaceObject, @Override public void postView(DSpaceObject dspaceObject, String ip, String userAgent, String xforwardedfor, EPerson currentUser, String referrer) { + if (dspaceObject instanceof Bitstream && !isBitstreamLoggable((Bitstream) dspaceObject)) { + return; + } + if (solr == null) { return; } @@ -1713,4 +1722,35 @@ public Object anonymizeIp(String ip) throws UnknownHostException { throw new UnknownHostException("unknown ip format"); } + + /** + * Checks if a given Bitstream's bundles are configured to be logged in Solr statistics. + * + * @param bitstream The bitstream to check. + * @return {@code true} if the bitstream event should be logged, {@code false} otherwise. + */ + private boolean isBitstreamLoggable(Bitstream bitstream) { + String[] allowedBundles = configurationService + .getArrayProperty("solr-statistics.query.filter.bundles"); + if (allowedBundles == null || allowedBundles.length == 0) { + return true; + } + List allowedBundlesList = Arrays.asList(allowedBundles); + try { + List actualBundles = bitstream.getBundles(); + if (actualBundles.isEmpty()) { + return true; + } + for (Bundle bundle : actualBundles) { + if (allowedBundlesList.contains(bundle.getName())) { + return true; + } + } + } catch (SQLException e) { + log.error("Error checking bitstream bundles for logging statistics for bitstream {}", + bitstream.getID(), e); + return true; + } + return false; + } } From eeba9846217001e592c2f1a234ee83854485014b Mon Sep 17 00:00:00 2001 From: im-shivamb Date: Thu, 18 Sep 2025 19:19:35 +0530 Subject: [PATCH 697/939] Fix for bitstreams with old style embargo lift date in metadata not rendering on simple item pages. --- .../src/main/java/org/dspace/embargo/DefaultEmbargoSetter.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/embargo/DefaultEmbargoSetter.java b/dspace-api/src/main/java/org/dspace/embargo/DefaultEmbargoSetter.java index 7857a45eb8d..265ec213da6 100644 --- a/dspace-api/src/main/java/org/dspace/embargo/DefaultEmbargoSetter.java +++ b/dspace-api/src/main/java/org/dspace/embargo/DefaultEmbargoSetter.java @@ -94,7 +94,6 @@ public void setEmbargo(Context context, Item item) if (!(bnn.equals(Constants.LICENSE_BUNDLE_NAME) || bnn.equals(Constants.METADATA_BUNDLE_NAME) || bnn .equals(CreativeCommonsServiceImpl.CC_BUNDLE_NAME))) { //AuthorizeManager.removePoliciesActionFilter(context, bn, Constants.READ); - generatePolicies(context, liftDate.toDate(), null, bn, item.getOwningCollection()); for (Bitstream bs : bn.getBitstreams()) { //AuthorizeManager.removePoliciesActionFilter(context, bs, Constants.READ); generatePolicies(context, liftDate.toDate(), null, bs, item.getOwningCollection()); From cad7e9397bf8d77af1625d58692261c9dc29016a Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Tue, 23 Sep 2025 16:23:57 +0200 Subject: [PATCH 698/939] remove ratingreviewaction bean in workflow-actions.xml (cherry picked from commit 770ab0ca79c3125cd1063f654644328bc6f87ed9) --- dspace/config/spring/api/workflow-actions.xml | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/dspace/config/spring/api/workflow-actions.xml b/dspace/config/spring/api/workflow-actions.xml index d01f1b6b4c8..66d3a54e367 100644 --- a/dspace/config/spring/api/workflow-actions.xml +++ b/dspace/config/spring/api/workflow-actions.xml @@ -21,7 +21,6 @@ - @@ -44,7 +43,6 @@ - @@ -64,21 +62,14 @@ - - - - - - - + - - + From 7a95781068f13d59119f3545553ddc05c7e79ac6 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Tue, 23 Sep 2025 16:25:33 +0200 Subject: [PATCH 699/939] remove ratingreviewaction in test context (cherry picked from commit ddccb342d6aa2d43af67cf2d38dc9d2b25c98313) --- .../config/spring/api/workflow-actions.xml | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/workflow-actions.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/workflow-actions.xml index 0d074362279..a7c725c524f 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/workflow-actions.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/workflow-actions.xml @@ -23,7 +23,6 @@ - @@ -46,7 +45,6 @@ - @@ -66,21 +64,14 @@ - - - - - - - + - - + From de50420624d101b3cdbcc4c9aeb2b13b32d2b132 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 22 Sep 2025 16:57:20 -0500 Subject: [PATCH 700/939] Change to "mode=min" in order to minimize our cache size for Docker images --- .github/workflows/reusable-docker-build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml index 528f5779ca9..8165fe7f840 100644 --- a/.github/workflows/reusable-docker-build.yml +++ b/.github/workflows/reusable-docker-build.yml @@ -165,7 +165,7 @@ jobs: # Use GitHub cache to load cached Docker images and cache the results of this build # This decreases the number of images we need to fetch from DockerHub cache-from: type=gha,scope=${{ inputs.build_id }} - cache-to: type=gha,scope=${{ inputs.build_id }},mode=max + cache-to: type=gha,scope=${{ inputs.build_id }},mode=min # Export the digest of Docker build locally - name: Export Docker build digest @@ -217,7 +217,7 @@ jobs: # Use GitHub cache to load cached Docker images and cache the results of this build # This decreases the number of images we need to fetch from DockerHub cache-from: type=gha,scope=${{ inputs.build_id }} - cache-to: type=gha,scope=${{ inputs.build_id }},mode=max + cache-to: type=gha,scope=${{ inputs.build_id }},mode=min # Export image to a local TAR file outputs: type=docker,dest=/tmp/${{ inputs.build_id }}.tar From 23cd58280189ab86572491549562cc58de2a58b3 Mon Sep 17 00:00:00 2001 From: nwoodward Date: Fri, 19 Sep 2025 14:06:09 -0500 Subject: [PATCH 701/939] fix hibernate syntax bug (cherry picked from commit d9cbb9665569431596e980d583bb7c802b899a47) --- .../java/org/dspace/content/dao/impl/CollectionDAOImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java index befa1397a8e..e5d05abe5d6 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java @@ -159,7 +159,7 @@ public List findAuthorizedByGroup(Context context, EPerson ePerson, @Override public List findCollectionsWithSubscribers(Context context) throws SQLException { - return list(createQuery(context, "SELECT DISTINCT c FROM Collection c JOIN Subscription s ON c.id = " + + return list(createQuery(context, "SELECT DISTINCT c FROM Collection c JOIN Subscription s ON c = " + "s.dSpaceObject")); } From f8ac04b5911276d2c941625ff59928a99feac65b Mon Sep 17 00:00:00 2001 From: nwoodward Date: Fri, 19 Sep 2025 14:06:52 -0500 Subject: [PATCH 702/939] lint fixes (cherry picked from commit b8812aad458e41b7aeed78430c06dd4e6d282d23) --- .../src/main/java/org/dspace/health/UserCheck.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/health/UserCheck.java b/dspace-api/src/main/java/org/dspace/health/UserCheck.java index 19a2a9ced35..875bb1bb0b6 100644 --- a/dspace-api/src/main/java/org/dspace/health/UserCheck.java +++ b/dspace-api/src/main/java/org/dspace/health/UserCheck.java @@ -50,26 +50,26 @@ public String run(ReportInfo ri) { info.put("Self registered", 0); for (EPerson e : epersons) { - if (e.getEmail() != null && e.getEmail().length() > 0) { + if (e.getEmail() != null && !e.getEmail().isEmpty()) { info.put("Have email", info.get("Have email") + 1); } if (e.canLogIn()) { info.put("Can log in (password)", info.get("Can log in (password)") + 1); } - if (e.getFirstName() != null && e.getFirstName().length() > 0) { + if (e.getFirstName() != null && !e.getFirstName().isEmpty()) { info.put("Have 1st name", info.get("Have 1st name") + 1); } - if (e.getLastName() != null && e.getLastName().length() > 0) { + if (e.getLastName() != null && !e.getLastName().isEmpty()) { info.put("Have 2nd name", info.get("Have 2nd name") + 1); } - if (e.getLanguage() != null && e.getLanguage().length() > 0) { + if (e.getLanguage() != null && !e.getLanguage().isEmpty()) { info.put("Have lang", info.get("Have lang") + 1); } - if (e.getNetid() != null && e.getNetid().length() > 0) { + if (e.getNetid() != null && !e.getNetid().isEmpty()) { info.put("Have netid", info.get("Have netid") + 1); } - if (e.getNetid() != null && e.getNetid().length() > 0) { + if (e.getNetid() != null && !e.getNetid().isEmpty()) { info.put("Self registered", info.get("Self registered") + 1); } } From 0246588ab572bc4b9e1401fd2d79a3469b88b91b Mon Sep 17 00:00:00 2001 From: JohnnyMendesC <177888064+JohnnyMendesC@users.noreply.github.com> Date: Fri, 19 Sep 2025 12:50:37 -0300 Subject: [PATCH 703/939] Fix (#9694): Change Solr dynamic field *.year to *_year (cherry picked from commit 9fc163fbdacd99c78518a5372881df1427c30275) --- dspace/solr/search/conf/schema.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace/solr/search/conf/schema.xml b/dspace/solr/search/conf/schema.xml index 66f2f176d2d..5c79519159a 100644 --- a/dspace/solr/search/conf/schema.xml +++ b/dspace/solr/search/conf/schema.xml @@ -324,7 +324,7 @@ - + From ccd3d12ad8cc2feec9f8c1674ed576536c857fcd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Oct 2025 02:33:13 +0000 Subject: [PATCH 704/939] Bump the build-tools group with 6 updates Bumps the build-tools group with 6 updates: | Package | From | To | | --- | --- | --- | | [com.github.spotbugs:spotbugs](https://github.com/spotbugs/spotbugs) | `4.9.4` | `4.9.6` | | [org.apache.maven.plugins:maven-compiler-plugin](https://github.com/apache/maven-compiler-plugin) | `3.14.0` | `3.14.1` | | [com.github.spotbugs:spotbugs-maven-plugin](https://github.com/spotbugs/spotbugs-maven-plugin) | `4.9.4.2` | `4.9.6.0` | | [org.sonatype.central:central-publishing-maven-plugin](https://github.com/sonatype/central-publishing-maven-plugin) | `0.8.0` | `0.9.0` | | [org.apache.maven.plugins:maven-javadoc-plugin](https://github.com/apache/maven-javadoc-plugin) | `3.11.3` | `3.12.0` | | [org.codehaus.mojo:license-maven-plugin](https://github.com/mojohaus/license-maven-plugin) | `2.6.0` | `2.7.0` | Updates `com.github.spotbugs:spotbugs` from 4.9.4 to 4.9.6 - [Release notes](https://github.com/spotbugs/spotbugs/releases) - [Changelog](https://github.com/spotbugs/spotbugs/blob/master/CHANGELOG.md) - [Commits](https://github.com/spotbugs/spotbugs/compare/4.9.4...4.9.6) Updates `org.apache.maven.plugins:maven-compiler-plugin` from 3.14.0 to 3.14.1 - [Release notes](https://github.com/apache/maven-compiler-plugin/releases) - [Commits](https://github.com/apache/maven-compiler-plugin/compare/maven-compiler-plugin-3.14.0...maven-compiler-plugin-3.14.1) Updates `com.github.spotbugs:spotbugs-maven-plugin` from 4.9.4.2 to 4.9.6.0 - [Release notes](https://github.com/spotbugs/spotbugs-maven-plugin/releases) - [Commits](https://github.com/spotbugs/spotbugs-maven-plugin/compare/spotbugs-maven-plugin-4.9.4.2...spotbugs-maven-plugin-4.9.6.0) Updates `org.sonatype.central:central-publishing-maven-plugin` from 0.8.0 to 0.9.0 - [Commits](https://github.com/sonatype/central-publishing-maven-plugin/commits) Updates `org.apache.maven.plugins:maven-javadoc-plugin` from 3.11.3 to 3.12.0 - [Release notes](https://github.com/apache/maven-javadoc-plugin/releases) - [Commits](https://github.com/apache/maven-javadoc-plugin/compare/maven-javadoc-plugin-3.11.3...maven-javadoc-plugin-3.12.0) Updates `org.codehaus.mojo:license-maven-plugin` from 2.6.0 to 2.7.0 - [Release notes](https://github.com/mojohaus/license-maven-plugin/releases) - [Commits](https://github.com/mojohaus/license-maven-plugin/compare/2.6.0...2.7.0) --- updated-dependencies: - dependency-name: com.github.spotbugs:spotbugs dependency-version: 4.9.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: build-tools - dependency-name: org.apache.maven.plugins:maven-compiler-plugin dependency-version: 3.14.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: build-tools - dependency-name: com.github.spotbugs:spotbugs-maven-plugin dependency-version: 4.9.6.0 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: build-tools - dependency-name: org.sonatype.central:central-publishing-maven-plugin dependency-version: 0.9.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools - dependency-name: org.apache.maven.plugins:maven-javadoc-plugin dependency-version: 3.12.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools - dependency-name: org.codehaus.mojo:license-maven-plugin dependency-version: 2.7.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools ... Signed-off-by: dependabot[bot] --- pom.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index ae290671496..9f972b35caa 100644 --- a/pom.xml +++ b/pom.xml @@ -140,7 +140,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.14.0 + 3.14.1 11 @@ -295,7 +295,7 @@ com.github.spotbugs spotbugs-maven-plugin - 4.9.4.2 + 4.9.6.0 Max Low @@ -305,7 +305,7 @@ com.github.spotbugs spotbugs - 4.9.4 + 4.9.6 @@ -357,13 +357,13 @@ org.sonatype.central central-publishing-maven-plugin - 0.8.0 + 0.9.0 org.apache.maven.plugins maven-javadoc-plugin - 3.11.3 + 3.12.0 false @@ -680,7 +680,7 @@ org.codehaus.mojo license-maven-plugin - 2.6.0 + 2.7.0 false From 5bc41cb8ab5aa381e9f3fbb15c588a8e551332a9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Oct 2025 02:34:10 +0000 Subject: [PATCH 705/939] Bump the test-tools group with 2 updates Bumps the test-tools group with 2 updates: [com.h2database:h2](https://github.com/h2database/h2database) and [org.xmlunit:xmlunit-core](https://github.com/xmlunit/xmlunit). Updates `com.h2database:h2` from 2.3.232 to 2.4.240 - [Release notes](https://github.com/h2database/h2database/releases) - [Commits](https://github.com/h2database/h2database/compare/version-2.3.232...version-2.4.240) Updates `org.xmlunit:xmlunit-core` from 2.10.3 to 2.10.4 - [Release notes](https://github.com/xmlunit/xmlunit/releases) - [Changelog](https://github.com/xmlunit/xmlunit/blob/main/RELEASE_NOTES.md) - [Commits](https://github.com/xmlunit/xmlunit/compare/v2.10.3...v2.10.4) --- updated-dependencies: - dependency-name: com.h2database:h2 dependency-version: 2.4.240 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: test-tools - dependency-name: org.xmlunit:xmlunit-core dependency-version: 2.10.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index d073d26f46d..923dc2ba341 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -905,7 +905,7 @@ org.xmlunit xmlunit-core - 2.10.3 + 2.10.4 test diff --git a/pom.xml b/pom.xml index ae290671496..ca758cc9942 100644 --- a/pom.xml +++ b/pom.xml @@ -1684,7 +1684,7 @@ com.h2database h2 - 2.3.232 + 2.4.240 test From fa9df3a4ca1863b64e990ef791d30bb4780a175f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Oct 2025 02:34:22 +0000 Subject: [PATCH 706/939] Bump org.apache.commons:commons-lang3 in the apache-commons group Bumps the apache-commons group with 1 update: org.apache.commons:commons-lang3. Updates `org.apache.commons:commons-lang3` from 3.18.0 to 3.19.0 --- updated-dependencies: - dependency-name: org.apache.commons:commons-lang3 dependency-version: 3.19.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: apache-commons ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ae290671496..02567a35126 100644 --- a/pom.xml +++ b/pom.xml @@ -1503,7 +1503,7 @@ org.apache.commons commons-lang3 - 3.18.0 + 3.19.0 From cf0c599b854a31bc99423bad9d816803b59bb9f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Oct 2025 02:35:50 +0000 Subject: [PATCH 707/939] Bump org.postgresql:postgresql from 42.7.7 to 42.7.8 Bumps [org.postgresql:postgresql](https://github.com/pgjdbc/pgjdbc) from 42.7.7 to 42.7.8. - [Release notes](https://github.com/pgjdbc/pgjdbc/releases) - [Changelog](https://github.com/pgjdbc/pgjdbc/blob/master/CHANGELOG.md) - [Commits](https://github.com/pgjdbc/pgjdbc/compare/REL42.7.7...REL42.7.8) --- updated-dependencies: - dependency-name: org.postgresql:postgresql dependency-version: 42.7.8 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ae290671496..2418352ce0b 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ 5.7.14 5.6.15.Final 6.2.5.Final - 42.7.7 + 42.7.8 8.11.4 3.11.1 From 0953ef445635343327e1031d82c585d3dc83282f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Oct 2025 02:35:50 +0000 Subject: [PATCH 708/939] Bump bouncycastle.version from 1.81 to 1.82 Bumps `bouncycastle.version` from 1.81 to 1.82. Updates `org.bouncycastle:bcpkix-jdk18on` from 1.81 to 1.82 - [Changelog](https://github.com/bcgit/bc-java/blob/main/docs/releasenotes.html) - [Commits](https://github.com/bcgit/bc-java/commits) Updates `org.bouncycastle:bcprov-jdk18on` from 1.81 to 1.82 - [Changelog](https://github.com/bcgit/bc-java/blob/main/docs/releasenotes.html) - [Commits](https://github.com/bcgit/bc-java/commits) Updates `org.bouncycastle:bcutil-jdk18on` from 1.81 to 1.82 - [Changelog](https://github.com/bcgit/bc-java/blob/main/docs/releasenotes.html) - [Commits](https://github.com/bcgit/bc-java/commits) --- updated-dependencies: - dependency-name: org.bouncycastle:bcpkix-jdk18on dependency-version: '1.82' dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.bouncycastle:bcprov-jdk18on dependency-version: '1.82' dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.bouncycastle:bcutil-jdk18on dependency-version: '1.82' dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ae290671496..837f5ccba99 100644 --- a/pom.xml +++ b/pom.xml @@ -44,7 +44,7 @@ 1.7.36 2.9.4 - 1.81 + 1.82 From dd6b3b4a551d48d8c85f1788fc61348815d9879a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Oct 2025 02:36:10 +0000 Subject: [PATCH 709/939] Bump log4j.version from 2.25.1 to 2.25.2 Bumps `log4j.version` from 2.25.1 to 2.25.2. Updates `org.apache.logging.log4j:log4j-api` from 2.25.1 to 2.25.2 Updates `org.apache.logging.log4j:log4j-core` from 2.25.1 to 2.25.2 Updates `org.apache.logging.log4j:log4j-1.2-api` from 2.25.1 to 2.25.2 --- updated-dependencies: - dependency-name: org.apache.logging.log4j:log4j-api dependency-version: 2.25.2 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.logging.log4j:log4j-core dependency-version: 2.25.2 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.logging.log4j:log4j-1.2-api dependency-version: 2.25.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ae290671496..97567f2147f 100644 --- a/pom.xml +++ b/pom.xml @@ -38,7 +38,7 @@ 1.1.1 9.4.58.v20250814 - 2.25.1 + 2.25.2 2.0.34 1.19.0 1.7.36 From 832b641b404247752d6cfee8a1e4737e03a7aadd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Oct 2025 02:36:55 +0000 Subject: [PATCH 710/939] Bump org.scala-lang:scala-library from 2.13.16 to 2.13.17 Bumps [org.scala-lang:scala-library](https://github.com/scala/scala) from 2.13.16 to 2.13.17. - [Release notes](https://github.com/scala/scala/releases) - [Commits](https://github.com/scala/scala/compare/v2.13.16...v2.13.17) --- updated-dependencies: - dependency-name: org.scala-lang:scala-library dependency-version: 2.13.17 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index d073d26f46d..7183395d89e 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -926,7 +926,7 @@ org.scala-lang scala-library - 2.13.16 + 2.13.17 test From fc74a7ffdf344bc4a1a271cce3dc69dc474e4908 Mon Sep 17 00:00:00 2001 From: nwoodward Date: Wed, 1 Oct 2025 13:41:41 -0500 Subject: [PATCH 711/939] fix Hibernate bugs --- .../content/dao/impl/BitstreamDAOImpl.java | 2 +- .../content/dao/impl/CollectionDAOImpl.java | 25 ++++++++++++++++--- .../org/dspace/core/AbstractHibernateDAO.java | 10 ++++++++ 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/BitstreamDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/BitstreamDAOImpl.java index 276ea2b3430..27ef8fc65ae 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/BitstreamDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/BitstreamDAOImpl.java @@ -152,7 +152,7 @@ public int countDeleted(Context context) throws SQLException { @Override public int countWithNoPolicy(Context context) throws SQLException { Query query = createQuery(context, - "SELECT count(bit.id) from Bitstream bit where bit.deleted<>true and bit.id not in" + + "SELECT count(bit.id) from Bitstream bit where bit.deleted<>true and bit not in" + " (select res.dSpaceObject from ResourcePolicy res where res.resourceTypeId = " + ":typeId )"); query.setParameter("typeId", Constants.BITSTREAM); diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java index e5d05abe5d6..0f485a17338 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java @@ -12,6 +12,8 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.UUID; + import javax.persistence.Query; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; @@ -19,6 +21,7 @@ import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; +import org.apache.logging.log4j.Logger; import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.ResourcePolicy_; import org.dspace.content.Collection; @@ -40,6 +43,11 @@ * @author kevinvandevelde at atmire.com */ public class CollectionDAOImpl extends AbstractHibernateDSODAO implements CollectionDAO { + /** + * log4j logger + */ + private static Logger log = org.apache.logging.log4j.LogManager.getLogger(CollectionDAOImpl.class); + protected CollectionDAOImpl() { super(); } @@ -172,14 +180,25 @@ public int countRows(Context context) throws SQLException { @SuppressWarnings("unchecked") public List> getCollectionsWithBitstreamSizesTotal(Context context) throws SQLException { - String q = "select col as collection, sum(bit.sizeBytes) as totalBytes from Item i join i.collections col " + - "join i.bundles bun join bun.bitstreams bit group by col"; + String q = "select col.id, sum(bit.sizeBytes) as totalBytes from Item i join i.collections col " + + "join i.bundles bun join bun.bitstreams bit group by col.id"; Query query = createQuery(context, q); + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + List list = query.getResultList(); List> returnList = new ArrayList<>(list.size()); for (Object[] o : list) { - returnList.add(new AbstractMap.SimpleEntry<>((Collection) o[0], (Long) o[1])); + CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Collection.class); + Root collectionRoot = criteriaQuery.from(Collection.class); + criteriaQuery.select(collectionRoot).where(criteriaBuilder.equal(collectionRoot.get("id"), (UUID) o[0])); + Query collectionQuery = createQuery(context, criteriaQuery); + Collection collection = (Collection) collectionQuery.getSingleResult(); + if (collection != null) { + returnList.add(new AbstractMap.SimpleEntry<>(collection, (Long) o[1])); + } else { + log.warn("Unable to find Collection with UUID: {}", o[0]); + } } return returnList; } diff --git a/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDAO.java b/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDAO.java index 2101f521854..b410cd5a7fe 100644 --- a/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDAO.java +++ b/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDAO.java @@ -464,4 +464,14 @@ public List findByX(Context context, Class clazz, Map equals, return executeCriteriaQuery(context, criteria, cacheable, maxResults, offset); } + /** + * Create a Query object from a CriteriaQuery + * @param context current Context + * @param criteriaQuery CriteriaQuery built via CriteriaBuilder + * @return corresponding Query + * @throws SQLException if error occurs + */ + public Query createQuery(Context context, CriteriaQuery criteriaQuery) throws SQLException { + return this.getHibernateSession(context).createQuery(criteriaQuery); + } } From 29e13b77fc9f7bff159c76f61e40126a759cf43d Mon Sep 17 00:00:00 2001 From: nwoodward Date: Wed, 1 Oct 2025 13:46:40 -0500 Subject: [PATCH 712/939] checkstyle fix --- .../main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java index 0f485a17338..2dd2bad26a8 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java @@ -13,7 +13,6 @@ import java.util.List; import java.util.Map; import java.util.UUID; - import javax.persistence.Query; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; From d8fbe16ede592cffe840a389e58887df1282a4ed Mon Sep 17 00:00:00 2001 From: JohnnyMendesC <177888064+JohnnyMendesC@users.noreply.github.com> Date: Wed, 1 Oct 2025 17:24:51 -0300 Subject: [PATCH 713/939] fix(#10721): Sanitize non-characters during OAI indexing (#11139) * fix(#10721): Sanitize non-characters during OAI indexing * refactor: Use StringEscapeUtils as suggested in review * fix: Removed whitespace before the import that was causing error Maven Unit Test failed due to the whitespace before the import https://github.com/DSpace/DSpace/actions/runs/16891881837/job/47853392956?pr=11139#step:4:1959 * fix: Removed trailing whitespace that was causing error --- .../java/org/dspace/xoai/util/ItemUtils.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 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 8f8b7380307..af130ccb5bd 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 @@ -16,6 +16,7 @@ import com.lyncode.xoai.dataprovider.xml.xoai.Element; import com.lyncode.xoai.dataprovider.xml.xoai.Metadata; import com.lyncode.xoai.util.Base64Utils; +import org.apache.commons.text.StringEscapeUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.app.util.factory.UtilServiceFactory; @@ -159,6 +160,19 @@ private static Element createBundlesElement(Context context, Item item) throws S return bundles; } + /** + * Sanitizes a string to remove characters that are invalid + * in XML 1.0 using the Apache Commons Text library. + * @param value The string to sanitize. + * @return A sanitized string, or null if the input was null. + */ + private static String sanitize(String value) { + if (value == null) { + return null; + } + return StringEscapeUtils.escapeXml10(value); + } + private static Element createLicenseElement(Context context, Item item) throws SQLException, AuthorizeException, IOException { Element license = create("license"); @@ -232,7 +246,7 @@ private static void fillSchemaElement(Element schema, MetadataValue val) throws valueElem = language; } - valueElem.getField().add(createValue("value", val.getValue())); + valueElem.getField().add(createValue("value", sanitize(val.getValue()))); if (val.getAuthority() != null) { valueElem.getField().add(createValue("authority", val.getAuthority())); if (val.getConfidence() != Choices.CF_NOVALUE) { From b5501981ab09efc1a9788d0df7722cae1cf8d4fd Mon Sep 17 00:00:00 2001 From: nwoodward Date: Wed, 17 Sep 2025 11:46:05 -0500 Subject: [PATCH 714/939] added limit to number of typed links to bitstreams to display, falls back to Link Sets if limit is exceeded --- .../service/impl/LinksetServiceImpl.java | 35 ++++++++++- .../controller/LinksetRestControllerIT.java | 58 +++++++++++++++++++ dspace/config/modules/signposting.cfg | 7 ++- 3 files changed, 97 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/service/impl/LinksetServiceImpl.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/service/impl/LinksetServiceImpl.java index de555617355..8ce992d1e88 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/service/impl/LinksetServiceImpl.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/service/impl/LinksetServiceImpl.java @@ -18,6 +18,7 @@ import org.dspace.app.rest.security.BitstreamMetadataReadPermissionEvaluatorPlugin; import org.dspace.app.rest.signposting.model.LinksetNode; import org.dspace.app.rest.signposting.processor.bitstream.BitstreamSignpostingProcessor; +import org.dspace.app.rest.signposting.processor.item.ItemLinksetProcessor; import org.dspace.app.rest.signposting.processor.item.ItemSignpostingProcessor; import org.dspace.app.rest.signposting.processor.metadata.MetadataSignpostingProcessor; import org.dspace.app.rest.signposting.service.LinksetService; @@ -28,6 +29,7 @@ import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; import org.dspace.utils.DSpace; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -40,12 +42,18 @@ public class LinksetServiceImpl implements LinksetService { private static final Logger log = LogManager.getLogger(LinksetServiceImpl.class); + @Autowired + private ConfigurationService configurationService; + @Autowired protected ItemService itemService; @Autowired private BitstreamMetadataReadPermissionEvaluatorPlugin bitstreamMetadataReadPermissionEvaluatorPlugin; + @Autowired + ItemLinksetProcessor itemLinksetProcessor; + private final List bitstreamProcessors = new DSpace().getServiceManager() .getServicesByType(BitstreamSignpostingProcessor.class); @@ -74,10 +82,20 @@ public List createLinksetNodesForSingleLinkset( Context context, DSpaceObject object ) { + int itemBitstreamsLimit = configurationService.getIntProperty("signposting.item.bitstreams.limit", 10); + List linksetNodes = new ArrayList<>(); if (object.getType() == Constants.ITEM) { - for (ItemSignpostingProcessor processor : itemProcessors) { - processor.addLinkSetNodes(context, request, (Item) object, linksetNodes); + int itemBitstreamsCount = countItemBitstreams((Item) object); + + // Do not include individual bitstream typed links if their number exceeds + // the limit in the configuration. + if (itemBitstreamsCount < itemBitstreamsLimit) { + for (ItemSignpostingProcessor processor : itemProcessors) { + processor.addLinkSetNodes(context, request, (Item) object, linksetNodes); + } + } else { + itemLinksetProcessor.addLinkSetNodes(context, request, (Item) object, linksetNodes); } } else if (object.getType() == Constants.BITSTREAM) { for (BitstreamSignpostingProcessor processor : bitstreamProcessors) { @@ -151,4 +169,17 @@ private Iterator getItemBitstreams(Context context, Item item) { throw new RuntimeException(e); } } + + private int countItemBitstreams(Item item) { + try { + int countBitstreams = 0; + List bundles = itemService.getBundles(item, Constants.DEFAULT_BUNDLE_NAME); + for (Bundle bundle: bundles) { + countBitstreams += bundle.getBitstreams().size(); + } + return countBitstreams; + } catch (SQLException e) { + throw new RuntimeException(e); + } + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java index a65357f97bf..167fbeb9e3f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/signposting/controller/LinksetRestControllerIT.java @@ -8,6 +8,7 @@ package org.dspace.app.rest.signposting.controller; import static org.dspace.content.MetadataSchemaEnum.PERSON; +import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; @@ -18,7 +19,9 @@ import java.text.DateFormat; import java.text.MessageFormat; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Date; +import java.util.UUID; import org.apache.commons.codec.CharEncoding; import org.apache.commons.io.IOUtils; @@ -691,6 +694,61 @@ public void findTypedLinkForItemWithAuthor() throws Exception { "&& @.type == 'application/linkset+json')]").exists()); } + @Test + public void showTypedLinksMissingForItemWithMoreBitstreamsThanLimit() throws Exception { + String bitstreamContent = "ThisIsSomeDummyText"; + String bitstreamMimeType = "text/plain"; + + int itemBitstreamsLimit = configurationService.getIntProperty("signposting.item.bitstreams.limit", 10); + + context.turnOffAuthorisationSystem(); + Item item = ItemBuilder.createItem(context, collection) + .withTitle("Item Test") + .withMetadata("dc", "identifier", "doi", doi) + .build(); + + // Add more bitstreams than the configured limit + ArrayList bitstreamIDs = new ArrayList<>(); + for (int i = 0; i <= itemBitstreamsLimit; i++) { + Bitstream bitstream = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder.createBitstream(context, item, is) + .withName("Bitstream " + i) + .withDescription("description") + .withMimeType(bitstreamMimeType) + .build(); + + if (bitstream != null) { + bitstreamIDs.add(bitstream.getID()); + } + } + } + context.restoreAuthSystemState(); + + // Make sure the bitstreams were successfully added. + assertTrue("There was a problem ingesting bitstreams.", bitstreamIDs.size() > itemBitstreamsLimit); + + String url = configurationService.getProperty("dspace.ui.url"); + String signpostingUrl = configurationService.getProperty("signposting.path"); + + // There should be typed links to the Link Sets but no typed links to the Bitstreams in the response. + // We only need to check for one of the Bitstream UUIDs, since all of them should be absent. + UUID firstBitstreamId = bitstreamIDs.get(0); + getClient().perform(get("/signposting/links/" + item.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[?(@.href == '" + url + "/" + signpostingUrl + "/linksets/" + + item.getID().toString() + "' " + + "&& @.rel == 'linkset' " + + "&& @.type == 'application/linkset')]").exists()) + .andExpect(jsonPath("$[?(@.href == '" + url + "/" + signpostingUrl + "/linksets/" + + item.getID().toString() + "/json' " + + "&& @.rel == 'linkset' " + + "&& @.type == 'application/linkset+json')]").exists()) + .andExpect(jsonPath("$[?(@.href == '" + url + "/bitstreams/" + firstBitstreamId + "/download' " + + "&& @.rel == 'item' " + + "&& @.type == 'text/plain')]").doesNotExist());; + } + @Test public void findTypedLinkForBitstream() throws Exception { String bitstreamContent = "ThisIsSomeDummyText"; diff --git a/dspace/config/modules/signposting.cfg b/dspace/config/modules/signposting.cfg index fba80da4148..5f8d933a92b 100644 --- a/dspace/config/modules/signposting.cfg +++ b/dspace/config/modules/signposting.cfg @@ -32,4 +32,9 @@ signposting.enabled = true signposting.describedby.crosswalk-name = DataCite # Mime-type of response of handling of 'describedby' links. -signposting.describedby.mime-type = application/vnd.datacite.datacite+xml \ No newline at end of file +signposting.describedby.mime-type = application/vnd.datacite.datacite+xml + +# Limit to the number of an item's bitstreams to return as typed links. +# If there are more bitstreams than this limit then only the typed links to the Link Sets are added to the header. +# Defaults to 10 if the value is unspecified +# signposting.item.bitstreams.limit = 10 \ No newline at end of file From a30d65a72ed4db3b64bdbc3eea78808d08048fd5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 19:56:17 +0000 Subject: [PATCH 715/939] Bump the build-tools group with 2 updates Bumps the build-tools group with 2 updates: [org.apache.maven.plugins:maven-enforcer-plugin](https://github.com/apache/maven-enforcer) and [org.apache.maven.plugins:maven-dependency-plugin](https://github.com/apache/maven-dependency-plugin). Updates `org.apache.maven.plugins:maven-enforcer-plugin` from 3.6.1 to 3.6.2 - [Release notes](https://github.com/apache/maven-enforcer/releases) - [Commits](https://github.com/apache/maven-enforcer/compare/enforcer-3.6.1...enforcer-3.6.2) Updates `org.apache.maven.plugins:maven-dependency-plugin` from 3.8.1 to 3.9.0 - [Release notes](https://github.com/apache/maven-dependency-plugin/releases) - [Commits](https://github.com/apache/maven-dependency-plugin/compare/maven-dependency-plugin-3.8.1...maven-dependency-plugin-3.9.0) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-enforcer-plugin dependency-version: 3.6.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: build-tools - dependency-name: org.apache.maven.plugins:maven-dependency-plugin dependency-version: 3.9.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools ... Signed-off-by: dependabot[bot] --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index f5671523c55..a87b5586923 100644 --- a/pom.xml +++ b/pom.xml @@ -89,7 +89,7 @@ org.apache.maven.plugins maven-enforcer-plugin - 3.6.1 + 3.6.2 enforce-java @@ -340,7 +340,7 @@ org.apache.maven.plugins maven-dependency-plugin - 3.8.1 + 3.9.0 org.apache.maven.plugins From 7aa29edafd4cbbb578823813ea5fc5cdde08fc47 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 19:59:17 +0000 Subject: [PATCH 716/939] Bump pdfbox-version from 2.0.34 to 2.0.35 Bumps `pdfbox-version` from 2.0.34 to 2.0.35. Updates `org.apache.pdfbox:pdfbox` from 2.0.34 to 2.0.35 Updates `org.apache.pdfbox:fontbox` from 2.0.34 to 2.0.35 --- updated-dependencies: - dependency-name: org.apache.pdfbox:pdfbox dependency-version: 2.0.35 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.pdfbox:fontbox dependency-version: 2.0.35 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f5671523c55..015d23c85db 100644 --- a/pom.xml +++ b/pom.xml @@ -39,7 +39,7 @@ 9.4.58.v20250814 2.25.2 - 2.0.34 + 2.0.35 1.19.0 1.7.36 2.9.4 From 2a4ac311c2fe93f35f84165a08ba1fc242b5dbaa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 20:00:09 +0000 Subject: [PATCH 717/939] Bump com.amazonaws:aws-java-sdk-s3 from 1.12.791 to 1.12.792 Bumps [com.amazonaws:aws-java-sdk-s3](https://github.com/aws/aws-sdk-java) from 1.12.791 to 1.12.792. - [Changelog](https://github.com/aws/aws-sdk-java/blob/master/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-java/compare/1.12.791...1.12.792) --- updated-dependencies: - dependency-name: com.amazonaws:aws-java-sdk-s3 dependency-version: 1.12.792 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 923dc2ba341..2e9d6df1dfe 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -738,7 +738,7 @@ com.amazonaws aws-java-sdk-s3 - 1.12.791 + 1.12.792 From e6a818fe7b085e07fd6462ce564ddba65c774e57 Mon Sep 17 00:00:00 2001 From: Andreas Czerniak Date: Thu, 9 Oct 2025 16:41:28 +0200 Subject: [PATCH 718/939] =?UTF-8?q?Terminology=20update:=20=E2=80=98OpenAI?= =?UTF-8?q?RE=20Guidelines=20for=20Literature=20Repository=20Managers=20v4?= =?UTF-8?q?=E2=80=99=20=E2=86=92=20=E2=80=98OpenAIRE=20Guidelines=20for=20?= =?UTF-8?q?Institutional=20and=20Thematic=20Repository=20Managers=20v4?= =?UTF-8?q?=E2=80=99.=20Descriptions=20and=20links=20have=20been=20aligned?= =?UTF-8?q?=20with=20the=20current=20standard.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oai/metadataFormats/oai_openaire.xsl | 70 +++++++++---------- .../crosswalks/oai/transformers/openaire4.xsl | 6 +- dspace/config/crosswalks/oai/xoai.xml | 6 +- .../entities/openaire4-relationships.xml | 12 ++-- dspace/config/registries/openaire4-types.xml | 10 +-- dspace/config/submission-forms.xml | 2 +- 6 files changed, 53 insertions(+), 53 deletions(-) diff --git a/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl b/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl index 905187494dd..2b388ceac7c 100644 --- a/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl +++ b/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl @@ -101,7 +101,7 @@ - + @@ -137,7 +137,7 @@ - + @@ -206,7 +206,7 @@ - + @@ -406,7 +406,7 @@ - + @@ -482,7 +482,7 @@ - + @@ -592,7 +592,7 @@ - + @@ -611,7 +611,7 @@ - + @@ -633,7 +633,7 @@ - + @@ -663,7 +663,7 @@ - + @@ -697,7 +697,7 @@ - + @@ -717,8 +717,8 @@ - - + + @@ -729,7 +729,7 @@ - + @@ -739,7 +739,7 @@ @@ -772,7 +772,7 @@ - + @@ -784,7 +784,7 @@ - + @@ -795,7 +795,7 @@ - + @@ -820,7 +820,7 @@ - + @@ -833,7 +833,7 @@ - + @@ -849,7 +849,7 @@ - + @@ -868,7 +868,7 @@ - + @@ -913,7 +913,7 @@ - + @@ -922,7 +922,7 @@ - + @@ -931,7 +931,7 @@ - + @@ -940,7 +940,7 @@ - + @@ -949,7 +949,7 @@ - + @@ -958,7 +958,7 @@ - + @@ -967,7 +967,7 @@ - + @@ -977,7 +977,7 @@ - + @@ -1039,7 +1039,7 @@ - + @@ -1051,7 +1051,7 @@ - + Available @@ -1393,7 +1393,7 @@ @@ -1446,7 +1446,7 @@ This template will return the COAR Resource Type Vocabulary URI like http://purl.org/coar/resource_type/c_6501 based on a valued text like 'article' - https://openaire-guidelines-for-literature-repository-managers.readthedocs.io/en/v4.0.0/field_publicationtype.html#attribute-uri-m + https://openaire-guidelines-for-literature-repository-managers.readthedocs.io/en/4.0.1/field_publicationtype.html#attribute-uri-m --> @@ -1642,7 +1642,7 @@ like "open access" based on the values from DSpace Access Status mechanism like String 'open.access' please check class org.dspace.access.status.DefaultAccessStatusHelper for more information - https://openaire-guidelines-for-literature-repository-managers.readthedocs.io/en/v4.0.0/field_accessrights.html#definition-and-usage-instruction + https://openaire-guidelines-for-literature-repository-managers.readthedocs.io/en/4.0.1/field_accessrights.html#definition-and-usage-instruction --> @@ -1672,7 +1672,7 @@ This template will return the COAR Access Right Vocabulary URI like http://purl.org/coar/access_right/c_abf2 based on a value text like 'open access' - https://openaire-guidelines-for-literature-repository-managers.readthedocs.io/en/v4.0.0/field_accessrights.html#definition-and-usage-instruction + https://openaire-guidelines-for-literature-repository-managers.readthedocs.io/en/4.0.1/field_accessrights.html#definition-and-usage-instruction --> diff --git a/dspace/config/crosswalks/oai/transformers/openaire4.xsl b/dspace/config/crosswalks/oai/transformers/openaire4.xsl index cece890450b..e9be2d54edb 100644 --- a/dspace/config/crosswalks/oai/transformers/openaire4.xsl +++ b/dspace/config/crosswalks/oai/transformers/openaire4.xsl @@ -13,7 +13,7 @@ @@ -80,7 +80,7 @@ Normalizing dc.rights according to COAR Controlled Vocabulary for Access Rights (Version 1.0) (http://vocabularies.coar-repositories.org/documentation/access_rights/) available at - https://openaire-guidelines-for-literature-repository-managers.readthedocs.io/en/v4.0.0/field_accessrights.html#definition-and-usage-instruction + https://openaire-guidelines-for-literature-repository-managers.readthedocs.io/en/4.0.1/field_accessrights.html#definition-and-usage-instruction --> @@ -116,7 +116,7 @@ diff --git a/dspace/config/crosswalks/oai/xoai.xml b/dspace/config/crosswalks/oai/xoai.xml index e843814dd7b..8ecaeca84ee 100644 --- a/dspace/config/crosswalks/oai/xoai.xml +++ b/dspace/config/crosswalks/oai/xoai.xml @@ -73,7 +73,7 @@ - This contexts complies with OpenAIRE Guidelines for Literature Repositories v4.0. + This contexts complies with OpenAIRE Guidelines for Institutional and Thematic Repository Managers v4.0. @@ -177,7 +177,7 @@ http://irdb.nii.ac.jp/oai/junii2-3-1.xsd oai_openaire diff --git a/dspace/config/entities/openaire4-relationships.xml b/dspace/config/entities/openaire4-relationships.xml index daa0e2c1da9..d77371b603a 100644 --- a/dspace/config/entities/openaire4-relationships.xml +++ b/dspace/config/entities/openaire4-relationships.xml @@ -3,7 +3,7 @@ - Publication @@ -17,7 +17,7 @@ 0 - Publication @@ -31,7 +31,7 @@ 0 - Publication @@ -45,7 +45,7 @@ 0 - Publication @@ -59,7 +59,7 @@ 0 - Publication @@ -73,7 +73,7 @@ 0 - Project diff --git a/dspace/config/registries/openaire4-types.xml b/dspace/config/registries/openaire4-types.xml index b3290ac1203..749b13fa315 100644 --- a/dspace/config/registries/openaire4-types.xml +++ b/dspace/config/registries/openaire4-types.xml @@ -2,13 +2,13 @@ - OpenAIRE4 fields definition + OpenAIRE v4 fields definition @@ -108,7 +108,7 @@ datacite @@ -116,7 +116,7 @@ Spatial region or named place where the data was gathered or about which the data is focused. - + datacite subject diff --git a/dspace/config/submission-forms.xml b/dspace/config/submission-forms.xml index bdee08e7534..ff4af24fb3e 100644 --- a/dspace/config/submission-forms.xml +++ b/dspace/config/submission-forms.xml @@ -1557,7 +1557,7 @@ From c17d0a6e2a389ad5468e508191e8cbef1c1f823f Mon Sep 17 00:00:00 2001 From: Joran De Braekeleer Date: Mon, 8 Sep 2025 12:42:39 +0200 Subject: [PATCH 719/939] 133566: Add group-eperson logging (cherry picked from commit b6fefd46aabeb173694fe2e050e4b39abb131961) --- .../main/java/org/dspace/eperson/GroupServiceImpl.java | 8 ++++++++ dspace/config/dstat.map | 6 +++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java index 4cec4c9c0d9..44727d3e5fb 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java @@ -142,6 +142,8 @@ public void addMember(Context context, Group group, EPerson e) { context.addEvent( new Event(Event.ADD, Constants.GROUP, group.getID(), Constants.EPERSON, e.getID(), e.getEmail(), getIdentifiers(context, group))); + log.info(LogHelper.getHeader(context, "add_group_eperson", + "group_id=" + group.getID() + ", eperson_id=" + e.getID())); } @Override @@ -157,6 +159,8 @@ public void addMember(Context context, Group groupParent, Group groupChild) thro context.addEvent(new Event(Event.ADD, Constants.GROUP, groupParent.getID(), Constants.GROUP, groupChild.getID(), groupChild.getName(), getIdentifiers(context, groupParent))); + log.info(LogHelper.getHeader(context, "add_group_subgroup", + "group_id=" + groupParent.getID() + ", subgroup_id=" + groupChild.getID())); } /** @@ -214,6 +218,8 @@ public void removeMember(Context context, Group group, EPerson ePerson) throws S if (group.remove(ePerson)) { context.addEvent(new Event(Event.REMOVE, Constants.GROUP, group.getID(), Constants.EPERSON, ePerson.getID(), ePerson.getEmail(), getIdentifiers(context, group))); + log.info(LogHelper.getHeader(context, "remove_group_eperson", + "group_id=" + group.getID() + ", eperson_id=" + ePerson.getID())); } } @@ -242,6 +248,8 @@ public void removeMember(Context context, Group groupParent, Group childGroup) t context.addEvent( new Event(Event.REMOVE, Constants.GROUP, groupParent.getID(), Constants.GROUP, childGroup.getID(), childGroup.getName(), getIdentifiers(context, groupParent))); + log.info(LogHelper.getHeader(context, "remove_group_subgroup", + "group_id=" + groupParent.getID() + ", subgroup_id=" + childGroup.getID())); } } diff --git a/dspace/config/dstat.map b/dspace/config/dstat.map index 140049ee13a..bfd08ede3c5 100644 --- a/dspace/config/dstat.map +++ b/dspace/config/dstat.map @@ -100,4 +100,8 @@ show_feedback_form=Feedback Form Displayed create_dc_type=New Dublin Core Type Created remove_template_item=Item Template Removed withdraw_item=Item Withdrawn -download_export_archive = Download Export Archive \ No newline at end of file +download_export_archive = Download Export Archive +add_group_eperson = EPerson Added to Group +remove_group_eperson = EPerson Removed from Group +add_group_subgroup = Child Group Added to Group +remove_group_subgroup = Child Group Removed from Group \ No newline at end of file From 50e4795b9d63c7a63bfea25e430e86de41604f48 Mon Sep 17 00:00:00 2001 From: fribeiro-fccn <65163266+fribeiro-fccn@users.noreply.github.com> Date: Tue, 21 Oct 2025 20:29:08 +0100 Subject: [PATCH 720/939] Fix 10740: Add more efficient Collection.findAuthorized() to improve performance of SWORDv2 servicedocument (#11333) * Changed Authentication for a user in Sword - Added new methods to handle the queries to get collections of the current user only * Changed to handle the community request if get communities is enabled in config * Added javadoc * Correct stylecheck * Corrected comments and changed AND to OR as the action_id should be an OR * Removed trailing space * Changed visibilty to getParentGroups to public and changed the DAO method to use Hibernate and bypass the WITH RECURSIVE sql query and the need the use o NativeQuery * Added Constants.ADMIN * Removed unused code * Refactor of DAO method to use CritriaBuilder * Changed return method * Changed findByEPersonGroupTypeIdAction it returns a List and maxim results was set to 1. Changed to -1 to get the multiple results * Changed the Service to get all collections when a user is admin of a Community * Minor Update to sword authenticator * Added some more tests --- .../dao/impl/ResourcePolicyDAOImpl.java | 2 +- .../dspace/content/CollectionServiceImpl.java | 84 +++++++++ .../org/dspace/content/dao/CollectionDAO.java | 12 ++ .../content/dao/impl/CollectionDAOImpl.java | 100 +++++++++- .../content/service/CollectionService.java | 12 ++ .../main/java/org/dspace/eperson/Group.java | 2 +- .../org/dspace/content/CollectionTest.java | 178 ++++++++++++++++++ .../org/dspace/sword2/SwordAuthenticator.java | 41 ++-- 8 files changed, 409 insertions(+), 22 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/authorize/dao/impl/ResourcePolicyDAOImpl.java b/dspace-api/src/main/java/org/dspace/authorize/dao/impl/ResourcePolicyDAOImpl.java index 26b6bb1d734..96dd08e1ae9 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/dao/impl/ResourcePolicyDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/dao/impl/ResourcePolicyDAOImpl.java @@ -165,7 +165,7 @@ public List findByEPersonGroupTypeIdAction(Context context, EPer (resourcePolicyRoot.get(ResourcePolicy_.epersonGroup).in(groups))) ) ); - return list(context, criteriaQuery, false, ResourcePolicy.class, 1, -1); + return list(context, criteriaQuery, false, ResourcePolicy.class, -1, -1); } @Override diff --git a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java index 1b051ae32ff..60cf769ae24 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -13,13 +13,17 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.MissingResourceException; import java.util.Objects; +import java.util.Queue; import java.util.Set; import java.util.UUID; +import java.util.stream.Collectors; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; @@ -838,6 +842,86 @@ public List findAuthorized(Context context, Community community, int return myResults; } + @Override + public List findAuthorized(Context context, Community community, List actions) + throws SQLException { + + List myCollections = new ArrayList<>(); + EPerson eperson = context.getCurrentUser(); + + //If eperson is Administrator return all colls or if a community is not null only the community's collections + if (authorizeService.isAdmin(context, eperson)) { + if (community != null) { + return community.getCollections(); + } + myCollections = this.findAll(context); + return myCollections; + } + + //Get the collections of the eperson where is is admin of a community + List directGroups = new ArrayList<>(eperson.getGroups()); // direct membership + Queue queue = new LinkedList<>(directGroups); + while (!queue.isEmpty()) { + Group current = queue.poll(); + List parents = current.getParentGroups(); + + for (Group parent : parents) { + if (directGroups.add(parent)) { + queue.add(parent); + } + } + } + + List resourcePolicies = resourcePolicyService + .find(context, eperson, directGroups, Constants.ADMIN, Constants.COMMUNITY); + List uuids = resourcePolicies.stream() + .map(policy -> policy.getdSpaceObject().getID()) + .toList(); + + List communities = uuids.stream() + .map(uuid -> { + try { + return communityService.find(context, uuid); + } catch (SQLException e) { + return null; //ignore that uuid + } + }) + .filter(Objects::nonNull) + .toList(); + + Set allCommunities = new HashSet<>(communities); + Set allCommAdminCollections = communities.stream() + .flatMap(cm -> cm.getCollections().stream()) + .collect(Collectors.toSet()); + Queue queueComm = new LinkedList<>(communities); + + while (!queueComm.isEmpty()) { + Community com = queueComm.poll(); + List childrenComms = com.getSubcommunities(); + for (Community childComm : childrenComms) { + if (allCommunities.add(childComm)) { + queueComm.add(childComm); + allCommAdminCollections.addAll(childComm.getCollections()); + } + } + } + + //Now get the collection when the eperson can deposit or is admin or is in a group with those privileges + myCollections = collectionDAO.findAuthorizedByEPerson(context, eperson, actions); + Set allCollections = new HashSet<>(myCollections); + //Join EPerson Community Admin Collections with Collection Admins + allCollections.addAll(allCommAdminCollections); + + List collsAllowed = new ArrayList<>(allCollections); + + //A community is passed, only the community's collections will be used and existing in eperson Authorizations + if (community != null) { + collsAllowed.retainAll(community.getCollections()); + } + + return collsAllowed; + } + @Override public Collection findByGroup(Context context, Group group) throws SQLException { return collectionDAO.findByGroup(context, group); diff --git a/dspace-api/src/main/java/org/dspace/content/dao/CollectionDAO.java b/dspace-api/src/main/java/org/dspace/content/dao/CollectionDAO.java index 6bb65bbb46d..13bcf5f52c0 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/CollectionDAO.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/CollectionDAO.java @@ -48,6 +48,18 @@ public List findAll(Context context, MetadataField order, Integer li List findAuthorizedByGroup(Context context, EPerson ePerson, List actions) throws SQLException; + /** + * Get all authorized collections of the current EPerson + * + * @param context DSpace context object + * @param ePerson the current EPerson + * @param actions list of actionsID ADD, READ, etc. + * @return the collections the eperson is defined + * @throws SQLException if database error + */ + List findAuthorizedByEPerson(Context context, EPerson ePerson, List actions) + throws SQLException; + List findCollectionsWithSubscribers(Context context) throws SQLException; int countRows(Context context) throws SQLException; diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java index 2dd2bad26a8..e54542eda2d 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java @@ -10,8 +10,12 @@ import java.sql.SQLException; import java.util.AbstractMap; import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Queue; +import java.util.Set; import java.util.UUID; import javax.persistence.Query; import javax.persistence.criteria.CriteriaBuilder; @@ -164,6 +168,100 @@ public List findAuthorizedByGroup(Context context, EPerson ePerson, } + /** + * Get all authorized collections of the current EPerson + * + * @param context DSpace context object + * @param ePerson the current EPerson + * @param actions list of actionsID ADD, READ, etc. + * @return the collections the eperson is defined + * @throws SQLException if database error + */ + @Override + public List findAuthorizedByEPerson(Context context, EPerson ePerson, List actions) + throws SQLException { + + //NOTE steps 1) and 2) removes the need of WITH RECURSIVE and a NativeQuery + + // 1) Get all groups a eperson belongs + /*ArrayList<>(ePerson.getGroups()) - This ensures you have a concrete copy and can modify it safely. + instead if List directGroups = ePerson.getGroups(); + Also - Can be done using this query: + List directGroups = createQuery(context, """ + SELECT g + FROM Group g + JOIN g.epeople e + WHERE e.id = :epersonId + """) + .setParameter("epersonId", ePerson.getID()) + .getResultList(); + */ + List directGroups = new ArrayList<>(ePerson.getGroups()); // direct membership + + // 2) Expand hierarquy of groups in memory (recursively) + Set allGroups = new HashSet<>(directGroups); + Queue queue = new LinkedList<>(directGroups); + + /* + * Using the query avoids the change of the getParentGroups visibility in Group + * The List parents = current.getParentGroups() could be achieved using: + * List parents = createQuery(context,""" + SELECT g + FROM Group g + JOIN g.groups child + WHERE child = :child + """) + */ + // //current.getMemberGroups()- Making public getParentGroups in Group Class (why it isn't already public?) + while (!queue.isEmpty()) { + Group current = queue.poll(); + List parents = current.getParentGroups(); + + for (Group parent : parents) { + if (allGroups.add(parent)) { + queue.add(parent); + } + } + } + + CriteriaBuilder cb = getCriteriaBuilder(context); + CriteriaQuery cq = getCriteriaQuery(cb, Collection.class); + Root collectionRoot = cq.from(Collection.class); + + // Join to ResourcePolicy using metamodel + Join rpJoin = collectionRoot.join("resourcePolicies"); + // Use metamodel for typesafe access + cq.select(collectionRoot).distinct(true); + + List predicates = new ArrayList<>(actions.size()); + // WHERE rp.resourceTypeId = :resourceType + predicates.add(cb.equal(rpJoin.get(ResourcePolicy_.resourceTypeId), Constants.COLLECTION)); + // AND (:hasActions = false OR rp.actionId IN :actionIds) + if (actions != null && !actions.isEmpty()) { + predicates.add(rpJoin.get(ResourcePolicy_.actionId).in(actions)); + } + + // AND (rp.eperson.id = :epersonId OR (:hasGroups = true AND rp.epersonGroup.id IN :groupIds)) + Predicate epersonPredicate = cb.equal( + rpJoin.get(ResourcePolicy_.eperson), ePerson + ); + // Using only groups instead of groupsIDs + Predicate groupPredicate = cb.disjunction(); // false by default + if (allGroups != null && !allGroups.isEmpty()) { + groupPredicate = rpJoin.get(ResourcePolicy_.epersonGroup).in(allGroups); + } + + // Combine access condition + Predicate accessPredicate = cb.or(epersonPredicate, groupPredicate); + predicates.add(accessPredicate); + + // Apply WHERE clause + cq.where(cb.and(predicates.toArray(new Predicate[0]))); + + // Execute + return list(context, cq, true, Collection.class, -1, -1); + } + @Override public List findCollectionsWithSubscribers(Context context) throws SQLException { return list(createQuery(context, "SELECT DISTINCT c FROM Collection c JOIN Subscription s ON c = " + @@ -201,4 +299,4 @@ public List> getCollectionsWithBitstreamSizesTotal(C } return returnList; } -} \ No newline at end of file +} diff --git a/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java b/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java index fd681f68f01..5e55c725691 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java @@ -327,6 +327,18 @@ public void canEdit(Context context, Collection collection, boolean useInheritan public List findAuthorized(Context context, Community community, int actionID) throws java.sql.SQLException; + /** + * return an array of collections that user has a given permission on + * + * @param context DSpace Context + * @param community (optional) restrict search to a community, else null + * @param actions Listo of the of the action ADD, READ, ADMIN, etc. + * @return Collection [] of collections with matching permissions + * @throws SQLException if database error + */ + public List findAuthorized(Context context, Community community, List actions) + throws java.sql.SQLException; + /** * * @param context DSpace Context diff --git a/dspace-api/src/main/java/org/dspace/eperson/Group.java b/dspace-api/src/main/java/org/dspace/eperson/Group.java index 67655e0e0aa..af7d14661d2 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/Group.java +++ b/dspace-api/src/main/java/org/dspace/eperson/Group.java @@ -142,7 +142,7 @@ boolean contains(EPerson e) { return getMembers().contains(e); } - List getParentGroups() { + public List getParentGroups() { return parentGroups; } diff --git a/dspace-api/src/test/java/org/dspace/content/CollectionTest.java b/dspace-api/src/test/java/org/dspace/content/CollectionTest.java index 13d037abf82..f2604328106 100644 --- a/dspace-api/src/test/java/org/dspace/content/CollectionTest.java +++ b/dspace-api/src/test/java/org/dspace/content/CollectionTest.java @@ -1159,6 +1159,184 @@ public void testFindAuthorizedOptimized() throws Exception { assertFalse("testFindAuthorizeOptimized D.C", personDCollections.contains(collectionC)); } + /** + * Test of findAuthorizedEpersonAndGroups method, of class Collection. + * We create some collections and a user and groups and subgroups and add the user to one subgroup + * and one collection + * The parent group will be added to the other collection. + */ + @Test + public void testFindAuthorizedByEPerson() throws Exception { + context.turnOffAuthorisationSystem(); + Community com = communityService.create(null, context); + Collection collectionA = collectionService.create(context, com); + Collection collectionB = collectionService.create(context, com); + Collection collectionC = collectionService.create(context, com); + + com.addCollection(collectionA); + com.addCollection(collectionB); + com.addCollection(collectionC); + + Group groupParent = groupService.create(context); + Group groupChild = groupService.create(context); + + groupService.addMember(context, groupParent, groupChild); + + EPerson epersonA = ePersonService.create(context); + + //Add epersonA to the child group + groupService.addMember(context, groupChild, epersonA); + + //personA can submit to collectionA and collectionC + authorizeService.addPolicy(context, collectionA, Constants.ADD, epersonA); + authorizeService.addPolicy(context, collectionB, Constants.ADD, groupParent); + + context.restoreAuthSystemState(); + + context.setCurrentUser(epersonA); + List personACollections = + collectionService.findAuthorized(context, null, List.of(Constants.ADD, Constants.ADMIN)); + assertTrue("testFindAuthorizedByEPerson A", personACollections.size() == 2); + assertTrue("testFindAuthorizedByEPerson A.A", personACollections.contains(collectionA)); + assertTrue("testFindAuthorizedByEPerson A.B", personACollections.contains(collectionB)); + assertFalse("testFindAuthorizedByEPerson A.C", personACollections.contains(collectionC)); + } + + /** + * Test of testFindAuthorizedEPersonCommunityAdmin method, of class Collection. + * This will test what collections care retrieved if a user is a Com Administrator + * eperson A is Top of B (and by the caso of B,C and D) but not of E + * eperson E is Top of E nad of D so it can get THE E and D Collections + * + */ + @Test + public void testFindAuthorizedEPersonCommunityAdmin() throws Exception { + context.turnOffAuthorisationSystem(); + Community comA = communityService.create(null, context); + Community comB = communityService.create(null, context); + Community comC = communityService.create(null, context); + Community comD = communityService.create(null, context); + Community comE = communityService.create(null, context); + + Collection collectionA1 = collectionService.create(context, comA); + Collection collectionC1 = collectionService.create(context, comC); + Collection collectionC2 = collectionService.create(context, comC); + Collection collectionD1 = collectionService.create(context, comD); + Collection collectionE1 = collectionService.create(context, comE); + Collection collectionE2 = collectionService.create(context, comE); + + //Create Com hierarchies + comA.addSubCommunity(comB); + comA.addSubCommunity(comC); + comB.addSubCommunity(comD); + + comA.addCollection(collectionA1); + comC.addCollection(collectionC1); + comC.addCollection(collectionC2); + comD.addCollection(collectionD1); + comE.addCollection(collectionE1); + comE.addCollection(collectionE2); + + + Group groupA = groupService.create(context); + Group groupB = groupService.create(context); + Group groupC = groupService.create(context); + Group groupD = groupService.create(context); + Group groupE = groupService.create(context); + + EPerson epersonA = ePersonService.create(context); + EPerson epersonB = ePersonService.create(context); + + //Add epersonA to the child group + groupService.addMember(context, groupA, epersonA); + //Add epersonB to the child group + groupService.addMember(context, groupE, epersonB); + groupService.addMember(context, groupD, epersonB); + + //personA can submit to collectionA and collectionB + authorizeService.addPolicy(context, comA, Constants.ADMIN, groupA); + authorizeService.addPolicy(context, comD, Constants.ADMIN, groupD); + authorizeService.addPolicy(context, comE, Constants.ADMIN, groupE); + + context.restoreAuthSystemState(); + + //PersonA Can get AllCollection From Top to Bottom com ComA, but not from ComE + context.setCurrentUser(epersonA); + List personACollectionsAdminCommA = + collectionService.findAuthorized(context, null, List.of(Constants.ADD, Constants.ADMIN)); + assertTrue("testFindAuthorizedEPersonCommunityAdmin A", personACollectionsAdminCommA.size() == 4); + assertTrue("testFindAuthorizedEPersonCommunityAdmin A.A", personACollectionsAdminCommA + .containsAll(List.of(collectionA1, collectionD1, collectionC1, collectionC2))); + assertFalse("testFindAuthorizedEPersonCommunityAdmin A.B", personACollectionsAdminCommA + .containsAll(List.of(collectionE1, collectionE2))); + + //PersonB Can get AllCollection From Top to Bottom com ComE, but not from ComA + context.setCurrentUser(epersonB); + List personACollectionsAdminCommE = + collectionService.findAuthorized(context, null, List.of(Constants.ADD, Constants.ADMIN)); + assertTrue("testFindAuthorizedEPersonCommunityAdmin B", personACollectionsAdminCommE.size() == 3); + assertFalse("testFindAuthorizedEPersonCommunityAdmin B.A", personACollectionsAdminCommE + .containsAll(List.of(collectionA1, collectionC1, collectionC2))); + assertTrue("testFindAuthorizedEPersonCommunityAdmin B.B", personACollectionsAdminCommE + .containsAll(List.of(collectionD1, collectionE1, collectionE2))); + } + + /** + * Test of testFindNotAuthorizedEPersonDifferentActions method, of class Collection. + * We create some collections and a user and a group add the user as ADMIN by adding ti + * toa group and that group to a Collection and add the user as submitter to another + * we pass actions that shouldn't return collections if only those actions are passed + * And we test if only on collection is retrieved if we pass the Corresponding action + */ + @Test + public void testFindAuthorizedEPersonDifferentActions() throws Exception { + context.turnOffAuthorisationSystem(); + Community com = communityService.create(null, context); + Collection collectionA = collectionService.create(context, com); + Collection collectionB = collectionService.create(context, com); + + com.addCollection(collectionA); + com.addCollection(collectionB); + + Group group = groupService.create(context); + + EPerson epersonA = ePersonService.create(context); + + //Add epersonA to the child group + groupService.addMember(context, group, epersonA); + + //personA can submit to collectionA and collectionB + authorizeService.addPolicy(context, collectionA, Constants.ADD, epersonA); + authorizeService.addPolicy(context, collectionB, Constants.ADMIN, group); + + context.restoreAuthSystemState(); + + //Person does not Have other permission than ADD - So should not return a Colelction if we pass other + //Actions. In this case WRITE OR DELETE + context.setCurrentUser(epersonA); + List personACollectionsRD = + collectionService.findAuthorized(context, null, List.of(Constants.WRITE, Constants.DELETE)); + assertTrue("testFindAuthorizedEPersonDifferentActions A", personACollectionsRD.isEmpty()); + assertFalse("testFindAuthorizedEPersonDifferentActions A.A", personACollectionsRD.contains(collectionA)); + assertFalse("testFindAuthorizedEPersonDifferentActions A.B", personACollectionsRD.contains(collectionB)); + + //But It Should get Collection B if we pass the ADMIN Action too + List personACollectionsADD = + collectionService.findAuthorized(context, null, + List.of(Constants.WRITE, Constants.DELETE, Constants.ADD)); + assertTrue("testFindAuthorizedEPersonDifferentActions B", personACollectionsADD.size() == 1); + assertTrue("testFindAuthorizedEPersonDifferentActions B.A", personACollectionsADD.contains(collectionA)); + assertFalse("testFindAuthorizedEPersonDifferentActions B.B", personACollectionsADD.contains(collectionB)); + + //But It Should get Collection A if we pass the ADD Action too + List personACollections = + collectionService.findAuthorized(context, null, + List.of(Constants.WRITE, Constants.DELETE, Constants.ADMIN)); + assertTrue("testFindAuthorizedEPersonDifferentActions C", personACollections.size() == 1); + assertFalse("testFindAuthorizedEPersonDifferentActions C.A", personACollections.contains(collectionA)); + assertTrue("testFindAuthorizedEPersonDifferentActions C.B", personACollections.contains(collectionB)); + } + /** * Test of countItems method, of class Collection. */ diff --git a/dspace-swordv2/src/main/java/org/dspace/sword2/SwordAuthenticator.java b/dspace-swordv2/src/main/java/org/dspace/sword2/SwordAuthenticator.java index 54b769388c6..e47c0f076b9 100644 --- a/dspace-swordv2/src/main/java/org/dspace/sword2/SwordAuthenticator.java +++ b/dspace-swordv2/src/main/java/org/dspace/sword2/SwordAuthenticator.java @@ -9,6 +9,7 @@ import java.sql.SQLException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Iterator; import java.util.List; @@ -618,31 +619,33 @@ public List getAllowedCollections( // short cut by obtaining the collections to which the authenticated user can submit List cols = collectionService.findAuthorized( - authContext, community, Constants.ADD); + authContext, community, Arrays.asList(Constants.ADD, Constants.ADMIN)); + List allowed = new ArrayList<>(); // now find out if the obo user is allowed to submit to any of these collections - for (Collection col : cols) { - boolean oboAllowed = false; - - // check for obo null - if (swordContext.getOnBehalfOf() == null) { - oboAllowed = true; - } - - // if we have not already determined that the obo user is ok to submit, look up the READ policy on the - // community. THis will include determining if the user is an administrator. - if (!oboAllowed) { - oboAllowed = authorizeService.authorizeActionBoolean( - swordContext.getOnBehalfOfContext(), col, - Constants.ADD); - } + if (swordContext.getOnBehalfOf() != null) { + for (Collection col : cols) { + boolean oboAllowed = false; + + //if we have not already determined that the obo user is ok to submit, + //look up the READ policy on the + // community. THis will include determining if the user is an administrator. + if (!oboAllowed) { + oboAllowed = authorizeService.authorizeActionBoolean( + swordContext.getOnBehalfOfContext(), col, + Constants.ADD); + } - // final check to see if we are allowed to READ - if (oboAllowed) { - allowed.add(col); + // final check to see if we are allowed to READ + if (oboAllowed) { + allowed.add(col); + } } + } else { + return cols; } + return allowed; } catch (SQLException e) { From 00ca9ccded6cf3dc4fc66dfa7d43a898de6ef712 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 21 Oct 2025 16:11:44 -0500 Subject: [PATCH 721/939] Stream.toList() doesn't exist in Java 11. Replace with Stream.collect(Collectors.toList()) --- .../main/java/org/dspace/content/CollectionServiceImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java index 60cf769ae24..beef87582ea 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -876,7 +876,7 @@ public List findAuthorized(Context context, Community community, Lis .find(context, eperson, directGroups, Constants.ADMIN, Constants.COMMUNITY); List uuids = resourcePolicies.stream() .map(policy -> policy.getdSpaceObject().getID()) - .toList(); + .collect(Collectors.toList()); List communities = uuids.stream() .map(uuid -> { @@ -887,7 +887,7 @@ public List findAuthorized(Context context, Community community, Lis } }) .filter(Objects::nonNull) - .toList(); + .collect(Collectors.toList()); Set allCommunities = new HashSet<>(communities); Set allCommAdminCollections = communities.stream() From dc338fcb501762f928eafe35cb8f5ad2f391f905 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Thu, 18 Sep 2025 15:23:06 +0200 Subject: [PATCH 722/939] 134319: Escape HTML tags from certain fields before applying hit highlights Co-authored-by: bram.maegerman@atmire.com --- .../org/dspace/discovery/SolrServiceImpl.java | 13 ++++ .../app/rest/DiscoveryRestControllerIT.java | 77 +++++++++++++++++++ dspace/config/modules/discovery.cfg | 8 +- dspace/solr/search/conf/solrconfig.xml | 6 ++ 4 files changed, 103 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java index 0cf2aa50af6..3dafaf241bd 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java @@ -18,6 +18,7 @@ import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.Date; @@ -956,8 +957,20 @@ protected SolrQuery resolveToSolrQuery(Context context, DiscoverQuery discoveryQ if (0 < discoveryQuery.getHitHighlightingFields().size()) { solrQuery.setHighlight(true); solrQuery.add(HighlightParams.USE_PHRASE_HIGHLIGHTER, Boolean.TRUE.toString()); + boolean escapeHTML = configurationService.getBooleanProperty("discovery.highlights.escape-html", true); + String[] renderHTMLForFields = + configurationService.getArrayProperty("discovery.highlights.html-allowed-fields"); for (DiscoverHitHighlightingField highlightingField : discoveryQuery.getHitHighlightingFields()) { solrQuery.addHighlightField(highlightingField.getField() + "_hl"); + boolean allowHTMLInField = Arrays.stream(renderHTMLForFields) + .anyMatch(field -> highlightingField.getField().matches(field)); + if (!escapeHTML || allowHTMLInField) { + solrQuery.add("f." + highlightingField.getField() + "_hl." + HighlightParams.METHOD, "original"); + } else { + solrQuery.add("f." + highlightingField.getField() + "_hl." + HighlightParams.METHOD, "unified"); + solrQuery.add("f." + highlightingField.getField() + "_hl." + HighlightParams.ENCODER, "html"); + } + solrQuery.add("f." + highlightingField.getField() + "_hl." + HighlightParams.FRAGSIZE, String.valueOf(highlightingField.getMaxChars())); solrQuery.add("f." + highlightingField.getField() + "_hl." + HighlightParams.SNIPPETS, diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java index a115c8aa2f1..bb29601be8c 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java @@ -85,6 +85,28 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest @Autowired ChoiceAuthorityService choiceAuthorityService; + /** + * Original value of the discovery.highlights.escape-html property, saved here to restore it after running the + * tests. + */ + boolean escapeHTML; + + @Override + public void setUp() throws Exception { + super.setUp(); + context.turnOffAuthorisationSystem(); + escapeHTML = configurationService.getBooleanProperty("discovery.highlights.escape-html"); + context.restoreAuthSystemState(); + } + + @Override + public void destroy() throws Exception { + context.turnOffAuthorisationSystem(); + configurationService.setProperty("discovery.highlights.escape-html", escapeHTML); + context.restoreAuthSystemState(); + super.destroy(); + } + @Test public void rootDiscoverTest() throws Exception { @@ -6805,4 +6827,59 @@ public void discoverSearchObjectsSupervisionConfigurationTest() throws Exception .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))); } + @Test + public void discoverSearchObjectsFirstEscapeHTMLTagsBeforeApplyingHitHighlights() throws Exception { + context.turnOffAuthorisationSystem(); + configurationService.setProperty("discovery.highlights.escape-html", true); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection") + .build(); + + ItemBuilder.createItem(context, col1) + .withTitle("This is a test title") + .build(); + context.restoreAuthSystemState(); + + // This test proves that the HTML tags that are in the original metadata, like test, + // are now escaped and should be returned like <a>test</a> + // Only after this happens should the hit highlights be applied + getClient().perform(get("/api/discover/search/objects") + .param("query", "title")) + .andExpect(status().isOk()) + .andExpect(jsonPath( + "$._embedded.searchResult._embedded.objects[0].hitHighlights['dc.title']", + contains("This is a <a>test</a> title"))); + } + + @Test + public void discoverSearchObjectsDontEscapeHTMLTagsBeforeApplyingHitHighlights() throws Exception { + context.turnOffAuthorisationSystem(); + configurationService.setProperty("discovery.highlights.escape-html", false); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection") + .build(); + + ItemBuilder.createItem(context, col1) + .withTitle("This is a test title") + .build(); + context.restoreAuthSystemState(); + + // This test proves that the HTML tags that are in the original metadata, like test, + // are not escaped and should be returned like test + // Only after this happens should the hit highlights be applied + getClient().perform(get("/api/discover/search/objects") + .param("query", "title")) + .andExpect(status().isOk()) + .andExpect(jsonPath( + "$._embedded.searchResult._embedded.objects[0].hitHighlights['dc.title']", + contains("This is a test title"))); + } } diff --git a/dspace/config/modules/discovery.cfg b/dspace/config/modules/discovery.cfg index 72088ddc49f..dcf2cd9e942 100644 --- a/dspace/config/modules/discovery.cfg +++ b/dspace/config/modules/discovery.cfg @@ -48,4 +48,10 @@ discovery.facet.namedtype.workflow.pooled = 004workflow\n|||\nWaiting for Contro # Set the number of retry of a query when stale objects are found. # Set to -1 if stale objects should be ignored. Set to 0 if you want to avoid extra query but take the chance to cleanup # the index each time that stale objects are found. Default 3 -discovery.removestale.attempts = 3 \ No newline at end of file +discovery.removestale.attempts = 3 + +# Set to true to escape HTML tags in hit highlight results +discovery.highlights.escape-html = true +# Set the fields that should not escape HTML tags in hit highlight results when discovery.highlights.escape-html is true +# It is possible to provide multiple fields by separating them by commas like this: dc.description.abstract, dc.title +# discovery.highlights.html-allowed-fields = diff --git a/dspace/solr/search/conf/solrconfig.xml b/dspace/solr/search/conf/solrconfig.xml index 97b1d1ddbbf..71c6c884694 100644 --- a/dspace/solr/search/conf/solrconfig.xml +++ b/dspace/solr/search/conf/solrconfig.xml @@ -148,6 +148,12 @@ + + + + + + false From 6fb3a700dea12080a114521a4c8568ed599ccfcd Mon Sep 17 00:00:00 2001 From: Zahraa Chreim Date: Wed, 29 Oct 2025 15:17:01 +0200 Subject: [PATCH 723/939] 135699: Fix deletion of request items referencing deleted bitstreams and add SQL cleanup script with corresponding test --- .../dspace/content/BitstreamServiceImpl.java | 13 +++++++ ...-request-items-with-deleted-bitstreams.sql | 14 ++++++++ .../app/rest/RequestItemRepositoryIT.java | 36 +++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2025.10.29__Fix-request-items-with-deleted-bitstreams.sql diff --git a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java index e23e5ce2c82..0c5d1299243 100644 --- a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java @@ -19,6 +19,8 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; +import org.dspace.app.requestitem.RequestItem; +import org.dspace.app.requestitem.service.RequestItemService; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.dao.BitstreamDAO; @@ -63,6 +65,8 @@ public class BitstreamServiceImpl extends DSpaceObjectServiceImpl imp protected BundleService bundleService; @Autowired(required = true) protected BitstreamStorageService bitstreamStorageService; + @Autowired(required = true) + protected RequestItemService requestItemService; protected BitstreamServiceImpl() { super(); @@ -287,6 +291,15 @@ public void delete(Context context, Bitstream bitstream) throws SQLException, Au //Remove all bundles from the bitstream object, clearing the connection in 2 ways bundles.clear(); + // Remove any RequestItem entities associated with this bitstream ensuring there are no requests referencing + // a deleted bitstream + List requestItems = requestItemService.findAll(context); + for (RequestItem requestItem : requestItems) { + if (requestItem.getBitstream().equals(bitstream)) { + requestItemService.delete(context, requestItem); + } + } + // Remove policies only after the bitstream has been updated (otherwise the current user has not WRITE rights) authorizeService.removeAllPolicies(context, bitstream); } diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2025.10.29__Fix-request-items-with-deleted-bitstreams.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2025.10.29__Fix-request-items-with-deleted-bitstreams.sql new file mode 100644 index 00000000000..4f0c54c975c --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2025.10.29__Fix-request-items-with-deleted-bitstreams.sql @@ -0,0 +1,14 @@ +-- +-- 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/ +-- + +DELETE +FROM requestitem +WHERE bitstream_id IN + (SELECT bs.uuid + FROM bitstream AS bs + WHERE bs.deleted IS TRUE) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java index fbbd179fd28..8495399169f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java @@ -13,6 +13,7 @@ import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; @@ -58,6 +59,7 @@ import org.dspace.content.Bitstream; import org.dspace.content.Collection; import org.dspace.content.Item; +import org.dspace.content.factory.ContentServiceFactory; import org.dspace.services.ConfigurationService; import org.hamcrest.Matchers; import org.junit.Before; @@ -655,4 +657,38 @@ public void testGetLinkTokenEmailWithoutSubPath() throws MalformedURLException, assertEquals(expectedUrl, generatedLink); configurationService.reloadConfig(); } + + /** + * Test that deleting a bitstream also removes any {@link RequestItem} entities associated with it. + */ + @Test + public void testDeleteBitstreamRemovesRequestItem() throws Exception { + // Fake up a request in REST form. + RequestItemRest rir = new RequestItemRest(); + rir.setAllfiles(false); + rir.setItemId(item.getID().toString()); + rir.setBitstreamId(bitstream.getID().toString()); + rir.setRequestEmail(eperson.getEmail()); + rir.setRequestName(eperson.getFullName()); + rir.setRequestMessage(RequestItemBuilder.REQ_MESSAGE); + + // Create it and see if it was created correctly. + ObjectMapper mapper = new ObjectMapper(); + String authToken = getAuthToken(eperson.getEmail(), password); + + getClient(authToken) + .perform(post(URI_ROOT) + .content(mapper.writeValueAsBytes(rir)) + .contentType(contentType)) + .andExpect(status().isCreated()) + // verify the body is empty + .andExpect(jsonPath("$").doesNotExist()); + + // Delete associated Bitstream + ContentServiceFactory.getInstance().getBitstreamService().delete(context, bitstream); + + // Verify that all RequestItems related to this bitstream have been removed + Iterator itemRequests = requestItemService.findByItem(context, item); + assertFalse(itemRequests.hasNext()); + } } From 00240f65f96f6d661278e06f47e8cb695558e2af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 02:31:29 +0000 Subject: [PATCH 724/939] Bump the test-tools group with 7 updates Bumps the test-tools group with 7 updates: | Package | From | To | | --- | --- | --- | | [org.xmlunit:xmlunit-core](https://github.com/xmlunit/xmlunit) | `2.10.4` | `2.11.0` | | [io.netty:netty-buffer](https://github.com/netty/netty) | `4.2.6.Final` | `4.2.7.Final` | | [io.netty:netty-transport](https://github.com/netty/netty) | `4.2.6.Final` | `4.2.7.Final` | | [io.netty:netty-transport-native-unix-common](https://github.com/netty/netty) | `4.2.6.Final` | `4.2.7.Final` | | [io.netty:netty-common](https://github.com/netty/netty) | `4.2.6.Final` | `4.2.7.Final` | | [io.netty:netty-handler](https://github.com/netty/netty) | `4.2.6.Final` | `4.2.7.Final` | | [io.netty:netty-codec](https://github.com/netty/netty) | `4.2.6.Final` | `4.2.7.Final` | Updates `org.xmlunit:xmlunit-core` from 2.10.4 to 2.11.0 - [Release notes](https://github.com/xmlunit/xmlunit/releases) - [Changelog](https://github.com/xmlunit/xmlunit/blob/main/RELEASE_NOTES.md) - [Commits](https://github.com/xmlunit/xmlunit/compare/v2.10.4...v2.11.0) Updates `io.netty:netty-buffer` from 4.2.6.Final to 4.2.7.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.6.Final...netty-4.2.7.Final) Updates `io.netty:netty-transport` from 4.2.6.Final to 4.2.7.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.6.Final...netty-4.2.7.Final) Updates `io.netty:netty-transport-native-unix-common` from 4.2.6.Final to 4.2.7.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.6.Final...netty-4.2.7.Final) Updates `io.netty:netty-common` from 4.2.6.Final to 4.2.7.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.6.Final...netty-4.2.7.Final) Updates `io.netty:netty-handler` from 4.2.6.Final to 4.2.7.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.6.Final...netty-4.2.7.Final) Updates `io.netty:netty-codec` from 4.2.6.Final to 4.2.7.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.6.Final...netty-4.2.7.Final) --- updated-dependencies: - dependency-name: org.xmlunit:xmlunit-core dependency-version: 2.11.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: test-tools - dependency-name: io.netty:netty-buffer dependency-version: 4.2.7.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-transport dependency-version: 4.2.7.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-transport-native-unix-common dependency-version: 4.2.7.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-common dependency-version: 4.2.7.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-handler dependency-version: 4.2.7.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-codec dependency-version: 4.2.7.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index d2e3477632e..6ffd50ddc10 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -870,32 +870,32 @@ io.netty netty-buffer - 4.2.6.Final + 4.2.7.Final io.netty netty-transport - 4.2.6.Final + 4.2.7.Final io.netty netty-transport-native-unix-common - 4.2.6.Final + 4.2.7.Final io.netty netty-common - 4.2.6.Final + 4.2.7.Final io.netty netty-handler - 4.2.6.Final + 4.2.7.Final io.netty netty-codec - 4.2.6.Final + 4.2.7.Final org.apache.velocity @@ -905,7 +905,7 @@ org.xmlunit xmlunit-core - 2.10.4 + 2.11.0 test From 6f8c2f1cd59055ef29cf3366febea7a4ad8888f1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 02:34:29 +0000 Subject: [PATCH 725/939] Bump org.apache.bcel:bcel from 6.10.0 to 6.11.0 Bumps [org.apache.bcel:bcel](https://github.com/apache/commons-bcel) from 6.10.0 to 6.11.0. - [Changelog](https://github.com/apache/commons-bcel/blob/master/RELEASE-NOTES.txt) - [Commits](https://github.com/apache/commons-bcel/compare/rel/commons-bcel-6.10.0...rel/commons-bcel-6.11.0) --- updated-dependencies: - dependency-name: org.apache.bcel:bcel dependency-version: 6.11.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index d2e3477632e..6214a806fdd 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -805,7 +805,7 @@ org.apache.bcel bcel - 6.10.0 + 6.11.0 test From 3f5df043b78f7bd1f8d682dcc56aa53dc9c67028 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 02:34:34 +0000 Subject: [PATCH 726/939] Bump com.healthmarketscience.jackcess:jackcess from 4.0.8 to 4.0.10 Bumps [com.healthmarketscience.jackcess:jackcess](https://github.com/jahlborn/jackcess) from 4.0.8 to 4.0.10. - [Commits](https://github.com/jahlborn/jackcess/compare/jackcess-4.0.8...jackcess-4.0.10) --- updated-dependencies: - dependency-name: com.healthmarketscience.jackcess:jackcess dependency-version: 4.0.10 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fb4f36062d9..067f7818dda 100644 --- a/pom.xml +++ b/pom.xml @@ -1318,7 +1318,7 @@ com.healthmarketscience.jackcess jackcess - 4.0.8 + 4.0.10 From b2719d6e30c25dc35d1b2a6faefc4711bfea33ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 02:34:53 +0000 Subject: [PATCH 727/939] Bump io.grpc:grpc-context from 1.75.0 to 1.76.0 Bumps [io.grpc:grpc-context](https://github.com/grpc/grpc-java) from 1.75.0 to 1.76.0. - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.75.0...v1.76.0) --- updated-dependencies: - dependency-name: io.grpc:grpc-context dependency-version: 1.76.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fb4f36062d9..4e97d514d9f 100644 --- a/pom.xml +++ b/pom.xml @@ -1718,7 +1718,7 @@ io.grpc grpc-context - 1.75.0 + 1.76.0 com.google.http-client From e98c6036d646e52b1268ac37f2a418e7187d650c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 02:35:09 +0000 Subject: [PATCH 728/939] Bump json-path.version from 2.9.0 to 2.10.0 Bumps `json-path.version` from 2.9.0 to 2.10.0. Updates `com.jayway.jsonpath:json-path` from 2.9.0 to 2.10.0 - [Release notes](https://github.com/jayway/JsonPath/releases) - [Changelog](https://github.com/json-path/JsonPath/blob/master/changelog.md) - [Commits](https://github.com/jayway/JsonPath/compare/json-path-2.9.0...json-path-2.10.0) Updates `com.jayway.jsonpath:json-path-assert` from 2.9.0 to 2.10.0 - [Release notes](https://github.com/jayway/JsonPath/releases) - [Changelog](https://github.com/json-path/JsonPath/blob/master/changelog.md) - [Commits](https://github.com/jayway/JsonPath/compare/json-path-2.9.0...json-path-2.10.0) --- updated-dependencies: - dependency-name: com.jayway.jsonpath:json-path dependency-version: 2.10.0 dependency-type: direct:development update-type: version-update:semver-minor - dependency-name: com.jayway.jsonpath:json-path-assert dependency-version: 2.10.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fb4f36062d9..0590faeb1f1 100644 --- a/pom.xml +++ b/pom.xml @@ -48,7 +48,7 @@ - 2.9.0 + 2.10.0 7.9 From c5b9565e3a98f76739867d3399055e8e2108c713 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Thu, 6 Nov 2025 10:10:00 +0300 Subject: [PATCH 729/939] pom.xml: adjust Jackson dependencies For some reason jackson-annotations no longer uses the patch version and jackson-databind now does. See: https://github.com/FasterXML/jackson/wiki/Jackson-Release-2.20 --- pom.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index fb4f36062d9..92d011ca2fc 100644 --- a/pom.xml +++ b/pom.xml @@ -29,9 +29,9 @@ 3.11.1 2.31.0 - - 2.19.2 - 2.19.2 + + 2.20.1 + 2.20 1.3.2 2.3.1 2.3.9 @@ -1771,12 +1771,12 @@ com.fasterxml classmate - 1.7.0 + 1.7.1 com.fasterxml.jackson.core jackson-annotations - ${jackson.version} + ${jackson-annotations.version} com.fasterxml.jackson.core @@ -1786,7 +1786,7 @@ com.fasterxml.jackson.core jackson-databind - ${jackson-databind.version} + ${jackson.version} com.google.guava From 4dfbabe3cf2bfd5e705a1996e3e6372f737f4de3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Tue, 21 Oct 2025 14:52:56 +0100 Subject: [PATCH 730/939] fix issue 10530 - latestForDiscovery (cherry picked from commit 95998638f9945aa3469cc2a8c4172363b9333c34) --- dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl b/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl index 2b388ceac7c..470a7c7211f 100644 --- a/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl +++ b/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl @@ -5,7 +5,7 @@ detailed in the LICENSE and NOTICE files at the root of the source tree and available online at - Developed by Paulo Graça + Developed by paulo-graca > https://www.openaire.eu/schema/repo-lit/4.0/openaire.xsd @@ -303,7 +303,7 @@ - From c040729ac60aaad7956cdb851c903a5bc39b5277 Mon Sep 17 00:00:00 2001 From: Mark Patton Date: Wed, 12 Nov 2025 13:22:40 -0500 Subject: [PATCH 731/939] Port AWS SDK v2 update of S3 Bitstore Service to 7x --- dspace-api/pom.xml | 43 +- .../storage/bitstore/S3BitStoreService.java | 445 +++++++----------- .../storage/bitstore/S3BitStoreServiceIT.java | 131 +++--- dspace-server-webapp/pom.xml | 11 +- dspace/config/modules/assetstore.cfg | 17 + dspace/config/spring/api/bitstore.xml | 19 + 6 files changed, 318 insertions(+), 348 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index d2e3477632e..091efb9c6ea 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -637,6 +637,7 @@ 1.1.1 + com.google.guava guava @@ -736,9 +737,25 @@ - com.amazonaws - aws-java-sdk-s3 - 1.12.792 + software.amazon.awssdk + s3 + 2.32.31 + + + software.amazon.awssdk + netty-nio-client + + + software.amazon.awssdk + apache-client + + + + + + software.amazon.awssdk.crt + aws-crt + 0.38.12 @@ -836,22 +853,12 @@
    - + - io.findify - s3mock_2.13 - 0.2.6 - test - - - com.amazonawsl - aws-java-sdk-s3 - - - com.amazonaws - aws-java-sdk-s3 - - + org.testcontainers + testcontainers-localstack + 2.0.1 + test diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java index e21a101a7e9..8f7a7b72830 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java @@ -9,36 +9,20 @@ import static java.lang.String.valueOf; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.net.URI; import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.UUID; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.function.Supplier; -import javax.validation.constraints.NotNull; - -import com.amazonaws.AmazonClientException; -import com.amazonaws.auth.AWSCredentials; -import com.amazonaws.auth.AWSStaticCredentialsProvider; -import com.amazonaws.auth.BasicAWSCredentials; -import com.amazonaws.regions.Region; -import com.amazonaws.regions.Regions; -import com.amazonaws.services.s3.AmazonS3; -import com.amazonaws.services.s3.AmazonS3ClientBuilder; -import com.amazonaws.services.s3.model.AmazonS3Exception; -import com.amazonaws.services.s3.model.GetObjectRequest; -import com.amazonaws.services.s3.model.ObjectMetadata; -import com.amazonaws.services.s3.transfer.Download; -import com.amazonaws.services.s3.transfer.TransferManager; -import com.amazonaws.services.s3.transfer.TransferManagerBuilder; -import com.amazonaws.services.s3.transfer.Upload; + import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.HelpFormatter; @@ -47,7 +31,6 @@ import org.apache.commons.cli.ParseException; import org.apache.commons.io.output.NullOutputStream; import org.apache.commons.lang3.StringUtils; -import org.apache.http.HttpStatus; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.content.Bitstream; @@ -58,6 +41,19 @@ import org.dspace.storage.bitstore.service.BitstreamStorageService; import org.dspace.util.FunctionalUtils; import org.springframework.beans.factory.annotation.Autowired; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.awscore.exception.AwsServiceException; +import software.amazon.awssdk.core.async.AsyncRequestBody; +import software.amazon.awssdk.core.async.AsyncResponseTransformer; +import software.amazon.awssdk.http.HttpStatusCode; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3AsyncClient; +import software.amazon.awssdk.services.s3.S3CrtAsyncClientBuilder; +import software.amazon.awssdk.services.s3.model.ChecksumAlgorithm; +import software.amazon.awssdk.services.s3.model.HeadObjectResponse; +import software.amazon.awssdk.services.s3.model.NoSuchBucketException; /** * Asset store using Amazon's Simple Storage Service (S3). @@ -66,7 +62,7 @@ * * @author Richard Rodgers, Peter Dietz * @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com) - * + * @author Mark Patton */ public class S3BitStoreService extends BaseBitStoreService { @@ -83,30 +79,21 @@ public class S3BitStoreService extends BaseBitStoreService { */ static final String CSA = "MD5"; - // These settings control the way an identifier is hashed into - // directory and file names - // - // With digitsPerLevel 2 and directoryLevels 3, an identifier - // like 12345678901234567890 turns into the relative name - // /12/34/56/12345678901234567890. - // - // You should not change these settings if you have data in the - // asset store, as the BitstreamStorageManager will be unable - // to find your existing data. - protected static final int digitsPerLevel = 2; - protected static final int directoryLevels = 3; - private boolean enabled = false; + /** + * Override AWS endpoint if not null + */ + private String endpoint = null; + private String awsAccessKey; private String awsSecretKey; private String awsRegionName; private boolean useRelativePath; - - /** - * The maximum size of individual chunk to download from S3 when a file is accessed. Default 5Mb - */ - private long bufferSize = 5 * 1024 * 1024; + private double targetThroughputGbps = 10.0; + private long minPartSizeBytes = 8 * 1024 * 1024L; + private ChecksumAlgorithm s3ChecksumAlgorithm = ChecksumAlgorithm.CRC32; + private Integer maxConcurrency = null; /** * container for all the assets @@ -121,13 +108,7 @@ public class S3BitStoreService extends BaseBitStoreService { /** * S3 service */ - private AmazonS3 s3Service = null; - - /** - * S3 transfer manager - * this is reused between put calls to use less resources for multiple uploads - */ - private TransferManager tm = null; + private S3AsyncClient s3AsyncClient = null; private static final ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); @@ -137,16 +118,42 @@ public class S3BitStoreService extends BaseBitStoreService { * * @param regions wanted regions in client * @param awsCredentials credentials of the client + * @param endpoint custom AWS endpoint + * @param targetThroughput target throughput in Gbps + * @param minPartSize minimum part size in bytes + * @param maxConcurrency maximum number of concurrent requests * @return builder with the specified parameters */ - protected static Supplier amazonClientBuilderBy( - @NotNull Regions regions, - @NotNull AWSCredentials awsCredentials + protected static Supplier amazonClientBuilderBy( + Region region, + AwsCredentialsProvider credentialsProvider, + String endpoint, + double targetThroughput, + long minPartSize, + Integer maxConcurrency ) { - return () -> AmazonS3ClientBuilder.standard() - .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) - .withRegion(regions) - .build(); + return () -> { + S3CrtAsyncClientBuilder crtBuilder = S3AsyncClient.crtBuilder(); + + if (credentialsProvider != null) { + crtBuilder.credentialsProvider(credentialsProvider); + } + + if (region != null) { + crtBuilder.region(region); + } + + if (maxConcurrency != null) { + crtBuilder.maxConcurrency(maxConcurrency); + } + + if (StringUtils.isNotBlank(endpoint)) { + crtBuilder.endpointOverride(URI.create(endpoint)); + crtBuilder.forcePathStyle(true); + } + + return crtBuilder.targetThroughputInGbps(targetThroughput).minimumPartSizeInBytes(minPartSize).build(); + }; } public S3BitStoreService() {} @@ -154,10 +161,10 @@ public S3BitStoreService() {} /** * This constructor is used for test purpose. * - * @param s3Service AmazonS3 service + * @param s3AsyncClient AmazonS3 service */ - protected S3BitStoreService(AmazonS3 s3Service) { - this.s3Service = s3Service; + protected S3BitStoreService(S3AsyncClient s3AsyncClient) { + this.s3AsyncClient = s3AsyncClient; } @Override @@ -174,7 +181,6 @@ public boolean isEnabled() { */ @Override public void init() throws IOException { - if (this.isInitialized() || !this.isEnabled()) { return; } @@ -183,29 +189,31 @@ public void init() throws IOException { if (StringUtils.isNotBlank(getAwsAccessKey()) && StringUtils.isNotBlank(getAwsSecretKey())) { log.warn("Use local defined S3 credentials"); // region - Regions regions = Regions.DEFAULT_REGION; + Region region = Region.US_EAST_1; if (StringUtils.isNotBlank(awsRegionName)) { try { - regions = Regions.fromName(awsRegionName); + region = Region.of(awsRegionName); } catch (IllegalArgumentException e) { log.warn("Invalid aws_region: " + awsRegionName); } } + // init client - s3Service = FunctionalUtils.getDefaultOrBuild( - this.s3Service, + s3AsyncClient = FunctionalUtils.getDefaultOrBuild( + this.s3AsyncClient, amazonClientBuilderBy( - regions, - new BasicAWSCredentials(getAwsAccessKey(), getAwsSecretKey()) - ) + region, + StaticCredentialsProvider.create(AwsBasicCredentials.create(getAwsAccessKey(), + getAwsSecretKey())), endpoint, targetThroughputGbps, + minPartSizeBytes, maxConcurrency) ); - log.warn("S3 Region set to: " + regions.getName()); + log.warn("S3 Region set to: " + region.id()); } else { log.info("Using a IAM role or aws environment credentials"); - s3Service = FunctionalUtils.getDefaultOrBuild( - this.s3Service, - AmazonS3ClientBuilder::defaultClient - ); + s3AsyncClient = FunctionalUtils.getDefaultOrBuild( + this.s3AsyncClient, + amazonClientBuilderBy(null, null , endpoint, targetThroughputGbps, + minPartSizeBytes, maxConcurrency)); } // bucket name @@ -216,13 +224,10 @@ public void init() throws IOException { log.warn("S3 BucketName is not configured, setting default: " + bucketName); } - try { - if (!s3Service.doesBucketExistV2(bucketName)) { - s3Service.createBucket(bucketName); - log.info("Creating new S3 Bucket: " + bucketName); - } - } catch (AmazonClientException e) { - throw new IOException(e); + + if (!doesBucketExist(bucketName)) { + s3AsyncClient.createBucket(r -> r.bucket(bucketName)).join(); + log.info("Creating new S3 Bucket: " + bucketName); } this.initialized = true; log.info("AWS S3 Assetstore ready to go! bucket:" + bucketName); @@ -230,13 +235,23 @@ public void init() throws IOException { this.initialized = false; log.error("Can't initialize this store!", e); } + } - log.info("AWS S3 Assetstore ready to go! bucket:" + bucketName); + /** + * @param bucketName + * @return whether or not the specified bucket exists + */ + public boolean doesBucketExist(String bucketName ) { + try { + s3AsyncClient.headBucket(r -> r.bucket(bucketName)).join(); + return true; + } catch (CompletionException ce) { + if (!(ce.getCause() instanceof NoSuchBucketException)) { + log.error("headBucket(" + bucketName + ")", ce.getCause()); + } - tm = FunctionalUtils.getDefaultOrBuild(tm, () -> TransferManagerBuilder.standard() - .withAlwaysCalculateMultipartMd5(true) - .withS3Client(s3Service) - .build()); + return false; + } } /** @@ -264,7 +279,15 @@ public InputStream get(Bitstream bitstream) throws IOException { if (isRegisteredBitstream(key)) { key = key.substring(REGISTERED_FLAG.length()); } - return new S3LazyInputStream(key, bufferSize, bitstream.getSizeBytes()); + + final String objectKey = key; + + try { + return s3AsyncClient.getObject(r -> r.bucket(bucketName).key(objectKey), + AsyncResponseTransformer.toBlockingInputStream()).join(); + } catch (CompletionException e) { + throw new IOException(e.getCause()); + } } /** @@ -281,44 +304,40 @@ public InputStream get(Bitstream bitstream) throws IOException { @Override public void put(Bitstream bitstream, InputStream in) throws IOException { String key = getFullKey(bitstream.getInternalId()); - //Copy istream to temp file, and send the file, with some metadata - File scratchFile = File.createTempFile(bitstream.getInternalId(), "s3bs"); - try ( - FileOutputStream fos = new FileOutputStream(scratchFile); - // Read through a digest input stream that will work out the MD5 - DigestInputStream dis = new DigestInputStream(in, MessageDigest.getInstance(CSA)); - ) { - Utils.bufferedCopy(dis, fos); - in.close(); + ExecutorService executor = Executors.newSingleThreadExecutor(); - Upload upload = tm.upload(bucketName, key, scratchFile); + try (DigestInputStream dis = new DigestInputStream(in, MessageDigest.getInstance(CSA))) { + AsyncRequestBody body = AsyncRequestBody.fromInputStream(dis, null, executor); - upload.waitForUploadResult(); + s3AsyncClient.putObject(b -> b.bucket(bucketName).key(key).checksumAlgorithm(s3ChecksumAlgorithm), + body).join(); + + bitstream.setSizeBytes(s3AsyncClient.headObject(r -> r.bucket(bucketName).key(key)) + .join().contentLength()); - bitstream.setSizeBytes(scratchFile.length()); // we cannot use the S3 ETAG here as it could be not a MD5 in case of multipart upload (large files) or if // the bucket is encrypted bitstream.setChecksum(Utils.toHex(dis.getMessageDigest().digest())); bitstream.setChecksumAlgorithm(CSA); - - } catch (AmazonClientException | IOException | InterruptedException e) { + } catch (CompletionException e) { + log.error("put(" + bitstream.getInternalId() + ", is)", e.getCause()); + throw new IOException(e.getCause()); + } catch (IOException e) { log.error("put(" + bitstream.getInternalId() + ", is)", e); throw new IOException(e); } catch (NoSuchAlgorithmException nsae) { // Should never happen log.warn("Caught NoSuchAlgorithmException", nsae); } finally { - if (!scratchFile.delete()) { - scratchFile.deleteOnExit(); - } + executor.shutdown(); + in.close(); } } /** * Obtain technical metadata about an asset in the asset store. * - * Checksum used is (ETag) hex encoded 128-bit MD5 digest of an object's content as calculated by Amazon S3 - * (Does not use getContentMD5, as that is 128-bit MD5 digest calculated on caller's side) + * The MD5 checksum is calculated locally because it is not supported by AWS. * * @param bitstream The asset to describe * @param attrs A List of desired metadata fields @@ -329,7 +348,6 @@ public void put(Bitstream bitstream, InputStream in) throws IOException { */ @Override public Map about(Bitstream bitstream, List attrs) throws IOException { - String key = getFullKey(bitstream.getInternalId()); // If this is a registered bitstream, strip the -R prefix before retrieving if (isRegisteredBitstream(key)) { @@ -339,20 +357,18 @@ public Map about(Bitstream bitstream, List attrs) throws Map metadata = new HashMap<>(); try { + final String objectKey = key; + HeadObjectResponse response = s3AsyncClient.headObject(r -> r.bucket(bucketName).key(objectKey)).join(); - ObjectMetadata objectMetadata = s3Service.getObjectMetadata(bucketName, key); - if (objectMetadata != null) { - putValueIfExistsKey(attrs, metadata, "size_bytes", objectMetadata.getContentLength()); - putValueIfExistsKey(attrs, metadata, "modified", valueOf(objectMetadata.getLastModified().getTime())); - } - + putValueIfExistsKey(attrs, metadata, "size_bytes", response.contentLength()); + putValueIfExistsKey(attrs, metadata, "modified", valueOf(response.lastModified().toEpochMilli())); putValueIfExistsKey(attrs, metadata, "checksum_algorithm", CSA); if (attrs.contains("checksum")) { try (InputStream in = get(bitstream); DigestInputStream dis = new DigestInputStream(in, MessageDigest.getInstance(CSA)) ) { - Utils.copy(dis, NullOutputStream.NULL_OUTPUT_STREAM); + Utils.copy(dis, NullOutputStream.INSTANCE); byte[] md5Digest = dis.getMessageDigest().digest(); metadata.put("checksum", Utils.toHex(md5Digest)); } catch (NoSuchAlgorithmException nsae) { @@ -362,15 +378,16 @@ public Map about(Bitstream bitstream, List attrs) throws } return metadata; - } catch (AmazonS3Exception e) { - if (e.getStatusCode() == HttpStatus.SC_NOT_FOUND) { - return metadata; + } catch (CompletionException e) { + if (e.getCause() instanceof AwsServiceException) { + if (((AwsServiceException)e.getCause()).statusCode() == HttpStatusCode.NOT_FOUND) { + return metadata; + } } - } catch (AmazonClientException e) { + log.error("about(" + key + ", attrs)", e); throw new IOException(e); } - return metadata; } /** @@ -383,10 +400,10 @@ public Map about(Bitstream bitstream, List attrs) throws public void remove(Bitstream bitstream) throws IOException { String key = getFullKey(bitstream.getInternalId()); try { - s3Service.deleteObject(bucketName, key); - } catch (AmazonClientException e) { - log.error("remove(" + key + ")", e); - throw new IOException(e); + s3AsyncClient.deleteObject(r -> r.bucket(bucketName).key(key)).join(); + } catch (CompletionException e) { + log.error("remove(" + key + ")", e.getCause()); + throw new IOException(e.getCause()); } } @@ -497,6 +514,46 @@ public void setUseRelativePath(boolean useRelativePath) { this.useRelativePath = useRelativePath; } + public double getTargetThroughputGbps() { + return targetThroughputGbps; + } + + public void setTargetThroughputGbps(double targetThroughputGbps) { + this.targetThroughputGbps = targetThroughputGbps; + } + + public long getMinPartSizeBytes() { + return minPartSizeBytes; + } + + public void setMinPartSizeBytes(long minPartSizeBytes) { + this.minPartSizeBytes = minPartSizeBytes; + } + + public ChecksumAlgorithm getS3ChecksumAlgorithm() { + return s3ChecksumAlgorithm; + } + + public void setS3ChecksumAlgorithm(ChecksumAlgorithm s3ChecksumAlgorithm) { + this.s3ChecksumAlgorithm = s3ChecksumAlgorithm; + } + + public Integer getMaxConcurrency() { + return maxConcurrency; + } + + public void setMaxConcurrency(Integer maxConcurrency) { + this.maxConcurrency = maxConcurrency; + } + + public String getEndpoint() { + return endpoint; + } + + public void setEndpoint(String endpoint) { + this.endpoint = endpoint; + } + /** * Contains a command-line testing tool. Expects arguments: * -a accessKey -s secretKey -f assetFileName @@ -537,73 +594,18 @@ public static void main(String[] args) throws Exception { S3BitStoreService store = new S3BitStoreService(); - AWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey); - - store.s3Service = AmazonS3ClientBuilder.standard() - .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) - .build(); + StaticCredentialsProvider credentialsProvider = StaticCredentialsProvider.create( + AwsBasicCredentials.create(accessKey, secretKey)); - //Todo configurable region - Region usEast1 = Region.getRegion(Regions.US_EAST_1); - store.s3Service.setRegion(usEast1); + // Todo configurable region + store.s3AsyncClient = S3AsyncClient.builder().credentialsProvider(credentialsProvider). + region(Region.US_EAST_1).build(); // get hostname of DSpace UI to use to name bucket String hostname = Utils.getHostName(configurationService.getProperty("dspace.ui.url")); //Bucketname should be lowercase store.bucketName = DEFAULT_BUCKET_PREFIX + hostname + ".s3test"; - store.s3Service.createBucket(store.bucketName); - /* Broken in DSpace 6 TODO Refactor - // time everything, todo, swtich to caliper - long start = System.currentTimeMillis(); - // Case 1: store a file - String id = store.generateId(); - System.out.print("put() file " + assetFile + " under ID " + id + ": "); - FileInputStream fis = new FileInputStream(assetFile); - //TODO create bitstream for assetfile... - Map attrs = store.put(fis, id); - long now = System.currentTimeMillis(); - System.out.println((now - start) + " msecs"); - start = now; - // examine the metadata returned - Iterator iter = attrs.keySet().iterator(); - System.out.println("Metadata after put():"); - while (iter.hasNext()) - { - String key = (String)iter.next(); - System.out.println( key + ": " + (String)attrs.get(key) ); - } - // Case 2: get metadata and compare - System.out.print("about() file with ID " + id + ": "); - Map attrs2 = store.about(id, attrs); - now = System.currentTimeMillis(); - System.out.println((now - start) + " msecs"); - start = now; - iter = attrs2.keySet().iterator(); - System.out.println("Metadata after about():"); - while (iter.hasNext()) - { - String key = (String)iter.next(); - System.out.println( key + ": " + (String)attrs.get(key) ); - } - // Case 3: retrieve asset and compare bits - System.out.print("get() file with ID " + id + ": "); - java.io.FileOutputStream fos = new java.io.FileOutputStream(assetFile+".echo"); - InputStream in = store.get(id); - Utils.bufferedCopy(in, fos); - fos.close(); - in.close(); - now = System.currentTimeMillis(); - System.out.println((now - start) + " msecs"); - start = now; - // Case 4: remove asset - System.out.print("remove() file with ID: " + id + ": "); - store.remove(id); - now = System.currentTimeMillis(); - System.out.println((now - start) + " msecs"); - System.out.flush(); - // should get nothing back now - will throw exception - store.get(id); -*/ + store.s3AsyncClient.createBucket(r -> r.bucket(store.bucketName)).join(); } /** @@ -614,85 +616,4 @@ public static void main(String[] args) throws Exception { public boolean isRegisteredBitstream(String internalId) { return internalId.startsWith(REGISTERED_FLAG); } - - public void setBufferSize(long bufferSize) { - this.bufferSize = bufferSize; - } - - /** - * This inner class represent an InputStream that uses temporary files to - * represent chunk of the object downloaded from S3. When the input stream is - * read the class look first to the current chunk and download a new one once if - * the current one as been fully read. The class is responsible to close a chunk - * as soon as a new one is retrieved, the last chunk is closed when the input - * stream itself is closed or the last byte is read (the first of the two) - */ - public class S3LazyInputStream extends InputStream { - private InputStream currentChunkStream; - private String objectKey; - private long endOfChunk = -1; - private long chunkMaxSize; - private long currPos = 0; - private long fileSize; - - public S3LazyInputStream(String objectKey, long chunkMaxSize, long fileSize) throws IOException { - this.objectKey = objectKey; - this.chunkMaxSize = chunkMaxSize; - this.endOfChunk = 0; - this.fileSize = fileSize; - downloadChunk(); - } - - @Override - public int read() throws IOException { - // is the current chunk completely read and other are available? - if (currPos == endOfChunk && currPos < fileSize) { - currentChunkStream.close(); - downloadChunk(); - } - - int byteRead = currPos < endOfChunk ? currentChunkStream.read() : -1; - // do we get any data or are we at the end of the file? - if (byteRead != -1) { - currPos++; - } else { - currentChunkStream.close(); - } - return byteRead; - } - - /** - * This method download the next chunk from S3 - * - * @throws IOException - * @throws FileNotFoundException - */ - private void downloadChunk() throws IOException, FileNotFoundException { - // Create a DownloadFileRequest with the desired byte range - long startByte = currPos; // Start byte (inclusive) - long endByte = Long.min(startByte + chunkMaxSize - 1, fileSize - 1); // End byte (inclusive) - GetObjectRequest getRequest = new GetObjectRequest(bucketName, objectKey) - .withRange(startByte, endByte); - - File currentChunkFile = File.createTempFile("s3-disk-copy-" + UUID.randomUUID(), "temp"); - currentChunkFile.deleteOnExit(); - try { - Download download = tm.download(getRequest, currentChunkFile); - download.waitForCompletion(); - currentChunkStream = new DeleteOnCloseFileInputStream(currentChunkFile); - endOfChunk = endOfChunk + download.getProgress().getBytesTransferred(); - } catch (AmazonClientException | InterruptedException e) { - currentChunkFile.delete(); - throw new IOException(e); - } - } - - @Override - public void close() throws IOException { - if (currentChunkStream != null) { - currentChunkStream.close(); - } - } - - } } diff --git a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java index 63d88e95011..e2f68baf3ae 100644 --- a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java +++ b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java @@ -7,13 +7,12 @@ */ package org.dspace.storage.bitstore; -import static com.amazonaws.regions.Regions.DEFAULT_REGION; import static java.nio.charset.StandardCharsets.UTF_8; import static org.dspace.storage.bitstore.S3BitStoreService.CSA; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; @@ -24,8 +23,10 @@ import static org.junit.Assert.assertTrue; import java.io.File; +import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; +import java.io.PrintWriter; import java.nio.file.Paths; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -34,15 +35,6 @@ import java.util.List; import java.util.Map; -import com.amazonaws.auth.AWSStaticCredentialsProvider; -import com.amazonaws.auth.AnonymousAWSCredentials; -import com.amazonaws.client.builder.AwsClientBuilder.EndpointConfiguration; -import com.amazonaws.services.s3.AmazonS3; -import com.amazonaws.services.s3.AmazonS3ClientBuilder; -import com.amazonaws.services.s3.model.AmazonS3Exception; -import com.amazonaws.services.s3.model.Bucket; -import com.amazonaws.services.s3.model.ObjectMetadata; -import io.findify.s3mock.S3Mock; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.BooleanUtils; import org.dspace.AbstractIntegrationTestWithDatabase; @@ -60,44 +52,69 @@ import org.dspace.services.factory.DSpaceServicesFactory; import org.hamcrest.Matcher; import org.hamcrest.Matchers; -import org.junit.After; +import org.junit.AfterClass; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; - - +import org.testcontainers.localstack.LocalStackContainer; +import org.testcontainers.utility.DockerImageName; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.awscore.exception.AwsServiceException; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3AsyncClient; +import software.amazon.awssdk.services.s3.model.Bucket; +import software.amazon.awssdk.services.s3.model.ChecksumAlgorithm; +import software.amazon.awssdk.services.s3.model.HeadObjectResponse; /** * @author Luca Giamminonni (luca.giamminonni at 4science.com) */ public class S3BitStoreServiceIT extends AbstractIntegrationTestWithDatabase { + private static DockerImageName localstackName = DockerImageName.parse("localstack/localstack:stable"); - private static final String DEFAULT_BUCKET_NAME = "dspace-asset-localhost"; + @SuppressWarnings("resource") + private static LocalStackContainer localstackContainer = new LocalStackContainer(localstackName).withServices("s3"); - private S3BitStoreService s3BitStoreService; + private static S3AsyncClient s3AsyncClient; - private AmazonS3 amazonS3Client; + private static final String DEFAULT_BUCKET_NAME = "dspace-asset-localhost"; - private S3Mock s3Mock; + private S3BitStoreService s3BitStoreService; private Collection collection; private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + @BeforeClass + public static void setupS3() { + localstackContainer.start(); + + s3AsyncClient = S3AsyncClient.crtBuilder() + .endpointOverride(localstackContainer.getEndpoint()) + .credentialsProvider(StaticCredentialsProvider.create( + AwsBasicCredentials.create(localstackContainer.getAccessKey(), + localstackContainer.getSecretKey()) + )) + .region(Region.of(localstackContainer.getRegion())) + .build(); + } + + @AfterClass + public static void cleanupS3() { + localstackContainer.close(); + s3AsyncClient.close(); + } @Before public void setup() throws Exception { - configurationService.setProperty("assetstore.s3.enabled", "true"); - s3Mock = new S3Mock.Builder().withPort(8001).withInMemoryBackend().build(); - s3Mock.start(); - - amazonS3Client = createAmazonS3Client(); - - s3BitStoreService = new S3BitStoreService(amazonS3Client); + s3BitStoreService = new S3BitStoreService(s3AsyncClient); s3BitStoreService.setEnabled(BooleanUtils.toBoolean( configurationService.getProperty("assetstore.s3.enabled"))); - s3BitStoreService.setBufferSize(22); + s3BitStoreService.setS3ChecksumAlgorithm(ChecksumAlgorithm.SHA256); + context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) @@ -109,22 +126,17 @@ public void setup() throws Exception { context.restoreAuthSystemState(); } - @After - public void cleanUp() { - s3Mock.shutdown(); - } - @Test public void testBitstreamPutAndGetWithAlreadyPresentBucket() throws IOException { String bucketName = "testbucket"; - amazonS3Client.createBucket(bucketName); + s3AsyncClient.createBucket(r -> r.bucket(bucketName)).join(); s3BitStoreService.setBucketName(bucketName); s3BitStoreService.init(); - assertThat(amazonS3Client.listBuckets(), contains(bucketNamed(bucketName))); + assertThat(s3AsyncClient.listBuckets().join().buckets(), hasItem(bucketNamed(bucketName))); context.turnOffAuthorisationSystem(); String content = "Test bitstream content"; @@ -146,7 +158,7 @@ public void testBitstreamPutAndGetWithAlreadyPresentBucket() throws IOException private void checkGetPut(String bucketName, String content, Bitstream bitstream) throws IOException { s3BitStoreService.put(bitstream, toInputStream(content)); - String expectedChecksum = Utils.toHex(generateChecksum(content)); + String expectedChecksum = Utils.toHex(generateChecksum("MD5", content)); assertThat(bitstream.getSizeBytes(), is((long) content.length())); assertThat(bitstream.getChecksum(), is(expectedChecksum)); @@ -154,20 +166,16 @@ private void checkGetPut(String bucketName, String content, Bitstream bitstream) InputStream inputStream = s3BitStoreService.get(bitstream); assertThat(IOUtils.toString(inputStream, UTF_8), is(content)); - - String key = s3BitStoreService.getFullKey(bitstream.getInternalId()); - ObjectMetadata objectMetadata = amazonS3Client.getObjectMetadata(bucketName, key); - assertThat(objectMetadata.getContentMD5(), is(expectedChecksum)); } @Test - public void testBitstreamPutAndGetWithoutSpecifingBucket() throws IOException { + public void testBitstreamPutAndGetWithoutSpecifyingBucket() throws IOException { s3BitStoreService.init(); assertThat(s3BitStoreService.getBucketName(), is(DEFAULT_BUCKET_NAME)); - assertThat(amazonS3Client.listBuckets(), contains(bucketNamed(DEFAULT_BUCKET_NAME))); + assertThat(s3AsyncClient.listBuckets().join().buckets(), hasItem(bucketNamed(DEFAULT_BUCKET_NAME))); context.turnOffAuthorisationSystem(); String content = "Test bitstream content"; @@ -176,7 +184,7 @@ public void testBitstreamPutAndGetWithoutSpecifingBucket() throws IOException { s3BitStoreService.put(bitstream, toInputStream(content)); - String expectedChecksum = Utils.toHex(generateChecksum(content)); + String expectedChecksum = Utils.toHex(generateChecksum("MD5", content)); assertThat(bitstream.getSizeBytes(), is((long) content.length())); assertThat(bitstream.getChecksum(), is(expectedChecksum)); @@ -184,11 +192,6 @@ public void testBitstreamPutAndGetWithoutSpecifingBucket() throws IOException { InputStream inputStream = s3BitStoreService.get(bitstream); assertThat(IOUtils.toString(inputStream, UTF_8), is(content)); - - String key = s3BitStoreService.getFullKey(bitstream.getInternalId()); - ObjectMetadata objectMetadata = amazonS3Client.getObjectMetadata(DEFAULT_BUCKET_NAME, key); - assertThat(objectMetadata.getContentMD5(), is(expectedChecksum)); - } @Test @@ -210,9 +213,9 @@ public void testBitstreamPutAndGetWithSubFolder() throws IOException { String key = s3BitStoreService.getFullKey(bitstream.getInternalId()); assertThat(key, startsWith("test/DSpace7/")); - ObjectMetadata objectMetadata = amazonS3Client.getObjectMetadata(DEFAULT_BUCKET_NAME, key); - assertThat(objectMetadata, notNullValue()); - + HeadObjectResponse response = s3AsyncClient.headObject(r -> + r.bucket(DEFAULT_BUCKET_NAME).key(key)).join(); + assertThat(response, notNullValue()); } @Test @@ -232,8 +235,8 @@ public void testBitstreamDeletion() throws IOException { s3BitStoreService.remove(bitstream); IOException exception = assertThrows(IOException.class, () -> s3BitStoreService.get(bitstream)); - assertThat(exception.getCause(), instanceOf(AmazonS3Exception.class)); - assertThat(((AmazonS3Exception) exception.getCause()).getStatusCode(), is(404)); + assertThat(exception.getCause(), instanceOf(AwsServiceException.class)); + assertThat(((AwsServiceException) exception.getCause()).statusCode(), is(404)); } @@ -253,6 +256,14 @@ public void testAbout() throws IOException { assertThat(about.size(), is(0)); about = s3BitStoreService.about(bitstream, List.of("size_bytes")); + + { + PrintWriter out = new PrintWriter(new FileWriter("/tmp/about.txt")); + out.println("moo"); + out.println(about); + out.close(); + } + assertThat(about, hasEntry("size_bytes", 22L)); assertThat(about.size(), is(1)); @@ -261,7 +272,7 @@ public void testAbout() throws IOException { assertThat(about, hasEntry(is("modified"), notNullValue())); assertThat(about.size(), is(2)); - String expectedChecksum = Utils.toHex(generateChecksum(content)); + String expectedChecksum = Utils.toHex(generateChecksum("MD5", content)); about = s3BitStoreService.about(bitstream, List.of("size_bytes", "modified", "checksum")); assertThat(about, hasEntry("size_bytes", 22L)); @@ -275,7 +286,6 @@ public void testAbout() throws IOException { assertThat(about, hasEntry("checksum", expectedChecksum)); assertThat(about, hasEntry("checksum_algorithm", CSA)); assertThat(about.size(), is(4)); - } @Test @@ -406,16 +416,16 @@ public void givenBitStreamIdentifierWithSlashesWhenSanitizedThenSlashesMustBeRem public void testDoNotInitializeConfigured() throws Exception { String assetstores3enabledOldValue = configurationService.getProperty("assetstore.s3.enabled"); configurationService.setProperty("assetstore.s3.enabled", "false"); - s3BitStoreService = new S3BitStoreService(amazonS3Client); + s3BitStoreService = new S3BitStoreService(s3AsyncClient); s3BitStoreService.init(); assertFalse(s3BitStoreService.isInitialized()); assertFalse(s3BitStoreService.isEnabled()); configurationService.setProperty("assetstore.s3.enabled", assetstores3enabledOldValue); } - private byte[] generateChecksum(String content) { + private byte[] generateChecksum(String algorithm, String content) { try { - MessageDigest m = MessageDigest.getInstance("MD5"); + MessageDigest m = MessageDigest.getInstance(algorithm); m.update(content.getBytes()); return m.digest(); } catch (NoSuchAlgorithmException e) { @@ -423,13 +433,6 @@ private byte[] generateChecksum(String content) { } } - private AmazonS3 createAmazonS3Client() { - return AmazonS3ClientBuilder.standard() - .withCredentials(new AWSStaticCredentialsProvider(new AnonymousAWSCredentials())) - .withEndpointConfiguration(new EndpointConfiguration("http://127.0.0.1:8001", DEFAULT_REGION.getName())) - .build(); - } - private Item createItem() { return ItemBuilder.createItem(context, collection) .withTitle("Test item") @@ -447,7 +450,7 @@ private Bitstream createBitstream(String content) { } private Matcher bucketNamed(String name) { - return LambdaMatcher.matches(bucket -> bucket.getName().equals(name)); + return LambdaMatcher.matches(bucket -> bucket.name().equals(name)); } private InputStream toInputStream(String content) { diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 84227b4a12f..0f03a7335c2 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -439,6 +439,13 @@ + + + + org.dspace + dspace-iiif + + org.dspace dspace-api @@ -458,10 +465,6 @@ - - org.dspace - dspace-iiif - org.dspace dspace-oai diff --git a/dspace/config/modules/assetstore.cfg b/dspace/config/modules/assetstore.cfg index 5e625e0c8a1..74989aad0e5 100644 --- a/dspace/config/modules/assetstore.cfg +++ b/dspace/config/modules/assetstore.cfg @@ -47,6 +47,9 @@ assetstore.s3.bucketName = # is shared. Optional, default is root level of bucket assetstore.s3.subfolder = +# Optional custom S3 endpoint URI. Leave this blank / commented to use the Amazon default +# assetstore.s3.endpoint = + # please don't use root credentials in production but rely on the aws credentials default # discovery mechanism to configure them (ENV VAR, EC2 Iam role, etc.) # The preferred approach for security reason is to use the IAM user credentials, but isn't always possible. @@ -58,3 +61,17 @@ assetstore.s3.awsSecretKey = # If the credentials are left empty, # then this setting is ignored and the default AWS region will be used. assetstore.s3.awsRegionName = + +# The target throughput for transfer requests in Gbps. Higher value means more connections will be established with S3. +assetstore.s3.targetThroughputGbps = 10.0 + +# Sets the minimum part size for transfer parts. Decreasing the minimum part size causes multipart transfer to be split +# into a larger number of smaller parts. +assetstore.s3.minPartSizeBytes = 8388608 + +# Specifies the maximum number of S3 connections that should be established during a transfer. +# If not provided, it will be based on targetThroughputGbps +assetstore.s3.maxConcurrency = + +# The algorithm the S3 client will use to create a checksum when doing putObject. +assetstore.s3.s3ChecksumAlgorithm = CRC32 diff --git a/dspace/config/spring/api/bitstore.xml b/dspace/config/spring/api/bitstore.xml index 15bb3ef1580..af50a4c16fc 100644 --- a/dspace/config/spring/api/bitstore.xml +++ b/dspace/config/spring/api/bitstore.xml @@ -27,6 +27,9 @@ + + + @@ -34,6 +37,22 @@ + + + + + + + + + + + + From 418be780e2bf5fd113b2cc49381d0b2dd6bb2288 Mon Sep 17 00:00:00 2001 From: igorbaptist4 Date: Wed, 29 Oct 2025 17:10:49 -0300 Subject: [PATCH 732/939] fix: #8443 - Remove the step 'complete' and fix the processing-class for step 'collection' (cherry picked from commit 677c7b5af6d070bd95f667a4cac24ed1f8b28644) --- dspace/config/migration/item-submissions.xsl | 61 +++++++++++--------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/dspace/config/migration/item-submissions.xsl b/dspace/config/migration/item-submissions.xsl index 9b1de738e1f..aed95992ab1 100644 --- a/dspace/config/migration/item-submissions.xsl +++ b/dspace/config/migration/item-submissions.xsl @@ -34,35 +34,44 @@ configuration file into a DSpace 7.x (or above) item-submission.xml --> - - - - - - - - - - - + + + + + + + + + + + + org.dspace.app.rest.submit.step.CollectionStep + + + + + + + + + + + + + submission-form + + + - - + + submission + + + submission - - submission-form - - - - - submission - - - submission - - - + + From 37633a0e8ff4906d9343abb57a60158377fc5ee9 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 12 Nov 2025 16:54:32 -0600 Subject: [PATCH 733/939] Switch to installing regctl manually instead of using a plugin --- .github/workflows/reusable-docker-build.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml index 8165fe7f840..c7c42edd30f 100644 --- a/.github/workflows/reusable-docker-build.yml +++ b/.github/workflows/reusable-docker-build.yml @@ -79,6 +79,8 @@ env: # Registry used during building of Docker images. (All images are later copied to docker.io registry) # We use GitHub's Container Registry to avoid aggressive rate limits at DockerHub. DOCKER_BUILD_REGISTRY: ghcr.io + # Version of 'regctl' to use for copying images to DOCKER_BUILD_REGISTRY + REGCTL_VERSION: 'v0.9.2' jobs: docker-build: @@ -299,9 +301,9 @@ jobs: # 'regctl' is used to more easily copy the image to DockerHub and obtain the digest from DockerHub # See https://github.com/regclient/regclient/blob/main/docs/regctl.md - name: Install regctl for Docker registry tools - uses: regclient/actions/regctl-installer@main - with: - release: 'v0.8.0' + run: | + curl -L https://github.com/regclient/regclient/releases/download/${REGCTL_VERSION}/regctl-linux-amd64 > /usr/bin/regctl + chmod 755 /usr/bin/regctl # This recreates Docker tags for DockerHub - name: Add Docker metadata for image From 68e66c5544cce89924319a87607d56497304d15b Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 12 Nov 2025 17:08:03 -0600 Subject: [PATCH 734/939] Fix bug in 11541 : Ensure regctl is added to GitHub path rather than installing in a directory that is access restricted --- .github/workflows/reusable-docker-build.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml index c7c42edd30f..86da7b9788f 100644 --- a/.github/workflows/reusable-docker-build.yml +++ b/.github/workflows/reusable-docker-build.yml @@ -79,8 +79,6 @@ env: # Registry used during building of Docker images. (All images are later copied to docker.io registry) # We use GitHub's Container Registry to avoid aggressive rate limits at DockerHub. DOCKER_BUILD_REGISTRY: ghcr.io - # Version of 'regctl' to use for copying images to DOCKER_BUILD_REGISTRY - REGCTL_VERSION: 'v0.9.2' jobs: docker-build: @@ -302,8 +300,11 @@ jobs: # See https://github.com/regclient/regclient/blob/main/docs/regctl.md - name: Install regctl for Docker registry tools run: | - curl -L https://github.com/regclient/regclient/releases/download/${REGCTL_VERSION}/regctl-linux-amd64 > /usr/bin/regctl - chmod 755 /usr/bin/regctl + export REGCTL_VERSION=v0.9.2 + mkdir -p bin + curl -sSLo bin/regctl https://github.com/regclient/regclient/releases/download/${REGCTL_VERSION}/regctl-linux-amd64 + chmod a+x bin/regctl + echo "$(pwd)/bin" >> $GITHUB_PATH # This recreates Docker tags for DockerHub - name: Add Docker metadata for image From 8e9650e0c8edc2b423ba68b2bca5f2ae657de36d Mon Sep 17 00:00:00 2001 From: Marsa Haoua Date: Thu, 13 Nov 2025 18:42:52 +0100 Subject: [PATCH 735/939] Fix 11547: SWORDv2 - read, update and delete operations (cherry picked from commit 9514ded1cd813f442a95df429487c2bfbbd9a4cf) --- .../src/main/java/org/dspace/sword2/SwordUrlManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-swordv2/src/main/java/org/dspace/sword2/SwordUrlManager.java b/dspace-swordv2/src/main/java/org/dspace/sword2/SwordUrlManager.java index eee3627c404..1d49bc27977 100644 --- a/dspace-swordv2/src/main/java/org/dspace/sword2/SwordUrlManager.java +++ b/dspace-swordv2/src/main/java/org/dspace/sword2/SwordUrlManager.java @@ -107,7 +107,7 @@ public String getSwordBaseUrl() "Unable to construct service document urls, due to missing/invalid " + "config in sword2.url and/or dspace.server.url"); } - sUrl = buildSWORDUrl("swordv2"); + sUrl = dspaceUrl + "/" + swordPath; } return sUrl; } From efac0f1c971b999533d2937f536a797fac13db03 Mon Sep 17 00:00:00 2001 From: Elios Buzo Date: Wed, 28 May 2025 10:34:01 +0200 Subject: [PATCH 736/939] [DURACOM-360] Removed the initial article filter (cherry picked from commit a322c5093334343b9ff89efd2f4073dff0b45dd6) --- .../org/dspace/sort/OrderFormatTitle.java | 3 +- .../dspace/sort/OrderFormatTitleMarc21.java | 3 +- .../text/filter/InitialArticleWord.java | 172 --------- .../text/filter/MARC21InitialArticleWord.java | 329 ------------------ .../filter/StandardInitialArticleWord.java | 30 -- 5 files changed, 2 insertions(+), 535 deletions(-) delete mode 100644 dspace-api/src/main/java/org/dspace/text/filter/InitialArticleWord.java delete mode 100644 dspace-api/src/main/java/org/dspace/text/filter/MARC21InitialArticleWord.java delete mode 100644 dspace-api/src/main/java/org/dspace/text/filter/StandardInitialArticleWord.java diff --git a/dspace-api/src/main/java/org/dspace/sort/OrderFormatTitle.java b/dspace-api/src/main/java/org/dspace/sort/OrderFormatTitle.java index b745f0719cb..f6f9aaa38e5 100644 --- a/dspace-api/src/main/java/org/dspace/sort/OrderFormatTitle.java +++ b/dspace-api/src/main/java/org/dspace/sort/OrderFormatTitle.java @@ -9,7 +9,6 @@ import org.dspace.text.filter.DecomposeDiactritics; import org.dspace.text.filter.LowerCaseAndTrim; -import org.dspace.text.filter.StandardInitialArticleWord; import org.dspace.text.filter.StripDiacritics; import org.dspace.text.filter.TextFilter; @@ -20,7 +19,7 @@ */ public class OrderFormatTitle extends AbstractTextFilterOFD { { - filters = new TextFilter[] {new StandardInitialArticleWord(), + filters = new TextFilter[] { new DecomposeDiactritics(), new StripDiacritics(), new LowerCaseAndTrim()}; diff --git a/dspace-api/src/main/java/org/dspace/sort/OrderFormatTitleMarc21.java b/dspace-api/src/main/java/org/dspace/sort/OrderFormatTitleMarc21.java index fa9ba297258..9148ca2a988 100644 --- a/dspace-api/src/main/java/org/dspace/sort/OrderFormatTitleMarc21.java +++ b/dspace-api/src/main/java/org/dspace/sort/OrderFormatTitleMarc21.java @@ -9,7 +9,6 @@ import org.dspace.text.filter.DecomposeDiactritics; import org.dspace.text.filter.LowerCaseAndTrim; -import org.dspace.text.filter.MARC21InitialArticleWord; import org.dspace.text.filter.StripDiacritics; import org.dspace.text.filter.StripLeadingNonAlphaNum; import org.dspace.text.filter.TextFilter; @@ -21,7 +20,7 @@ */ public class OrderFormatTitleMarc21 extends AbstractTextFilterOFD { { - filters = new TextFilter[] {new MARC21InitialArticleWord(), + filters = new TextFilter[] { new DecomposeDiactritics(), new StripDiacritics(), new StripLeadingNonAlphaNum(), diff --git a/dspace-api/src/main/java/org/dspace/text/filter/InitialArticleWord.java b/dspace-api/src/main/java/org/dspace/text/filter/InitialArticleWord.java deleted file mode 100644 index 167b201e0f7..00000000000 --- a/dspace-api/src/main/java/org/dspace/text/filter/InitialArticleWord.java +++ /dev/null @@ -1,172 +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.text.filter; - -/** - * Abstract class for implementing initial article word filters - * Allows you to create new classes with their own rules for mapping - * languages to article word lists. - * - * @author Graham Triggs - */ -public abstract class InitialArticleWord implements TextFilter { - /** - * When no language is passed, use null and let implementation decide what to do - */ - @Override - public String filter(String str) { - return filter(str, null); - } - - /** - * Do an initial definite/indefinite article filter on the passed string. - * On matching an initial word, can strip or move to the end, depending on the - * configuration of the implementing class. - * - * @param str The string to parse - * @param lang The language of the passed string - * @return String The filtered string - */ - @Override - public String filter(String str, String lang) { - // Get the list of article words for this language - String[] articleWordArr = getArticleWords(lang); - - // If we have an article word array, process the string - if (articleWordArr != null && articleWordArr.length > 0) { - String initialArticleWord = null; - int curPos = 0; - int initialStart = -1; - int initialEnd = -1; - - // Iterate through the characters until we find something significant, or hit the end - while (initialEnd < 0 && curPos < str.length()) { - // Have we found a significant character - if (Character.isLetterOrDigit(str.charAt(curPos))) { - // Mark this as the cut point for the initial word - initialStart = curPos; - - // Loop through the article words looking for a match - for (int idx = 0; initialEnd < 0 && idx < articleWordArr.length; idx++) { - // Extract a fragment from the string to test - // Must be same length as the article word - if (idx > 1 && initialArticleWord != null) { - // Only need to do so if we haven't already got one - // of the right length - if (initialArticleWord.length() != articleWordArr[idx].length()) { - initialArticleWord = extractText(str, curPos, articleWordArr[idx].length()); - } - } else { - initialArticleWord = extractText(str, curPos, articleWordArr[idx].length()); - } - - // Does the fragment match an article word? - if (initialArticleWord != null && initialArticleWord.equalsIgnoreCase(articleWordArr[idx])) { - // Check to see if the next character in the source - // is a whitespace - boolean isNextWhitespace = Character.isWhitespace( - str.charAt(curPos + articleWordArr[idx].length()) - ); - - // Check to see if the last character of the article word is a letter or digit - boolean endsLetterOrDigit = Character - .isLetterOrDigit(initialArticleWord.charAt(initialArticleWord.length() - 1)); - - // If the last character of the article word is a letter or digit, - // then it must be followed by whitespace, if not, it can be anything - // Setting endPos signifies that we have found an article word - if (endsLetterOrDigit && isNextWhitespace) { - initialEnd = curPos + initialArticleWord.length(); - } else if (!endsLetterOrDigit) { - initialEnd = curPos + initialArticleWord.length(); - } - } - } - - // Quit the loop, as we have a significant character - break; - } - - // Keep going - curPos++; - } - - // If endPos is positive, then we've found an article word - if (initialEnd > 0) { - // Find a cut point in the source string, removing any whitespace after the article word - int cutPos = initialEnd; - while (cutPos < str.length() && Character.isWhitespace(str.charAt(cutPos))) { - cutPos++; - } - - // Are we stripping the article word? - if (stripInitialArticle) { - // Yes, simply return everything after the cut - return str.substring(cutPos); - } else { - // No - move the initial article word to the end - return new StringBuilder(str.substring(cutPos)) - .append(wordSeparator) - .append(str.substring(initialStart, initialEnd)) - .toString(); - } - } - } - - // Didn't do any processing, or didn't find an initial article word - // Return the original string - return str; - } - - protected InitialArticleWord(boolean stripWord) { - this.wordSeparator = ", "; - stripInitialArticle = stripWord; - } - - protected InitialArticleWord() { - this.wordSeparator = ", "; - stripInitialArticle = false; - } - - /** - * Abstract method to get the list of words to use in the initial word filter - * - * @param lang The language to retrieve article words for - * @return An array of definite/indefinite article words - */ - protected abstract String[] getArticleWords(String lang); - // Separator to use when appending article to end - private final String wordSeparator; - - // Flag to signify initial article word should be removed - // If false, then the initial article word is appended to the end - private boolean stripInitialArticle = false; - - /** - * Helper method to extract text from a string. - * Ensures that there is significant data (ie. non-whitespace) - * after the segment requested. - * - * @param str - * @param pos - * @param len - * @return - */ - private String extractText(String str, int pos, int len) { - int testPos = pos + len; - while (testPos < str.length() && Character.isWhitespace(str.charAt(testPos))) { - testPos++; - } - - if (testPos < str.length()) { - return str.substring(pos, pos + len); - } - - return null; - } -} diff --git a/dspace-api/src/main/java/org/dspace/text/filter/MARC21InitialArticleWord.java b/dspace-api/src/main/java/org/dspace/text/filter/MARC21InitialArticleWord.java deleted file mode 100644 index c82b9ccfcf8..00000000000 --- a/dspace-api/src/main/java/org/dspace/text/filter/MARC21InitialArticleWord.java +++ /dev/null @@ -1,329 +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.text.filter; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.apache.commons.lang3.ArrayUtils; -import org.apache.commons.lang3.StringUtils; -import org.dspace.services.factory.DSpaceServicesFactory; - -/** - * Implements MARC 21 standards to disregard initial - * definite or indefinite article in sorting. - * - * Note: This only works for languages defined with IANA code entries. - * - * @author Graham Triggs - */ -public class MARC21InitialArticleWord extends InitialArticleWord { - public MARC21InitialArticleWord() { - // Default behaviour is to strip the initial word completely - super(true); - } - - public MARC21InitialArticleWord(boolean stripWord) { - super(stripWord); - } - - /** - * Return the list of definite and indefinite article codes - * for this language. - */ - @Override - protected String[] getArticleWords(String lang) { - // No language - no words - if (StringUtils.isEmpty(lang)) { - return defaultWords; - } - - Language l = Language.getLanguage(lang); - - // Is the language in our map? - if (l != null && ianaArticleMap.containsKey(l.IANA)) { - // Get the list of words for this language - ArticlesForLang articles = ianaArticleMap.get(l.IANA); - - if (articles != null) { - return articles.words; - } - } - - return null; - } - - // Mapping of IANA codes to article word lists - private static Map ianaArticleMap = new HashMap(); - - private static String[] defaultWords = null; - - // Static initialisation - convert word -> languages map - // into language -> words map - static { - /* Define a mapping for article words to the languages that have them. - * Take from: http://www.loc.gov/marc/bibliographic/bdapp-e.html - */ - Object[][] articleWordArray = { - {"a", Language.ENGLISH, Language.GALICIAN, Language.HUNGARIAN, Language.PORTUGUESE, Language.ROMANIAN, - Language.SCOTS, Language.YIDDISH}, - {"a'", Language.SCOTTISH_GAELIC}, - {"al", Language.ROMANIAN}, - {"al-", Language.ARABIC, Language.BALUCHI, Language.BRAHUI, Language.PANJABI, Language.PERSIAN, - Language.TURKISH, Language.URDU}, - {"am", Language.SCOTTISH_GAELIC}, - {"an", Language.ENGLISH, Language.IRISH, Language.SCOTS, Language.SCOTTISH_GAELIC, Language.YIDDISH}, - {"an t-", Language.IRISH, Language.SCOTTISH_GAELIC}, - {"ane", Language.SCOTS}, - {"ang", Language.TAGALOG}, - {"ang mga", Language.TAGALOG}, - {"as", Language.GALICIAN, Language.PORTUGUESE}, - {"az", Language.HUNGARIAN}, - {"bat", Language.BASQUE}, - {"bir", Language.TURKISH}, - {"d'", Language.ENGLISH}, - {"da", Language.SHETLAND_ENGLISH}, - {"das", Language.GERMAN}, - {"de", Language.DANISH, Language.DUTCH, Language.ENGLISH, Language.FRISIAN, Language.NORWEGIAN, - Language.SWEDISH}, - {"dei", Language.NORWEGIAN}, - {"dem", Language.GERMAN}, - {"den", Language.DANISH, Language.GERMAN, Language.NORWEGIAN, Language.SWEDISH}, - {"der", Language.GERMAN, Language.YIDDISH}, - {"des", Language.GERMAN, Language.WALLOON}, - {"det", Language.DANISH, Language.NORWEGIAN, Language.SWEDISH}, - {"di", Language.YIDDISH}, - {"die", Language.AFRIKAANS, Language.GERMAN, Language.YIDDISH}, - {"dos", Language.YIDDISH}, - {"e", Language.NORWEGIAN}, - {"e", Language.FRISIAN}, // should be 'e - leading apostrophes are ignored - {"een", Language.DUTCH}, - {"eene", Language.DUTCH}, - {"egy", Language.HUNGARIAN}, - {"ei", Language.NORWEGIAN}, - {"ein", Language.GERMAN, Language.NORWEGIAN, Language.WALLOON}, - {"eine", Language.GERMAN}, - {"einem", Language.GERMAN}, - {"einen", Language.GERMAN}, - {"einer", Language.GERMAN}, - {"eines", Language.GERMAN}, - {"eit", Language.NORWEGIAN}, - {"el", Language.CATALAN, Language.SPANISH}, - {"el-", Language.ARABIC}, - {"els", Language.CATALAN}, - {"en", Language.CATALAN, Language.DANISH, Language.NORWEGIAN, Language.SWEDISH}, - {"enne", Language.WALLOON}, - {"et", Language.DANISH, Language.NORWEGIAN}, - {"ett", Language.SWEDISH}, - {"eyn", Language.YIDDISH}, - {"eyne", Language.YIDDISH}, - {"gl'", Language.ITALIAN}, - {"gli", Language.PROVENCAL}, - {"ha-", Language.HEBREW}, - {"hai", Language.CLASSICAL_GREEK, Language.GREEK}, - {"he", Language.HAWAIIAN}, - {"h\u0113", Language.CLASSICAL_GREEK, Language.GREEK}, // e macron - {"he-", Language.HEBREW}, - {"heis", Language.GREEK}, - {"hen", Language.GREEK}, - {"hena", Language.GREEK}, - {"henas", Language.GREEK}, - {"het", Language.DUTCH}, - {"hin", Language.ICELANDIC}, - {"hina", Language.ICELANDIC}, - {"hinar", Language.ICELANDIC}, - {"hinir", Language.ICELANDIC}, - {"hinn", Language.ICELANDIC}, - {"hinna", Language.ICELANDIC}, - {"hinnar", Language.ICELANDIC}, - {"hinni", Language.ICELANDIC}, - {"hins", Language.ICELANDIC}, - {"hinu", Language.ICELANDIC}, - {"hinum", Language.ICELANDIC}, - {"hi\u01d2", Language.ICELANDIC}, - {"ho", Language.CLASSICAL_GREEK, Language.GREEK}, - {"hoi", Language.CLASSICAL_GREEK, Language.GREEK}, - {"i", Language.ITALIAN}, - {"ih'", Language.PROVENCAL}, - {"il", Language.ITALIAN, Language.PROVENCAL_OCCITAN}, - {"il-", Language.MALTESE}, - {"in", Language.FRISIAN}, - {"it", Language.FRISIAN}, - {"ka", Language.HAWAIIAN}, - {"ke", Language.HAWAIIAN}, - {"l'", Language.CATALAN, Language.FRENCH, Language.ITALIAN, Language.PROVENCAL_OCCITAN, Language.WALLOON}, - {"l-", Language.MALTESE}, - {"la", Language.CATALAN, Language.ESPERANTO, Language.FRENCH, Language.ITALIAN, Language.PROVENCAL_OCCITAN, - Language.SPANISH}, - {"las", Language.PROVENCAL_OCCITAN, Language.SPANISH}, - {"le", Language.FRENCH, Language.ITALIAN, Language.PROVENCAL_OCCITAN}, - {"les", Language.CATALAN, Language.FRENCH, Language.PROVENCAL_OCCITAN, Language.WALLOON}, - {"lh", Language.PROVENCAL_OCCITAN}, - {"lhi", Language.PROVENCAL_OCCITAN}, - {"li", Language.PROVENCAL_OCCITAN}, - {"lis", Language.PROVENCAL_OCCITAN}, - {"lo", Language.ITALIAN, Language.PROVENCAL_OCCITAN, Language.SPANISH}, - {"los", Language.PROVENCAL_OCCITAN, Language.SPANISH}, - {"lou", Language.PROVENCAL_OCCITAN}, - {"lu", Language.PROVENCAL_OCCITAN}, - {"mga", Language.TAGALOG}, - {"m\u0303ga", Language.TAGALOG}, - {"mia", Language.GREEK}, - {"n", Language.AFRIKAANS, Language.DUTCH, Language.FRISIAN}, // should be 'n - leading - // apostrophes are ignored - {"na", Language.HAWAIIAN, Language.IRISH, Language.SCOTTISH_GAELIC}, - {"na h-", Language.IRISH, Language.SCOTTISH_GAELIC}, - {"nje", Language.ALBANIAN}, - {"ny", Language.MALAGASY}, - {"o", Language.NEAPOLITAN_ITALIAN}, // should be 'o - leading apostrophes are ignored - {"o", Language.GALICIAN, Language.HAWAIIAN, Language.PORTUGUESE, Language.ROMANIAN}, - {"os", Language.PORTUGUESE}, - {"r", Language.ICELANDIC}, // should be 'r - leading apostrophes are ignored - {"s", Language.GERMAN}, // should be 's - leading apostrophes are ignored - {"sa", Language.TAGALOG}, - {"sa mga", Language.TAGALOG}, - {"si", Language.TAGALOG}, - {"sin\u00e1", Language.TAGALOG}, - {"t", Language.DUTCH, Language.FRISIAN}, // should be 't - leading apostrophes are ignored - {"ta", Language.CLASSICAL_GREEK, Language.GREEK}, - {"tais", Language.CLASSICAL_GREEK}, - {"tas", Language.CLASSICAL_GREEK}, - {"t\u0113", Language.CLASSICAL_GREEK}, // e macron - {"t\u0113n", Language.CLASSICAL_GREEK, Language.GREEK}, // e macron - {"t\u0113s", Language.CLASSICAL_GREEK, Language.GREEK}, // e macron - {"the", Language.ENGLISH}, - {"t\u014d", Language.CLASSICAL_GREEK, Language.GREEK}, // o macron - {"tois", Language.CLASSICAL_GREEK}, - {"t\u014dn", Language.CLASSICAL_GREEK, Language.GREEK}, // o macron - {"tou", Language.CLASSICAL_GREEK, Language.GREEK}, - {"um", Language.PORTUGUESE}, - {"uma", Language.PORTUGUESE}, - {"un", Language.CATALAN, Language.FRENCH, Language.ITALIAN, Language.PROVENCAL_OCCITAN, Language.ROMANIAN, - Language.SPANISH}, - {"un'", Language.ITALIAN}, - {"una", Language.CATALAN, Language.ITALIAN, Language.PROVENCAL_OCCITAN, Language.SPANISH}, - {"une", Language.FRENCH}, - {"unei", Language.ROMANIAN}, - {"unha", Language.GALICIAN}, - {"uno", Language.ITALIAN, Language.PROVENCAL_OCCITAN}, - {"uns", Language.PROVENCAL_OCCITAN}, - {"unui", Language.ROMANIAN}, - {"us", Language.PROVENCAL_OCCITAN}, - {"y", Language.WELSH}, - {"ye", Language.ENGLISH}, - {"yr", Language.WELSH} - }; - - // Initialize the lang -> article map - ianaArticleMap = new HashMap(); - - int wordIdx = 0; - int langIdx = 0; - - // Iterate through word/language array - // Generate temporary language map - Map> langWordMap = new HashMap>(); - for (wordIdx = 0; wordIdx < articleWordArray.length; wordIdx++) { - for (langIdx = 1; langIdx < articleWordArray[wordIdx].length; langIdx++) { - Language lang = (Language) articleWordArray[wordIdx][langIdx]; - - if (lang != null && lang.IANA.length() > 0) { - List words = langWordMap.get(lang); - - if (words == null) { - words = new ArrayList(); - langWordMap.put(lang, words); - } - - // Add language to list if we haven't done so already - if (!words.contains(articleWordArray[wordIdx][0])) { - words.add((String) articleWordArray[wordIdx][0]); - } - } - } - } - - // Iterate through languages - for (Map.Entry> langToWord : langWordMap.entrySet()) { - Language lang = langToWord.getKey(); - List wordList = langToWord.getValue(); - - // Convert the list into an array of strings - String[] words = new String[wordList.size()]; - - for (int idx = 0; idx < wordList.size(); idx++) { - words[idx] = wordList.get(idx); - } - - // Sort the array into length order - longest to shortest - // This ensures maximal matching on the article words - Arrays.sort(words, new MARC21InitialArticleWord.InverseLengthComparator()); - - // Add language/article entry to map - ianaArticleMap.put(lang.IANA, new MARC21InitialArticleWord.ArticlesForLang(lang, words)); - } - - // Setup default stop words for null languages - String[] defaultLangs = DSpaceServicesFactory.getInstance().getConfigurationService() - .getArrayProperty("marc21wordfilter.defaultlang"); - if (ArrayUtils.isNotEmpty(defaultLangs)) { - int wordCount = 0; - ArticlesForLang[] afl = new ArticlesForLang[defaultLangs.length]; - - for (int idx = 0; idx < afl.length; idx++) { - Language l = Language.getLanguage(defaultLangs[idx]); - if (l != null && ianaArticleMap.containsKey(l.IANA)) { - afl[idx] = ianaArticleMap.get(l.IANA); - if (afl[idx] != null) { - wordCount += afl[idx].words.length; - } - } - } - - if (wordCount > 0) { - int destPos = 0; - defaultWords = new String[wordCount]; - for (int idx = 0; idx < afl.length; idx++) { - if (afl[idx] != null) { - System.arraycopy(afl[idx].words, 0, defaultWords, destPos, afl[idx].words.length); - destPos += afl[idx].words.length; - } - } - } - } - } - - // Wrapper class for inserting word arrays into a map - private static class ArticlesForLang { - final Language lang; - final String[] words; - - ArticlesForLang(Language lang, String[] words) { - this.lang = lang; - this.words = (String[]) ArrayUtils.clone(words); - } - } - - // Compare strings according to their length - longest to shortest - private static class InverseLengthComparator implements Comparator, Serializable { - @Override - public int compare(Object arg0, Object arg1) { - return ((String) arg1).length() - ((String) arg0).length(); - } - - ; - - } - - ; -} diff --git a/dspace-api/src/main/java/org/dspace/text/filter/StandardInitialArticleWord.java b/dspace-api/src/main/java/org/dspace/text/filter/StandardInitialArticleWord.java deleted file mode 100644 index ade72b150f5..00000000000 --- a/dspace-api/src/main/java/org/dspace/text/filter/StandardInitialArticleWord.java +++ /dev/null @@ -1,30 +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.text.filter; - -/** - * Implements existing DSpace initial article word behaviour - * - * Note: This only works for languages defined with ISO code entries. - * - * @author Graham Triggs - */ -public class StandardInitialArticleWord extends InitialArticleWord { - private static final String[] articleWords = {"the", "an", "a"}; - - @Override - protected String[] getArticleWords(String lang) { - if (lang != null && lang.startsWith("en")) { - return articleWords; - } - - return null; - } - -} - From d4d430ead7acd21479658383530309037302321d Mon Sep 17 00:00:00 2001 From: Elios Buzo Date: Wed, 28 May 2025 11:46:19 +0200 Subject: [PATCH 737/939] [DURACOM-360] Removed unused class (cherry picked from commit 57ef735a51ff30673fce6ca04a7674f45755ed7d) --- .../java/org/dspace/text/filter/Language.java | 142 ------------------ 1 file changed, 142 deletions(-) delete mode 100644 dspace-api/src/main/java/org/dspace/text/filter/Language.java diff --git a/dspace-api/src/main/java/org/dspace/text/filter/Language.java b/dspace-api/src/main/java/org/dspace/text/filter/Language.java deleted file mode 100644 index 9be68d2ddfb..00000000000 --- a/dspace-api/src/main/java/org/dspace/text/filter/Language.java +++ /dev/null @@ -1,142 +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.text.filter; - -import java.util.HashMap; -import java.util.Map; - -/** - * Define languages - both as IANA and ISO639-2 codes - * - * @author Graham Triggs - */ -public class Language { - public final String IANA; - public final String ISO639_1; - public final String ISO639_2; - - public static final Language AFRIKAANS = Language.create("af", "af", "afr"); - public static final Language ALBANIAN = Language.create("sq", "sq", "alb"); - public static final Language ARABIC = Language.create("ar", "ar", "ara"); - public static final Language BALUCHI = Language.create("bal", "", "bal"); - public static final Language BASQUE = Language.create("eu", "", "baq"); - public static final Language BRAHUI = Language.create("", "", ""); - public static final Language CATALAN = Language.create("ca", "ca", "cat"); - public static final Language CLASSICAL_GREEK = Language.create("grc", "", "grc"); - public static final Language DANISH = Language.create("da", "da", "dan"); - public static final Language DUTCH = Language.create("nl", "ni", "dut"); - public static final Language ENGLISH = Language.create("en", "en", "eng"); - public static final Language ESPERANTO = Language.create("eo", "eo", "epo"); - public static final Language FRENCH = Language.create("fr", "fr", "fre"); - public static final Language FRISIAN = Language.create("fy", "fy", "fri"); - public static final Language GALICIAN = Language.create("gl", "gl", "glg"); - public static final Language GERMAN = Language.create("de", "de", "ger"); - public static final Language GREEK = Language.create("el", "el", "gre"); - public static final Language HAWAIIAN = Language.create("haw", "", "haw"); - public static final Language HEBREW = Language.create("he", "he", "heb"); - public static final Language HUNGARIAN = Language.create("hu", "hu", "hun"); - public static final Language ICELANDIC = Language.create("is", "is", "ice"); - public static final Language IRISH = Language.create("ga", "ga", "gle"); - public static final Language ITALIAN = Language.create("it", "it", "ita"); - public static final Language MALAGASY = Language.create("mg", "mg", "mlg"); - public static final Language MALTESE = Language.create("mt", "mt", "mlt"); - public static final Language NEAPOLITAN_ITALIAN = Language.create("nap", "", "nap"); - public static final Language NORWEGIAN = Language.create("no", "no", "nor"); - public static final Language PORTUGUESE = Language.create("pt", "pt", "por"); - public static final Language PANJABI = Language.create("pa", "pa", "pan"); - public static final Language PERSIAN = Language.create("fa", "fa", "per"); - public static final Language PROVENCAL = Language.create("pro", "", "pro"); - public static final Language PROVENCAL_OCCITAN = Language.create("oc", "oc", "oci"); - public static final Language ROMANIAN = Language.create("ro", "ro", "rum"); - public static final Language SCOTS = Language.create("sco", "", "sco"); - public static final Language SCOTTISH_GAELIC = Language.create("gd", "gd", "gae"); - public static final Language SHETLAND_ENGLISH = Language.create("", "", ""); - public static final Language SPANISH = Language.create("es", "es", "spa"); - public static final Language SWEDISH = Language.create("sv", "sv", "swe"); - public static final Language TAGALOG = Language.create("tl", "tl", "tgl"); - public static final Language TURKISH = Language.create("tr", "tr", "tur"); - public static final Language URDU = Language.create("ur", "ur", "urd"); - public static final Language WALLOON = Language.create("wa", "wa", "wln"); - public static final Language WELSH = Language.create("cy", "cy", "wel"); - public static final Language YIDDISH = Language.create("yi", "yi", "yid"); - - public static Language getLanguage(String lang) { - return LanguageMaps.getLanguage(lang); - } - - public static Language getLanguageForIANA(String iana) { - return LanguageMaps.getLanguageForIANA(iana); - } - - public static Language getLanguageForISO639_2(String iso) { - return LanguageMaps.getLanguageForISO639_2(iso); - } - - private static synchronized Language create(String iana, String iso639_1, String iso639_2) { - Language lang = LanguageMaps.getLanguageForIANA(iana); - - lang = (lang != null ? lang : LanguageMaps.getLanguageForISO639_1(iso639_1)); - lang = (lang != null ? lang : LanguageMaps.getLanguageForISO639_2(iso639_2)); - - return (lang != null ? lang : new Language(iana, iso639_1, iso639_2)); - } - - private static class LanguageMaps { - private static final Map langMapIANA = new HashMap(); - private static final Map langMapISO639_1 = new HashMap(); - private static final Map langMapISO639_2 = new HashMap(); - - static void add(Language l) { - if (l.IANA != null && l.IANA.length() > 0 && !langMapIANA.containsKey(l.IANA)) { - langMapIANA.put(l.IANA, l); - } - - if (l.ISO639_1 != null && l.ISO639_1.length() > 0 && !langMapISO639_1.containsKey(l.ISO639_1)) { - langMapISO639_1.put(l.ISO639_1, l); - } - - if (l.ISO639_2 != null && l.ISO639_2.length() > 0 && !langMapISO639_2.containsKey(l.ISO639_2)) { - langMapISO639_2.put(l.ISO639_2, l); - } - } - - public static Language getLanguage(String lang) { - if (langMapIANA.containsKey(lang)) { - return langMapIANA.get(lang); - } - - return langMapISO639_2.get(lang); - } - - public static Language getLanguageForIANA(String iana) { - return langMapIANA.get(iana); - } - - public static Language getLanguageForISO639_1(String iso) { - return langMapISO639_1.get(iso); - } - - public static Language getLanguageForISO639_2(String iso) { - return langMapISO639_2.get(iso); - } - } - - private Language(String iana, String iso639_1, String iso639_2) { - IANA = iana; - ISO639_1 = iso639_1; - ISO639_2 = iso639_2; - - LanguageMaps.add(this); - } - - private Language() { - IANA = null; - ISO639_1 = null; - ISO639_2 = null; - } -} From d791eb6b720a9ddce0c843a3d75f1455c0688bd5 Mon Sep 17 00:00:00 2001 From: Miika Nurminen Date: Sat, 15 Feb 2025 11:56:57 +0200 Subject: [PATCH 738/939] Clarify command line options help and messages in checker and checker-emailer Based on discussion in #10008 and #8999 with arvoConsultores's original Messages patch. Adds error message if mutually exclusive checker options are attempted to be used in combination. Adds note to log on sent report emails and unifies email report formatting. (cherry picked from commit c5000903617ed10221297df9b00abfbced9b58a5) --- .../dspace/app/checker/ChecksumChecker.java | 53 +++++++++++++------ .../dspace/checker/DailyReportEmailer.java | 41 +++++++------- .../checker/SimpleReporterServiceImpl.java | 2 +- .../src/main/resources/Messages.properties | 10 ++-- 4 files changed, 64 insertions(+), 42 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/checker/ChecksumChecker.java b/dspace-api/src/main/java/org/dspace/app/checker/ChecksumChecker.java index ec024c34526..160d23e3220 100644 --- a/dspace-api/src/main/java/org/dspace/app/checker/ChecksumChecker.java +++ b/dspace-api/src/main/java/org/dspace/app/checker/ChecksumChecker.java @@ -98,7 +98,7 @@ public static void main(String[] args) throws SQLException { options.addOption("h", "help", false, "Help"); options.addOption("d", "duration", true, "Checking duration"); options.addOption("c", "count", true, "Check count"); - options.addOption("a", "handle", true, "Specify a handle to check"); + options.addOption("i", "handle", true, "Specify a handle to check"); options.addOption("v", "verbose", false, "Report all processing"); Option option; @@ -106,7 +106,7 @@ public static void main(String[] args) throws SQLException { option = Option.builder("b") .longOpt("bitstream-ids") .hasArgs() - .desc("Space separated list of bitstream ids") + .desc("Space separated list of bitstream UUIDs") .build(); options.addOption(option); @@ -132,6 +132,17 @@ public static void main(String[] args) throws SQLException { try { context = new Context(); + int mutuallyExclusiveOpts = 0; + for (char c : new char[]{'l', 'L', 'd', 'b', 'i','c'}) { + if (line.hasOption(c)) { + mutuallyExclusiveOpts++; + } + } + if (mutuallyExclusiveOpts > 1) { + System.err.println("Please use only one option of -l, -L, -d, -b, -i, or -c"); + LOG.error("Please use only one option of -l, -L, -d, -b, -i, or -c"); + System.exit(1); + } // Prune stage if (line.hasOption('p')) { @@ -169,13 +180,13 @@ public static void main(String[] args) throws SQLException { bitstreams.add(bitstreamService.find(context, UUID.fromString(ids[i]))); } catch (NumberFormatException nfe) { System.err.println("The following argument: " + ids[i] - + " is not an integer"); + + " is not an UUID"); System.exit(0); } } dispatcher = new IteratorDispatcher(bitstreams.iterator()); - } else if (line.hasOption('a')) { - dispatcher = new HandleDispatcher(context, line.getOptionValue('a')); + } else if (line.hasOption('i')) { + dispatcher = new HandleDispatcher(context, line.getOptionValue('i')); } else if (line.hasOption('d')) { // run checker process for specified duration try { @@ -185,6 +196,8 @@ public static void main(String[] args) throws SQLException { + Utils.parseDuration(line .getOptionValue('d')))); } catch (Exception e) { + System.err.println("Couldn't parse " + line.getOptionValue('d') + + " as a duration"); LOG.fatal("Couldn't parse " + line.getOptionValue('d') + " as a duration: ", e); System.exit(0); @@ -228,18 +241,24 @@ public static void main(String[] args) throws SQLException { private static void printHelp(Options options) { HelpFormatter myhelp = new HelpFormatter(); - myhelp.printHelp("Checksum Checker\n", options); - System.out.println("\nSpecify a duration for checker process, using s(seconds)," - + "m(minutes), or h(hours): ChecksumChecker -d 30s" - + " OR ChecksumChecker -d 30m" - + " OR ChecksumChecker -d 2h"); - System.out.println("\nSpecify bitstream IDs: ChecksumChecker -b 13 15 17 20"); - System.out.println("\nLoop once through all bitstreams: " - + "ChecksumChecker -l"); - System.out.println("\nLoop continuously through all bitstreams: ChecksumChecker -L"); - System.out.println("\nCheck a defined number of bitstreams: ChecksumChecker -c 10"); - System.out.println("\nReport all processing (verbose)(default reports only errors): ChecksumChecker -v"); - System.out.println("\nDefault (no arguments) is equivalent to '-c 1'"); + myhelp.printHelp("checker\n", options); + System.out.println("\nChecksum Checker usage examples:"); + System.out.println("\nThe following options are mutually exclusive:"); + System.out.println(" - Specify a duration for checker process, using s(seconds)," + + "m(minutes), or h(hours): checker -d 30s" + + " OR checker -d 30m" + + " OR checker -d 2h"); + System.out.println(" - Specify bitstream UUIDs: checker -b 550e8400-e29b-41d4-a716-446655440000" + + " f3f2e850-b5d4-11ef-ac7e-96584d5248b2"); + System.out.println(" - Specify handle: checker -i 12345/100"); + System.out.println(" - Loop once through all bitstreams: " + + "checker -l"); + System.out.println(" - Loop continuously through all bitstreams: checker -L"); + System.out.println(" - Check a defined number of bitstreams: checker -c 10"); + System.out.println("\nThe following options can be used in combination with others above:"); + System.out.println(" - Report all processing to checker.log (by default logs only errors): checker -v"); + System.out.println(" - Prune old results from the database: checker -p"); + System.out.println("\nDefault (no arguments) is equivalent to 'checker -c 1'\n"); System.exit(0); } diff --git a/dspace-api/src/main/java/org/dspace/checker/DailyReportEmailer.java b/dspace-api/src/main/java/org/dspace/checker/DailyReportEmailer.java index b291232e8b5..f32ddc23238 100644 --- a/dspace-api/src/main/java/org/dspace/checker/DailyReportEmailer.java +++ b/dspace-api/src/main/java/org/dspace/checker/DailyReportEmailer.java @@ -75,6 +75,7 @@ public void sendReport(File attachment, int numberOfBitstreams) email.setContent("Checker Report", "report is attached ..."); email.addAttachment(attachment, "checksum_checker_report.txt"); email.addRecipient(configurationService.getProperty("mail.admin")); + log.info("Sending checker report email to " + configurationService.getProperty("mail.admin")); email.send(); } } @@ -109,18 +110,18 @@ public static void main(String[] args) { Options options = new Options(); options.addOption("h", "help", false, "Help"); - options.addOption("d", "Deleted", false, - "Send E-mail report for all bitstreams set as deleted for today"); - options.addOption("m", "Missing", false, - "Send E-mail report for all bitstreams not found in assetstore for today"); - options.addOption("c", "Changed", false, - "Send E-mail report for all bitstreams where checksum has been changed for today"); - options.addOption("a", "All", false, - "Send all E-mail reports"); - options.addOption("u", "Unchecked", false, - "Send the Unchecked bitstream report"); - options.addOption("n", "Not Processed", false, - "Send E-mail report for all bitstreams set to longer be processed for today"); + options.addOption("d", "deleted", false, + "Send email report for all bitstreams set as deleted for today"); + options.addOption("m", "missing", false, + "Send email report for all bitstreams not found in assetstore for today"); + options.addOption("c", "changed", false, + "Send email report for all bitstreams where checksum has been changed for today"); + options.addOption("a", "all", false, + "Send all email reports (used by default)"); + options.addOption("u", "unchecked", false, + "Send the Unchecked bitstream email report"); + options.addOption("n", "not-processed", false, + "Send email report for all bitstreams set to longer be processed for today"); try { line = parser.parse(options, args); @@ -133,13 +134,15 @@ public static void main(String[] args) { if (line.hasOption('h')) { HelpFormatter myhelp = new HelpFormatter(); - myhelp.printHelp("Checksum Reporter\n", options); - System.out.println("\nSend Deleted bitstream email report: DailyReportEmailer -d"); - System.out.println("\nSend Missing bitstreams email report: DailyReportEmailer -m"); - System.out.println("\nSend Checksum Changed email report: DailyReportEmailer -c"); - System.out.println("\nSend bitstream not to be processed email report: DailyReportEmailer -n"); - System.out.println("\nSend Un-checked bitstream report: DailyReportEmailer -u"); - System.out.println("\nSend All email reports: DailyReportEmailer"); + myhelp.printHelp("checker-emailer\n", options); + System.out.println("\nChecksum Checker Reporter usage examples:\n"); + System.out.println(" - Send all email reports: checker-emailer -a"); + System.out.println(" - Send deleted bitstream email report: checker-emailer -d"); + System.out.println(" - Send missing bitstreams email report: checker-emailer -m"); + System.out.println(" - Send checksum changed email report: checker-emailer -c"); + System.out.println(" - Send bitstream not to be processed email report: checker-emailer -n"); + System.out.println(" - Send unchecked bitstream email report: checker-emailer -u"); + System.out.println("\nDefault (no arguments) is equivalent to 'checker-emailer -a'\n"); System.exit(0); } diff --git a/dspace-api/src/main/java/org/dspace/checker/SimpleReporterServiceImpl.java b/dspace-api/src/main/java/org/dspace/checker/SimpleReporterServiceImpl.java index ddefb28e1b5..6c69764fdc7 100644 --- a/dspace-api/src/main/java/org/dspace/checker/SimpleReporterServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/checker/SimpleReporterServiceImpl.java @@ -70,6 +70,7 @@ public int getDeletedBitstreamReport(Context context, Date startDate, Date endDa osw.write("\n"); osw.write(msg("deleted-bitstream-intro")); + osw.write(" "); osw.write(applyDateFormatShort(startDate)); osw.write(" "); osw.write(msg("date-range-to")); @@ -111,7 +112,6 @@ public int getChangedChecksumReport(Context context, Date startDate, Date endDat osw.write("\n"); osw.write(msg("checksum-did-not-match")); osw.write(" "); - osw.write("\n"); osw.write(applyDateFormatShort(startDate)); osw.write(" "); osw.write(msg("date-range-to")); diff --git a/dspace-api/src/main/resources/Messages.properties b/dspace-api/src/main/resources/Messages.properties index efbbeedde05..9d15bd0621a 100644 --- a/dspace-api/src/main/resources/Messages.properties +++ b/dspace-api/src/main/resources/Messages.properties @@ -72,20 +72,20 @@ org.dspace.checker.ResultsLogger.store-number org.dspace.checker.ResultsLogger.to-be-processed = To be processed org.dspace.checker.ResultsLogger.user-format-description = User format description org.dspace.checker.SimpleReporterImpl.bitstream-id = Bitstream Id -org.dspace.checker.SimpleReporterImpl.bitstream-not-found-report = The following is a BITSTREAM NOT FOUND report for -org.dspace.checker.SimpleReporterImpl.bitstream-will-no-longer-be-processed = The following is a BITSTREAM WILL NO LONGER BE PROCESSED report for +org.dspace.checker.SimpleReporterImpl.bitstream-not-found-report = The following is a BITSTREAM NOT FOUND report from +org.dspace.checker.SimpleReporterImpl.bitstream-will-no-longer-be-processed = The following is a BITSTREAM WILL NO LONGER BE PROCESSED report from org.dspace.checker.SimpleReporterImpl.check-id = Check Id org.dspace.checker.SimpleReporterImpl.checksum = Checksum org.dspace.checker.SimpleReporterImpl.checksum-algorithm = Checksum Algorithm org.dspace.checker.SimpleReporterImpl.checksum-calculated = Checksum Calculated -org.dspace.checker.SimpleReporterImpl.checksum-did-not-match = The following is a CHECKSUM DID NOT MATCH report for +org.dspace.checker.SimpleReporterImpl.checksum-did-not-match = The following is a CHECKSUM DID NOT MATCH report from org.dspace.checker.SimpleReporterImpl.checksum-expected = Checksum Expected org.dspace.checker.SimpleReporterImpl.date-range-to = to org.dspace.checker.SimpleReporterImpl.deleted = Deleted -org.dspace.checker.SimpleReporterImpl.deleted-bitstream-intro = The following is a BITSTREAM SET DELETED report for +org.dspace.checker.SimpleReporterImpl.deleted-bitstream-intro = The following is a BITSTREAM SET DELETED report from org.dspace.checker.SimpleReporterImpl.description = Description org.dspace.checker.SimpleReporterImpl.format-id = Format Id -org.dspace.checker.SimpleReporterImpl.howto-add-unchecked-bitstreams = To add these bitstreams to be checked run the checksum checker with the -u option +org.dspace.checker.SimpleReporterImpl.howto-add-unchecked-bitstreams = To add these bitstreams to be checked run the checksum checker again org.dspace.checker.SimpleReporterImpl.internal-id = Internal Id org.dspace.checker.SimpleReporterImpl.name = Name org.dspace.checker.SimpleReporterImpl.no-bitstreams-changed = There were no bitstreams found with changed checksums From 5db740aca101d6efd33908cedf39d0014dd05fc8 Mon Sep 17 00:00:00 2001 From: Miika Nurminen Date: Mon, 18 Aug 2025 12:12:49 +0300 Subject: [PATCH 739/939] Fix sum calculation for checker-emailer and clarify help on not to be processed report (cherry picked from commit 5ebdec62ed27d0c98430a8ff562a1b97da6f6e69) --- .../main/java/org/dspace/checker/DailyReportEmailer.java | 9 ++++++--- .../dspace/checker/service/SimpleReporterService.java | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/checker/DailyReportEmailer.java b/dspace-api/src/main/java/org/dspace/checker/DailyReportEmailer.java index f32ddc23238..ca5733920f6 100644 --- a/dspace-api/src/main/java/org/dspace/checker/DailyReportEmailer.java +++ b/dspace-api/src/main/java/org/dspace/checker/DailyReportEmailer.java @@ -119,9 +119,10 @@ public static void main(String[] args) { options.addOption("a", "all", false, "Send all email reports (used by default)"); options.addOption("u", "unchecked", false, - "Send the Unchecked bitstream email report"); + "Send the unchecked (i.e. recently added) bitstream email report"); options.addOption("n", "not-processed", false, - "Send email report for all bitstreams set to longer be processed for today"); + "Send email report for all bitstreams set to no longer be processed for today (includes" + + " bitstreams marked as deleted or not found)"); try { line = parser.parse(options, args); @@ -194,7 +195,9 @@ public static void main(String[] args) { writer.write("\n--------------------------------- Report Spacer ---------------------------\n\n"); numBitstreams += reporter.getBitstreamNotFoundReport(context, yesterday, tomorrow, writer); writer.write("\n--------------------------------- Report Spacer ---------------------------\n\n"); - numBitstreams += reporter.getNotToBeProcessedReport(context, yesterday, tomorrow, writer); + // not to be processed report includes deleted and not found bitstreams so it is not necessary to + // incude the sum in the counter + reporter.getNotToBeProcessedReport(context, yesterday, tomorrow, writer); writer.write("\n--------------------------------- Report Spacer ---------------------------\n\n"); numBitstreams += reporter.getUncheckedBitstreamsReport(context, writer); writer.write("\n--------------------------------- End Report ---------------------------\n\n"); diff --git a/dspace-api/src/main/java/org/dspace/checker/service/SimpleReporterService.java b/dspace-api/src/main/java/org/dspace/checker/service/SimpleReporterService.java index 1dc56c20a3d..f3e0b43d889 100644 --- a/dspace-api/src/main/java/org/dspace/checker/service/SimpleReporterService.java +++ b/dspace-api/src/main/java/org/dspace/checker/service/SimpleReporterService.java @@ -72,7 +72,8 @@ public int getBitstreamNotFoundReport(Context context, Date startDate, Date endD /** * The bitstreams that were set to not be processed report for the specified - * date range. + * date range. This includes bitstreams that are marked as deleted and bitstreams + * that are not found from the assetstore. * * @param context context * @param startDate the start date range. From 4ecec0950b4e5f7ddc265d8346590a77fb1f8db1 Mon Sep 17 00:00:00 2001 From: Miika Nurminen Date: Fri, 14 Nov 2025 10:04:56 +0200 Subject: [PATCH 740/939] Fix typo in comment (cherry picked from commit 2aa77e8f27cec22823debc0e57af6e9671843a30) --- .../src/main/java/org/dspace/checker/DailyReportEmailer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/checker/DailyReportEmailer.java b/dspace-api/src/main/java/org/dspace/checker/DailyReportEmailer.java index ca5733920f6..82c01bf0b9c 100644 --- a/dspace-api/src/main/java/org/dspace/checker/DailyReportEmailer.java +++ b/dspace-api/src/main/java/org/dspace/checker/DailyReportEmailer.java @@ -196,7 +196,7 @@ public static void main(String[] args) { numBitstreams += reporter.getBitstreamNotFoundReport(context, yesterday, tomorrow, writer); writer.write("\n--------------------------------- Report Spacer ---------------------------\n\n"); // not to be processed report includes deleted and not found bitstreams so it is not necessary to - // incude the sum in the counter + // include the sum in the counter reporter.getNotToBeProcessedReport(context, yesterday, tomorrow, writer); writer.write("\n--------------------------------- Report Spacer ---------------------------\n\n"); numBitstreams += reporter.getUncheckedBitstreamsReport(context, writer); From 1a5123de8375e590ec7dd5a4e0ec3aeac5d402d8 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 2 Oct 2025 16:12:51 -0500 Subject: [PATCH 741/939] Remove X509Authentication, its configuration, and all references to it within other files. --- .../authenticate/AuthenticationMethod.java | 8 +- .../AuthenticationServiceImpl.java | 8 +- .../authenticate/LDAPAuthentication.java | 2 +- .../authenticate/PasswordAuthentication.java | 4 +- .../authenticate/ShibAuthentication.java | 7 +- .../authenticate/X509Authentication.java | 616 ------------------ .../service/AuthenticationService.java | 12 +- .../org/dspace/eperson/EPersonCLITool.java | 2 +- dspace/config/dspace.cfg | 1 - dspace/config/local.cfg.EXAMPLE | 3 - dspace/config/modules/authentication-x509.cfg | 23 - dspace/config/modules/authentication.cfg | 6 - 12 files changed, 21 insertions(+), 671 deletions(-) delete mode 100644 dspace-api/src/main/java/org/dspace/authenticate/X509Authentication.java delete mode 100644 dspace/config/modules/authentication-x509.cfg diff --git a/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationMethod.java b/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationMethod.java index 500ee04a979..38488c41e30 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationMethod.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationMethod.java @@ -54,7 +54,7 @@ public interface AuthenticationMethod { public static final int BAD_CREDENTIALS = 2; /** - * Not allowed to login this way without X.509 certificate. + * Not allowed to login this way without a certificate. */ public static final int CERT_REQUIRED = 3; @@ -124,8 +124,8 @@ public boolean allowSetPassword(Context context, * Predicate, is this an implicit authentication method. * An implicit method gets credentials from the environment (such as * an HTTP request or even Java system properties) rather than the - * explicit username and password. For example, a method that reads - * the X.509 certificates in an HTTPS request is implicit. + * explicit username and password. For example, a method that provides + * IP-based authentication is implicit. * * @return true if this method uses implicit authentication. */ @@ -188,7 +188,7 @@ public default boolean areSpecialGroupsApplicable(Context context, HttpServletRe *

    Meaning: *
    SUCCESS - authenticated OK. *
    BAD_CREDENTIALS - user exists, but credentials (e.g. passwd) don't match - *
    CERT_REQUIRED - not allowed to login this way without X.509 cert. + *
    CERT_REQUIRED - not allowed to login this way without a cert. *
    NO_SUCH_USER - user not found using this method. *
    BAD_ARGS - user/pw not appropriate for this method * @throws SQLException if database error diff --git a/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationServiceImpl.java b/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationServiceImpl.java index 1d67da37ecb..493e4ade416 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationServiceImpl.java @@ -38,11 +38,11 @@ * Configuration
    * The stack of authentication methods is defined by one property in the DSpace configuration: *

    - *   plugin.sequence.org.dspace.eperson.AuthenticationMethod = a list of method class names
    + *   plugin.sequence.org.dspace.authenticate.AuthenticationMethod = a list of method class names
      *     e.g.
    - *   plugin.sequence.org.dspace.eperson.AuthenticationMethod = \
    - *       org.dspace.eperson.X509Authentication, \
    - *       org.dspace.eperson.PasswordAuthentication
    + *   plugin.sequence.org.dspace.authenticate.AuthenticationMethod = \
    + *       org.dspace.authenticate.IPAuthentication, \
    + *       org.dspace.authenticate.PasswordAuthentication
      * 
    *

    * The "stack" is always traversed in order, with the methods diff --git a/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java index da6a7092481..564ad78c951 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java @@ -203,7 +203,7 @@ public List getSpecialGroups(Context context, HttpServletRequest request) *

    Meaning: *
    SUCCESS - authenticated OK. *
    BAD_CREDENTIALS - user exists, but credentials (e.g. passwd) don't match - *
    CERT_REQUIRED - not allowed to login this way without X.509 cert. + *
    CERT_REQUIRED - not allowed to login this way without a cert. *
    NO_SUCH_USER - user not found using this method. *
    BAD_ARGS - user/pw not appropriate for this method */ diff --git a/dspace-api/src/main/java/org/dspace/authenticate/PasswordAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/PasswordAuthentication.java index 6d1ca862d30..69f0bd55f87 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/PasswordAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/PasswordAuthentication.java @@ -188,7 +188,7 @@ public List getSpecialGroups(Context context, HttpServletRequest request) *

    Meaning: *
    SUCCESS - authenticated OK. *
    BAD_CREDENTIALS - user exists, but password doesn't match - *
    CERT_REQUIRED - not allowed to login this way without X.509 cert. + *
    CERT_REQUIRED - not allowed to login this way without a cert. *
    NO_SUCH_USER - no EPerson with matching email address. *
    BAD_ARGS - missing username, or user matched but cannot login. * @throws SQLException if database error @@ -213,7 +213,7 @@ public int authenticate(Context context, // cannot login this way return BAD_ARGS; } else if (eperson.getRequireCertificate()) { - // this user can only login with x.509 certificate + // this user can only login with a certificate log.warn(LogHelper.getHeader(context, "authenticate", "rejecting PasswordAuthentication because " + username + " requires " + "certificate.")); diff --git a/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java index 791634a7dc2..ea6dd0347ee 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java @@ -160,7 +160,7 @@ public class ShibAuthentication implements AuthenticationMethod { * SUCCESS - authenticated OK.
    * BAD_CREDENTIALS - user exists, but credentials (e.g. passwd) * don't match
    - * CERT_REQUIRED - not allowed to login this way without X.509 cert. + * CERT_REQUIRED - not allowed to login this way without a cert. *
    * NO_SUCH_USER - user not found using this method.
    * BAD_ARGS - user/pw not appropriate for this method @@ -417,8 +417,7 @@ public boolean allowSetPassword(Context context, * Predicate, is this an implicit authentication method. An implicit method * gets credentials from the environment (such as an HTTP request or even * Java system properties) rather than the explicit username and password. - * For example, a method that reads the X.509 certificates in an HTTPS - * request is implicit. + * For example, a method that provides IP-based authentication is implicit. * * @return true if this method uses implicit authentication. */ @@ -917,7 +916,7 @@ protected int swordCompatibility(Context context, String username, String passwo " is not allowed to login."); return BAD_ARGS; } else if (eperson.getRequireCertificate()) { - // this user can only login with x.509 certificate + // this user can only login with a certificate log.error( "Shibboleth-based password authentication failed for user " + username + " because the eperson object" + " requires a certificate to authenticate.."); diff --git a/dspace-api/src/main/java/org/dspace/authenticate/X509Authentication.java b/dspace-api/src/main/java/org/dspace/authenticate/X509Authentication.java deleted file mode 100644 index 12dc5feda58..00000000000 --- a/dspace-api/src/main/java/org/dspace/authenticate/X509Authentication.java +++ /dev/null @@ -1,616 +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.authenticate; - -import java.io.BufferedInputStream; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.security.GeneralSecurityException; -import java.security.KeyStore; -import java.security.Principal; -import java.security.PublicKey; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; -import java.util.List; -import java.util.StringTokenizer; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; - -import org.apache.commons.lang3.ArrayUtils; -import org.apache.logging.log4j.Logger; -import org.dspace.authenticate.factory.AuthenticateServiceFactory; -import org.dspace.authenticate.service.AuthenticationService; -import org.dspace.authorize.AuthorizeException; -import org.dspace.core.Context; -import org.dspace.core.LogHelper; -import org.dspace.eperson.EPerson; -import org.dspace.eperson.Group; -import org.dspace.eperson.factory.EPersonServiceFactory; -import org.dspace.eperson.service.EPersonService; -import org.dspace.eperson.service.GroupService; -import org.dspace.services.ConfigurationService; -import org.dspace.services.factory.DSpaceServicesFactory; - -/** - * Implicit authentication method that gets credentials from the X.509 client - * certificate supplied by the HTTPS client when connecting to this server. The - * email address in that certificate is taken as the authenticated user name - * with no further checking, so be sure your HTTP server (e.g. Tomcat) is - * configured correctly to accept only client certificates it can validate. - *

    - * See the AuthenticationMethod interface for more details. - *

    - * Configuration: - * - *

    - *   x509.keystore.path =
    - * 
    - * path to Java keystore file
    - * 
    - *   keystore.password =
    - * 
    - * password to access the keystore
    - * 
    - *   ca.cert =
    - * 
    - * path to certificate file for CA whose client certs to accept.
    - * 
    - *   autoregister =
    - * 
    - * "true" if E-Person is created automatically for unknown new users.
    - * 
    - *   groups =
    - * 
    - * comma-delimited list of special groups to add user to if authenticated.
    - * 
    - *   emaildomain =
    - * 
    - * email address domain (after the 'at' symbol) to match before allowing
    - * membership in special groups.
    - * 
    - * 
    - * - * Only one of the "keystore.path" or "ca.cert" - * options is required. If you supply a keystore, then all of the "trusted" - * certificates in the keystore represent CAs whose client certificates will be - * accepted. The ca.cert option only allows a single CA to be - * named. - *

    - * You can configure both a keystore and a CA cert, and both will be - * used. - *

    - * The autoregister configuration parameter determines what the - * canSelfRegister() method returns. It also allows an EPerson - * record to be created automatically when the presented certificate is - * acceptable but there is no corresponding EPerson. - * - * @author Larry Stone - * @version $Revision$ - */ -public class X509Authentication implements AuthenticationMethod { - - /** - * log4j category - */ - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(X509Authentication.class); - - /** - * public key of CA to check client certs against. - */ - private static PublicKey caPublicKey = null; - - /** - * key store for CA certs if we use that - */ - private static KeyStore caCertKeyStore = null; - - private static String loginPageTitle = null; - - private static String loginPageURL = null; - - protected AuthenticationService authenticationService = AuthenticateServiceFactory.getInstance() - .getAuthenticationService(); - protected EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); - protected GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); - protected ConfigurationService configurationService = - DSpaceServicesFactory.getInstance().getConfigurationService(); - - private static final String X509_AUTHENTICATED = "x509.authenticated"; - - - /** - * Initialization: Set caPublicKey and/or keystore. This loads the - * information needed to check if a client cert presented is valid and - * acceptable. - */ - static { - ConfigurationService configurationService = - DSpaceServicesFactory.getInstance().getConfigurationService(); - /* - * allow identification of alternative entry points for certificate - * authentication when selected by the user rather than implicitly. - */ - loginPageTitle = configurationService - .getProperty("authentication-x509.chooser.title.key"); - loginPageURL = configurationService - .getProperty("authentication-x509.chooser.uri"); - - String keystorePath = configurationService - .getProperty("authentication-x509.keystore.path"); - String keystorePassword = configurationService - .getProperty("authentication-x509.keystore.password"); - String caCertPath = configurationService - .getProperty("authentication-x509.ca.cert"); - - // First look for keystore full of trusted certs. - if (keystorePath != null) { - FileInputStream fis = null; - if (keystorePassword == null) { - keystorePassword = ""; - } - try { - KeyStore ks = KeyStore.getInstance("JKS"); - fis = new FileInputStream(keystorePath); - ks.load(fis, keystorePassword.toCharArray()); - caCertKeyStore = ks; - } catch (IOException e) { - log - .error("X509Authentication: Failed to load CA keystore, file=" - + keystorePath + ", error=" + e.toString()); - } catch (GeneralSecurityException e) { - log - .error("X509Authentication: Failed to extract CA keystore, file=" - + keystorePath + ", error=" + e.toString()); - } finally { - if (fis != null) { - try { - fis.close(); - } catch (IOException ioe) { - // ignore - } - } - } - } - - // Second, try getting public key out of CA cert, if that's configured. - if (caCertPath != null) { - InputStream is = null; - FileInputStream fis = null; - try { - fis = new FileInputStream(caCertPath); - is = new BufferedInputStream(fis); - X509Certificate cert = (X509Certificate) CertificateFactory - .getInstance("X.509").generateCertificate(is); - if (cert != null) { - caPublicKey = cert.getPublicKey(); - } - } catch (IOException e) { - log.error("X509Authentication: Failed to load CA cert, file=" - + caCertPath + ", error=" + e.toString()); - } catch (CertificateException e) { - log - .error("X509Authentication: Failed to extract CA cert, file=" - + caCertPath + ", error=" + e.toString()); - } finally { - if (is != null) { - try { - is.close(); - } catch (IOException ioe) { - // ignore - } - } - - if (fis != null) { - try { - fis.close(); - } catch (IOException ioe) { - // ignore - } - } - } - } - } - - /** - * Return the email address from certificate, or null if an - * email address cannot be found in the certificate. - *

    - * Note that the certificate parsing has only been tested with certificates - * granted by the MIT Certification Authority, and may not work elsewhere. - * - * @param certificate - - * An X509 certificate object - * @return - The email address found in certificate, or null if an email - * address cannot be found in the certificate. - */ - private static String getEmail(X509Certificate certificate) - throws SQLException { - Principal principal = certificate.getSubjectDN(); - - if (principal == null) { - return null; - } - - String dn = principal.getName(); - if (dn == null) { - return null; - } - - StringTokenizer tokenizer = new StringTokenizer(dn, ","); - String token = null; - while (tokenizer.hasMoreTokens()) { - int len = "emailaddress=".length(); - - token = (String) tokenizer.nextToken(); - - if (token.toLowerCase().startsWith("emailaddress=")) { - // Make sure the token actually contains something - if (token.length() <= len) { - return null; - } - - return token.substring(len).toLowerCase(); - } - } - - return null; - } - - /** - * Verify CERTIFICATE against KEY. Return true if and only if CERTIFICATE is - * valid and can be verified against KEY. - * - * @param context The current DSpace context - * @param certificate - - * An X509 certificate object - * @return - True if CERTIFICATE is valid and can be verified against KEY, - * false otherwise. - */ - private static boolean isValid(Context context, X509Certificate certificate) { - if (certificate == null) { - return false; - } - - // This checks that current time is within cert's validity window: - try { - certificate.checkValidity(); - } catch (CertificateException e) { - log.info(LogHelper.getHeader(context, "authentication", - "X.509 Certificate is EXPIRED or PREMATURE: " - + e.toString())); - return false; - } - - // Try CA public key, if available. - if (caPublicKey != null) { - try { - certificate.verify(caPublicKey); - return true; - } catch (GeneralSecurityException e) { - log.info(LogHelper.getHeader(context, "authentication", - "X.509 Certificate FAILED SIGNATURE check: " - + e.toString())); - } - } - - // Try it with keystore, if available. - if (caCertKeyStore != null) { - try { - Enumeration ke = caCertKeyStore.aliases(); - - while (ke.hasMoreElements()) { - String alias = (String) ke.nextElement(); - if (caCertKeyStore.isCertificateEntry(alias)) { - Certificate ca = caCertKeyStore.getCertificate(alias); - try { - certificate.verify(ca.getPublicKey()); - return true; - } catch (CertificateException ce) { - // ignore - } - } - } - log - .info(LogHelper - .getHeader(context, "authentication", - "Keystore method FAILED SIGNATURE check on client cert.")); - } catch (GeneralSecurityException e) { - log.info(LogHelper.getHeader(context, "authentication", - "X.509 Certificate FAILED SIGNATURE check: " - + e.toString())); - } - - } - return false; - } - - /** - * Predicate, can new user automatically create EPerson. Checks - * configuration value. You'll probably want this to be true to take - * advantage of a Web certificate infrastructure with many more users than - * are already known by DSpace. - * - * @throws SQLException if database error - */ - @Override - public boolean canSelfRegister(Context context, HttpServletRequest request, - String username) throws SQLException { - return configurationService - .getBooleanProperty("authentication-x509.autoregister"); - } - - /** - * Nothing extra to initialize. - * - * @throws SQLException if database error - */ - @Override - public void initEPerson(Context context, HttpServletRequest request, - EPerson eperson) throws SQLException { - } - - /** - * We don't use EPerson password so there is no reason to change it. - * - * @throws SQLException if database error - */ - @Override - public boolean allowSetPassword(Context context, - HttpServletRequest request, String username) throws SQLException { - return false; - } - - /** - * Returns true, this is an implicit method. - */ - @Override - public boolean isImplicit() { - return true; - } - - /** - * Returns a list of group names that the user should be added to upon - * successful authentication, configured in dspace.cfg. - * - * @return List of special groups configured for this authenticator - */ - private List getX509Groups() { - List groupNames = new ArrayList(); - - String[] groups = configurationService - .getArrayProperty("authentication-x509.groups"); - - if (ArrayUtils.isNotEmpty(groups)) { - for (String group : groups) { - groupNames.add(group.trim()); - } - } - - return groupNames; - } - - /** - * Checks for configured email domain required to grant special groups - * membership. If no email domain is configured to verify, special group - * membership is simply granted. - * - * @param request - - * The current request object - * @param email - - * The email address from the x509 certificate - */ - private void setSpecialGroupsFlag(HttpServletRequest request, String email) { - String emailDomain = null; - emailDomain = (String) request - .getAttribute("authentication.x509.emaildomain"); - - HttpSession session = request.getSession(true); - - if (null != emailDomain && !"".equals(emailDomain)) { - if (email.substring(email.length() - emailDomain.length()).equals( - emailDomain)) { - session.setAttribute("x509Auth", Boolean.TRUE); - } - } else { - // No configured email domain to verify. Just flag - // as authenticated so special groups are granted. - session.setAttribute("x509Auth", Boolean.TRUE); - } - } - - /** - * Return special groups configured in dspace.cfg for X509 certificate - * authentication. - * - * @param context context - * @param request object potentially containing the cert - * @return An int array of group IDs - * @throws SQLException if database error - */ - @Override - public List getSpecialGroups(Context context, HttpServletRequest request) - throws SQLException { - if (request == null) { - return Collections.EMPTY_LIST; - } - - Boolean authenticated = false; - HttpSession session = request.getSession(false); - authenticated = (Boolean) session.getAttribute("x509Auth"); - authenticated = (null == authenticated) ? false : authenticated; - - if (authenticated) { - List groupNames = getX509Groups(); - List groups = new ArrayList<>(); - - if (groupNames != null) { - for (String groupName : groupNames) { - if (groupName != null) { - Group group = groupService.findByName(context, groupName); - if (group != null) { - groups.add(group); - } else { - log.warn(LogHelper.getHeader(context, - "configuration_error", "unknown_group=" - + groupName)); - } - } - } - } - - return groups; - } - - return Collections.EMPTY_LIST; - } - - /** - * X509 certificate authentication. The client certificate is obtained from - * the ServletRequest object. - *

      - *
    • If the certificate is valid, and corresponds to an existing EPerson, - * and the user is allowed to login, return success.
    • - *
    • If the user is matched but is not allowed to login, it fails.
    • - *
    • If the certificate is valid, but there is no corresponding EPerson, - * the "authentication.x509.autoregister" configuration - * parameter is checked (via canSelfRegister()) - *
        - *
      • If it's true, a new EPerson record is created for the certificate, - * and the result is success.
      • - *
      • If it's false, return that the user was unknown.
      • - *
      - *
    • - *
    - * - * @return One of: SUCCESS, BAD_CREDENTIALS, NO_SUCH_USER, BAD_ARGS - * @throws SQLException if database error - */ - @Override - public int authenticate(Context context, String username, String password, - String realm, HttpServletRequest request) throws SQLException { - // Obtain the certificate from the request, if any - X509Certificate[] certs = null; - if (request != null) { - certs = (X509Certificate[]) request - .getAttribute("javax.servlet.request.X509Certificate"); - } - - if ((certs == null) || (certs.length == 0)) { - return BAD_ARGS; - } else { - // We have a cert -- check it and get username from it. - try { - if (!isValid(context, certs[0])) { - log - .warn(LogHelper - .getHeader(context, "authenticate", - "type=x509certificate, status=BAD_CREDENTIALS (not valid)")); - return BAD_CREDENTIALS; - } - - // And it's valid - try and get an e-person - String email = getEmail(certs[0]); - EPerson eperson = null; - if (email != null) { - eperson = ePersonService.findByEmail(context, email); - } - if (eperson == null) { - // Cert is valid, but no record. - if (email != null - && canSelfRegister(context, request, null)) { - // Register the new user automatically - log.info(LogHelper.getHeader(context, "autoregister", - "from=x.509, email=" + email)); - - // TEMPORARILY turn off authorisation - context.turnOffAuthorisationSystem(); - eperson = ePersonService.create(context); - eperson.setEmail(email); - eperson.setCanLogIn(true); - authenticationService.initEPerson(context, request, - eperson); - ePersonService.update(context, eperson); - context.dispatchEvents(); - context.restoreAuthSystemState(); - context.setCurrentUser(eperson); - request.setAttribute(X509_AUTHENTICATED, true); - setSpecialGroupsFlag(request, email); - return SUCCESS; - } else { - // No auto-registration for valid certs - log - .warn(LogHelper - .getHeader(context, "authenticate", - "type=cert_but_no_record, cannot auto-register")); - return NO_SUCH_USER; - } - } else if (!eperson.canLogIn()) { // make sure this is a login account - log.warn(LogHelper.getHeader(context, "authenticate", - "type=x509certificate, email=" + email - + ", canLogIn=false, rejecting.")); - return BAD_ARGS; - } else { - log.info(LogHelper.getHeader(context, "login", - "type=x509certificate")); - context.setCurrentUser(eperson); - request.setAttribute(X509_AUTHENTICATED, true); - setSpecialGroupsFlag(request, email); - return SUCCESS; - } - } catch (AuthorizeException ce) { - log.warn(LogHelper.getHeader(context, "authorize_exception", - ""), ce); - } - - return BAD_ARGS; - } - } - - /** - * Returns URL of password-login servlet. - * - * @param context DSpace context, will be modified (EPerson set) upon success. - * @param request The HTTP request that started this operation, or null if not - * applicable. - * @param response The HTTP response from the servlet method. - * @return fully-qualified URL - */ - @Override - public String loginPageURL(Context context, HttpServletRequest request, - HttpServletResponse response) { - return loginPageURL; - } - - @Override - public String getName() { - return "x509"; - } - - @Override - public boolean isUsed(final Context context, final HttpServletRequest request) { - if (request != null && - context.getCurrentUser() != null && - request.getAttribute(X509_AUTHENTICATED) != null) { - return true; - } - return false; - } - - @Override - public boolean canChangePassword(Context context, EPerson ePerson, String currentPassword) { - return false; - } -} diff --git a/dspace-api/src/main/java/org/dspace/authenticate/service/AuthenticationService.java b/dspace-api/src/main/java/org/dspace/authenticate/service/AuthenticationService.java index e955302ec3d..105e30d9bdf 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/service/AuthenticationService.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/service/AuthenticationService.java @@ -29,11 +29,11 @@ * Configuration
    * The stack of authentication methods is defined by one property in the DSpace configuration: *
    - *   plugin.sequence.org.dspace.eperson.AuthenticationMethod = a list of method class names
    + *   plugin.sequence.org.dspace.authenticate.AuthenticationMethod = a list of method class names
      *     e.g.
    - *   plugin.sequence.org.dspace.eperson.AuthenticationMethod = \
    - *       org.dspace.eperson.X509Authentication, \
    - *       org.dspace.eperson.PasswordAuthentication
    + *   plugin.sequence.org.dspace.authenticate.AuthenticationMethod = \
    + *       org.dspace.authenticate.IPAuthentication, \
    + *       org.dspace.authenticate.PasswordAuthentication
      * 
    *

    * The "stack" is always traversed in order, with the methods @@ -64,7 +64,7 @@ public interface AuthenticationService { *

    Meaning: *
    SUCCESS - authenticated OK. *
    BAD_CREDENTIALS - user exists, but credentials (e.g. password) don't match - *
    CERT_REQUIRED - not allowed to login this way without X.509 cert. + *
    CERT_REQUIRED - not allowed to login this way without a cert. *
    NO_SUCH_USER - user not found using this method. *
    BAD_ARGS - user/password not appropriate for this method */ @@ -91,7 +91,7 @@ public int authenticate(Context context, *

    Meaning: *
    SUCCESS - authenticated OK. *
    BAD_CREDENTIALS - user exists, but credentials (e.g. password) don't match - *
    CERT_REQUIRED - not allowed to login this way without X.509 cert. + *
    CERT_REQUIRED - not allowed to login this way without a cert. *
    NO_SUCH_USER - user not found using this method. *
    BAD_ARGS - user/password not appropriate for this method */ diff --git a/dspace-api/src/main/java/org/dspace/eperson/EPersonCLITool.java b/dspace-api/src/main/java/org/dspace/eperson/EPersonCLITool.java index 343ddcccfa3..53451e07a9e 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/EPersonCLITool.java +++ b/dspace-api/src/main/java/org/dspace/eperson/EPersonCLITool.java @@ -50,7 +50,7 @@ public class EPersonCLITool { private static final Option OPT_PHONE = new Option("t", "telephone", true, "telephone number, empty for none"); private static final Option OPT_LANGUAGE = new Option("l", "language", true, "the person's preferred language"); private static final Option OPT_REQUIRE_CERTIFICATE = new Option("c", "requireCertificate", true, - "if 'true', an X.509 certificate will be " + + "if 'true', a certificate will be " + "required for login"); private static final Option OPT_CAN_LOGIN = new Option("C", "canLogIn", true, "'true' if the user can log in"); diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 55ba2e0bffb..0fcf0022bd1 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1641,7 +1641,6 @@ include = ${module_dir}/authentication-ldap.cfg include = ${module_dir}/authentication-oidc.cfg include = ${module_dir}/authentication-password.cfg include = ${module_dir}/authentication-shibboleth.cfg -include = ${module_dir}/authentication-x509.cfg include = ${module_dir}/authority.cfg include = ${module_dir}/bulkedit.cfg include = ${module_dir}/citation-page.cfg diff --git a/dspace/config/local.cfg.EXAMPLE b/dspace/config/local.cfg.EXAMPLE index 7a565823e8b..b35e7d20eef 100644 --- a/dspace/config/local.cfg.EXAMPLE +++ b/dspace/config/local.cfg.EXAMPLE @@ -210,9 +210,6 @@ db.schema = public # ORCID certificate authentication. # plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authenticate.OrcidAuthentication -# X.509 certificate authentication. See authentication-x509.cfg for default configuration. -#plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authenticate.X509Authentication - # Authentication by Password (encrypted in DSpace's database). See authentication-password.cfg for default configuration. # Enabled by default in authentication.cfg #plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authenticate.PasswordAuthentication diff --git a/dspace/config/modules/authentication-x509.cfg b/dspace/config/modules/authentication-x509.cfg deleted file mode 100644 index d3f05c7d17d..00000000000 --- a/dspace/config/modules/authentication-x509.cfg +++ /dev/null @@ -1,23 +0,0 @@ -#---------------------------------------------------------------# -#------X.509 CERTIFICATE AUTHENTICATION CONFIGURATIONS----------# -#---------------------------------------------------------------# -# Configuration properties used by the X.509 Certificate # -# Authentication plugin, when it is enabled. # -#---------------------------------------------------------------# - -## method 1, using keystore -#authentication-x509.keystore.path = /tomcat/conf/keystore -#authentication-x509.keystore.password = changeit - -## method 2, using CA certificate -#authentication-x509.ca.cert = ${dspace.dir}/config/MyClientCA.pem - -## Create e-persons for unknown names in valid certificates? -#authentication-x509.autoregister = true - -## Allow Certificate auth to show as a choice in chooser -# Use Messages.properties key for title -#authentication-x509.chooser.title.key=org.dspace.eperson.X509Authentication.title -# -# Identify the location of the Certificate Login Servlet. -#authentication-x509.chooser.uri=/certificate-login diff --git a/dspace/config/modules/authentication.cfg b/dspace/config/modules/authentication.cfg index 568f871e3cd..41c28df1d7c 100644 --- a/dspace/config/modules/authentication.cfg +++ b/dspace/config/modules/authentication.cfg @@ -21,9 +21,6 @@ # * IP Address Authentication # Plugin class: org.dspace.authenticate.IPAuthentication # Configuration file: authentication-ip.cfg -# * X.509 Certificate Authentication -# Plugin class: org.dspace.authenticate.X509Authentication -# Configuration file: authentication-x509.cfg # * ORCID certificate authentication. # Plugin class: org.dspace.authenticate.OrcidAuthentication # Configuration file: orcid.cfg @@ -49,9 +46,6 @@ # Shibboleth authentication/authorization. See authentication-shibboleth.cfg for default configuration. #plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authenticate.ShibAuthentication -# X.509 certificate authentication. See authentication-x509.cfg for default configuration. -#plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authenticate.X509Authentication - # ORCID certificate authentication. # plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authenticate.OrcidAuthentication From b2f862500a56fa585dd16509bffb47401c2f2419 Mon Sep 17 00:00:00 2001 From: Zahraa Chreim Date: Wed, 19 Nov 2025 08:42:33 +0200 Subject: [PATCH 742/939] 135699: Fix NPE in Bitstream deletion by reversing bitstream comparison --- .../src/main/java/org/dspace/content/BitstreamServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java index 0c5d1299243..c19b87089e6 100644 --- a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java @@ -295,7 +295,7 @@ public void delete(Context context, Bitstream bitstream) throws SQLException, Au // a deleted bitstream List requestItems = requestItemService.findAll(context); for (RequestItem requestItem : requestItems) { - if (requestItem.getBitstream().equals(bitstream)) { + if (bitstream.equals(requestItem.getBitstream())) { requestItemService.delete(context, requestItem); } } From 5147dea804491e52a9cd73d79f308bb7026a6633 Mon Sep 17 00:00:00 2001 From: "David P. Steelman" Date: Fri, 26 Sep 2025 10:29:43 -0400 Subject: [PATCH 743/939] Use canonical path for bitstream path/assetstore path comparisons Replaced `baseDir.getAbsolutePath()` with `baseDir.getCanonicalPath()` in the bitstream path check. This is necessary because the "normzalizedPath" is generated via the "getCanonicalFile" method. This method follows symbolic links, unlike the "getAbsolutePath" method, which does not. Therefore, when comparing the "normalizedPath" to the "baseDir", it is necessary to use "getCanonicalPath" so that any symbolic links are resolved in the same way. This issue was uncovered by running the "org.dspace.storage.bitstore.BitstreamStorageServiceImplIT" integration tests on macOS, in which a temporary file asset store is used. On macOS the temporary files are stored in "/var" which is a symbolic link to "/private/var". (cherry picked from commit 8f8cb52658954234c05e1ea71e43375f080b7d0f) --- .../java/org/dspace/storage/bitstore/DSBitStoreService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/DSBitStoreService.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/DSBitStoreService.java index fd4e23b8258..a08af8104cc 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/DSBitStoreService.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/DSBitStoreService.java @@ -256,11 +256,11 @@ protected File getFile(Bitstream bitstream) throws IOException { Path normalizedPath = bitstreamFile.toPath().normalize(); String[] allowedAssetstoreRoots = DSpaceServicesFactory.getInstance().getConfigurationService() .getArrayProperty("assetstore.allowed.roots", new String[]{}); - if (!normalizedPath.startsWith(baseDir.getAbsolutePath()) + if (!normalizedPath.startsWith(baseDir.getCanonicalPath()) && !StringUtils.startsWithAny(normalizedPath.toString(), allowedAssetstoreRoots)) { log.error("Bitstream path outside of assetstore root requested:" + "bitstream={}, path={}, assetstore={}", - bitstream.getID(), normalizedPath, baseDir.getAbsolutePath()); + bitstream.getID(), normalizedPath, baseDir.getCanonicalPath()); throw new IOException("Illegal bitstream path constructed"); } return bitstreamFile; From 8e1b94f6834087a8525d0d2ced1f71818e5767a4 Mon Sep 17 00:00:00 2001 From: JohnnyMendesC <177888064+JohnnyMendesC@users.noreply.github.com> Date: Fri, 14 Nov 2025 18:50:04 -0300 Subject: [PATCH 744/939] fix(#10176): Fix inverted parameters for Shibboleth metadata update (cherry picked from commit 779e929d51758acd6b03780b2b562877e53b3ce2) --- .../main/java/org/dspace/authenticate/ShibAuthentication.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java index ea6dd0347ee..3012c907845 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java @@ -870,7 +870,7 @@ protected void updateEPerson(Context context, HttpServletRequest request, EPerso String[] nameParts = MetadataFieldName.parse(field); ePersonService.setMetadataSingleValue(context, eperson, - nameParts[0], nameParts[1], nameParts[2], value, null); + nameParts[0], nameParts[1], nameParts[2], null, value); log.debug("Updated the eperson's '{}' metadata using header: '{}' = '{}'.", field, header, value); } From 1cb11cbc23f24601f9bf4228aa40670de9ec7fde Mon Sep 17 00:00:00 2001 From: JohnnyMendesC <177888064+JohnnyMendesC@users.noreply.github.com> Date: Tue, 18 Nov 2025 18:14:07 -0300 Subject: [PATCH 745/939] test(DSpace#10176): Add unit test for Shibboleth metadata update regression This class contains unit tests for the ShibAuthentication functionality, verifying metadata updates and initialization behavior. (cherry picked from commit 748a11fa20208017a78b1d97a0d9095a15064c83) --- .../authenticate/ShibAuthenticationTest.java | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 dspace-api/src/test/java/org/dspace/authenticate/ShibAuthenticationTest.java diff --git a/dspace-api/src/test/java/org/dspace/authenticate/ShibAuthenticationTest.java b/dspace-api/src/test/java/org/dspace/authenticate/ShibAuthenticationTest.java new file mode 100644 index 00000000000..0f9daddf57a --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/authenticate/ShibAuthenticationTest.java @@ -0,0 +1,128 @@ +package org.dspace.authenticate; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import jakarta.servlet.http.HttpServletRequest; +import org.dspace.AbstractUnitTest; +import org.dspace.content.service.MetadataFieldService; +import org.dspace.content.MetadataField; +import org.dspace.content.MetadataSchema; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.service.EPersonService; +import org.dspace.services.ConfigurationService; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +/** + * Unit tests for ShibAuthentication + */ +public class ShibAuthenticationTest extends AbstractUnitTest { + + private ShibAuthentication shibAuthentication; + private EPersonService ePersonService; + private ConfigurationService configurationService; + + @Before + public void setup() { + shibAuthentication = new ShibAuthentication(); + ePersonService = mock(EPersonService.class); + shibAuthentication.ePersonService = ePersonService; + configurationService = mock(ConfigurationService.class); + shibAuthentication.configurationService = configurationService; + when(configurationService.getProperty("authentication-shibboleth.netid-header")).thenReturn("SHIB-NETID"); + when(configurationService.getProperty("authentication-shibboleth.email-header")).thenReturn("SHIB-MAIL"); + when(configurationService.getArrayProperty("authentication-shibboleth.eperson.metadata")) + .thenReturn(new String[]{"SHIB-telephone => eperson.phone"}); + when(configurationService.getBooleanProperty("authentication-shibboleth.eperson.metadata.autocreate", true)).thenReturn(true); + MetadataFieldService metadataFieldService = mock(MetadataFieldService.class); + shibAuthentication.metadataFieldService = metadataFieldService; + + try { + when(metadataFieldService.findByElement(any(Context.class), any(String.class), any(String.class), any())) + .thenReturn(mock(MetadataField.class)); + } catch (Exception e) { + // ignore checked exceptions from mock + } + } + + @Test + public void testPhoneMetadataUpdateOrder() throws Exception { + Context context = mock(Context.class); + HttpServletRequest request = mock(HttpServletRequest.class); + EPerson eperson = mock(EPerson.class); + when(request.getAttribute("SHIB-NETID")).thenReturn("test-user"); + when(request.getAttribute("SHIB-MAIL")).thenReturn("test@example.com"); + String phoneValue = "555-1234"; + when(request.getAttribute("SHIB-telephone")).thenReturn(phoneValue); + shibAuthentication.initialize(context); + assertNotNull("metadataHeaderMap should be initialized", shibAuthentication.metadataHeaderMap); + assertTrue("metadataHeaderMap should contain SHIB-telephone", shibAuthentication.metadataHeaderMap.containsKey("SHIB-telephone")); + shibAuthentication.updateEPerson(context, request, eperson); + ArgumentCaptor languageCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor valueCaptor = ArgumentCaptor.forClass(String.class); + + verify(ePersonService, times(1)).setMetadataSingleValue( + any(Context.class), + eq(eperson), + eq("eperson"), + eq("phone"), + isNull(), + languageCaptor.capture(), + valueCaptor.capture() + ); + + assertNull("The language argument should be NULL.", languageCaptor.getValue()); + assertEquals("The value argument should be the phone number.", phoneValue, valueCaptor.getValue()); + } + + @Test + public void testInitializeLoadsMultipleMappings() throws Exception { + Context context = mock(Context.class); + when(configurationService.getArrayProperty("authentication-shibboleth.eperson.metadata")) + .thenReturn(new String[]{ + "SHIB-telephone => eperson.phone", + "SHIB-dept => eperson.department" + }); + shibAuthentication.initialize(context); + + assertNotNull("metadataHeaderMap should be initialized", shibAuthentication.metadataHeaderMap); + assertTrue("metadataHeaderMap should contain SHIB-telephone", shibAuthentication.metadataHeaderMap.containsKey("SHIB-telephone")); + assertTrue("metadataHeaderMap should contain SHIB-dept", shibAuthentication.metadataHeaderMap.containsKey("SHIB-dept")); + } + + @Test + public void testNoMetadataMappingNoUpdate() throws Exception { + Context context = mock(Context.class); + HttpServletRequest request = mock(HttpServletRequest.class); + EPerson eperson = mock(EPerson.class); + when(configurationService.getArrayProperty("authentication-shibboleth.eperson.metadata")) + .thenReturn(new String[0]); + shibAuthentication.initialize(context); + shibAuthentication.updateEPerson(context, request, eperson); + + verify(ePersonService, times(0)).setMetadataSingleValue( + any(Context.class), + any(EPerson.class), + anyString(), + anyString(), + any(), + any(), + any() + ); + } +} From 915a6a4448a4e494754bb4382d6fad89889a36a2 Mon Sep 17 00:00:00 2001 From: JohnnyMendesC <177888064+JohnnyMendesC@users.noreply.github.com> Date: Wed, 19 Nov 2025 09:39:02 -0300 Subject: [PATCH 746/939] Added missing license header (cherry picked from commit f742549f72c71aebcc71a54fa35df643f617d741) --- .../org/dspace/authenticate/ShibAuthenticationTest.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dspace-api/src/test/java/org/dspace/authenticate/ShibAuthenticationTest.java b/dspace-api/src/test/java/org/dspace/authenticate/ShibAuthenticationTest.java index 0f9daddf57a..d3fe53f2b5b 100644 --- a/dspace-api/src/test/java/org/dspace/authenticate/ShibAuthenticationTest.java +++ b/dspace-api/src/test/java/org/dspace/authenticate/ShibAuthenticationTest.java @@ -1,3 +1,10 @@ +/** + * 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.authenticate; import static org.junit.Assert.assertEquals; From 8f0a4c9a1ceb73334b58e70552d3d78f087dd990 Mon Sep 17 00:00:00 2001 From: JohnnyMendesC <177888064+JohnnyMendesC@users.noreply.github.com> Date: Wed, 19 Nov 2025 10:55:16 -0300 Subject: [PATCH 747/939] Fixed checkstyle errors (cherry picked from commit deccd07d7d2821dc593e43cc0d2697d263a1905c) --- .../authenticate/ShibAuthenticationTest.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/authenticate/ShibAuthenticationTest.java b/dspace-api/src/test/java/org/dspace/authenticate/ShibAuthenticationTest.java index d3fe53f2b5b..e0f0fc57a9e 100644 --- a/dspace-api/src/test/java/org/dspace/authenticate/ShibAuthenticationTest.java +++ b/dspace-api/src/test/java/org/dspace/authenticate/ShibAuthenticationTest.java @@ -8,25 +8,22 @@ package org.dspace.authenticate; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import java.util.Collections; import jakarta.servlet.http.HttpServletRequest; import org.dspace.AbstractUnitTest; -import org.dspace.content.service.MetadataFieldService; import org.dspace.content.MetadataField; -import org.dspace.content.MetadataSchema; +import org.dspace.content.service.MetadataFieldService; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.service.EPersonService; @@ -55,7 +52,8 @@ public void setup() { when(configurationService.getProperty("authentication-shibboleth.email-header")).thenReturn("SHIB-MAIL"); when(configurationService.getArrayProperty("authentication-shibboleth.eperson.metadata")) .thenReturn(new String[]{"SHIB-telephone => eperson.phone"}); - when(configurationService.getBooleanProperty("authentication-shibboleth.eperson.metadata.autocreate", true)).thenReturn(true); + when(configurationService.getBooleanProperty("authentication-shibboleth.eperson.metadata.autocreate", true)) + .thenReturn(true); MetadataFieldService metadataFieldService = mock(MetadataFieldService.class); shibAuthentication.metadataFieldService = metadataFieldService; @@ -78,7 +76,8 @@ public void testPhoneMetadataUpdateOrder() throws Exception { when(request.getAttribute("SHIB-telephone")).thenReturn(phoneValue); shibAuthentication.initialize(context); assertNotNull("metadataHeaderMap should be initialized", shibAuthentication.metadataHeaderMap); - assertTrue("metadataHeaderMap should contain SHIB-telephone", shibAuthentication.metadataHeaderMap.containsKey("SHIB-telephone")); + assertTrue("metadataHeaderMap should contain SHIB-telephone", shibAuthentication.metadataHeaderMap + .containsKey("SHIB-telephone")); shibAuthentication.updateEPerson(context, request, eperson); ArgumentCaptor languageCaptor = ArgumentCaptor.forClass(String.class); ArgumentCaptor valueCaptor = ArgumentCaptor.forClass(String.class); @@ -92,7 +91,7 @@ public void testPhoneMetadataUpdateOrder() throws Exception { languageCaptor.capture(), valueCaptor.capture() ); - + assertNull("The language argument should be NULL.", languageCaptor.getValue()); assertEquals("The value argument should be the phone number.", phoneValue, valueCaptor.getValue()); } @@ -108,8 +107,10 @@ public void testInitializeLoadsMultipleMappings() throws Exception { shibAuthentication.initialize(context); assertNotNull("metadataHeaderMap should be initialized", shibAuthentication.metadataHeaderMap); - assertTrue("metadataHeaderMap should contain SHIB-telephone", shibAuthentication.metadataHeaderMap.containsKey("SHIB-telephone")); - assertTrue("metadataHeaderMap should contain SHIB-dept", shibAuthentication.metadataHeaderMap.containsKey("SHIB-dept")); + assertTrue("metadataHeaderMap should contain SHIB-telephone", shibAuthentication.metadataHeaderMap + .containsKey("SHIB-telephone")); + assertTrue("metadataHeaderMap should contain SHIB-dept", shibAuthentication.metadataHeaderMap + .containsKey("SHIB-dept")); } @Test From b6b7e9d1bfa6361dd87bb1ffd8041b4efe31a2ad Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 19 Nov 2025 13:23:40 -0600 Subject: [PATCH 748/939] Replace with javax version which is required in DSpace 7 --- .../java/org/dspace/authenticate/ShibAuthenticationTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/test/java/org/dspace/authenticate/ShibAuthenticationTest.java b/dspace-api/src/test/java/org/dspace/authenticate/ShibAuthenticationTest.java index e0f0fc57a9e..11005608f32 100644 --- a/dspace-api/src/test/java/org/dspace/authenticate/ShibAuthenticationTest.java +++ b/dspace-api/src/test/java/org/dspace/authenticate/ShibAuthenticationTest.java @@ -20,7 +20,8 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import jakarta.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequest; + import org.dspace.AbstractUnitTest; import org.dspace.content.MetadataField; import org.dspace.content.service.MetadataFieldService; From 20b5152a1f369f61a2e18ce6686801b7fc7ee163 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 16:15:54 +0000 Subject: [PATCH 749/939] build(deps-dev): bump org.scala-lang:scala-library Bumps [org.scala-lang:scala-library](https://github.com/scala/scala) from 2.13.17 to 2.13.18. - [Release notes](https://github.com/scala/scala/releases) - [Commits](https://github.com/scala/scala/compare/v2.13.17...v2.13.18) --- updated-dependencies: - dependency-name: org.scala-lang:scala-library dependency-version: 2.13.18 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 04291725975..196754215b1 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -933,7 +933,7 @@ org.scala-lang scala-library - 2.13.17 + 2.13.18 test From d36a026f076eb44de7b5aa0f686fa84952eb1ac1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 16:16:32 +0000 Subject: [PATCH 750/939] build(deps-dev): bump org.testcontainers:testcontainers-localstack Bumps [org.testcontainers:testcontainers-localstack](https://github.com/testcontainers/testcontainers-java) from 2.0.1 to 2.0.2. - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/2.0.1...2.0.2) --- updated-dependencies: - dependency-name: org.testcontainers:testcontainers-localstack dependency-version: 2.0.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 04291725975..c86267f09ef 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -857,7 +857,7 @@ org.testcontainers testcontainers-localstack - 2.0.1 + 2.0.2 test From 3b94ea42764d110265b6bf3c669604d6447d1380 Mon Sep 17 00:00:00 2001 From: Pierre Lasou Date: Thu, 30 Oct 2025 12:33:37 -0400 Subject: [PATCH 751/939] Update OrcidQueueServiceImpl.java Change to limit ORCID item syncrhonization to Archive items, not workspace or workflow items. (cherry picked from commit 31fc22f289414a079a4132d3c52c43f39d6796c5) --- .../org/dspace/orcid/service/impl/OrcidQueueServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/orcid/service/impl/OrcidQueueServiceImpl.java b/dspace-api/src/main/java/org/dspace/orcid/service/impl/OrcidQueueServiceImpl.java index 261f8ef9a9f..d69e8842f5a 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/service/impl/OrcidQueueServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/orcid/service/impl/OrcidQueueServiceImpl.java @@ -224,7 +224,7 @@ private List findAllEntitiesLinkableWith(Context context, Item profile, St return findRelationshipsByItem(context, profile).stream() .map(relationship -> getRelatedItem(relationship, profile)) - .filter(item -> entityType.equals(itemService.getEntityTypeLabel(item))) + .filter(item -> item.isArchived() && entityType.equals(itemService.getEntityTypeLabel(item))) .collect(Collectors.toList()); } From 9437249174ee6562977c2a8c343654efa500bd4d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 16:39:06 +0000 Subject: [PATCH 752/939] build(deps): bump the amazon-s3 group with 2 updates Bumps the amazon-s3 group with 2 updates: software.amazon.awssdk:s3 and [software.amazon.awssdk.crt:aws-crt](https://github.com/awslabs/aws-crt-java). Updates `software.amazon.awssdk:s3` from 2.32.31 to 2.38.8 Updates `software.amazon.awssdk.crt:aws-crt` from 0.38.12 to 0.39.4 - [Release notes](https://github.com/awslabs/aws-crt-java/releases) - [Commits](https://github.com/awslabs/aws-crt-java/compare/v0.38.12...v0.39.4) --- updated-dependencies: - dependency-name: software.amazon.awssdk:s3 dependency-version: 2.38.8 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: amazon-s3 - dependency-name: software.amazon.awssdk.crt:aws-crt dependency-version: 0.39.4 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: amazon-s3 ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 85da9632f09..e0baedbcd8c 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -739,7 +739,7 @@ software.amazon.awssdk s3 - 2.32.31 + 2.38.8 software.amazon.awssdk @@ -755,7 +755,7 @@ software.amazon.awssdk.crt aws-crt - 0.38.12 + 0.39.4 From 51c9c4bf8884396468e1fff58db921a0a57a573c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 16:42:15 +0000 Subject: [PATCH 753/939] build(deps): bump io.grpc:grpc-context from 1.76.0 to 1.77.0 Bumps [io.grpc:grpc-context](https://github.com/grpc/grpc-java) from 1.76.0 to 1.77.0. - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.76.0...v1.77.0) --- updated-dependencies: - dependency-name: io.grpc:grpc-context dependency-version: 1.77.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2b8075d9669..6989408427d 100644 --- a/pom.xml +++ b/pom.xml @@ -1718,7 +1718,7 @@ io.grpc grpc-context - 1.76.0 + 1.77.0 com.google.http-client From ed0793e6914e194fc8ba50f7370cdd08b42df474 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Mon, 1 Dec 2025 13:39:09 +0100 Subject: [PATCH 754/939] Fix CurationScriptIT failing when someone the 123456789/1 community is removed on demo.dspace.org HTTP requests to google.com will also result in a redirect status code so we can use that URL instead (cherry picked from commit 0eba85deec4fcd761a6025fd43ca1b562b49d41b) --- .../src/test/java/org/dspace/curate/CurationScriptIT.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java b/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java index 8c0744a09cc..8745613d7af 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java @@ -667,7 +667,7 @@ public void testURLRedirectCurateTest() throws Exception { // MetadataValueLinkChecker uri field with regular link .withMetadata("dc", "description", null, "https://google.com") // MetadataValueLinkChecker uri field with redirect link - .withMetadata("dc", "description", "uri", "https://demo7.dspace.org/handle/123456789/1") + .withMetadata("dc", "description", "uri", "http://google.com") // MetadataValueLinkChecker uri field with non resolving link .withMetadata("dc", "description", "uri", "https://www.atmire.com/broken-link") .withSubject("ExtraEntry") @@ -690,9 +690,9 @@ public void testURLRedirectCurateTest() throws Exception { // field that should be ignored assertFalse(checkIfInfoTextLoggedByHandler(handler, "demo.dspace.org/home")); - // redirect links in field that should not be ignored (https) => expect OK - assertTrue(checkIfInfoTextLoggedByHandler(handler, "https://demo7.dspace.org/handle/123456789/1 = 200 - OK")); - // regular link in field that should not be ignored (http) => expect OK + // redirect links in field that should not be ignored => expect OK (even though curl responds with 301) + assertTrue(checkIfInfoTextLoggedByHandler(handler, "http://google.com = 200 - OK")); + // regular link in field that should not be ignored => expect OK assertTrue(checkIfInfoTextLoggedByHandler(handler, "https://google.com = 200 - OK")); // nonexistent link in field that should not be ignored => expect 404 assertTrue(checkIfInfoTextLoggedByHandler(handler, "https://www.atmire.com/broken-link = 404 - FAILED")); From 172958e43304b6e3bc0f626c31e5d86614e83b49 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 18:31:06 +0000 Subject: [PATCH 755/939] build(deps): bump the apache-commons group across 1 directory with 6 updates Bumps the apache-commons group with 6 updates in the / directory: | Package | From | To | | --- | --- | --- | | [commons-cli:commons-cli](https://github.com/apache/commons-cli) | `1.10.0` | `1.11.0` | | [commons-codec:commons-codec](https://github.com/apache/commons-codec) | `1.19.0` | `1.20.0` | | org.apache.commons:commons-configuration2 | `2.12.0` | `2.13.0` | | [commons-io:commons-io](https://github.com/apache/commons-io) | `2.20.0` | `2.21.0` | | org.apache.commons:commons-lang3 | `3.19.0` | `3.20.0` | | [commons-validator:commons-validator](https://github.com/apache/commons-validator) | `1.10.0` | `1.10.1` | Updates `commons-cli:commons-cli` from 1.10.0 to 1.11.0 - [Changelog](https://github.com/apache/commons-cli/blob/master/RELEASE-NOTES.txt) - [Commits](https://github.com/apache/commons-cli/compare/rel/commons-cli-1.10.0...rel/commons-cli-1.11.0) Updates `commons-codec:commons-codec` from 1.19.0 to 1.20.0 - [Changelog](https://github.com/apache/commons-codec/blob/master/RELEASE-NOTES.txt) - [Commits](https://github.com/apache/commons-codec/compare/rel/commons-codec-1.19.0...rel/commons-codec-1.20.0) Updates `org.apache.commons:commons-configuration2` from 2.12.0 to 2.13.0 Updates `commons-io:commons-io` from 2.20.0 to 2.21.0 - [Changelog](https://github.com/apache/commons-io/blob/master/RELEASE-NOTES.txt) - [Commits](https://github.com/apache/commons-io/compare/rel/commons-io-2.20.0...rel/commons-io-2.21.0) Updates `org.apache.commons:commons-lang3` from 3.19.0 to 3.20.0 Updates `commons-validator:commons-validator` from 1.10.0 to 1.10.1 - [Changelog](https://github.com/apache/commons-validator/blob/master/RELEASE-NOTES.txt) - [Commits](https://github.com/apache/commons-validator/compare/rel/commons-validator-1.10.0...rel/commons-validator-1.10.1) --- updated-dependencies: - dependency-name: commons-cli:commons-cli dependency-version: 1.11.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: apache-commons - dependency-name: commons-codec:commons-codec dependency-version: 1.20.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: apache-commons - dependency-name: org.apache.commons:commons-configuration2 dependency-version: 2.13.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: apache-commons - dependency-name: commons-io:commons-io dependency-version: 2.21.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: apache-commons - dependency-name: org.apache.commons:commons-lang3 dependency-version: 3.20.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: apache-commons - dependency-name: commons-validator:commons-validator dependency-version: 1.10.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: apache-commons ... Signed-off-by: dependabot[bot] --- pom.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index 2b8075d9669..b0c3cd447fe 100644 --- a/pom.xml +++ b/pom.xml @@ -1473,12 +1473,12 @@ commons-cli commons-cli - 1.10.0 + 1.11.0 commons-codec commons-codec - 1.19.0 + 1.20.0 org.apache.commons @@ -1488,7 +1488,7 @@ org.apache.commons commons-configuration2 - 2.12.0 + 2.13.0 org.apache.commons @@ -1498,12 +1498,12 @@ commons-io commons-io - 2.20.0 + 2.21.0 org.apache.commons commons-lang3 - 3.19.0 + 3.20.0 @@ -1530,7 +1530,7 @@ commons-validator commons-validator - 1.10.0 + 1.10.1 joda-time From 94fc89c23fac156de5233870609035899c8d00ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 18:48:21 +0000 Subject: [PATCH 756/939] build(deps): bump the build-tools group across 1 directory with 9 updates Bumps the build-tools group with 9 updates in the / directory: | Package | From | To | | --- | --- | --- | | [com.github.spotbugs:spotbugs](https://github.com/spotbugs/spotbugs) | `4.9.6` | `4.9.8` | | [org.apache.maven.plugins:maven-jar-plugin](https://github.com/apache/maven-jar-plugin) | `3.4.2` | `3.5.0` | | [org.apache.maven.plugins:maven-war-plugin](https://github.com/apache/maven-war-plugin) | `3.4.0` | `3.5.0` | | [com.github.spotbugs:spotbugs-maven-plugin](https://github.com/spotbugs/spotbugs-maven-plugin) | `4.9.6.0` | `4.9.8.2` | | [org.apache.maven.plugins:maven-assembly-plugin](https://github.com/apache/maven-assembly-plugin) | `3.7.1` | `3.8.0` | | [org.apache.maven.plugins:maven-resources-plugin](https://github.com/apache/maven-resources-plugin) | `3.3.1` | `3.4.0` | | [org.apache.maven.plugins:maven-source-plugin](https://github.com/apache/maven-source-plugin) | `3.3.1` | `3.4.0` | | [org.jacoco:jacoco-maven-plugin](https://github.com/jacoco/jacoco) | `0.8.13` | `0.8.14` | | [org.codehaus.mojo:xml-maven-plugin](https://github.com/mojohaus/xml-maven-plugin) | `1.1.0` | `1.2.0` | Updates `com.github.spotbugs:spotbugs` from 4.9.6 to 4.9.8 - [Release notes](https://github.com/spotbugs/spotbugs/releases) - [Changelog](https://github.com/spotbugs/spotbugs/blob/master/CHANGELOG.md) - [Commits](https://github.com/spotbugs/spotbugs/compare/4.9.6...4.9.8) Updates `org.apache.maven.plugins:maven-jar-plugin` from 3.4.2 to 3.5.0 - [Release notes](https://github.com/apache/maven-jar-plugin/releases) - [Commits](https://github.com/apache/maven-jar-plugin/compare/maven-jar-plugin-3.4.2...maven-jar-plugin-3.5.0) Updates `org.apache.maven.plugins:maven-war-plugin` from 3.4.0 to 3.5.0 - [Release notes](https://github.com/apache/maven-war-plugin/releases) - [Commits](https://github.com/apache/maven-war-plugin/compare/maven-war-plugin-3.4.0...maven-war-plugin-3.5.0) Updates `com.github.spotbugs:spotbugs-maven-plugin` from 4.9.6.0 to 4.9.8.2 - [Release notes](https://github.com/spotbugs/spotbugs-maven-plugin/releases) - [Commits](https://github.com/spotbugs/spotbugs-maven-plugin/compare/spotbugs-maven-plugin-4.9.6.0...spotbugs-maven-plugin-4.9.8.2) Updates `org.apache.maven.plugins:maven-assembly-plugin` from 3.7.1 to 3.8.0 - [Release notes](https://github.com/apache/maven-assembly-plugin/releases) - [Commits](https://github.com/apache/maven-assembly-plugin/compare/maven-assembly-plugin-3.7.1...v3.8.0) Updates `org.apache.maven.plugins:maven-resources-plugin` from 3.3.1 to 3.4.0 - [Release notes](https://github.com/apache/maven-resources-plugin/releases) - [Commits](https://github.com/apache/maven-resources-plugin/compare/maven-resources-plugin-3.3.1...v3.4.0) Updates `org.apache.maven.plugins:maven-source-plugin` from 3.3.1 to 3.4.0 - [Release notes](https://github.com/apache/maven-source-plugin/releases) - [Commits](https://github.com/apache/maven-source-plugin/compare/maven-source-plugin-3.3.1...maven-source-plugin-3.4.0) Updates `org.jacoco:jacoco-maven-plugin` from 0.8.13 to 0.8.14 - [Release notes](https://github.com/jacoco/jacoco/releases) - [Commits](https://github.com/jacoco/jacoco/compare/v0.8.13...v0.8.14) Updates `org.codehaus.mojo:xml-maven-plugin` from 1.1.0 to 1.2.0 - [Release notes](https://github.com/mojohaus/xml-maven-plugin/releases) - [Commits](https://github.com/mojohaus/xml-maven-plugin/compare/1.1.0...1.2.0) --- updated-dependencies: - dependency-name: com.github.spotbugs:spotbugs dependency-version: 4.9.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: build-tools - dependency-name: org.apache.maven.plugins:maven-jar-plugin dependency-version: 3.5.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools - dependency-name: org.apache.maven.plugins:maven-war-plugin dependency-version: 3.5.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools - dependency-name: com.github.spotbugs:spotbugs-maven-plugin dependency-version: 4.9.8.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: build-tools - dependency-name: org.apache.maven.plugins:maven-assembly-plugin dependency-version: 3.8.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools - dependency-name: org.apache.maven.plugins:maven-resources-plugin dependency-version: 3.4.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools - dependency-name: org.apache.maven.plugins:maven-source-plugin dependency-version: 3.4.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools - dependency-name: org.jacoco:jacoco-maven-plugin dependency-version: 0.8.14 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: build-tools - dependency-name: org.codehaus.mojo:xml-maven-plugin dependency-version: 1.2.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools ... Signed-off-by: dependabot[bot] --- pom.xml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 2b8075d9669..bed7191044b 100644 --- a/pom.xml +++ b/pom.xml @@ -174,7 +174,7 @@ org.apache.maven.plugins maven-jar-plugin - 3.4.2 + 3.5.0 @@ -188,7 +188,7 @@ org.apache.maven.plugins maven-war-plugin - 3.4.0 + 3.5.1 false @@ -295,7 +295,7 @@ com.github.spotbugs spotbugs-maven-plugin - 4.9.6.0 + 4.9.8.2 Max Low @@ -305,7 +305,7 @@ com.github.spotbugs spotbugs - 4.9.6 + 4.9.8 @@ -335,7 +335,7 @@ maven-assembly-plugin - 3.7.1 + 3.8.0 org.apache.maven.plugins @@ -345,7 +345,7 @@ org.apache.maven.plugins maven-resources-plugin - 3.3.1 + 3.4.0 @@ -373,7 +373,7 @@ org.apache.maven.plugins maven-source-plugin - 3.3.1 + 3.4.0 @@ -392,7 +392,7 @@ org.jacoco jacoco-maven-plugin - 0.8.13 + 0.8.14 @@ -472,7 +472,7 @@ org.codehaus.mojo xml-maven-plugin - 1.1.0 + 1.2.0 validate-ALL-xml-and-xsl From 8d848a1555c003d2ed55ad7be525954a10698249 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Thu, 7 Aug 2025 11:20:18 +0300 Subject: [PATCH 757/939] pom.xml: upgrade pdfbox and tika Use latest pdfbox 3.0.5 and tika 3.2.2. See: https://pdfbox.apache.org/3.0/migration.html See: https://dist.apache.org/repos/dist/release/tika/3.2.2/CHANGES-3.2.2.txt --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 512254a9a32..6ee3ac3d46d 100644 --- a/pom.xml +++ b/pom.xml @@ -39,10 +39,10 @@ 9.4.58.v20250814 2.25.2 - 2.0.35 + 3.0.5 1.19.0 1.7.36 - 2.9.4 + 3.2.2 1.82 From a44c9bbcca6ce8b76be7af3815f9986451b1e5e2 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Thu, 7 Aug 2025 11:39:09 +0300 Subject: [PATCH 758/939] dspace-api: Update syntax for pdfbox 3.0.x Conflicts resolved with CitationDocumentServiceImpl to satisfy the older implementation. --- .../ImageMagickThumbnailFilter.java | 6 ++--- .../app/mediafilter/PDFBoxThumbnail.java | 4 +++- .../dspace/content/packager/PDFPackager.java | 23 +++++++++++-------- .../EpoImportMetadataSourceServiceImpl.java | 2 +- .../app/rest/BitstreamRestControllerIT.java | 6 +++-- 5 files changed, 25 insertions(+), 16 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java index 408982d157e..7543410a796 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java @@ -14,7 +14,7 @@ import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; -import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.Loader; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.dspace.content.Bitstream; @@ -153,8 +153,8 @@ public File getImageFile(File f, boolean verbose) // the CropBox is missing or empty because pdfbox will set it to the // same size as the MediaBox if it doesn't exist. Also note that we // only need to check the first page, since that's what we use for - // generating the thumbnail (PDDocument uses a zero-based index). - PDPage pdfPage = PDDocument.load(f).getPage(0); + // generating the thumbnail (PDPage uses a zero-based index). + PDPage pdfPage = Loader.loadPDF(f).getPage(0); PDRectangle pdfPageMediaBox = pdfPage.getMediaBox(); PDRectangle pdfPageCropBox = pdfPage.getCropBox(); diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/PDFBoxThumbnail.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/PDFBoxThumbnail.java index 577f1dec4a1..eb23e9daa08 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/PDFBoxThumbnail.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/PDFBoxThumbnail.java @@ -11,6 +11,8 @@ import java.io.InputStream; import org.apache.logging.log4j.Logger; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.io.RandomAccessReadBuffer; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.encryption.InvalidPasswordException; import org.apache.pdfbox.rendering.PDFRenderer; @@ -71,7 +73,7 @@ public InputStream getDestinationStream(Item currentItem, InputStream source, bo BufferedImage buf; // Render the page image. - try ( PDDocument doc = PDDocument.load(source); ) { + try ( PDDocument doc = Loader.loadPDF(new RandomAccessReadBuffer(source)); ) { PDFRenderer renderer = new PDFRenderer(doc); buf = renderer.renderImage(0); } catch (InvalidPasswordException ex) { diff --git a/dspace-api/src/main/java/org/dspace/content/packager/PDFPackager.java b/dspace-api/src/main/java/org/dspace/content/packager/PDFPackager.java index 6c7baad4549..f63585f3c49 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/PDFPackager.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/PDFPackager.java @@ -18,11 +18,11 @@ import org.apache.commons.lang3.ArrayUtils; import org.apache.logging.log4j.Logger; +import org.apache.pdfbox.Loader; import org.apache.pdfbox.cos.COSDocument; import org.apache.pdfbox.io.MemoryUsageSetting; -import org.apache.pdfbox.io.RandomAccessBufferedFileInputStream; +import org.apache.pdfbox.io.RandomAccessReadBuffer; import org.apache.pdfbox.io.ScratchFile; -import org.apache.pdfbox.pdfparser.PDFParser; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocumentInformation; import org.dspace.authorize.AuthorizeException; @@ -330,19 +330,24 @@ private void crosswalkPDF(Context context, Item item, InputStream metadata) COSDocument cos = null; try { - ScratchFile scratchFile = null; + PDDocument document = null; + try { - long useRAM = Runtime.getRuntime().freeMemory() * 80 / 100; // use up to 80% of JVM free memory - scratchFile = new ScratchFile( - MemoryUsageSetting.setupMixed(useRAM)); // then fallback to temp file (unlimited size) + // Use up to 80% of JVM free memory and fall back to a temp file (unlimited size) + long useRAM = Runtime.getRuntime().freeMemory() * 80 / 100; + document = Loader.loadPDF( + new RandomAccessReadBuffer(metadata), + () -> new ScratchFile(MemoryUsageSetting.setupMixed(useRAM))); } catch (IOException ioe) { log.warn("Error initializing scratch file: " + ioe.getMessage()); } - PDFParser parser = new PDFParser(new RandomAccessBufferedFileInputStream(metadata), scratchFile); - parser.parse(); - cos = parser.getDocument(); + // sanity check: loaded PDF document must not be null. + if (document == null) { + throw new MetadataValidationException("The provided stream could not be parsed into a PDF document."); + } + cos = document.getDocument(); // sanity check: PDFBox breaks on encrypted documents, so give up. if (cos.getEncryptionDictionary() != null) { throw new MetadataValidationException("This packager cannot accept an encrypted PDF document."); diff --git a/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoImportMetadataSourceServiceImpl.java index 552f607827a..4ec1f4db39e 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoImportMetadataSourceServiceImpl.java @@ -29,9 +29,9 @@ import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpException; import org.apache.http.client.utils.URIBuilder; +import org.apache.jena.ext.xerces.impl.dv.util.Base64; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.xerces.impl.dv.util.Base64; import org.dspace.app.util.XMLUtils; import org.dspace.content.Item; import org.dspace.importer.external.datamodel.ImportRecord; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java index 095d4d89fb1..b4649ce4c5b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java @@ -56,6 +56,8 @@ import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.CharEncoding; import org.apache.commons.lang3.StringUtils; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.io.RandomAccessReadBuffer; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.text.PDFTextStripper; import org.apache.solr.client.solrj.SolrServerException; @@ -994,7 +996,7 @@ private String extractPDFText(byte[] content) throws IOException { try (ByteArrayInputStream source = new ByteArrayInputStream(content); Writer writer = new StringWriter(); - PDDocument pdfDoc = PDDocument.load(source)) { + PDDocument pdfDoc = Loader.loadPDF(new RandomAccessReadBuffer(source))) { pts.writeText(pdfDoc, writer); return writer.toString(); @@ -1003,7 +1005,7 @@ private String extractPDFText(byte[] content) throws IOException { private int getNumberOfPdfPages(byte[] content) throws IOException { try (ByteArrayInputStream source = new ByteArrayInputStream(content); - PDDocument pdfDoc = PDDocument.load(source)) { + PDDocument pdfDoc = Loader.loadPDF(new RandomAccessReadBuffer(source))) { return pdfDoc.getNumberOfPages(); } } From 461cd778af3fb99132476e91fc1edaaa0da6154c Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 10 Dec 2025 17:56:36 +0100 Subject: [PATCH 759/939] Apply PDFBox 3.x changes to CitationDocumentServiceImpl --- .../disseminate/CitationDocumentServiceImpl.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/disseminate/CitationDocumentServiceImpl.java b/dspace-api/src/main/java/org/dspace/disseminate/CitationDocumentServiceImpl.java index b1441867772..bf9f7b9d6a0 100644 --- a/dspace-api/src/main/java/org/dspace/disseminate/CitationDocumentServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/disseminate/CitationDocumentServiceImpl.java @@ -23,6 +23,8 @@ import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.io.RandomAccessReadBuffer; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPageContentStream; @@ -30,6 +32,7 @@ import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.font.PDFont; import org.apache.pdfbox.pdmodel.font.PDType1Font; +import org.apache.pdfbox.pdmodel.font.Standard14Fonts; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Bitstream; @@ -304,7 +307,7 @@ public Pair makeCitedDocument(Context context, Bitstream bitstream Item item = (Item) bitstreamService.getParentObject(context, bitstream); final InputStream inputStream = bitstreamService.retrieve(context, bitstream); try { - sourceDocument = sourceDocument.load(inputStream); + sourceDocument = Loader.loadPDF(new RandomAccessReadBuffer(inputStream)); } finally { inputStream.close(); } @@ -335,9 +338,10 @@ protected void generateCoverPage(Context context, PDDocument document, PDPage co int xwidth = 550; int ygap = 20; - PDFont fontHelvetica = PDType1Font.HELVETICA; - PDFont fontHelveticaBold = PDType1Font.HELVETICA_BOLD; - PDFont fontHelveticaOblique = PDType1Font.HELVETICA_OBLIQUE; + PDFont fontHelvetica = new PDType1Font(Standard14Fonts.FontName.HELVETICA); + PDFont fontHelveticaBold = new PDType1Font(Standard14Fonts.FontName.HELVETICA_BOLD); + PDFont fontHelveticaOblique = new PDType1Font(Standard14Fonts.FontName.HELVETICA_OBLIQUE); + contentStream.setNonStrokingColor(Color.BLACK); String[][] content = {header1}; From ba927cec47174b8bfc35ced93cafc5adfe177023 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 19:56:59 +0000 Subject: [PATCH 760/939] Bump tika.version from 3.2.2 to 3.2.3 Bumps `tika.version` from 3.2.2 to 3.2.3. Updates `org.apache.tika:tika-core` from 3.2.2 to 3.2.3 - [Changelog](https://github.com/apache/tika/blob/main/CHANGES.txt) - [Commits](https://github.com/apache/tika/compare/3.2.2...3.2.3) Updates `org.apache.tika:tika-parsers-standard-package` from 3.2.2 to 3.2.3 --- updated-dependencies: - dependency-name: org.apache.tika:tika-core dependency-version: 3.2.3 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.tika:tika-parsers-standard-package dependency-version: 3.2.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6ee3ac3d46d..eaaf4d9d017 100644 --- a/pom.xml +++ b/pom.xml @@ -42,7 +42,7 @@ 3.0.5 1.19.0 1.7.36 - 3.2.2 + 3.2.3 1.82 From 903e8d43c641b770e15d82b717794d8eef90581c Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 10 Dec 2025 18:08:30 +0100 Subject: [PATCH 761/939] Revert org.apache.xerces import change --- .../epo/service/EpoImportMetadataSourceServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoImportMetadataSourceServiceImpl.java index 4ec1f4db39e..552f607827a 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoImportMetadataSourceServiceImpl.java @@ -29,9 +29,9 @@ import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpException; import org.apache.http.client.utils.URIBuilder; -import org.apache.jena.ext.xerces.impl.dv.util.Base64; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.xerces.impl.dv.util.Base64; import org.dspace.app.util.XMLUtils; import org.dspace.content.Item; import org.dspace.importer.external.datamodel.ImportRecord; From 686b06f5abeee96a8e32873d4f4ef35e358a394d Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Wed, 10 Dec 2025 15:04:16 +0100 Subject: [PATCH 762/939] [DURACOM-428] fix closed http client before reading entity (cherry picked from commit 5ac1c4a863b02bcdf7cf034d73c7aaa56952de52) --- .../impl/OrcidV3AuthorDataProvider.java | 3 + .../model/factory/OrcidFactoryUtils.java | 106 ++++++++++++------ 2 files changed, 73 insertions(+), 36 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/external/provider/impl/OrcidV3AuthorDataProvider.java b/dspace-api/src/main/java/org/dspace/external/provider/impl/OrcidV3AuthorDataProvider.java index a9e10f92948..fad0e75b4fb 100644 --- a/dspace-api/src/main/java/org/dspace/external/provider/impl/OrcidV3AuthorDataProvider.java +++ b/dspace-api/src/main/java/org/dspace/external/provider/impl/OrcidV3AuthorDataProvider.java @@ -89,6 +89,9 @@ public void init() throws IOException { public void initializeAccessToken() { // If we have reaches max retries or the access token is already set, return immediately if (maxClientRetries <= 0 || StringUtils.isNotBlank(accessToken)) { + if (maxClientRetries <= 0) { + log.warn("Maximum retry attempts reached for ORCID token retrieval"); + } return; } try { diff --git a/dspace-api/src/main/java/org/dspace/orcid/model/factory/OrcidFactoryUtils.java b/dspace-api/src/main/java/org/dspace/orcid/model/factory/OrcidFactoryUtils.java index ce68ab47c26..f08aff74058 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/model/factory/OrcidFactoryUtils.java +++ b/dspace-api/src/main/java/org/dspace/orcid/model/factory/OrcidFactoryUtils.java @@ -7,21 +7,29 @@ */ package org.dspace.orcid.model.factory; -import java.io.BufferedReader; +import static java.nio.charset.StandardCharsets.UTF_8; + import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Base64; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpResponse; +import org.apache.http.NameValuePair; +import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.message.BasicNameValuePair; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.client.DSpaceHttpClientFactory; import org.json.JSONObject; +import org.json.JSONTokener; /** * Utility class for Orcid factory classes. This is used to parse the @@ -29,13 +37,12 @@ * contributors and external ids configuration). * * @author Luca Giamminonni (luca.giamminonni at 4science.it) - * */ public final class OrcidFactoryUtils { - private OrcidFactoryUtils() { + private static final Logger log = LogManager.getLogger(OrcidFactoryUtils.class); - } + private OrcidFactoryUtils() { } /** * Parse the given configurations value and returns a map with metadata fields @@ -46,7 +53,7 @@ private OrcidFactoryUtils() { * @return the configurations parsing result as map */ public static Map parseConfigurations(String configurations) { - Map configurationMap = new HashMap(); + Map configurationMap = new HashMap<>(); if (StringUtils.isBlank(configurations)) { return configurationMap; } @@ -55,7 +62,6 @@ public static Map parseConfigurations(String configurations) { String[] configurationSections = parseConfiguration(configuration); configurationMap.put(configurationSections[0], configurationSections[1]); } - return configurationMap; } @@ -87,37 +93,65 @@ private static String[] parseConfiguration(String configuration) { */ public static Optional retrieveAccessToken(String clientId, String clientSecret, String oauthUrl) throws IOException { - if (StringUtils.isNotBlank(clientSecret) && StringUtils.isNotBlank(clientId) - && StringUtils.isNotBlank(oauthUrl)) { - String authenticationParameters = "?client_id=" + clientId + - "&client_secret=" + clientSecret + - "&scope=/read-public&grant_type=client_credentials"; - HttpPost httpPost = new HttpPost(oauthUrl + authenticationParameters); - httpPost.addHeader("Accept", "application/json"); - httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded"); - - HttpResponse response; - try (CloseableHttpClient httpClient = DSpaceHttpClientFactory.getInstance().build()) { - response = httpClient.execute(httpPost); + if (StringUtils.isBlank(clientSecret) || StringUtils.isBlank(clientId) || StringUtils.isBlank(oauthUrl)) { + String missingParams = (StringUtils.isBlank(clientId) ? "clientId " : "") + + (StringUtils.isBlank(clientSecret) ? "clientSecret " : "") + + (StringUtils.isBlank(oauthUrl) ? "oauthUrl" : ""); + log.error("Cannot retrieve ORCID access token: missing required parameters:{} ", missingParams.trim()); + return Optional.empty(); + } + + HttpPost httpPost = new HttpPost(oauthUrl); + + String auth = clientId + ":" + clientSecret; + String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes(UTF_8)); + addHeaders(httpPost, encodedAuth); + + List params = new ArrayList<>(); + params.add(new BasicNameValuePair("grant_type", "client_credentials")); + params.add(new BasicNameValuePair("scope", "/read-public")); + httpPost.setEntity(new UrlEncodedFormEntity(params, UTF_8)); + + try (CloseableHttpClient httpClient = DSpaceHttpClientFactory.getInstance().build()) { + log.debug("Sending ORCID token request to {}", oauthUrl); + HttpResponse response = httpClient.execute(httpPost); + if (!isSuccessful(response)) { + log.error("Failed to retrieve ORCID access token"); + return Optional.empty(); } - JSONObject responseObject = null; - if (response != null && response.getStatusLine().getStatusCode() == 200) { - try (InputStream is = response.getEntity().getContent(); - BufferedReader streamReader = new BufferedReader(new InputStreamReader(is, - StandardCharsets.UTF_8))) { - String inputStr; - while ((inputStr = streamReader.readLine()) != null && responseObject == null) { - if (inputStr.startsWith("{") && inputStr.endsWith("}") && inputStr.contains("access_token")) { - responseObject = new JSONObject(inputStr); - } - } + // Parsing JSON response + try (InputStream is = response.getEntity().getContent()) { + JSONObject responseObject = new JSONObject(new JSONTokener(is)); + if (responseObject.has("access_token")) { + String token = responseObject.getString("access_token"); + log.debug("Successfully retrieved ORCID access token"); + return Optional.of(token); + } else { + log.error("ORCID response missing access_token field:{} ", responseObject); + return Optional.empty(); } } - if (responseObject != null && responseObject.has("access_token")) { - return Optional.of((String) responseObject.get("access_token")); - } } - // Return empty by default - return Optional.empty(); } + + private static void addHeaders(HttpPost httpPost, String encodedAuth) { + httpPost.addHeader("Authorization", "Basic " + encodedAuth); + httpPost.addHeader("Accept", "application/json"); + httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded"); + } + + private static boolean isSuccessful(HttpResponse response) { + if (response == null) { + log.error("ORCID API request failed: null response received"); + return false; + } + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode != 200) { + var errorMsg = "ORCID API request failed with status code {}: {}"; + log.error(errorMsg, statusCode, response.getStatusLine().getReasonPhrase()); + return false; + } + return true; + } + } From d0e14fde74c0a039a07911b7d6dca72f104c7768 Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Wed, 10 Dec 2025 15:05:00 +0100 Subject: [PATCH 763/939] [DURACOM-428] improve ORCID connector (cherry picked from commit 768c669985ed106d819074c8f336f4c69cd7e568) --- .../dspace/external/OrcidRestConnector.java | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/external/OrcidRestConnector.java b/dspace-api/src/main/java/org/dspace/external/OrcidRestConnector.java index aa16af7a524..1e9688aa4ce 100644 --- a/dspace-api/src/main/java/org/dspace/external/OrcidRestConnector.java +++ b/dspace-api/src/main/java/org/dspace/external/OrcidRestConnector.java @@ -9,13 +9,13 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.util.Scanner; import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpResponse; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.util.EntityUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.app.client.DSpaceHttpClientFactory; @@ -28,9 +28,6 @@ */ public class OrcidRestConnector { - /** - * log4j logger - */ private static final Logger log = LogManager.getLogger(OrcidRestConnector.class); private final String url; @@ -40,32 +37,31 @@ public OrcidRestConnector(String url) { } public InputStream get(String path, String accessToken) { - CloseableHttpResponse getResponse = null; - InputStream result = null; - path = trimSlashes(path); - - String fullPath = url + '/' + path; + String fullPath = url + '/' + trimSlashes(path); HttpGet httpGet = new HttpGet(fullPath); if (StringUtils.isNotBlank(accessToken)) { httpGet.addHeader("Content-Type", "application/vnd.orcid+xml"); httpGet.addHeader("Authorization","Bearer " + accessToken); } try (CloseableHttpClient httpClient = DSpaceHttpClientFactory.getInstance().build()) { - getResponse = httpClient.execute(httpGet); - try (InputStream responseStream = getResponse.getEntity().getContent()) { + CloseableHttpResponse httpResponse = httpClient.execute(httpGet); + if (!isSuccessful(httpResponse)) { + // Consume entity to avoid memory leak + EntityUtils.consumeQuietly(httpResponse.getEntity()); + var statusCode = getStatusCode(httpResponse); + var reason = httpResponse.getStatusLine().getReasonPhrase(); + var error = String.format("The request failed with:%d code, reason:%s ", statusCode, reason); + throw new RuntimeException(error); + } + try (InputStream responseStream = httpResponse.getEntity().getContent()) { // Read all the content of the response stream into a byte array to prevent TruncatedChunkException byte[] content = responseStream.readAllBytes(); - result = new ByteArrayInputStream(content); + return new ByteArrayInputStream(content); } } catch (Exception e) { - getGotError(e, fullPath); + log.error("Error in rest connector for path: " + fullPath, e); + throw new RuntimeException("Failed to execute ORCID request: " + fullPath, e); } - - return result; - } - - protected void getGotError(Exception e, String fullPath) { - log.error("Error in rest connector for path: " + fullPath, e); } public static String trimSlashes(String path) { @@ -78,8 +74,13 @@ public static String trimSlashes(String path) { return path; } - public static String convertStreamToString(InputStream is) { - Scanner s = new Scanner(is, StandardCharsets.UTF_8).useDelimiter("\\A"); - return s.hasNext() ? s.next() : ""; + private boolean isSuccessful(HttpResponse response) { + int statusCode = getStatusCode(response); + return statusCode >= 200 || statusCode <= 299; + } + + private int getStatusCode(HttpResponse response) { + return response.getStatusLine().getStatusCode(); } + } From a1e107db7ef4b0c135e0022f244ea9bca71af4be Mon Sep 17 00:00:00 2001 From: Mykhaylo Boychuk Date: Wed, 10 Dec 2025 17:30:23 +0100 Subject: [PATCH 764/939] [DURACOM-428] implemented custom exception for orcid connector (cherry picked from commit e3187bf201b82bec7ab1506ea10f6444c75e9c83) --- .../orcid/Orcidv3SolrAuthorityImpl.java | 58 ++++++++++++------- .../external/OrcidConnectionException.java | 33 +++++++++++ .../dspace/external/OrcidRestConnector.java | 34 +++++------ .../impl/OrcidV3AuthorDataProvider.java | 51 ++++++++++------ .../org/dspace/authority/orcid/MockOrcid.java | 7 ++- 5 files changed, 124 insertions(+), 59 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/external/OrcidConnectionException.java diff --git a/dspace-api/src/main/java/org/dspace/authority/orcid/Orcidv3SolrAuthorityImpl.java b/dspace-api/src/main/java/org/dspace/authority/orcid/Orcidv3SolrAuthorityImpl.java index 494daa97734..312a00c146a 100644 --- a/dspace-api/src/main/java/org/dspace/authority/orcid/Orcidv3SolrAuthorityImpl.java +++ b/dspace-api/src/main/java/org/dspace/authority/orcid/Orcidv3SolrAuthorityImpl.java @@ -10,6 +10,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; @@ -20,6 +21,7 @@ import org.apache.logging.log4j.Logger; import org.dspace.authority.AuthorityValue; import org.dspace.authority.SolrAuthorityInterface; +import org.dspace.external.OrcidConnectionException; import org.dspace.external.OrcidRestConnector; import org.dspace.external.provider.orcid.xml.XMLtoBio; import org.dspace.orcid.model.factory.OrcidFactoryUtils; @@ -142,9 +144,15 @@ public Person getBio(String id) { return null; } initializeAccessToken(); - InputStream bioDocument = orcidRestConnector.get(id + ((id.endsWith("/person")) ? "" : "/person"), accessToken); - XMLtoBio converter = new XMLtoBio(); - return converter.convertSinglePerson(bioDocument); + try { + InputStream bioDocument = orcidRestConnector.get(id + ((id.endsWith("/person")) ? "" : "/person"), + accessToken); + XMLtoBio converter = new XMLtoBio(); + return converter.convertSinglePerson(bioDocument); + } catch (OrcidConnectionException e) { + log.error("Error retrieving ORCID bio for ID=" + id, e); + return null; + } } @@ -167,29 +175,35 @@ public List queryBio(String text, int start, int rows) { // Check / init access token initializeAccessToken(); - String searchPath = "search?q=" + URLEncoder.encode(text) + "&start=" + start + "&rows=" + rows; + String searchPath = "search?q=" + URLEncoder.encode(text, StandardCharsets.UTF_8) + "&start=" + start + + "&rows=" + rows; log.debug("queryBio searchPath=" + searchPath + " accessToken=" + accessToken); - InputStream bioDocument = orcidRestConnector.get(searchPath, accessToken); - XMLtoBio converter = new XMLtoBio(); - List results = converter.convert(bioDocument); - List bios = new LinkedList<>(); - for (Result result : results) { - OrcidIdentifier orcidIdentifier = result.getOrcidIdentifier(); - if (orcidIdentifier != null) { - log.debug("Found OrcidId=" + orcidIdentifier.toString()); - String orcid = orcidIdentifier.getPath(); - Person bio = getBio(orcid); - if (bio != null) { - bios.add(bio); + try { + InputStream bioDocument = orcidRestConnector.get(searchPath, accessToken); + XMLtoBio converter = new XMLtoBio(); + List results = converter.convert(bioDocument); + List bios = new LinkedList<>(); + for (Result result : results) { + OrcidIdentifier orcidIdentifier = result.getOrcidIdentifier(); + if (orcidIdentifier != null) { + log.debug("Found OrcidId=" + orcidIdentifier); + String orcid = orcidIdentifier.getPath(); + Person bio = getBio(orcid); + if (bio != null) { + bios.add(bio); + } } } + try { + bioDocument.close(); + } catch (IOException e) { + log.error(e.getMessage(), e); + } + return bios; + } catch (OrcidConnectionException e) { + log.error("Error searching ORCID for query=" + text, e); + return Collections.emptyList(); } - try { - bioDocument.close(); - } catch (IOException e) { - log.error(e.getMessage(), e); - } - return bios; } /** diff --git a/dspace-api/src/main/java/org/dspace/external/OrcidConnectionException.java b/dspace-api/src/main/java/org/dspace/external/OrcidConnectionException.java new file mode 100644 index 00000000000..3574045aab2 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/external/OrcidConnectionException.java @@ -0,0 +1,33 @@ +/** + * 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.external; + +/** + * Exception thrown when there are issues with ORCID service connections. + * + * @author Boychuk Mykhaylo (mykhaylo.boychuk@4science.com) + */ +public class OrcidConnectionException extends Exception { + + private final int statusCode; + + public OrcidConnectionException(String message, int statusCode) { + super(message); + this.statusCode = statusCode; + } + + public OrcidConnectionException(String message, int statusCode, Throwable cause) { + super(message, cause); + this.statusCode = statusCode; + } + + public int getStatusCode() { + return statusCode; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/external/OrcidRestConnector.java b/dspace-api/src/main/java/org/dspace/external/OrcidRestConnector.java index 1e9688aa4ce..3d61462cc35 100644 --- a/dspace-api/src/main/java/org/dspace/external/OrcidRestConnector.java +++ b/dspace-api/src/main/java/org/dspace/external/OrcidRestConnector.java @@ -15,7 +15,6 @@ import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.util.EntityUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.app.client.DSpaceHttpClientFactory; @@ -36,7 +35,7 @@ public OrcidRestConnector(String url) { this.url = url; } - public InputStream get(String path, String accessToken) { + public InputStream get(String path, String accessToken) throws OrcidConnectionException { String fullPath = url + '/' + trimSlashes(path); HttpGet httpGet = new HttpGet(fullPath); if (StringUtils.isNotBlank(accessToken)) { @@ -44,23 +43,24 @@ public InputStream get(String path, String accessToken) { httpGet.addHeader("Authorization","Bearer " + accessToken); } try (CloseableHttpClient httpClient = DSpaceHttpClientFactory.getInstance().build()) { - CloseableHttpResponse httpResponse = httpClient.execute(httpGet); - if (!isSuccessful(httpResponse)) { - // Consume entity to avoid memory leak - EntityUtils.consumeQuietly(httpResponse.getEntity()); - var statusCode = getStatusCode(httpResponse); - var reason = httpResponse.getStatusLine().getReasonPhrase(); - var error = String.format("The request failed with:%d code, reason:%s ", statusCode, reason); - throw new RuntimeException(error); - } - try (InputStream responseStream = httpResponse.getEntity().getContent()) { - // Read all the content of the response stream into a byte array to prevent TruncatedChunkException - byte[] content = responseStream.readAllBytes(); - return new ByteArrayInputStream(content); + try (CloseableHttpResponse httpResponse = httpClient.execute(httpGet)) { + if (!isSuccessful(httpResponse)) { + var statusCode = getStatusCode(httpResponse); + var reason = httpResponse.getStatusLine().getReasonPhrase(); + var error = String.format("The request failed with:%d code, reason:%s ", statusCode, reason); + throw new OrcidConnectionException(error, statusCode); + } + try (InputStream responseStream = httpResponse.getEntity().getContent()) { + // Read all the content of the response stream into a byte array to prevent TruncatedChunkException + byte[] content = responseStream.readAllBytes(); + return new ByteArrayInputStream(content); + } } + } catch (OrcidConnectionException e) { + throw e; } catch (Exception e) { log.error("Error in rest connector for path: " + fullPath, e); - throw new RuntimeException("Failed to execute ORCID request: " + fullPath, e); + throw new OrcidConnectionException("Failed to execute ORCID request: " + fullPath, 0, e); } } @@ -83,4 +83,4 @@ private int getStatusCode(HttpResponse response) { return response.getStatusLine().getStatusCode(); } -} +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/external/provider/impl/OrcidV3AuthorDataProvider.java b/dspace-api/src/main/java/org/dspace/external/provider/impl/OrcidV3AuthorDataProvider.java index fad0e75b4fb..fe2fdfa9538 100644 --- a/dspace-api/src/main/java/org/dspace/external/provider/impl/OrcidV3AuthorDataProvider.java +++ b/dspace-api/src/main/java/org/dspace/external/provider/impl/OrcidV3AuthorDataProvider.java @@ -21,6 +21,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.content.dto.MetadataValueDTO; +import org.dspace.external.OrcidConnectionException; import org.dspace.external.OrcidRestConnector; import org.dspace.external.model.ExternalDataObject; import org.dspace.external.provider.AbstractExternalDataProvider; @@ -171,8 +172,14 @@ public Person getBio(String id) { return null; } initializeAccessToken(); - InputStream bioDocument = orcidRestConnector.get(id + ((id.endsWith("/person")) ? "" : "/person"), accessToken); - return converter.convertSinglePerson(bioDocument); + try { + InputStream bioDocument = orcidRestConnector.get(id + ((id.endsWith("/person")) ? "" : "/person"), + accessToken); + return converter.convertSinglePerson(bioDocument); + } catch (OrcidConnectionException e) { + log.error("Error retrieving ORCID bio for ID=" + id, e); + return null; + } } /** @@ -203,21 +210,26 @@ public List searchExternalDataObjects(String query, int star + "&start=" + start + "&rows=" + limit; log.debug("queryBio searchPath=" + searchPath + " accessToken=" + accessToken); - InputStream bioDocument = orcidRestConnector.get(searchPath, accessToken); - List results = converter.convert(bioDocument); - List bios = new LinkedList<>(); - for (Result result : results) { - OrcidIdentifier orcidIdentifier = result.getOrcidIdentifier(); - if (orcidIdentifier != null) { - log.debug("Found OrcidId=" + orcidIdentifier.getPath()); - String orcid = orcidIdentifier.getPath(); - Person bio = getBio(orcid); - if (bio != null) { - bios.add(bio); + try { + InputStream bioDocument = orcidRestConnector.get(searchPath, accessToken); + List results = converter.convert(bioDocument); + List bios = new LinkedList<>(); + for (Result result : results) { + OrcidIdentifier orcidIdentifier = result.getOrcidIdentifier(); + if (orcidIdentifier != null) { + log.debug("Found OrcidId=" + orcidIdentifier.getPath()); + String orcid = orcidIdentifier.getPath(); + Person bio = getBio(orcid); + if (bio != null) { + bios.add(bio); + } } } + return bios.stream().map(bio -> convertToExternalDataObject(bio)).collect(Collectors.toList()); + } catch (OrcidConnectionException e) { + log.error("Error searching ORCID for query=" + query, e); + return Collections.emptyList(); } - return bios.stream().map(bio -> convertToExternalDataObject(bio)).collect(Collectors.toList()); } @Override @@ -236,8 +248,13 @@ public int getNumberOfResults(String query) { + "&start=" + 0 + "&rows=" + 0; log.debug("queryBio searchPath=" + searchPath + " accessToken=" + accessToken); - InputStream bioDocument = orcidRestConnector.get(searchPath, accessToken); - return Math.min(converter.getNumberOfResultsFromXml(bioDocument), MAX_INDEX); + try { + InputStream bioDocument = orcidRestConnector.get(searchPath, accessToken); + return Math.min(converter.getNumberOfResultsFromXml(bioDocument), MAX_INDEX); + } catch (OrcidConnectionException e) { + log.error("Error getting number of results from ORCID for query=" + query, e); + return 0; + } } @@ -299,4 +316,4 @@ public void setOrcidRestConnector(OrcidRestConnector orcidRestConnector) { this.orcidRestConnector = orcidRestConnector; } -} +} \ No newline at end of file diff --git a/dspace-api/src/test/java/org/dspace/authority/orcid/MockOrcid.java b/dspace-api/src/test/java/org/dspace/authority/orcid/MockOrcid.java index 511df79f1e5..b6be6d1f3ac 100644 --- a/dspace-api/src/test/java/org/dspace/authority/orcid/MockOrcid.java +++ b/dspace-api/src/test/java/org/dspace/authority/orcid/MockOrcid.java @@ -11,6 +11,7 @@ import java.io.InputStream; +import org.dspace.external.OrcidConnectionException; import org.dspace.external.OrcidRestConnector; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; @@ -38,7 +39,7 @@ public void init() { * Call this to set up mocking for any test classes that need it. We don't set it in init() * or other AbstractIntegrationTest implementations will complain of unnecessary Mockito stubbing */ - public void setupNoResultsSearch() { + public void setupNoResultsSearch() throws OrcidConnectionException { when(orcidRestConnector.get(ArgumentMatchers.startsWith("search?"), ArgumentMatchers.any())) .thenAnswer(new Answer() { @Override @@ -51,7 +52,7 @@ public InputStream answer(InvocationOnMock invocation) { * Call this to set up mocking for any test classes that need it. We don't set it in init() * or other AbstractIntegrationTest implementations will complain of unnecessary Mockito stubbing */ - public void setupSingleSearch() { + public void setupSingleSearch() throws OrcidConnectionException { when(orcidRestConnector.get(ArgumentMatchers.startsWith("search?q=Bollini"), ArgumentMatchers.any())) .thenAnswer(new Answer() { @Override @@ -64,7 +65,7 @@ public InputStream answer(InvocationOnMock invocation) { * Call this to set up mocking for any test classes that need it. We don't set it in init() * or other AbstractIntegrationTest implementations will complain of unnecessary Mockito stubbing */ - public void setupSearchWithResults() { + public void setupSearchWithResults() throws OrcidConnectionException { when(orcidRestConnector.get(ArgumentMatchers.endsWith("/person"), ArgumentMatchers.any())) .thenAnswer(new Answer() { @Override From 01e66eed48ccdb35b6be0fcbc3b85e4844bd7e7c Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Fri, 28 Nov 2025 17:45:01 +0100 Subject: [PATCH 765/939] fix(authentication-method): Sets the authentication method just after the login has been concluded successfully ref: DURACOM-401 (cherry picked from commit 008998bee45e76ea5e3180dd8ec6c339b88ede51) --- .../java/org/dspace/authenticate/AuthenticationServiceImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationServiceImpl.java b/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationServiceImpl.java index 493e4ade416..4ad7fbd8c34 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationServiceImpl.java @@ -111,6 +111,7 @@ protected int authenticateInternal(Context context, } if (ret == AuthenticationMethod.SUCCESS) { updateLastActiveDate(context); + context.setAuthenticationMethod(aMethodStack.getName()); return ret; } if (ret < bestRet) { From 9271a2550b4128d7238c2c7334659b41dad7d8db Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Fri, 28 Nov 2025 17:51:54 +0100 Subject: [PATCH 766/939] fix(shib-auth-groups): Fixes special-groups check inside AuthenticationMethod.java Adds some IT to verify the correctness of the authentication. ref: DURACOM-401 (cherry picked from commit cf8e7f3c43697b82a45c05d3cfc9c0c8d50ec678) --- .../authenticate/AuthenticationMethod.java | 2 +- .../rest/AuthenticationRestControllerIT.java | 96 +++++++++++++++++++ 2 files changed, 97 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationMethod.java b/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationMethod.java index 38488c41e30..2273275f5d2 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationMethod.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationMethod.java @@ -166,7 +166,7 @@ public List getSpecialGroups(Context context, HttpServletRequest request) * otherwise */ public default boolean areSpecialGroupsApplicable(Context context, HttpServletRequest request) { - return getName().equals(context.getAuthenticationMethod()); + return getName().equals(context.getAuthenticationMethod()) || isUsed(context,request); } /** diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java index 1da807ad718..988e9065ab4 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java @@ -1804,6 +1804,102 @@ private boolean tokenClaimsEqual(String token1, String token2) { } } + @Test + public void testShibbolethStaffMappedToStaffAndMembers() throws Exception { + context.turnOffAuthorisationSystem(); + + GroupBuilder.createGroup(context) + .withName("Staff") + .build(); + GroupBuilder.createGroup(context) + .withName("Member") + .build(); + + configurationService.setProperty("plugin.sequence.org.dspace.authenticate.AuthenticationMethod", SHIB_ONLY); + configurationService.setProperty("authentication-shibboleth.role.staff", "Staff, Member"); + configurationService.setProperty("authentication-shibboleth.default-roles", "staff"); + configurationService.setProperty("authentication-shibboleth.netid-header", "mail"); + configurationService.setProperty("authentication-shibboleth.email-header", "mail"); + + context.restoreAuthSystemState(); + + String shibToken = getClient().perform(post("/api/authn/login") + .requestAttr("mail", eperson.getEmail()) + .requestAttr("SHIB-SCOPED-AFFILIATION", "staff")) + .andExpect(status().isOk()) + .andReturn().getResponse().getHeader(AUTHORIZATION_HEADER).replace(AUTHORIZATION_TYPE, ""); + + getClient(shibToken).perform(get("/api/authn/status").param("projection", "full")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.okay", is(true))) + .andExpect(jsonPath("$.authenticated", is(true))) + .andExpect(jsonPath("$.authenticationMethod", is("shibboleth"))) + .andExpect(jsonPath("$._embedded.specialGroups._embedded.specialGroups", + Matchers.containsInAnyOrder( + matchGroupWithName("Staff"), + matchGroupWithName("Member") + ) + )); + + getClient(shibToken).perform(get("/api/authn/status/specialGroups").param("projection", "full")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.specialGroups", + Matchers.containsInAnyOrder( + matchGroupWithName("Staff"), + matchGroupWithName("Member") + ) + )); + } + + @Test + public void testPasswordLoginNotMappedToStaffAndMembers() throws Exception { + context.turnOffAuthorisationSystem(); + + GroupBuilder.createGroup(context) + .withName("Staff") + .build(); + GroupBuilder.createGroup(context) + .withName("Member") + .build(); + GroupBuilder.createGroup(context) + .withName("specialGroupPwd") + .build(); + + + configurationService.setProperty("plugin.sequence.org.dspace.authenticate.AuthenticationMethod", + "org.dspace.authenticate.PasswordAuthentication, org.dspace.authenticate.ShibAuthentication"); + configurationService.setProperty("authentication-shibboleth.role.staff", "Staff, Member"); + configurationService.setProperty("authentication-shibboleth.default-roles", "staff"); + configurationService.setProperty("authentication-shibboleth.netid-header", "mail"); + configurationService.setProperty("authentication-shibboleth.email-header", "mail"); + configurationService.setProperty("authentication-password.login.specialgroup", "specialGroupPwd"); + + context.restoreAuthSystemState(); + + String passwordToken = getAuthToken(eperson.getEmail(), password); + + getClient(passwordToken).perform(get("/api/authn/status").param("projection", "full")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.okay", is(true))) + .andExpect(jsonPath("$.authenticated", is(true))) + .andExpect(jsonPath("$.authenticationMethod", is("password"))) + .andExpect(jsonPath("$._embedded.specialGroups._embedded.specialGroups", + Matchers.containsInAnyOrder( + matchGroupWithName("specialGroupPwd") + ) + )); + + getClient(passwordToken).perform(get("/api/authn/status/specialGroups").param("projection", "full")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.specialGroups", + Matchers.containsInAnyOrder( + matchGroupWithName("specialGroupPwd") + ) + )); + } + + + private OrcidTokenResponseDTO buildOrcidTokenResponse(String orcid, String accessToken) { OrcidTokenResponseDTO token = new OrcidTokenResponseDTO(); token.setAccessToken(accessToken); From 0910df2113bfb61c64e48fe4aa7671e260efa2bc Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Mon, 15 Dec 2025 13:06:52 +0100 Subject: [PATCH 767/939] fix(style): Adds missing space between parameters of isUsed method ref: DURACOM-401 (cherry picked from commit 40cc61b493e0bec624416fe8aa6f9744d142b6ff) --- .../main/java/org/dspace/authenticate/AuthenticationMethod.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationMethod.java b/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationMethod.java index 2273275f5d2..7c8793a6c21 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationMethod.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationMethod.java @@ -166,7 +166,7 @@ public List getSpecialGroups(Context context, HttpServletRequest request) * otherwise */ public default boolean areSpecialGroupsApplicable(Context context, HttpServletRequest request) { - return getName().equals(context.getAuthenticationMethod()) || isUsed(context,request); + return getName().equals(context.getAuthenticationMethod()) || isUsed(context, request); } /** From 34dfdd502fcf48cee5b306fc33a0881953449581 Mon Sep 17 00:00:00 2001 From: Daniel Coelho Date: Fri, 19 Sep 2025 15:53:03 -0300 Subject: [PATCH 768/939] Trigger object reindex when updating eperson or eperson group of an associated resource policy (fixes #11325) (cherry picked from commit fbddb06ae2377b8f4475df27c6b371fa5358fb45) --- .../app/rest/ResourcePolicyEPersonReplaceRestController.java | 1 + .../app/rest/ResourcePolicyGroupReplaceRestController.java | 1 + 2 files changed, 2 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ResourcePolicyEPersonReplaceRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ResourcePolicyEPersonReplaceRestController.java index e772aa0abe1..e625644c610 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ResourcePolicyEPersonReplaceRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ResourcePolicyEPersonReplaceRestController.java @@ -75,6 +75,7 @@ public ResponseEntity> replaceEPersonOfResourcePolicy(@Pa } EPerson newEPerson = (EPerson) dsoList.get(0); resourcePolicy.setEPerson(newEPerson); + resourcePolicyService.update(context, resourcePolicy); context.commit(); return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ResourcePolicyGroupReplaceRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ResourcePolicyGroupReplaceRestController.java index e9ba0dff442..c6927df8150 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ResourcePolicyGroupReplaceRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ResourcePolicyGroupReplaceRestController.java @@ -75,6 +75,7 @@ public ResponseEntity> replaceGroupOfResourcePolicy(@Path Group newGroup = (Group) dsoList.get(0); resourcePolicy.setGroup(newGroup); + resourcePolicyService.update(context, resourcePolicy); context.commit(); return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT); } From e388b83c7d542de5abcaca9685bdd6a6a9b06214 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 16 Dec 2025 15:03:41 -0600 Subject: [PATCH 769/939] Update LICENSES_THIRD_PARTY for v7.6.6 --- LICENSES_THIRD_PARTY | 314 +++++++++++++++++++++++-------------------- 1 file changed, 165 insertions(+), 149 deletions(-) diff --git a/LICENSES_THIRD_PARTY b/LICENSES_THIRD_PARTY index 92868467c98..174c8760cab 100644 --- a/LICENSES_THIRD_PARTY +++ b/LICENSES_THIRD_PARTY @@ -21,19 +21,14 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines Apache Software License, Version 2.0: * Ant-Contrib Tasks (ant-contrib:ant-contrib:1.0b3 - http://ant-contrib.sourceforge.net) - * AWS SDK for Java - Core (com.amazonaws:aws-java-sdk-core:1.12.785 - https://aws.amazon.com/sdkforjava) - * AWS Java SDK for AWS KMS (com.amazonaws:aws-java-sdk-kms:1.12.785 - https://aws.amazon.com/sdkforjava) - * AWS Java SDK for Amazon S3 (com.amazonaws:aws-java-sdk-s3:1.12.785 - https://aws.amazon.com/sdkforjava) - * JMES Path Query library (com.amazonaws:jmespath-java:1.12.785 - https://aws.amazon.com/sdkforjava) * HPPC Collections (com.carrotsearch:hppc:0.8.1 - http://labs.carrotsearch.com/hppc.html/hppc) * com.drewnoakes:metadata-extractor (com.drewnoakes:metadata-extractor:2.19.0 - https://drewnoakes.com/code/exif/) * parso (com.epam:parso:2.0.14 - https://github.com/epam/parso) * Internet Time Utility (com.ethlo.time:itu:1.7.0 - https://github.com/ethlo/itu) - * ClassMate (com.fasterxml:classmate:1.7.0 - https://github.com/FasterXML/java-classmate) - * Jackson-annotations (com.fasterxml.jackson.core:jackson-annotations:2.19.1 - https://github.com/FasterXML/jackson) - * Jackson-core (com.fasterxml.jackson.core:jackson-core:2.19.1 - https://github.com/FasterXML/jackson-core) - * jackson-databind (com.fasterxml.jackson.core:jackson-databind:2.19.1 - https://github.com/FasterXML/jackson) - * Jackson dataformat: CBOR (com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.17.2 - https://github.com/FasterXML/jackson-dataformats-binary) + * ClassMate (com.fasterxml:classmate:1.7.1 - https://github.com/FasterXML/java-classmate) + * Jackson-annotations (com.fasterxml.jackson.core:jackson-annotations:2.20 - https://github.com/FasterXML/jackson) + * Jackson-core (com.fasterxml.jackson.core:jackson-core:2.20.1 - https://github.com/FasterXML/jackson-core) + * jackson-databind (com.fasterxml.jackson.core:jackson-databind:2.20.1 - https://github.com/FasterXML/jackson) * Jackson dataformat: Smile (com.fasterxml.jackson.dataformat:jackson-dataformat-smile:2.15.2 - https://github.com/FasterXML/jackson-dataformats-binary) * Jackson-dataformat-YAML (com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.14.0 - https://github.com/FasterXML/jackson-dataformats-text) * Jackson datatype: jdk8 (com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.13.5 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8) @@ -45,6 +40,9 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * zjsonpatch (com.flipkart.zjsonpatch:zjsonpatch:0.4.16 - https://github.com/flipkart-incubator/zjsonpatch/) * Caffeine cache (com.github.ben-manes.caffeine:caffeine:2.9.3 - https://github.com/ben-manes/caffeine) * JSON.simple (com.github.cliftonlabs:json-simple:3.0.2 - https://cliftonlabs.github.io/json-simple/) + * docker-java-api (com.github.docker-java:docker-java-api:3.7.0 - https://github.com/docker-java/docker-java) + * docker-java-transport (com.github.docker-java:docker-java-transport:3.7.0 - https://github.com/docker-java/docker-java) + * docker-java-transport-zerodep (com.github.docker-java:docker-java-transport-zerodep:3.7.0 - https://github.com/docker-java/docker-java) * btf (com.github.java-json-tools:btf:1.3 - https://github.com/java-json-tools/btf) * jackson-coreutils (com.github.java-json-tools:jackson-coreutils:2.0 - https://github.com/java-json-tools/jackson-coreutils) * jackson-coreutils-equivalence (com.github.java-json-tools:jackson-coreutils-equivalence:1.0 - https://github.com/java-json-tools/jackson-coreutils) @@ -62,26 +60,26 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Guava InternalFutureFailureAccess and InternalFutures (com.google.guava:failureaccess:1.0.1 - https://github.com/google/guava/failureaccess) * Guava: Google Core Libraries for Java (com.google.guava:guava:32.1.3-jre - https://github.com/google/guava) * Guava ListenableFuture only (com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava - https://github.com/google/guava/listenablefuture) - * Google HTTP Client Library for Java (com.google.http-client:google-http-client:1.47.0 - https://github.com/googleapis/google-http-java-client/google-http-client) + * Google HTTP Client Library for Java (com.google.http-client:google-http-client:1.47.1 - https://github.com/googleapis/google-http-java-client/google-http-client) * Apache HTTP transport v2 for the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-apache-v2:1.42.0 - https://github.com/googleapis/google-http-java-client/google-http-client-apache-v2) - * GSON extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-gson:1.47.0 - https://github.com/googleapis/google-http-java-client/google-http-client-gson) - * Jackson 2 extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-jackson2:1.47.0 - https://github.com/googleapis/google-http-java-client/google-http-client-jackson2) + * GSON extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-gson:1.47.1 - https://github.com/googleapis/google-http-java-client/google-http-client-gson) + * Jackson 2 extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-jackson2:1.47.1 - https://github.com/googleapis/google-http-java-client/google-http-client-jackson2) * J2ObjC Annotations (com.google.j2objc:j2objc-annotations:1.3 - https://github.com/google/j2objc/) * J2ObjC Annotations (com.google.j2objc:j2objc-annotations:2.8 - https://github.com/google/j2objc/) * Google OAuth Client Library for Java (com.google.oauth-client:google-oauth-client:1.39.0 - https://github.com/googleapis/google-oauth-java-client/google-oauth-client) * ConcurrentLinkedHashMap (com.googlecode.concurrentlinkedhashmap:concurrentlinkedhashmap-lru:1.4.2 - http://code.google.com/p/concurrentlinkedhashmap) * libphonenumber (com.googlecode.libphonenumber:libphonenumber:8.11.1 - https://github.com/google/libphonenumber/) - * Jackcess (com.healthmarketscience.jackcess:jackcess:4.0.8 - https://jackcess.sourceforge.io) + * Jackcess (com.healthmarketscience.jackcess:jackcess:4.0.10 - https://jackcess.sourceforge.io) * Jackcess Encrypt (com.healthmarketscience.jackcess:jackcess-encrypt:4.0.3 - http://jackcessencrypt.sf.net) - * json-path (com.jayway.jsonpath:json-path:2.9.0 - https://github.com/jayway/JsonPath) - * json-path-assert (com.jayway.jsonpath:json-path-assert:2.9.0 - https://github.com/jayway/JsonPath) + * json-path (com.jayway.jsonpath:json-path:2.10.0 - https://github.com/jayway/JsonPath) + * json-path-assert (com.jayway.jsonpath:json-path-assert:2.10.0 - https://github.com/jayway/JsonPath) * Disruptor Framework (com.lmax:disruptor:3.4.2 - http://lmax-exchange.github.com/disruptor) * MaxMind DB Reader (com.maxmind.db:maxmind-db:2.1.0 - http://dev.maxmind.com/) * MaxMind GeoIP2 API (com.maxmind.geoip2:geoip2:2.17.0 - https://dev.maxmind.com/geoip?lang=en) * JsonSchemaValidator (com.networknt:json-schema-validator:1.0.76 - https://github.com/networknt/json-schema-validator) * Nimbus JOSE+JWT (com.nimbusds:nimbus-jose-jwt:7.9 - https://bitbucket.org/connect2id/nimbus-jose-jwt) * Nimbus JOSE+JWT (com.nimbusds:nimbus-jose-jwt:9.28 - https://bitbucket.org/connect2id/nimbus-jose-jwt) - * opencsv (com.opencsv:opencsv:5.11.1 - http://opencsv.sf.net) + * opencsv (com.opencsv:opencsv:5.12.0 - http://opencsv.sf.net) * java-libpst (com.pff:java-libpst:0.9.3 - https://github.com/rjohnsondev/java-libpst) * rome (com.rometools:rome:1.19.0 - http://rometools.com/rome) * rome-modules (com.rometools:rome-modules:1.19.0 - http://rometools.com/rome-modules) @@ -91,27 +89,18 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * okio (com.squareup.okio:okio:3.6.0 - https://github.com/square/okio/) * okio (com.squareup.okio:okio-jvm:3.6.0 - https://github.com/square/okio/) * T-Digest (com.tdunning:t-digest:3.1 - https://github.com/tdunning/t-digest) - * config (com.typesafe:config:1.3.3 - https://github.com/lightbend/config) - * ssl-config-core (com.typesafe:ssl-config-core_2.13:0.3.8 - https://github.com/lightbend/ssl-config) - * akka-actor (com.typesafe.akka:akka-actor_2.13:2.5.31 - https://akka.io/) - * akka-http-core (com.typesafe.akka:akka-http-core_2.13:10.1.12 - https://akka.io) - * akka-http (com.typesafe.akka:akka-http_2.13:10.1.12 - https://akka.io) - * akka-parsing (com.typesafe.akka:akka-parsing_2.13:10.1.12 - https://akka.io) - * akka-protobuf (com.typesafe.akka:akka-protobuf_2.13:2.5.31 - https://akka.io/) - * akka-stream (com.typesafe.akka:akka-stream_2.13:2.5.31 - https://akka.io/) - * scala-logging (com.typesafe.scala-logging:scala-logging_2.13:3.9.2 - https://github.com/lightbend/scala-logging) * JSON library from Android SDK (com.vaadin.external.google:android-json:0.0.20131108.vaadin1 - http://developer.android.com/sdk) * SparseBitSet (com.zaxxer:SparseBitSet:1.3 - https://github.com/brettwooldridge/SparseBitSet) * Apache Commons BeanUtils (commons-beanutils:commons-beanutils:1.11.0 - https://commons.apache.org/proper/commons-beanutils) - * Apache Commons CLI (commons-cli:commons-cli:1.9.0 - https://commons.apache.org/proper/commons-cli/) - * Apache Commons Codec (commons-codec:commons-codec:1.18.0 - https://commons.apache.org/proper/commons-codec/) + * Apache Commons CLI (commons-cli:commons-cli:1.11.0 - https://commons.apache.org/proper/commons-cli/) + * Apache Commons Codec (commons-codec:commons-codec:1.20.0 - https://commons.apache.org/proper/commons-codec/) * Apache Commons Collections (commons-collections:commons-collections:3.2.2 - http://commons.apache.org/collections/) * Commons Digester (commons-digester:commons-digester:2.1 - http://commons.apache.org/digester/) * Commons FileUpload (commons-fileupload:commons-fileupload:1.2.1 - http://commons.apache.org/fileupload/) - * Apache Commons IO (commons-io:commons-io:2.19.0 - https://commons.apache.org/proper/commons-io/) + * Apache Commons IO (commons-io:commons-io:2.21.0 - https://commons.apache.org/proper/commons-io/) * Commons Lang (commons-lang:commons-lang:2.6 - http://commons.apache.org/lang/) * Apache Commons Logging (commons-logging:commons-logging:1.3.5 - https://commons.apache.org/proper/commons-logging/) - * Apache Commons Validator (commons-validator:commons-validator:1.9.0 - http://commons.apache.org/proper/commons-validator/) + * Apache Commons Validator (commons-validator:commons-validator:1.10.1 - https://commons.apache.org/proper/commons-validator/) * GeoJson POJOs for Jackson (de.grundid.opendatalab:geojson-jackson:1.14 - https://github.com/opendatalab-de/geojson-jackson) * OpenAIRE Funders Model (eu.openaire:funders-model:2.0.0 - https://api.openaire.eu) * Metrics Core (io.dropwizard.metrics:metrics-core:4.1.5 - https://metrics.dropwizard.io/metrics-core) @@ -119,34 +108,34 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Metrics Integration for Jetty 9.3 and higher (io.dropwizard.metrics:metrics-jetty9:4.1.5 - https://metrics.dropwizard.io/metrics-jetty9) * Metrics Integration with JMX (io.dropwizard.metrics:metrics-jmx:4.1.5 - https://metrics.dropwizard.io/metrics-jmx) * JVM Integration for Metrics (io.dropwizard.metrics:metrics-jvm:4.1.5 - https://metrics.dropwizard.io/metrics-jvm) - * io.grpc:grpc-api (io.grpc:grpc-api:1.73.0 - https://github.com/grpc/grpc-java) - * io.grpc:grpc-context (io.grpc:grpc-context:1.73.0 - https://github.com/grpc/grpc-java) + * io.grpc:grpc-api (io.grpc:grpc-api:1.77.0 - https://github.com/grpc/grpc-java) + * io.grpc:grpc-context (io.grpc:grpc-context:1.77.0 - https://github.com/grpc/grpc-java) * micrometer-core (io.micrometer:micrometer-core:1.9.17 - https://github.com/micrometer-metrics/micrometer) * Netty/Buffer (io.netty:netty-buffer:4.1.99.Final - https://netty.io/netty-buffer/) - * Netty/Buffer (io.netty:netty-buffer:4.2.2.Final - https://netty.io/netty-buffer/) + * Netty/Buffer (io.netty:netty-buffer:4.2.7.Final - https://netty.io/netty-buffer/) * Netty/Codec (io.netty:netty-codec:4.1.99.Final - https://netty.io/netty-codec/) - * Netty/Codec (io.netty:netty-codec:4.2.2.Final - https://netty.io/netty-codec/) - * Netty/Codec/Base (io.netty:netty-codec-base:4.2.2.Final - https://netty.io/netty-codec-base/) - * Netty/Codec/Compression (io.netty:netty-codec-compression:4.2.2.Final - https://netty.io/netty-codec-compression/) + * Netty/Codec (io.netty:netty-codec:4.2.7.Final - https://netty.io/netty-codec/) + * Netty/Codec/Base (io.netty:netty-codec-base:4.2.7.Final - https://netty.io/netty-codec-base/) + * Netty/Codec/Compression (io.netty:netty-codec-compression:4.2.7.Final - https://netty.io/netty-codec-compression/) * Netty/Codec/HTTP (io.netty:netty-codec-http:4.1.86.Final - https://netty.io/netty-codec-http/) * Netty/Codec/HTTP2 (io.netty:netty-codec-http2:4.1.86.Final - https://netty.io/netty-codec-http2/) - * Netty/Codec/Marshalling (io.netty:netty-codec-marshalling:4.2.2.Final - https://netty.io/netty-codec-marshalling/) - * Netty/Codec/Protobuf (io.netty:netty-codec-protobuf:4.2.2.Final - https://netty.io/netty-codec-protobuf/) + * Netty/Codec/Marshalling (io.netty:netty-codec-marshalling:4.2.7.Final - https://netty.io/netty-codec-marshalling/) + * Netty/Codec/Protobuf (io.netty:netty-codec-protobuf:4.2.7.Final - https://netty.io/netty-codec-protobuf/) * Netty/Codec/Socks (io.netty:netty-codec-socks:4.1.86.Final - https://netty.io/netty-codec-socks/) * Netty/Common (io.netty:netty-common:4.1.99.Final - https://netty.io/netty-common/) - * Netty/Common (io.netty:netty-common:4.2.2.Final - https://netty.io/netty-common/) + * Netty/Common (io.netty:netty-common:4.2.7.Final - https://netty.io/netty-common/) * Netty/Handler (io.netty:netty-handler:4.1.99.Final - https://netty.io/netty-handler/) - * Netty/Handler (io.netty:netty-handler:4.2.2.Final - https://netty.io/netty-handler/) + * Netty/Handler (io.netty:netty-handler:4.2.7.Final - https://netty.io/netty-handler/) * Netty/Handler/Proxy (io.netty:netty-handler-proxy:4.1.86.Final - https://netty.io/netty-handler-proxy/) * Netty/Resolver (io.netty:netty-resolver:4.1.99.Final - https://netty.io/netty-resolver/) * Netty/TomcatNative [BoringSSL - Static] (io.netty:netty-tcnative-boringssl-static:2.0.56.Final - https://github.com/netty/netty-tcnative/netty-tcnative-boringssl-static/) * Netty/TomcatNative [OpenSSL - Classes] (io.netty:netty-tcnative-classes:2.0.56.Final - https://github.com/netty/netty-tcnative/netty-tcnative-classes/) * Netty/Transport (io.netty:netty-transport:4.1.99.Final - https://netty.io/netty-transport/) - * Netty/Transport (io.netty:netty-transport:4.2.2.Final - https://netty.io/netty-transport/) + * Netty/Transport (io.netty:netty-transport:4.2.7.Final - https://netty.io/netty-transport/) * Netty/Transport/Classes/Epoll (io.netty:netty-transport-classes-epoll:4.1.99.Final - https://netty.io/netty-transport-classes-epoll/) * Netty/Transport/Native/Epoll (io.netty:netty-transport-native-epoll:4.1.99.Final - https://netty.io/netty-transport-native-epoll/) * Netty/Transport/Native/Unix/Common (io.netty:netty-transport-native-unix-common:4.1.99.Final - https://netty.io/netty-transport-native-unix-common/) - * Netty/Transport/Native/Unix/Common (io.netty:netty-transport-native-unix-common:4.2.2.Final - https://netty.io/netty-transport-native-unix-common/) + * Netty/Transport/Native/Unix/Common (io.netty:netty-transport-native-unix-common:4.2.7.Final - https://netty.io/netty-transport-native-unix-common/) * OpenCensus (io.opencensus:opencensus-api:0.31.1 - https://github.com/census-instrumentation/opencensus-java) * OpenCensus (io.opencensus:opencensus-contrib-http-util:0.31.1 - https://github.com/census-instrumentation/opencensus-java) * OpenTracing API (io.opentracing:opentracing-api:0.33.0 - https://github.com/opentracing/opentracing-java/opentracing-api) @@ -180,33 +169,32 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Byte Buddy (without dependencies) (net.bytebuddy:byte-buddy:1.12.18 - https://bytebuddy.net/byte-buddy) * Byte Buddy agent (net.bytebuddy:byte-buddy-agent:1.11.13 - https://bytebuddy.net/byte-buddy-agent) * eigenbase-properties (net.hydromatic:eigenbase-properties:1.1.5 - http://github.com/julianhyde/eigenbase-properties) + * Java Native Access (net.java.dev.jna:jna:5.18.1 - https://github.com/java-native-access/jna) * json-unit-core (net.javacrumbs.json-unit:json-unit-core:2.36.0 - https://github.com/lukas-krecan/JsonUnit/json-unit-core) * "Java Concurrency in Practice" book annotations (net.jcip:jcip-annotations:1.0 - http://jcip.net/) - * ASM based accessors helper used by json-smart (net.minidev:accessors-smart:2.5.0 - https://urielch.github.io/) - * ASM based accessors helper used by json-smart (net.minidev:accessors-smart:2.5.2 - https://urielch.github.io/) - * JSON Small and Fast Parser (net.minidev:json-smart:2.5.0 - https://urielch.github.io/) - * JSON Small and Fast Parser (net.minidev:json-smart:2.5.2 - https://urielch.github.io/) + * ASM based accessors helper used by json-smart (net.minidev:accessors-smart:2.6.0 - https://urielch.github.io/) + * JSON Small and Fast Parser (net.minidev:json-smart:2.6.0 - https://urielch.github.io/) * Abdera Core (org.apache.abdera:abdera-core:1.1.3 - http://abdera.apache.org/abdera-core) * I18N Libraries (org.apache.abdera:abdera-i18n:1.1.3 - http://abdera.apache.org) * Apache Ant Core (org.apache.ant:ant:1.10.15 - https://ant.apache.org/) * Apache Ant Launcher (org.apache.ant:ant-launcher:1.10.15 - https://ant.apache.org/) - * Apache Commons BCEL (org.apache.bcel:bcel:6.10.0 - https://commons.apache.org/proper/commons-bcel) + * Apache Commons BCEL (org.apache.bcel:bcel:6.11.0 - https://commons.apache.org/proper/commons-bcel) * Calcite Core (org.apache.calcite:calcite-core:1.35.0 - https://calcite.apache.org) * Calcite Linq4j (org.apache.calcite:calcite-linq4j:1.35.0 - https://calcite.apache.org) * Apache Calcite Avatica (org.apache.calcite.avatica:avatica-core:1.23.0 - https://calcite.apache.org/avatica) * Apache Calcite Avatica Metrics (org.apache.calcite.avatica:avatica-metrics:1.23.0 - https://calcite.apache.org/avatica) * Apache Commons Collections (org.apache.commons:commons-collections4:4.5.0 - https://commons.apache.org/proper/commons-collections/) - * Apache Commons Compress (org.apache.commons:commons-compress:1.27.1 - https://commons.apache.org/proper/commons-compress/) - * Apache Commons Configuration (org.apache.commons:commons-configuration2:2.12.0 - https://commons.apache.org/proper/commons-configuration/) - * Apache Commons CSV (org.apache.commons:commons-csv:1.14.0 - https://commons.apache.org/proper/commons-csv/) + * Apache Commons Compress (org.apache.commons:commons-compress:1.28.0 - https://commons.apache.org/proper/commons-compress/) + * Apache Commons Configuration (org.apache.commons:commons-configuration2:2.13.0 - https://commons.apache.org/proper/commons-configuration/) + * Apache Commons CSV (org.apache.commons:commons-csv:1.14.1 - https://commons.apache.org/proper/commons-csv/) * Apache Commons DBCP (org.apache.commons:commons-dbcp2:2.13.0 - https://commons.apache.org/proper/commons-dbcp/) * Apache Commons Digester (org.apache.commons:commons-digester3:3.2 - http://commons.apache.org/digester/) * Apache Commons Exec (org.apache.commons:commons-exec:1.3 - http://commons.apache.org/proper/commons-exec/) - * Apache Commons Exec (org.apache.commons:commons-exec:1.4.0 - https://commons.apache.org/proper/commons-exec/) - * Apache Commons Lang (org.apache.commons:commons-lang3:3.17.0 - https://commons.apache.org/proper/commons-lang/) + * Apache Commons Exec (org.apache.commons:commons-exec:1.5.0 - https://commons.apache.org/proper/commons-exec/) + * Apache Commons Lang (org.apache.commons:commons-lang3:3.20.0 - https://commons.apache.org/proper/commons-lang/) * Apache Commons Math (org.apache.commons:commons-math3:3.6.1 - http://commons.apache.org/proper/commons-math/) * Apache Commons Pool (org.apache.commons:commons-pool2:2.12.1 - https://commons.apache.org/proper/commons-pool/) - * Apache Commons Text (org.apache.commons:commons-text:1.13.1 - https://commons.apache.org/proper/commons-text) + * Apache Commons Text (org.apache.commons:commons-text:1.14.0 - https://commons.apache.org/proper/commons-text) * Curator Client (org.apache.curator:curator-client:2.13.0 - http://curator.apache.org/curator-client) * Curator Framework (org.apache.curator:curator-framework:2.13.0 - http://curator.apache.org/curator-framework) * Curator Recipes (org.apache.curator:curator-recipes:2.13.0 - http://curator.apache.org/curator-recipes) @@ -222,8 +210,8 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Apache HttpClient (org.apache.httpcomponents.client5:httpclient5:5.1.3 - https://hc.apache.org/httpcomponents-client-5.0.x/5.1.3/httpclient5/) * Apache HttpComponents Core HTTP/1.1 (org.apache.httpcomponents.core5:httpcore5:5.1.3 - https://hc.apache.org/httpcomponents-core-5.1.x/5.1.3/httpcore5/) * Apache HttpComponents Core HTTP/2 (org.apache.httpcomponents.core5:httpcore5-h2:5.1.3 - https://hc.apache.org/httpcomponents-core-5.1.x/5.1.3/httpcore5-h2/) - * Apache James :: Mime4j :: Core (org.apache.james:apache-mime4j-core:0.8.12 - http://james.apache.org/mime4j/apache-mime4j-core) - * Apache James :: Mime4j :: DOM (org.apache.james:apache-mime4j-dom:0.8.12 - http://james.apache.org/mime4j/apache-mime4j-dom) + * Apache James :: Mime4j :: Core (org.apache.james:apache-mime4j-core:0.8.13 - http://james.apache.org/mime4j/apache-mime4j-core) + * Apache James :: Mime4j :: DOM (org.apache.james:apache-mime4j-dom:0.8.13 - http://james.apache.org/mime4j/apache-mime4j-dom) * Apache Jena - Libraries POM (org.apache.jena:apache-jena-libs:2.13.0 - http://jena.apache.org/apache-jena-libs/) * Apache Jena - ARQ (SPARQL 1.1 Query Engine) (org.apache.jena:jena-arq:2.13.0 - http://jena.apache.org/jena-arq/) * Apache Jena - Core (org.apache.jena:jena-core:2.13.0 - http://jena.apache.org/jena-core/) @@ -233,9 +221,9 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Kerby-kerb Util (org.apache.kerby:kerb-util:1.0.1 - http://directory.apache.org/kerby/kerby-kerb/kerb-util) * Kerby ASN1 Project (org.apache.kerby:kerby-asn1:1.0.1 - http://directory.apache.org/kerby/kerby-common/kerby-asn1) * Kerby PKIX Project (org.apache.kerby:kerby-pkix:1.0.1 - http://directory.apache.org/kerby/kerby-pkix) - * Apache Log4j 1.x Compatibility API (org.apache.logging.log4j:log4j-1.2-api:2.25.1 - https://logging.apache.org/log4j/2.x/) - * Apache Log4j API (org.apache.logging.log4j:log4j-api:2.25.1 - https://logging.apache.org/log4j/2.x/) - * Apache Log4j Core (org.apache.logging.log4j:log4j-core:2.25.1 - https://logging.apache.org/log4j/2.x/) + * Apache Log4j 1.x Compatibility API (org.apache.logging.log4j:log4j-1.2-api:2.25.2 - https://logging.apache.org/log4j/2.x/) + * Apache Log4j API (org.apache.logging.log4j:log4j-api:2.25.2 - https://logging.apache.org/log4j/2.x/) + * Apache Log4j Core (org.apache.logging.log4j:log4j-core:2.25.2 - https://logging.apache.org/log4j/2.x/) * Apache Log4j JUL Adapter (org.apache.logging.log4j:log4j-jul:2.17.2 - https://logging.apache.org/log4j/2.x/log4j-jul/) * Apache Log4j Layout for JSON template (org.apache.logging.log4j:log4j-layout-template-json:2.17.2 - https://logging.apache.org/log4j/2.x/log4j-layout-template-json/) * Apache Log4j SLF4J Binding (org.apache.logging.log4j:log4j-slf4j-impl:2.17.2 - https://logging.apache.org/log4j/2.x/log4j-slf4j-impl/) @@ -263,12 +251,13 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Lucene Spatial Extras (org.apache.lucene:lucene-spatial-extras:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-spatial-extras) * Lucene Spatial 3D (org.apache.lucene:lucene-spatial3d:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-spatial3d) * Lucene Suggest (org.apache.lucene:lucene-suggest:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-suggest) - * Apache FontBox (org.apache.pdfbox:fontbox:2.0.34 - http://pdfbox.apache.org/) + * Apache FontBox (org.apache.pdfbox:fontbox:3.0.5 - http://pdfbox.apache.org/) * PDFBox JBIG2 ImageIO plugin (org.apache.pdfbox:jbig2-imageio:3.0.4 - https://www.apache.org/jbig2-imageio/) * Apache JempBox (org.apache.pdfbox:jempbox:1.8.17 - http://www.apache.org/pdfbox-parent/jempbox/) - * Apache PDFBox (org.apache.pdfbox:pdfbox:2.0.34 - https://www.apache.org/pdfbox-parent/pdfbox/) - * Apache PDFBox tools (org.apache.pdfbox:pdfbox-tools:2.0.34 - https://www.apache.org/pdfbox-parent/pdfbox-tools/) - * Apache XmpBox (org.apache.pdfbox:xmpbox:2.0.34 - https://www.apache.org/pdfbox-parent/xmpbox/) + * Apache PDFBox (org.apache.pdfbox:pdfbox:3.0.5 - https://www.apache.org/pdfbox-parent/pdfbox/) + * Apache PDFBox io (org.apache.pdfbox:pdfbox-io:3.0.5 - https://www.apache.org/pdfbox-parent/pdfbox-io/) + * Apache PDFBox tools (org.apache.pdfbox:pdfbox-tools:3.0.5 - https://www.apache.org/pdfbox-parent/pdfbox-tools/) + * Apache XmpBox (org.apache.pdfbox:xmpbox:3.0.5 - https://www.apache.org/pdfbox-parent/xmpbox/) * Apache POI - Common (org.apache.poi:poi:5.4.1 - https://poi.apache.org/) * Apache POI - API based on OPC and OOXML schemas (org.apache.poi:poi-ooxml:5.4.1 - https://poi.apache.org/) * Apache POI (org.apache.poi:poi-ooxml-lite:5.4.1 - https://poi.apache.org/) @@ -278,30 +267,30 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Apache Standard Taglib Implementation (org.apache.taglibs:taglibs-standard-impl:1.2.5 - http://tomcat.apache.org/taglibs/standard-1.2.5/taglibs-standard-impl) * Apache Standard Taglib Specification API (org.apache.taglibs:taglibs-standard-spec:1.2.5 - http://tomcat.apache.org/taglibs/standard-1.2.5/taglibs-standard-spec) * Apache Thrift (org.apache.thrift:libthrift:0.9.2 - http://thrift.apache.org) - * Apache Tika core (org.apache.tika:tika-core:2.9.4 - https://tika.apache.org/) - * Apache Tika Apple parser module (org.apache.tika:tika-parser-apple-module:2.9.4 - https://tika.apache.org/tika-parser-apple-module/) - * Apache Tika audiovideo parser module (org.apache.tika:tika-parser-audiovideo-module:2.9.4 - https://tika.apache.org/tika-parser-audiovideo-module/) - * Apache Tika cad parser module (org.apache.tika:tika-parser-cad-module:2.9.4 - https://tika.apache.org/tika-parser-cad-module/) - * Apache Tika code parser module (org.apache.tika:tika-parser-code-module:2.9.4 - https://tika.apache.org/tika-parser-code-module/) - * Apache Tika crypto parser module (org.apache.tika:tika-parser-crypto-module:2.9.4 - https://tika.apache.org/tika-parser-crypto-module/) - * Apache Tika digest commons (org.apache.tika:tika-parser-digest-commons:2.9.4 - https://tika.apache.org/tika-parser-digest-commons/) - * Apache Tika font parser module (org.apache.tika:tika-parser-font-module:2.9.4 - https://tika.apache.org/tika-parser-font-module/) - * Apache Tika html parser module (org.apache.tika:tika-parser-html-module:2.9.4 - https://tika.apache.org/tika-parser-html-module/) - * Apache Tika image parser module (org.apache.tika:tika-parser-image-module:2.9.4 - https://tika.apache.org/tika-parser-image-module/) - * Apache Tika mail commons (org.apache.tika:tika-parser-mail-commons:2.9.4 - https://tika.apache.org/tika-parser-mail-commons/) - * Apache Tika mail parser module (org.apache.tika:tika-parser-mail-module:2.9.4 - https://tika.apache.org/tika-parser-mail-module/) - * Apache Tika Microsoft parser module (org.apache.tika:tika-parser-microsoft-module:2.9.4 - https://tika.apache.org/tika-parser-microsoft-module/) - * Apache Tika miscellaneous office format parser module (org.apache.tika:tika-parser-miscoffice-module:2.9.4 - https://tika.apache.org/tika-parser-miscoffice-module/) - * Apache Tika news parser module (org.apache.tika:tika-parser-news-module:2.9.4 - https://tika.apache.org/tika-parser-news-module/) - * Apache Tika OCR parser module (org.apache.tika:tika-parser-ocr-module:2.9.4 - https://tika.apache.org/tika-parser-ocr-module/) - * Apache Tika PDF parser module (org.apache.tika:tika-parser-pdf-module:2.9.4 - https://tika.apache.org/tika-parser-pdf-module/) - * Apache Tika package parser module (org.apache.tika:tika-parser-pkg-module:2.9.4 - https://tika.apache.org/tika-parser-pkg-module/) - * Apache Tika text parser module (org.apache.tika:tika-parser-text-module:2.9.4 - https://tika.apache.org/tika-parser-text-module/) - * Apache Tika WARC parser module (org.apache.tika:tika-parser-webarchive-module:2.9.4 - https://tika.apache.org/tika-parser-webarchive-module/) - * Apache Tika XML parser module (org.apache.tika:tika-parser-xml-module:2.9.4 - https://tika.apache.org/tika-parser-xml-module/) - * Apache Tika XMP commons (org.apache.tika:tika-parser-xmp-commons:2.9.4 - https://tika.apache.org/tika-parser-xmp-commons/) - * Apache Tika ZIP commons (org.apache.tika:tika-parser-zip-commons:2.9.4 - https://tika.apache.org/tika-parser-zip-commons/) - * Apache Tika standard parser package (org.apache.tika:tika-parsers-standard-package:2.9.4 - https://tika.apache.org/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/) + * Apache Tika core (org.apache.tika:tika-core:3.2.3 - https://tika.apache.org/) + * Apache Tika Apple parser module (org.apache.tika:tika-parser-apple-module:3.2.3 - https://tika.apache.org/tika-parser-apple-module/) + * Apache Tika audiovideo parser module (org.apache.tika:tika-parser-audiovideo-module:3.2.3 - https://tika.apache.org/tika-parser-audiovideo-module/) + * Apache Tika cad parser module (org.apache.tika:tika-parser-cad-module:3.2.3 - https://tika.apache.org/tika-parser-cad-module/) + * Apache Tika code parser module (org.apache.tika:tika-parser-code-module:3.2.3 - https://tika.apache.org/tika-parser-code-module/) + * Apache Tika crypto parser module (org.apache.tika:tika-parser-crypto-module:3.2.3 - https://tika.apache.org/tika-parser-crypto-module/) + * Apache Tika digest commons (org.apache.tika:tika-parser-digest-commons:3.2.3 - https://tika.apache.org/tika-parser-digest-commons/) + * Apache Tika font parser module (org.apache.tika:tika-parser-font-module:3.2.3 - https://tika.apache.org/tika-parser-font-module/) + * Apache Tika html parser module (org.apache.tika:tika-parser-html-module:3.2.3 - https://tika.apache.org/tika-parser-html-module/) + * Apache Tika image parser module (org.apache.tika:tika-parser-image-module:3.2.3 - https://tika.apache.org/tika-parser-image-module/) + * Apache Tika mail commons (org.apache.tika:tika-parser-mail-commons:3.2.3 - https://tika.apache.org/tika-parser-mail-commons/) + * Apache Tika mail parser module (org.apache.tika:tika-parser-mail-module:3.2.3 - https://tika.apache.org/tika-parser-mail-module/) + * Apache Tika Microsoft parser module (org.apache.tika:tika-parser-microsoft-module:3.2.3 - https://tika.apache.org/tika-parser-microsoft-module/) + * Apache Tika miscellaneous office format parser module (org.apache.tika:tika-parser-miscoffice-module:3.2.3 - https://tika.apache.org/tika-parser-miscoffice-module/) + * Apache Tika news parser module (org.apache.tika:tika-parser-news-module:3.2.3 - https://tika.apache.org/tika-parser-news-module/) + * Apache Tika OCR parser module (org.apache.tika:tika-parser-ocr-module:3.2.3 - https://tika.apache.org/tika-parser-ocr-module/) + * Apache Tika PDF parser module (org.apache.tika:tika-parser-pdf-module:3.2.3 - https://tika.apache.org/tika-parser-pdf-module/) + * Apache Tika package parser module (org.apache.tika:tika-parser-pkg-module:3.2.3 - https://tika.apache.org/tika-parser-pkg-module/) + * Apache Tika text parser module (org.apache.tika:tika-parser-text-module:3.2.3 - https://tika.apache.org/tika-parser-text-module/) + * Apache Tika WARC parser module (org.apache.tika:tika-parser-webarchive-module:3.2.3 - https://tika.apache.org/tika-parser-webarchive-module/) + * Apache Tika XML parser module (org.apache.tika:tika-parser-xml-module:3.2.3 - https://tika.apache.org/tika-parser-xml-module/) + * Apache Tika XMP commons (org.apache.tika:tika-parser-xmp-commons:3.2.3 - https://tika.apache.org/tika-parser-xmp-commons/) + * Apache Tika ZIP commons (org.apache.tika:tika-parser-zip-commons:3.2.3 - https://tika.apache.org/tika-parser-zip-commons/) + * Apache Tika standard parser package (org.apache.tika:tika-parsers-standard-package:3.2.3 - https://tika.apache.org/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/) * tomcat-embed-core (org.apache.tomcat.embed:tomcat-embed-core:9.0.83 - https://tomcat.apache.org/) * tomcat-embed-el (org.apache.tomcat.embed:tomcat-embed-el:9.0.83 - https://tomcat.apache.org/) * tomcat-embed-websocket (org.apache.tomcat.embed:tomcat-embed-websocket:9.0.83 - https://tomcat.apache.org/) @@ -317,43 +306,42 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * AssertJ fluent assertions (org.assertj:assertj-core:3.22.0 - https://assertj.github.io/doc/assertj-core/) * Evo Inflector (org.atteo:evo-inflector:1.3 - http://atteo.org/static/evo-inflector) * jose4j (org.bitbucket.b_c:jose4j:0.6.5 - https://bitbucket.org/b_c/jose4j/) - * TagSoup (org.ccil.cowan.tagsoup:tagsoup:1.2.1 - http://home.ccil.org/~cowan/XML/tagsoup/) * jems (org.dmfs:jems:1.18 - https://github.com/dmfs/jems) * rfc3986-uri (org.dmfs:rfc3986-uri:0.8.1 - https://github.com/dmfs/uri-toolkit) * Jetty :: Apache JSP Implementation (org.eclipse.jetty:apache-jsp:9.4.15.v20190215 - http://www.eclipse.org/jetty) * Apache :: JSTL module (org.eclipse.jetty:apache-jstl:9.4.15.v20190215 - http://tomcat.apache.org/taglibs/standard/) * Jetty :: ALPN :: Client (org.eclipse.jetty:jetty-alpn-client:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-client) * Jetty :: ALPN :: JDK9 Client Implementation (org.eclipse.jetty:jetty-alpn-java-client:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-client) - * Jetty :: ALPN :: JDK9 Server Implementation (org.eclipse.jetty:jetty-alpn-java-server:9.4.57.v20241219 - https://jetty.org/jetty-alpn-parent/jetty-alpn-java-server/) + * Jetty :: ALPN :: JDK9 Server Implementation (org.eclipse.jetty:jetty-alpn-java-server:9.4.58.v20250814 - https://jetty.org/jetty-alpn-parent/jetty-alpn-java-server/) * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server) - * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.57.v20241219 - https://jetty.org/jetty-alpn-parent/jetty-alpn-server/) + * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.58.v20250814 - https://jetty.org/jetty-alpn-parent/jetty-alpn-server/) * Jetty :: Servlet Annotations (org.eclipse.jetty:jetty-annotations:9.4.15.v20190215 - http://www.eclipse.org/jetty) * Jetty :: Asynchronous HTTP Client (org.eclipse.jetty:jetty-client:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-client) * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-continuation) - * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.57.v20241219 - https://jetty.org/jetty-continuation/) - * Jetty :: Deployers (org.eclipse.jetty:jetty-deploy:9.4.57.v20241219 - https://jetty.org/jetty-deploy/) - * Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.57.v20241219 - https://jetty.org/jetty-http/) - * Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.57.v20241219 - https://jetty.org/jetty-io/) + * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.58.v20250814 - https://jetty.org/jetty-continuation/) + * Jetty :: Deployers (org.eclipse.jetty:jetty-deploy:9.4.58.v20250814 - https://jetty.org/jetty-deploy/) + * Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.58.v20250814 - https://jetty.org/jetty-http/) + * Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.58.v20250814 - https://jetty.org/jetty-io/) * Jetty :: JMX Management (org.eclipse.jetty:jetty-jmx:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-jmx) * Jetty :: JNDI Naming (org.eclipse.jetty:jetty-jndi:9.4.15.v20190215 - http://www.eclipse.org/jetty) * Jetty :: Plus (org.eclipse.jetty:jetty-plus:9.4.15.v20190215 - http://www.eclipse.org/jetty) * Jetty :: Rewrite Handler (org.eclipse.jetty:jetty-rewrite:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-rewrite) * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-security) - * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.57.v20241219 - https://jetty.org/jetty-security/) - * Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.57.v20241219 - https://jetty.org/jetty-server/) - * Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.57.v20241219 - https://jetty.org/jetty-servlet/) - * Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.57.v20241219 - https://jetty.org/jetty-servlets/) - * Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.57.v20241219 - https://jetty.org/jetty-util/) - * Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.57.v20241219 - https://jetty.org/jetty-util-ajax/) - * Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.57.v20241219 - https://jetty.org/jetty-webapp/) - * Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.57.v20241219 - https://jetty.org/jetty-xml/) + * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.58.v20250814 - https://jetty.org/jetty-security/) + * Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.58.v20250814 - https://jetty.org/jetty-server/) + * Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.58.v20250814 - https://jetty.org/jetty-servlet/) + * Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.58.v20250814 - https://jetty.org/jetty-servlets/) + * Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.58.v20250814 - https://jetty.org/jetty-util/) + * Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.58.v20250814 - https://jetty.org/jetty-util-ajax/) + * Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.58.v20250814 - https://jetty.org/jetty-webapp/) + * Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.58.v20250814 - https://jetty.org/jetty-xml/) * Jetty :: HTTP2 :: Client (org.eclipse.jetty.http2:http2-client:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-client) - * Jetty :: HTTP2 :: Common (org.eclipse.jetty.http2:http2-common:9.4.57.v20241219 - https://jetty.org/http2-parent/http2-common/) + * Jetty :: HTTP2 :: Common (org.eclipse.jetty.http2:http2-common:9.4.58.v20250814 - https://jetty.org/http2-parent/http2-common/) * Jetty :: HTTP2 :: HPACK (org.eclipse.jetty.http2:http2-hpack:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-hpack) * Jetty :: HTTP2 :: HTTP Client Transport (org.eclipse.jetty.http2:http2-http-client-transport:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-http-client-transport) - * Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.57.v20241219 - https://jetty.org/http2-parent/http2-server/) + * Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.58.v20250814 - https://jetty.org/http2-parent/http2-server/) * Jetty :: Schemas (org.eclipse.jetty.toolchain:jetty-schemas:3.1.2 - https://eclipse.org/jetty/jetty-schemas) - * Ehcache (org.ehcache:ehcache:3.10.8 - http://ehcache.org) + * Ehcache (org.ehcache:ehcache:3.11.1 - http://ehcache.org) * flyway-core (org.flywaydb:flyway-core:8.5.13 - https://flywaydb.org/flyway-core) * Ogg and Vorbis for Java, Core (org.gagravarr:vorbis-java-core:0.8 - https://github.com/Gagravarr/VorbisJava) * Apache Tika plugin for Ogg, Vorbis and FLAC (org.gagravarr:vorbis-java-tika:0.8 - https://github.com/Gagravarr/VorbisJava) @@ -363,13 +351,11 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Hibernate Validator Engine (org.hibernate.validator:hibernate-validator:6.2.5.Final - http://hibernate.org/validator/hibernate-validator) * Hibernate Validator Portable Extension (org.hibernate.validator:hibernate-validator-cdi:6.2.5.Final - http://hibernate.org/validator/hibernate-validator-cdi) * org.immutables.value-annotations (org.immutables:value-annotations:2.9.2 - http://immutables.org/value-annotations) - * leveldb (org.iq80.leveldb:leveldb:0.12 - http://github.com/dain/leveldb/leveldb) - * leveldb-api (org.iq80.leveldb:leveldb-api:0.12 - http://github.com/dain/leveldb/leveldb-api) * Javassist (org.javassist:javassist:3.30.2-GA - https://www.javassist.org/) * Java Annotation Indexer (org.jboss:jandex:2.4.2.Final - http://www.jboss.org/jandex) * JBoss Logging 3 (org.jboss.logging:jboss-logging:3.6.1.Final - http://www.jboss.org) * JDOM (org.jdom:jdom2:2.0.6.1 - http://www.jdom.org) - * IntelliJ IDEA Annotations (org.jetbrains:annotations:13.0 - http://www.jetbrains.org) + * JetBrains Java Annotations (org.jetbrains:annotations:17.0.0 - https://github.com/JetBrains/java-annotations) * Kotlin Stdlib (org.jetbrains.kotlin:kotlin-stdlib:1.8.21 - https://kotlinlang.org/) * Kotlin Stdlib Common (org.jetbrains.kotlin:kotlin-stdlib-common:1.8.21 - https://kotlinlang.org/) * Kotlin Stdlib Jdk7 (org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.21 - https://kotlinlang.org/) @@ -391,18 +377,11 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Jetty Servlet Tester (org.mortbay.jetty:jetty-servlet-tester:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/jetty-servlet-tester) * Jetty Utilities (org.mortbay.jetty:jetty-util:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/jetty-util) * Servlet Specification API (org.mortbay.jetty:servlet-api:2.5-20081211 - http://jetty.mortbay.org/servlet-api) - * jwarc (org.netpreserve:jwarc:0.31.1 - https://github.com/iipc/jwarc) + * jwarc (org.netpreserve:jwarc:0.32.0 - https://github.com/iipc/jwarc) * Objenesis (org.objenesis:objenesis:3.2 - http://objenesis.org/objenesis) - * org.opentest4j:opentest4j (org.opentest4j:opentest4j:1.3.0 - https://github.com/ota4j-team/opentest4j) * parboiled-core (org.parboiled:parboiled-core:1.1.7 - http://parboiled.org) * parboiled-java (org.parboiled:parboiled-java:1.1.7 - http://parboiled.org) * RRD4J (org.rrd4j:rrd4j:3.5 - https://github.com/rrd4j/rrd4j/) - * Scala Library (org.scala-lang:scala-library:2.13.16 - https://www.scala-lang.org/) - * Scala Compiler (org.scala-lang:scala-reflect:2.13.0 - https://www.scala-lang.org/) - * scala-collection-compat (org.scala-lang.modules:scala-collection-compat_2.13:2.1.6 - http://www.scala-lang.org/) - * scala-java8-compat (org.scala-lang.modules:scala-java8-compat_2.13:0.9.0 - http://www.scala-lang.org/) - * scala-parser-combinators (org.scala-lang.modules:scala-parser-combinators_2.13:1.1.2 - http://www.scala-lang.org/) - * scala-xml (org.scala-lang.modules:scala-xml_2.13:1.3.0 - http://www.scala-lang.org/) * JSONassert (org.skyscreamer:jsonassert:1.5.1 - https://github.com/skyscreamer/JSONassert) * JCL 1.2 implemented over SLF4J (org.slf4j:jcl-over-slf4j:1.7.36 - http://www.slf4j.org) * Spring AOP (org.springframework:spring-aop:5.3.39 - https://github.com/spring-projects/spring-framework) @@ -453,10 +432,40 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * SWORD v2 :: Common Server Library (org.swordapp:sword2-server:1.0 - http://www.swordapp.org/) * snappy-java (org.xerial.snappy:snappy-java:1.1.10.1 - https://github.com/xerial/snappy-java) * xml-matchers (org.xmlmatchers:xml-matchers:0.10 - http://code.google.com/p/xml-matchers/) - * org.xmlunit:xmlunit-core (org.xmlunit:xmlunit-core:2.10.2 - https://www.xmlunit.org/) + * org.xmlunit:xmlunit-core (org.xmlunit:xmlunit-core:2.11.0 - https://www.xmlunit.org/) * org.xmlunit:xmlunit-core (org.xmlunit:xmlunit-core:2.9.1 - https://www.xmlunit.org/) * org.xmlunit:xmlunit-placeholders (org.xmlunit:xmlunit-placeholders:2.9.1 - https://www.xmlunit.org/xmlunit-placeholders/) * SnakeYAML (org.yaml:snakeyaml:1.30 - https://bitbucket.org/snakeyaml/snakeyaml) + * AWS Java SDK :: Annotations (software.amazon.awssdk:annotations:2.38.8 - https://aws.amazon.com/sdkforjava/core/annotations) + * AWS Java SDK :: Arns (software.amazon.awssdk:arns:2.38.8 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: Auth (software.amazon.awssdk:auth:2.38.8 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: AWS Core (software.amazon.awssdk:aws-core:2.38.8 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: Core :: Protocols :: AWS Query Protocol (software.amazon.awssdk:aws-query-protocol:2.38.8 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: Core :: Protocols :: AWS Xml Protocol (software.amazon.awssdk:aws-xml-protocol:2.38.8 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: Checksums (software.amazon.awssdk:checksums:2.38.8 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: Checksums SPI (software.amazon.awssdk:checksums-spi:2.38.8 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: AWS CRT Core (software.amazon.awssdk:crt-core:2.38.8 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: Endpoints SPI (software.amazon.awssdk:endpoints-spi:2.38.8 - https://aws.amazon.com/sdkforjava/core/endpoints-spi) + * AWS Java SDK :: HTTP Auth (software.amazon.awssdk:http-auth:2.38.8 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: HTTP Auth AWS (software.amazon.awssdk:http-auth-aws:2.38.8 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: HTTP Auth Event Stream (software.amazon.awssdk:http-auth-aws-eventstream:2.38.8 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: HTTP Auth SPI (software.amazon.awssdk:http-auth-spi:2.38.8 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: HTTP Client Interface (software.amazon.awssdk:http-client-spi:2.38.8 - https://aws.amazon.com/sdkforjava/http-client-spi) + * AWS Java SDK :: Identity SPI (software.amazon.awssdk:identity-spi:2.38.8 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: Core :: Protocols :: Json Utils (software.amazon.awssdk:json-utils:2.38.8 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: Metrics SPI (software.amazon.awssdk:metrics-spi:2.38.8 - https://aws.amazon.com/sdkforjava/core/metrics-spi) + * AWS Java SDK :: Profiles (software.amazon.awssdk:profiles:2.38.8 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: Core :: Protocols :: Protocol Core (software.amazon.awssdk:protocol-core:2.38.8 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: Regions (software.amazon.awssdk:regions:2.38.8 - https://aws.amazon.com/sdkforjava/core/regions) + * AWS Java SDK :: Retries (software.amazon.awssdk:retries:2.38.8 - https://aws.amazon.com/sdkforjava/core/retries) + * AWS Java SDK :: Retries API (software.amazon.awssdk:retries-spi:2.38.8 - https://aws.amazon.com/sdkforjava/core/retries-spi) + * AWS Java SDK :: Services :: Amazon S3 (software.amazon.awssdk:s3:2.38.8 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: SDK Core (software.amazon.awssdk:sdk-core:2.38.8 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: Third Party :: Jackson-core (software.amazon.awssdk:third-party-jackson-core:2.38.8 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: Utilities (software.amazon.awssdk:utils:2.38.8 - https://aws.amazon.com/sdkforjava/utils) + * AWS Java SDK :: Utils Lite (software.amazon.awssdk:utils-lite:2.38.8 - https://aws.amazon.com/sdkforjava) + * software.amazon.awssdk.crt:aws-crt (software.amazon.awssdk.crt:aws-crt:0.39.4 - https://github.com/awslabs/aws-crt-java) + * AWS Event Stream (software.amazon.eventstream:eventstream:1.0.1 - https://github.com/awslabs/aws-eventstream-java) * Xerces2-j (xerces:xercesImpl:2.12.2 - https://xerces.apache.org/xerces2-j/) * XML Commons External Components XML APIs (xml-apis:xml-apis:1.4.01 - http://xml.apache.org/commons/components/external/) @@ -489,16 +498,12 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * asm-commons (org.ow2.asm:asm-commons:9.3 - http://asm.ow2.io/) * ASM Tree (org.ow2.asm:asm-tree:5.0.3 - http://asm.objectweb.org/asm-tree/) * ASM Util (org.ow2.asm:asm-util:5.0.3 - http://asm.objectweb.org/asm-util/) - * PostgreSQL JDBC Driver (org.postgresql:postgresql:42.7.7 - https://jdbc.postgresql.org) + * PostgreSQL JDBC Driver (org.postgresql:postgresql:42.7.8 - https://jdbc.postgresql.org) * Reflections (org.reflections:reflections:0.9.12 - http://github.com/ronmamo/reflections) * JMatIO (org.tallison:jmatio:1.5 - https://github.com/tballison/jmatio) * XZ for Java (org.tukaani:xz:1.10 - https://tukaani.org/xz/java.html) * XMLUnit for Java (xmlunit:xmlunit:1.3 - http://xmlunit.sourceforge.net/) - CC0: - - * reactive-streams (org.reactivestreams:reactive-streams:1.0.2 - http://www.reactive-streams.org/) - Common Development and Distribution License (CDDL): * JavaMail API (com.sun.mail:javax.mail:1.6.2 - http://javaee.github.io/javamail/javax.mail) @@ -552,7 +557,7 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines Eclipse Public License: * System Rules (com.github.stefanbirkner:system-rules:1.19.0 - http://stefanbirkner.github.io/system-rules/) - * H2 Database Engine (com.h2database:h2:2.3.232 - https://h2database.com) + * H2 Database Engine (com.h2database:h2:2.4.240 - https://h2database.com) * Jakarta Annotations API (jakarta.annotation:jakarta.annotation-api:1.3.5 - https://projects.eclipse.org/projects/ee4j.ca) * javax.transaction API (jakarta.transaction:jakarta.transaction-api:1.3.3 - https://projects.eclipse.org/projects/ee4j.jta) * jakarta.ws.rs-api (jakarta.ws.rs:jakarta.ws.rs-api:2.1.6 - https://github.com/eclipse-ee4j/jaxrs-api) @@ -564,34 +569,34 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Apache :: JSTL module (org.eclipse.jetty:apache-jstl:9.4.15.v20190215 - http://tomcat.apache.org/taglibs/standard/) * Jetty :: ALPN :: Client (org.eclipse.jetty:jetty-alpn-client:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-client) * Jetty :: ALPN :: JDK9 Client Implementation (org.eclipse.jetty:jetty-alpn-java-client:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-client) - * Jetty :: ALPN :: JDK9 Server Implementation (org.eclipse.jetty:jetty-alpn-java-server:9.4.57.v20241219 - https://jetty.org/jetty-alpn-parent/jetty-alpn-java-server/) + * Jetty :: ALPN :: JDK9 Server Implementation (org.eclipse.jetty:jetty-alpn-java-server:9.4.58.v20250814 - https://jetty.org/jetty-alpn-parent/jetty-alpn-java-server/) * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server) - * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.57.v20241219 - https://jetty.org/jetty-alpn-parent/jetty-alpn-server/) + * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.58.v20250814 - https://jetty.org/jetty-alpn-parent/jetty-alpn-server/) * Jetty :: Servlet Annotations (org.eclipse.jetty:jetty-annotations:9.4.15.v20190215 - http://www.eclipse.org/jetty) * Jetty :: Asynchronous HTTP Client (org.eclipse.jetty:jetty-client:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-client) * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-continuation) - * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.57.v20241219 - https://jetty.org/jetty-continuation/) - * Jetty :: Deployers (org.eclipse.jetty:jetty-deploy:9.4.57.v20241219 - https://jetty.org/jetty-deploy/) - * Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.57.v20241219 - https://jetty.org/jetty-http/) - * Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.57.v20241219 - https://jetty.org/jetty-io/) + * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.58.v20250814 - https://jetty.org/jetty-continuation/) + * Jetty :: Deployers (org.eclipse.jetty:jetty-deploy:9.4.58.v20250814 - https://jetty.org/jetty-deploy/) + * Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.58.v20250814 - https://jetty.org/jetty-http/) + * Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.58.v20250814 - https://jetty.org/jetty-io/) * Jetty :: JMX Management (org.eclipse.jetty:jetty-jmx:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-jmx) * Jetty :: JNDI Naming (org.eclipse.jetty:jetty-jndi:9.4.15.v20190215 - http://www.eclipse.org/jetty) * Jetty :: Plus (org.eclipse.jetty:jetty-plus:9.4.15.v20190215 - http://www.eclipse.org/jetty) * Jetty :: Rewrite Handler (org.eclipse.jetty:jetty-rewrite:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-rewrite) * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-security) - * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.57.v20241219 - https://jetty.org/jetty-security/) - * Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.57.v20241219 - https://jetty.org/jetty-server/) - * Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.57.v20241219 - https://jetty.org/jetty-servlet/) - * Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.57.v20241219 - https://jetty.org/jetty-servlets/) - * Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.57.v20241219 - https://jetty.org/jetty-util/) - * Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.57.v20241219 - https://jetty.org/jetty-util-ajax/) - * Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.57.v20241219 - https://jetty.org/jetty-webapp/) - * Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.57.v20241219 - https://jetty.org/jetty-xml/) + * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.58.v20250814 - https://jetty.org/jetty-security/) + * Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.58.v20250814 - https://jetty.org/jetty-server/) + * Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.58.v20250814 - https://jetty.org/jetty-servlet/) + * Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.58.v20250814 - https://jetty.org/jetty-servlets/) + * Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.58.v20250814 - https://jetty.org/jetty-util/) + * Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.58.v20250814 - https://jetty.org/jetty-util-ajax/) + * Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.58.v20250814 - https://jetty.org/jetty-webapp/) + * Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.58.v20250814 - https://jetty.org/jetty-xml/) * Jetty :: HTTP2 :: Client (org.eclipse.jetty.http2:http2-client:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-client) - * Jetty :: HTTP2 :: Common (org.eclipse.jetty.http2:http2-common:9.4.57.v20241219 - https://jetty.org/http2-parent/http2-common/) + * Jetty :: HTTP2 :: Common (org.eclipse.jetty.http2:http2-common:9.4.58.v20250814 - https://jetty.org/http2-parent/http2-common/) * Jetty :: HTTP2 :: HPACK (org.eclipse.jetty.http2:http2-hpack:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-hpack) * Jetty :: HTTP2 :: HTTP Client Transport (org.eclipse.jetty.http2:http2-http-client-transport:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-http-client-transport) - * Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.57.v20241219 - https://jetty.org/http2-parent/http2-server/) + * Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.58.v20250814 - https://jetty.org/http2-parent/http2-server/) * Jetty :: Schemas (org.eclipse.jetty.toolchain:jetty-schemas:3.1.2 - https://eclipse.org/jetty/jetty-schemas) * HK2 API module (org.glassfish.hk2:hk2-api:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-api) * ServiceLocator Default Implementation (org.glassfish.hk2:hk2-locator:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-locator) @@ -603,9 +608,6 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * Java Persistence API, Version 2.1 (org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.2.Final - http://hibernate.org) - * JUnit Platform Commons (org.junit.platform:junit-platform-commons:1.11.4 - https://junit.org/junit5/) - * JUnit Platform Engine API (org.junit.platform:junit-platform-engine:1.11.4 - https://junit.org/junit5/) - * JUnit Vintage Engine (org.junit.vintage:junit-vintage-engine:5.11.4 - https://junit.org/junit5/) * org.locationtech.jts:jts-core (org.locationtech.jts:jts-core:1.19.0 - https://www.locationtech.org/projects/technology.jts/jts-modules/jts-core) * org.locationtech.jts.io:jts-io-common (org.locationtech.jts.io:jts-io-common:1.19.0 - https://www.locationtech.org/projects/technology.jts/jts-modules/jts-io/jts-io-common) * Jetty Server (org.mortbay.jetty:jetty:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/modules/jetty) @@ -652,29 +654,35 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Simple Magic (com.j256.simplemagic:simplemagic:1.17 - https://256stuff.com/sources/simplemagic/) + LGPL-2.1-or-later: + + * Java Native Access (net.java.dev.jna:jna:5.18.1 - https://github.com/java-native-access/jna) + MIT License: - * better-files (com.github.pathikrit:better-files_2.13:3.9.1 - https://github.com/pathikrit/better-files) * Java SemVer (com.github.zafarkhaja:java-semver:0.9.0 - https://github.com/zafarkhaja/jsemver) * dd-plist (com.googlecode.plist:dd-plist:1.28 - http://www.github.com/3breadt/dd-plist) * DigitalCollections: IIIF API Library (de.digitalcollections.iiif:iiif-apis:0.3.11 - https://github.com/dbmdz/iiif-apis) - * s3mock (io.findify:s3mock_2.13:0.2.6 - https://github.com/findify/s3mock) * ClassGraph (io.github.classgraph:classgraph:4.8.154 - https://github.com/classgraph/classgraph) * JOpt Simple (net.sf.jopt-simple:jopt-simple:5.0.4 - http://jopt-simple.github.io/jopt-simple) - * Bouncy Castle JavaMail S/MIME APIs (org.bouncycastle:bcmail-jdk18on:1.80 - https://www.bouncycastle.org/download/bouncy-castle-java/) - * Bouncy Castle PKIX, CMS, EAC, TSP, PKCS, OCSP, CMP, and CRMF APIs (org.bouncycastle:bcpkix-jdk18on:1.81 - https://www.bouncycastle.org/download/bouncy-castle-java/) - * Bouncy Castle Provider (org.bouncycastle:bcprov-jdk18on:1.81 - https://www.bouncycastle.org/download/bouncy-castle-java/) - * Bouncy Castle ASN.1 Extension and Utility APIs (org.bouncycastle:bcutil-jdk18on:1.81 - https://www.bouncycastle.org/download/bouncy-castle-java/) + * Bouncy Castle JavaMail Jakarta S/MIME APIs (org.bouncycastle:bcjmail-jdk18on:1.81 - https://www.bouncycastle.org/download/bouncy-castle-java/) + * Bouncy Castle PKIX, CMS, EAC, TSP, PKCS, OCSP, CMP, and CRMF APIs (org.bouncycastle:bcpkix-jdk18on:1.82 - https://www.bouncycastle.org/download/bouncy-castle-java/) + * Bouncy Castle Provider (org.bouncycastle:bcprov-jdk18on:1.82 - https://www.bouncycastle.org/download/bouncy-castle-java/) + * Bouncy Castle ASN.1 Extension and Utility APIs (org.bouncycastle:bcutil-jdk18on:1.82 - https://www.bouncycastle.org/download/bouncy-castle-java/) * org.brotli:dec (org.brotli:dec:0.1.2 - http://brotli.org/dec) * Checker Qual (org.checkerframework:checker-qual:3.23.0 - https://checkerframework.org) - * Checker Qual (org.checkerframework:checker-qual:3.49.3 - https://checkerframework.org/) + * Checker Qual (org.checkerframework:checker-qual:3.49.5 - https://checkerframework.org/) * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jsoup Java HTML Parser (org.jsoup:jsoup:1.21.2 - https://jsoup.org/) * mockito-core (org.mockito:mockito-core:3.12.4 - https://github.com/mockito/mockito) * mockito-inline (org.mockito:mockito-inline:3.12.4 - https://github.com/mockito/mockito) * ORCID - Model (org.orcid:orcid-model:3.0.2 - http://github.com/ORCID/orcid-model) + * Duct Tape (org.rnorth.duct-tape:duct-tape:1.0.8 - https://github.com/rnorth/duct-tape) * JUL to SLF4J bridge (org.slf4j:jul-to-slf4j:1.7.36 - http://www.slf4j.org) * SLF4J API Module (org.slf4j:slf4j-api:1.7.36 - http://www.slf4j.org) + * Testcontainers Core (org.testcontainers:testcontainers:2.0.2 - https://java.testcontainers.org) + * Testcontainers :: Localstack (org.testcontainers:testcontainers-localstack:2.0.2 - https://java.testcontainers.org) * HAL Browser (org.webjars:hal-browser:ad9b865 - http://webjars.org) * toastr (org.webjars.bowergithub.codeseven:toastr:2.1.4 - http://webjars.org) * backbone (org.webjars.bowergithub.jashkenas:backbone:1.4.1 - https://www.webjars.org) @@ -682,13 +690,17 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * jquery (org.webjars.bowergithub.jquery:jquery-dist:3.7.1 - https://www.webjars.org) * urijs (org.webjars.bowergithub.medialize:uri.js:1.19.11 - https://www.webjars.org) * bootstrap (org.webjars.bowergithub.twbs:bootstrap:4.6.2 - https://www.webjars.org) - * core-js (org.webjars.npm:core-js:3.42.0 - https://www.webjars.org) + * core-js (org.webjars.npm:core-js:3.46.0 - https://www.webjars.org) * @json-editor/json-editor (org.webjars.npm:json-editor__json-editor:2.15.2 - https://www.webjars.org) + MIT-0: + + * reactive-streams (org.reactivestreams:reactive-streams:1.0.4 - http://www.reactive-streams.org/) + Mozilla Public License: * juniversalchardet (com.github.albfernandez:juniversalchardet:2.5.0 - https://github.com/albfernandez/juniversalchardet) - * H2 Database Engine (com.h2database:h2:2.3.232 - https://h2database.com) + * H2 Database Engine (com.h2database:h2:2.4.240 - https://h2database.com) * Saxon-HE (net.sf.saxon:Saxon-HE:9.9.1-8 - http://www.saxonica.com/) * Javassist (org.javassist:javassist:3.30.2-GA - https://www.javassist.org/) * Mozilla Rhino (org.mozilla:rhino:1.7.7.2 - https://developer.mozilla.org/en/Rhino) @@ -703,6 +715,10 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * LatencyUtils (org.latencyutils:LatencyUtils:2.0.3 - http://latencyutils.github.io/LatencyUtils/) * Reflections (org.reflections:reflections:0.9.12 - http://github.com/ronmamo/reflections) + The Apache Software License, version 2.0: + + * picocli (info.picocli:picocli:4.7.6 - https://picocli.info) + UnRar License: * Java Unrar (com.github.junrar:junrar:7.5.5 - https://github.com/junrar/junrar) From 7e59b972518eff274659b6549283159e64d38aa3 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 17 Dec 2025 11:12:25 -0600 Subject: [PATCH 770/939] [maven-release-plugin] prepare release dspace-7.6.6 --- dspace-api/pom.xml | 2 +- dspace-iiif/pom.xml | 2 +- dspace-oai/pom.xml | 2 +- dspace-rdf/pom.xml | 2 +- dspace-rest/pom.xml | 4 ++-- dspace-server-webapp/pom.xml | 2 +- dspace-services/pom.xml | 2 +- dspace-sword/pom.xml | 2 +- dspace-swordv2/pom.xml | 2 +- dspace/modules/additions/pom.xml | 2 +- dspace/modules/pom.xml | 2 +- dspace/modules/rest/pom.xml | 2 +- dspace/modules/server/pom.xml | 2 +- dspace/pom.xml | 2 +- pom.xml | 32 ++++++++++++++++---------------- 15 files changed, 31 insertions(+), 31 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index e0baedbcd8c..46c251fc7d7 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6.6-SNAPSHOT + 7.6.6 .. diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index 9cc05dc5ceb..0f1e665002d 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.6-SNAPSHOT + 7.6.6 .. diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index c9211d5e592..df82bff7c4d 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - 7.6.6-SNAPSHOT + 7.6.6 .. diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index fc40a39d2a2..1fdc43af631 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.6-SNAPSHOT + 7.6.6 .. diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index fa5c56003bf..30a097bcd30 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -3,7 +3,7 @@ org.dspace dspace-rest war - 7.6.6-SNAPSHOT + 7.6.6 DSpace (Deprecated) REST Webapp DSpace RESTful Web Services API. NOTE: this REST API is DEPRECATED. Please consider using the REST API in the dspace-server-webapp instead! @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6.6-SNAPSHOT + 7.6.6 .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 0f03a7335c2..4c9bebcdd41 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.6-SNAPSHOT + 7.6.6 .. diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index 26f6ecff3ef..716302433d4 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.6-SNAPSHOT + 7.6.6 diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index 15d918c4d10..0585d2bbeed 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.6-SNAPSHOT + 7.6.6 .. diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index 4972cd4e98d..23cf37aa901 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - 7.6.6-SNAPSHOT + 7.6.6 .. diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index 41274052cd7..06b8244490e 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - 7.6.6-SNAPSHOT + 7.6.6 .. diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index ecc5b3fec7a..69d54221cef 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - 7.6.6-SNAPSHOT + 7.6.6 ../../pom.xml diff --git a/dspace/modules/rest/pom.xml b/dspace/modules/rest/pom.xml index 4e7adb7b922..7ed7268deba 100644 --- a/dspace/modules/rest/pom.xml +++ b/dspace/modules/rest/pom.xml @@ -13,7 +13,7 @@ org.dspace modules - 7.6.6-SNAPSHOT + 7.6.6 .. diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index 0e33182c698..919f687c97f 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -13,7 +13,7 @@ just adding new jar in the classloader modules org.dspace - 7.6.6-SNAPSHOT + 7.6.6 .. diff --git a/dspace/pom.xml b/dspace/pom.xml index 6338d764b51..9ca0c13d428 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - 7.6.6-SNAPSHOT + 7.6.6 ../pom.xml diff --git a/pom.xml b/pom.xml index eaaf4d9d017..1ba81c6b1fb 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - 7.6.6-SNAPSHOT + 7.6.6 DSpace Parent Project DSpace open source software is a turnkey institutional repository application. @@ -875,14 +875,14 @@ org.dspace dspace-rest - 7.6.6-SNAPSHOT + 7.6.6 jar classes org.dspace dspace-rest - 7.6.6-SNAPSHOT + 7.6.6 war @@ -1031,69 +1031,69 @@ org.dspace dspace-api - 7.6.6-SNAPSHOT + 7.6.6 org.dspace dspace-api test-jar - 7.6.6-SNAPSHOT + 7.6.6 test org.dspace.modules additions - 7.6.6-SNAPSHOT + 7.6.6 org.dspace dspace-sword - 7.6.6-SNAPSHOT + 7.6.6 org.dspace dspace-swordv2 - 7.6.6-SNAPSHOT + 7.6.6 org.dspace dspace-oai - 7.6.6-SNAPSHOT + 7.6.6 org.dspace dspace-services - 7.6.6-SNAPSHOT + 7.6.6 org.dspace dspace-server-webapp test-jar - 7.6.6-SNAPSHOT + 7.6.6 test org.dspace dspace-rdf - 7.6.6-SNAPSHOT + 7.6.6 org.dspace dspace-iiif - 7.6.6-SNAPSHOT + 7.6.6 org.dspace dspace-server-webapp - 7.6.6-SNAPSHOT + 7.6.6 jar classes org.dspace dspace-server-webapp - 7.6.6-SNAPSHOT + 7.6.6 war @@ -1939,7 +1939,7 @@ scm:git:git@github.com:DSpace/DSpace.git scm:git:git@github.com:DSpace/DSpace.git https://github.com/DSpace/DSpace - dspace-7_x + dspace-7.6.6 From 653b4a1ffe1d1f6a1cc55619d0cf74a4f341fb71 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 17 Dec 2025 11:12:30 -0600 Subject: [PATCH 771/939] [maven-release-plugin] prepare for next development iteration --- dspace-api/pom.xml | 2 +- dspace-iiif/pom.xml | 2 +- dspace-oai/pom.xml | 2 +- dspace-rdf/pom.xml | 2 +- dspace-rest/pom.xml | 4 ++-- dspace-server-webapp/pom.xml | 2 +- dspace-services/pom.xml | 2 +- dspace-sword/pom.xml | 2 +- dspace-swordv2/pom.xml | 2 +- dspace/modules/additions/pom.xml | 2 +- dspace/modules/pom.xml | 2 +- dspace/modules/rest/pom.xml | 2 +- dspace/modules/server/pom.xml | 2 +- dspace/pom.xml | 2 +- pom.xml | 32 ++++++++++++++++---------------- 15 files changed, 31 insertions(+), 31 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 46c251fc7d7..d0493a4c746 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6.6 + 7.6.7-SNAPSHOT .. diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index 0f1e665002d..38dc845d0b1 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.6 + 7.6.7-SNAPSHOT .. diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index df82bff7c4d..5ea46fc1db1 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - 7.6.6 + 7.6.7-SNAPSHOT .. diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index 1fdc43af631..8c7a6fa4225 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.6 + 7.6.7-SNAPSHOT .. diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index 30a097bcd30..f37cf04ecf2 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -3,7 +3,7 @@ org.dspace dspace-rest war - 7.6.6 + 7.6.7-SNAPSHOT DSpace (Deprecated) REST Webapp DSpace RESTful Web Services API. NOTE: this REST API is DEPRECATED. Please consider using the REST API in the dspace-server-webapp instead! @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6.6 + 7.6.7-SNAPSHOT .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 4c9bebcdd41..0ac7b38b078 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.6 + 7.6.7-SNAPSHOT .. diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index 716302433d4..fa5643c48eb 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.6 + 7.6.7-SNAPSHOT diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index 0585d2bbeed..7b43bd3765d 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.6 + 7.6.7-SNAPSHOT .. diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index 23cf37aa901..d97c39bba33 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - 7.6.6 + 7.6.7-SNAPSHOT .. diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index 06b8244490e..a316d600cd9 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - 7.6.6 + 7.6.7-SNAPSHOT .. diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index 69d54221cef..72649577de7 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - 7.6.6 + 7.6.7-SNAPSHOT ../../pom.xml diff --git a/dspace/modules/rest/pom.xml b/dspace/modules/rest/pom.xml index 7ed7268deba..39d1973c31e 100644 --- a/dspace/modules/rest/pom.xml +++ b/dspace/modules/rest/pom.xml @@ -13,7 +13,7 @@ org.dspace modules - 7.6.6 + 7.6.7-SNAPSHOT .. diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index 919f687c97f..ea548ba7f35 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -13,7 +13,7 @@ just adding new jar in the classloader modules org.dspace - 7.6.6 + 7.6.7-SNAPSHOT .. diff --git a/dspace/pom.xml b/dspace/pom.xml index 9ca0c13d428..da6820074a5 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - 7.6.6 + 7.6.7-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index 1ba81c6b1fb..36b14b13311 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - 7.6.6 + 7.6.7-SNAPSHOT DSpace Parent Project DSpace open source software is a turnkey institutional repository application. @@ -875,14 +875,14 @@ org.dspace dspace-rest - 7.6.6 + 7.6.7-SNAPSHOT jar classes org.dspace dspace-rest - 7.6.6 + 7.6.7-SNAPSHOT war @@ -1031,69 +1031,69 @@ org.dspace dspace-api - 7.6.6 + 7.6.7-SNAPSHOT org.dspace dspace-api test-jar - 7.6.6 + 7.6.7-SNAPSHOT test org.dspace.modules additions - 7.6.6 + 7.6.7-SNAPSHOT org.dspace dspace-sword - 7.6.6 + 7.6.7-SNAPSHOT org.dspace dspace-swordv2 - 7.6.6 + 7.6.7-SNAPSHOT org.dspace dspace-oai - 7.6.6 + 7.6.7-SNAPSHOT org.dspace dspace-services - 7.6.6 + 7.6.7-SNAPSHOT org.dspace dspace-server-webapp test-jar - 7.6.6 + 7.6.7-SNAPSHOT test org.dspace dspace-rdf - 7.6.6 + 7.6.7-SNAPSHOT org.dspace dspace-iiif - 7.6.6 + 7.6.7-SNAPSHOT org.dspace dspace-server-webapp - 7.6.6 + 7.6.7-SNAPSHOT jar classes org.dspace dspace-server-webapp - 7.6.6 + 7.6.7-SNAPSHOT war @@ -1939,7 +1939,7 @@ scm:git:git@github.com:DSpace/DSpace.git scm:git:git@github.com:DSpace/DSpace.git https://github.com/DSpace/DSpace - dspace-7.6.6 + dspace-7_x From 6d178b69d6edc33cfeb33b73d060d5ddbba0279e Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 17 Dec 2025 11:55:27 -0600 Subject: [PATCH 772/939] Revert bouncycastle to version that is compatible with maven-gpg-plugin. --- pom.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 36b14b13311..b3859104c30 100644 --- a/pom.xml +++ b/pom.xml @@ -44,8 +44,9 @@ 1.7.36 3.2.3 - 1.82 - + + 1.81 2.10.0 From 9241a748af82714d9d637bfc2917712855efbf5f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Dec 2025 23:24:04 +0000 Subject: [PATCH 773/939] build(deps): bump pdfbox-version from 3.0.5 to 3.0.6 Bumps `pdfbox-version` from 3.0.5 to 3.0.6. Updates `org.apache.pdfbox:pdfbox` from 3.0.5 to 3.0.6 Updates `org.apache.pdfbox:fontbox` from 3.0.5 to 3.0.6 --- updated-dependencies: - dependency-name: org.apache.pdfbox:pdfbox dependency-version: 3.0.6 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.pdfbox:fontbox dependency-version: 3.0.6 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b3859104c30..e5a30de034e 100644 --- a/pom.xml +++ b/pom.xml @@ -39,7 +39,7 @@ 9.4.58.v20250814 2.25.2 - 3.0.5 + 3.0.6 1.19.0 1.7.36 3.2.3 From c33204f3aa2bbbced2cb4fecbc6e3f6c67f7d20d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jan 2026 02:14:05 +0000 Subject: [PATCH 774/939] build(deps): bump io.grpc:grpc-context from 1.77.0 to 1.78.0 Bumps [io.grpc:grpc-context](https://github.com/grpc/grpc-java) from 1.77.0 to 1.78.0. - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.77.0...v1.78.0) --- updated-dependencies: - dependency-name: io.grpc:grpc-context dependency-version: 1.78.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b3859104c30..6dc975d0f43 100644 --- a/pom.xml +++ b/pom.xml @@ -1719,7 +1719,7 @@ io.grpc grpc-context - 1.77.0 + 1.78.0 com.google.http-client From 6283ab4c1adc63970e33d871246036f5158410ef Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 12 Jan 2026 16:42:01 -0600 Subject: [PATCH 775/939] Remove unnecessary tty/stdin_open settings from several Docker Compose scripts --- docker-compose-cli.yml | 2 -- docker-compose.yml | 6 ------ dspace/src/main/docker-compose/docker-compose-angular.yml | 2 -- .../src/main/docker-compose/docker-compose-shibboleth.yml | 2 -- 4 files changed, 12 deletions(-) diff --git a/docker-compose-cli.yml b/docker-compose-cli.yml index ce7676bdc6c..77901c97618 100644 --- a/docker-compose-cli.yml +++ b/docker-compose-cli.yml @@ -30,8 +30,6 @@ services: - ./dspace/config:/dspace/config entrypoint: /dspace/bin/dspace command: help - tty: true - stdin_open: true volumes: assetstore: diff --git a/docker-compose.yml b/docker-compose.yml index a16c3677503..54051d3ea37 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -43,8 +43,6 @@ services: target: 8009 - published: 8000 target: 8000 - stdin_open: true - tty: true volumes: # Keep DSpace assetstore directory between reboots - assetstore:/dspace/assetstore @@ -78,8 +76,6 @@ services: ports: - published: 5432 target: 5432 - stdin_open: true - tty: true volumes: # Keep Postgres data directory between reboots - pgdata:/pgdata @@ -99,8 +95,6 @@ services: ports: - published: 8983 target: 8983 - stdin_open: true - tty: true working_dir: /var/solr/data volumes: # Keep Solr data directory between reboots diff --git a/dspace/src/main/docker-compose/docker-compose-angular.yml b/dspace/src/main/docker-compose/docker-compose-angular.yml index c9b87c904f1..bafbd6ceecc 100644 --- a/dspace/src/main/docker-compose/docker-compose-angular.yml +++ b/dspace/src/main/docker-compose/docker-compose-angular.yml @@ -32,5 +32,3 @@ services: target: 4000 - published: 9876 target: 9876 - stdin_open: true - tty: true diff --git a/dspace/src/main/docker-compose/docker-compose-shibboleth.yml b/dspace/src/main/docker-compose/docker-compose-shibboleth.yml index f7fb2dcbd1a..0b3178b7545 100644 --- a/dspace/src/main/docker-compose/docker-compose-shibboleth.yml +++ b/dspace/src/main/docker-compose/docker-compose-shibboleth.yml @@ -30,8 +30,6 @@ services: target: 80 - published: 443 target: 443 - stdin_open: true - tty: true environment: # Default to using "localhost" for Apache & Shibboleth # However, you can override this via the "DSPACE_HOSTNAME" environment variable. From bc0768a4b1c32f5eee3431dbec95fd1ecb9dcd1d Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 12 Jan 2026 16:52:00 -0600 Subject: [PATCH 776/939] Correct several docker compose scripts to use $DOCKER_REGISTRY and $DOCKER_OWNER env variables. --- dspace/src/main/docker-compose/db.entities.yml | 2 +- dspace/src/main/docker-compose/db.restore.yml | 2 +- dspace/src/main/docker-compose/docker-compose-angular.yml | 4 +--- dspace/src/main/docker-compose/docker-compose-shibboleth.yml | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/dspace/src/main/docker-compose/db.entities.yml b/dspace/src/main/docker-compose/db.entities.yml index 3480c0df870..db28fef80b8 100644 --- a/dspace/src/main/docker-compose/db.entities.yml +++ b/dspace/src/main/docker-compose/db.entities.yml @@ -8,7 +8,7 @@ services: dspacedb: - image: dspace/dspace-postgres-pgcrypto:dspace-7_x-loadsql + image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-dspace-7_x}-loadsql" environment: # This SQL is available from https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data - LOADSQL=https://github.com/DSpace-Labs/AIP-Files/releases/download/demo-entities-data/dspace7-entities-data.sql diff --git a/dspace/src/main/docker-compose/db.restore.yml b/dspace/src/main/docker-compose/db.restore.yml index 09990e67561..8584eee8272 100644 --- a/dspace/src/main/docker-compose/db.restore.yml +++ b/dspace/src/main/docker-compose/db.restore.yml @@ -12,7 +12,7 @@ # This can be used to restore a "dspacedb" container from a pg_dump, or during upgrade to a new version of PostgreSQL. services: dspacedb: - image: dspace/dspace-postgres-pgcrypto:dspace-7_x-loadsql + image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-dspace-7_x}-loadsql" environment: # Location where the dump SQL file will be available on the running container - LOCALSQL=/tmp/pgdump.sql diff --git a/dspace/src/main/docker-compose/docker-compose-angular.yml b/dspace/src/main/docker-compose/docker-compose-angular.yml index bafbd6ceecc..536b34bf8cf 100644 --- a/dspace/src/main/docker-compose/docker-compose-angular.yml +++ b/dspace/src/main/docker-compose/docker-compose-angular.yml @@ -26,9 +26,7 @@ services: DSPACE_REST_HOST: localhost DSPACE_REST_PORT: 8080 DSPACE_REST_NAMESPACE: /server - image: dspace/dspace-angular:dspace-7_x + image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-angular:${DSPACE_VER:-dspace-7_x}" ports: - published: 4000 target: 4000 - - published: 9876 - target: 9876 diff --git a/dspace/src/main/docker-compose/docker-compose-shibboleth.yml b/dspace/src/main/docker-compose/docker-compose-shibboleth.yml index 0b3178b7545..1290fbc1e90 100644 --- a/dspace/src/main/docker-compose/docker-compose-shibboleth.yml +++ b/dspace/src/main/docker-compose/docker-compose-shibboleth.yml @@ -21,7 +21,7 @@ services: container_name: dspace-shibboleth depends_on: - dspace - image: dspace/dspace-shibboleth + image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-shibboleth" build: # Must be relative to root, so that it can be built alongside [src]/docker-compose.yml context: ./dspace/src/main/docker/dspace-shibboleth From c04b39efdbda13023b6a31c862f1294e0c86b46a Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 12 Jan 2026 16:54:13 -0600 Subject: [PATCH 777/939] Update Angular docker compose script to use production mode (as dev mode is only useful with src code). Requires minor update to backend docker compose script. --- docker-compose.yml | 3 +++ dspace/src/main/docker-compose/docker-compose-angular.yml | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 54051d3ea37..49139d9bab4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,6 +19,9 @@ services: # Uncomment to set a non-default value for dspace.server.url or dspace.ui.url # dspace__P__server__P__url: http://localhost:8080/server # dspace__P__ui__P__url: http://localhost:4000 + # Set SSR URL to the Docker container name so that UI can contact container directly in Production mode. + # (This is necessary for docker-compose-angular.yml as it uses production mode by default) + dspace__P__server__P__ssr__P__url: http://dspace:8080/server dspace__P__name: 'DSpace Started with Docker Compose' # db.url: Ensure we are using the 'dspacedb' image for our database db__P__url: 'jdbc:postgresql://dspacedb:5432/dspace' diff --git a/dspace/src/main/docker-compose/docker-compose-angular.yml b/dspace/src/main/docker-compose/docker-compose-angular.yml index 536b34bf8cf..dcfc6190523 100644 --- a/dspace/src/main/docker-compose/docker-compose-angular.yml +++ b/dspace/src/main/docker-compose/docker-compose-angular.yml @@ -26,7 +26,9 @@ services: DSPACE_REST_HOST: localhost DSPACE_REST_PORT: 8080 DSPACE_REST_NAMESPACE: /server - image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-angular:${DSPACE_VER:-dspace-7_x}" + # Ensure SSR can use the 'dspace' Docker image directly (see docker-compose-rest.yml) + DSPACE_REST_SSRBASEURL: http://dspace:8080/server + image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-angular:${DSPACE_VER:-dspace-7_x-dist}" ports: - published: 4000 target: 4000 From c7b1294330b6aafdf17624744de27c9ceabc7a9c Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 12 Jan 2026 17:03:15 -0600 Subject: [PATCH 778/939] Fix minor networking inconsistencies similar to https://github.com/DSpace/dspace-angular/pull/4987 --- docker-compose.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 49139d9bab4..83474bde86a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,6 +5,8 @@ networks: # Define a custom subnet for our DSpace network, so that we can easily trust requests from host to container. # If you customize this value, be sure to customize the 'proxies.trusted.ipranges' env variable below. - subnet: 172.23.0.0/16 + # Explicitly set external=false because this script creates the network. + external: false services: # DSpace (backend) webapp container dspace: From d6e84fddf22ce24789c6d8000aac4cfdc948ad93 Mon Sep 17 00:00:00 2001 From: April Herron Date: Mon, 13 Aug 2018 14:18:17 -0400 Subject: [PATCH 779/939] Fix updateMissingBitstreams to use single database operation instead of o-o operations [DS-3975] Includes toniprieto's suggested change in #2169 discussion and additional logging (e.g. how many bitstreams are missing). (cherry picked from commit fc1bd872b016695a2e60dfa554c55f206f9f359f) --- .../MostRecentChecksumServiceImpl.java | 55 ++----------------- .../checker/dao/MostRecentChecksumDAO.java | 2 + .../dao/impl/MostRecentChecksumDAOImpl.java | 18 ++++++ 3 files changed, 25 insertions(+), 50 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/checker/MostRecentChecksumServiceImpl.java b/dspace-api/src/main/java/org/dspace/checker/MostRecentChecksumServiceImpl.java index d267171aa0d..9ee777a3e15 100644 --- a/dspace-api/src/main/java/org/dspace/checker/MostRecentChecksumServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/checker/MostRecentChecksumServiceImpl.java @@ -90,61 +90,16 @@ public List findBitstreamResultTypeReport(Context context, D /** * Queries the bitstream table for bitstream IDs that are not yet in the * most_recent_checksum table, and inserts them into the - * most_recent_checksum and checksum_history tables. - * + * most_recent_checksum table. * @param context Context * @throws SQLException if database error */ @Override public void updateMissingBitstreams(Context context) throws SQLException { -// "insert into most_recent_checksum ( " -// + "bitstream_id, to_be_processed, expected_checksum, current_checksum, " -// + "last_process_start_date, last_process_end_date, " -// + "checksum_algorithm, matched_prev_checksum, result ) " -// + "select bitstream.bitstream_id, " -// + "CASE WHEN bitstream.deleted = false THEN true ELSE false END, " -// + "CASE WHEN bitstream.checksum IS NULL THEN '' ELSE bitstream.checksum END, " -// + "CASE WHEN bitstream.checksum IS NULL THEN '' ELSE bitstream.checksum END, " -// + "?, ?, CASE WHEN bitstream.checksum_algorithm IS NULL " -// + "THEN 'MD5' ELSE bitstream.checksum_algorithm END, true, " -// + "CASE WHEN bitstream.deleted = true THEN 'BITSTREAM_MARKED_DELETED' else 'CHECKSUM_MATCH' END " -// + "from bitstream where not exists( " -// + "select 'x' from most_recent_checksum " -// + "where most_recent_checksum.bitstream_id = bitstream.bitstream_id )"; - - List unknownBitstreams = bitstreamService.findBitstreamsWithNoRecentChecksum(context); - for (Bitstream bitstream : unknownBitstreams) { - log.info(bitstream + " " + bitstream.getID().toString() + " " + bitstream.getName()); - - MostRecentChecksum mostRecentChecksum = new MostRecentChecksum(); - mostRecentChecksum.setBitstream(bitstream); - //Only process if our bitstream isn't deleted - mostRecentChecksum.setToBeProcessed(!bitstream.isDeleted()); - if (bitstream.getChecksum() == null) { - mostRecentChecksum.setCurrentChecksum(""); - mostRecentChecksum.setExpectedChecksum(""); - } else { - mostRecentChecksum.setCurrentChecksum(bitstream.getChecksum()); - mostRecentChecksum.setExpectedChecksum(bitstream.getChecksum()); - } - mostRecentChecksum.setProcessStartDate(new Date()); - mostRecentChecksum.setProcessEndDate(new Date()); - if (bitstream.getChecksumAlgorithm() == null) { - mostRecentChecksum.setChecksumAlgorithm("MD5"); - } else { - mostRecentChecksum.setChecksumAlgorithm(bitstream.getChecksumAlgorithm()); - } - mostRecentChecksum.setMatchedPrevChecksum(true); - ChecksumResult checksumResult; - if (bitstream.isDeleted()) { - checksumResult = checksumResultService.findByCode(context, ChecksumResultCode.BITSTREAM_MARKED_DELETED); - } else { - checksumResult = checksumResultService.findByCode(context, ChecksumResultCode.CHECKSUM_MATCH); - } - mostRecentChecksum.setChecksumResult(checksumResult); - mostRecentChecksumDAO.create(context, mostRecentChecksum); - mostRecentChecksumDAO.save(context, mostRecentChecksum); - } + log.info("Retrieving missing bitsreams (bitstream IDs that are not yet in most_recent_checksum table)..."); + int updated = mostRecentChecksumDAO.updateMissingBitstreams(context); + log.info("Updated most_recent_checksum for " + updated + " bitstreams."); + log.info("Missing bitsreams processing done."); } @Override diff --git a/dspace-api/src/main/java/org/dspace/checker/dao/MostRecentChecksumDAO.java b/dspace-api/src/main/java/org/dspace/checker/dao/MostRecentChecksumDAO.java index 56485c9b4b4..73a81e4d9b4 100644 --- a/dspace-api/src/main/java/org/dspace/checker/dao/MostRecentChecksumDAO.java +++ b/dspace-api/src/main/java/org/dspace/checker/dao/MostRecentChecksumDAO.java @@ -33,6 +33,8 @@ public List findByNotProcessedInDateRange(Context context, D public List findByResultTypeInDateRange(Context context, Date startDate, Date endDate, ChecksumResultCode resultCode) throws SQLException; + public int updateMissingBitstreams(Context context) throws SQLException; + public void deleteByBitstream(Context context, Bitstream bitstream) throws SQLException; public MostRecentChecksum getOldestRecord(Context context) throws SQLException; diff --git a/dspace-api/src/main/java/org/dspace/checker/dao/impl/MostRecentChecksumDAOImpl.java b/dspace-api/src/main/java/org/dspace/checker/dao/impl/MostRecentChecksumDAOImpl.java index 6de60bd4fdb..3ca2f9f822e 100644 --- a/dspace-api/src/main/java/org/dspace/checker/dao/impl/MostRecentChecksumDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/checker/dao/impl/MostRecentChecksumDAOImpl.java @@ -66,6 +66,24 @@ public List findByNotProcessedInDateRange(Context context, D return list(context, criteriaQuery, false, MostRecentChecksum.class, -1, -1); } + @Override + public int updateMissingBitstreams(Context context) throws SQLException { + String hql = "INSERT INTO MostRecentChecksum(bitstream, toBeProcessed, expectedChecksum, currentChecksum, " + + "processStartDate, processEndDate, checksumAlgorithm, matchedPrevChecksum, checksumResult) " + + "SELECT b, " + + "CASE WHEN deleted = false THEN true ELSE false END, " + + "CASE WHEN checksum IS NULL THEN '' ELSE checksum END, " + + "CASE WHEN checksum IS NULL THEN '' ELSE checksum END, " + + "current_date(), current_date(), " + + "CASE WHEN checksumAlgorithm IS NULL THEN 'MD5' ELSE checksumAlgorithm END, " + + "CAST(1 AS boolean), " + + "(SELECT cr FROM ChecksumResult AS cr WHERE " + + "(resultCode='BITSTREAM_MARKED_DELETED' AND b.deleted = true) " + + "OR (resultCode='CHECKSUM_MATCH' AND b.deleted = false)) " + + "FROM Bitstream AS b WHERE NOT EXISTS(SELECT 'x' FROM MostRecentChecksum WHERE id=b.id)"; + Query query = createQuery(context, hql); + return query.executeUpdate(); + } @Override public MostRecentChecksum findByBitstream(Context context, Bitstream bitstream) throws SQLException { From 677bea1d7f5be200c3b1baf8472347b9e14822b3 Mon Sep 17 00:00:00 2001 From: Miika Nurminen Date: Fri, 15 Aug 2025 19:34:50 +0300 Subject: [PATCH 780/939] Fix types used in updateMissingBitstreams HQL Error found when running checker ITs in #11144. (cherry picked from commit c358e885ae6833ab8bf8d8a28789ef31443c4545) --- .../checker/dao/impl/MostRecentChecksumDAOImpl.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/checker/dao/impl/MostRecentChecksumDAOImpl.java b/dspace-api/src/main/java/org/dspace/checker/dao/impl/MostRecentChecksumDAOImpl.java index 3ca2f9f822e..6ecd94f67f9 100644 --- a/dspace-api/src/main/java/org/dspace/checker/dao/impl/MostRecentChecksumDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/checker/dao/impl/MostRecentChecksumDAOImpl.java @@ -74,13 +74,13 @@ public int updateMissingBitstreams(Context context) throws SQLException { "CASE WHEN deleted = false THEN true ELSE false END, " + "CASE WHEN checksum IS NULL THEN '' ELSE checksum END, " + "CASE WHEN checksum IS NULL THEN '' ELSE checksum END, " + - "current_date(), current_date(), " + + "current_timestamp(), current_timestamp(), " + "CASE WHEN checksumAlgorithm IS NULL THEN 'MD5' ELSE checksumAlgorithm END, " + "CAST(1 AS boolean), " + "(SELECT cr FROM ChecksumResult AS cr WHERE " + - "(resultCode='BITSTREAM_MARKED_DELETED' AND b.deleted = true) " + - "OR (resultCode='CHECKSUM_MATCH' AND b.deleted = false)) " + - "FROM Bitstream AS b WHERE NOT EXISTS(SELECT 'x' FROM MostRecentChecksum WHERE id=b.id)"; + "(resultCode = 'BITSTREAM_MARKED_DELETED' AND b.deleted = true) " + + "OR (resultCode = 'CHECKSUM_MATCH' AND b.deleted = false)) " + + "FROM Bitstream AS b WHERE NOT EXISTS(SELECT 'x' FROM MostRecentChecksum AS c WHERE c.bitstream = b)"; Query query = createQuery(context, hql); return query.executeUpdate(); } From 8488f0601a5fbfc352f679275bebbd7b1c773eb9 Mon Sep 17 00:00:00 2001 From: Drew Heles Date: Wed, 21 Jan 2026 08:50:15 -0500 Subject: [PATCH 781/939] Add DataCite sandbox url to crosswalk (cherry picked from commit 3a9f3b28b2028076d822b413b344acc49aa461e5) --- dspace/config/crosswalks/DIM2DataCite.xsl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dspace/config/crosswalks/DIM2DataCite.xsl b/dspace/config/crosswalks/DIM2DataCite.xsl index 935b3dc4038..64b004b063c 100644 --- a/dspace/config/crosswalks/DIM2DataCite.xsl +++ b/dspace/config/crosswalks/DIM2DataCite.xsl @@ -350,6 +350,9 @@ + + + From 9d9c031f87cd978ce831de3cc70a018534bfa351 Mon Sep 17 00:00:00 2001 From: milanmajchrak <90026355+milanmajchrak@users.noreply.github.com> Date: Tue, 15 Apr 2025 11:01:44 +0200 Subject: [PATCH 782/939] ZCU-PUB/The bitstream name is encoded in the URL. Updated the test to test special characters in the URL (#930) (cherry picked from commit 29199d5935ded1f48dbd30727af5530bc6c2ef6c) --- .../src/test/java/org/dspace/app/sword2/Swordv2IT.java | 3 ++- .../src/main/java/org/dspace/sword2/SwordUrlManager.java | 8 +++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/sword2/Swordv2IT.java b/dspace-server-webapp/src/test/java/org/dspace/app/sword2/Swordv2IT.java index 3ff79f78055..f24a09c8e56 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/sword2/Swordv2IT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/sword2/Swordv2IT.java @@ -220,7 +220,8 @@ public void depositAndEditViaSwordTest() throws Exception { // Add required headers HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.MULTIPART_FORM_DATA); - headers.setContentDisposition(ContentDisposition.attachment().filename("example.zip").build()); + // Test the file with spaces or special characters in the name + headers.setContentDisposition(ContentDisposition.attachment().filename("example .zip").build()); headers.set("Packaging", "http://purl.org/net/sword/package/METSDSpaceSIP"); headers.setAccept(List.of(MediaType.APPLICATION_ATOM_XML)); diff --git a/dspace-swordv2/src/main/java/org/dspace/sword2/SwordUrlManager.java b/dspace-swordv2/src/main/java/org/dspace/sword2/SwordUrlManager.java index 1d49bc27977..4ba0f9950e1 100644 --- a/dspace-swordv2/src/main/java/org/dspace/sword2/SwordUrlManager.java +++ b/dspace-swordv2/src/main/java/org/dspace/sword2/SwordUrlManager.java @@ -7,6 +7,8 @@ */ package org.dspace.sword2; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.sql.SQLException; import java.util.List; @@ -386,10 +388,10 @@ public String getBitstreamUrl(Bitstream bitstream) if (handle != null && !"".equals(handle)) { bsLink = bsLink + "/bitstream/" + handle + "/" + - bitstream.getSequenceID() + "/" + bitstream.getName(); + bitstream.getSequenceID() + "/" + URLEncoder.encode(bitstream.getName(), StandardCharsets.UTF_8); } else { bsLink = bsLink + "/retrieve/" + bitstream.getID() + "/" + - bitstream.getName(); + URLEncoder.encode(bitstream.getName(), StandardCharsets.UTF_8); } return bsLink; @@ -401,7 +403,7 @@ public String getBitstreamUrl(Bitstream bitstream) public String getActionableBitstreamUrl(Bitstream bitstream) throws DSpaceSwordException { return this.getSwordBaseUrl() + "/edit-media/bitstream/" + - bitstream.getID() + "/" + bitstream.getName(); + bitstream.getID() + "/" + URLEncoder.encode(bitstream.getName(), StandardCharsets.UTF_8); } public boolean isActionableBitstreamUrl(Context context, String url) { From fe54d800ea12731b032c6c08a54d5874e18cbfb9 Mon Sep 17 00:00:00 2001 From: Mark Patton Date: Mon, 15 Dec 2025 11:14:53 -0500 Subject: [PATCH 783/939] Make sure that bitstream downloads have a content-disposition header set (cherry picked from commit 9ae59e7a3e634e3bf71b3643d124ea9a8d625d84) --- .../main/java/org/dspace/app/rest/BitstreamRestController.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java index 242b231654c..1ff9f7520c1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java @@ -160,6 +160,8 @@ public ResponseEntity retrieve(@PathVariable UUID uuid, HttpServletResponse resp if ((dispositionThreshold >= 0 && filesize > dispositionThreshold) || checkFormatForContentDisposition(format)) { httpHeadersInitializer.withDisposition(HttpHeadersInitializer.CONTENT_DISPOSITION_ATTACHMENT); + } else { + httpHeadersInitializer.withDisposition(HttpHeadersInitializer.CONTENT_DISPOSITION_INLINE); } //We have all the data we need, close the connection to the database so that it doesn't stay open during From 905429773f473fd073e1b5691f155521e94212fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jan 2026 07:36:06 +0000 Subject: [PATCH 784/939] build(deps): bump log4j.version from 2.25.2 to 2.25.3 Bumps `log4j.version` from 2.25.2 to 2.25.3. Updates `org.apache.logging.log4j:log4j-api` from 2.25.2 to 2.25.3 Updates `org.apache.logging.log4j:log4j-core` from 2.25.2 to 2.25.3 Updates `org.apache.logging.log4j:log4j-1.2-api` from 2.25.2 to 2.25.3 --- updated-dependencies: - dependency-name: org.apache.logging.log4j:log4j-api dependency-version: 2.25.3 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.logging.log4j:log4j-core dependency-version: 2.25.3 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.logging.log4j:log4j-1.2-api dependency-version: 2.25.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e5a30de034e..ca643941936 100644 --- a/pom.xml +++ b/pom.xml @@ -38,7 +38,7 @@ 1.1.1 9.4.58.v20250814 - 2.25.2 + 2.25.3 3.0.6 1.19.0 1.7.36 From a70b71febaddb10b0cc8f5d789952f02eba29a1c Mon Sep 17 00:00:00 2001 From: hsb-app Date: Mon, 26 Jan 2026 13:53:51 +0100 Subject: [PATCH 785/939] #11792 Bug fix link checker stops when url has spaces (cherry picked from commit 570fb9c0fcc5623a8e5701abbc1a1370a7ed6019) --- .../java/org/dspace/ctask/general/BasicLinkChecker.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java b/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java index 02033184270..476c673ec95 100644 --- a/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java +++ b/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java @@ -9,12 +9,15 @@ import java.io.IOException; import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.utils.URIBuilder; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.logging.log4j.Logger; import org.dspace.app.client.DSpaceHttpClientFactory; @@ -141,7 +144,8 @@ protected boolean checkURL(String url, StringBuilder results) { protected int getResponseStatus(String url, int redirects) { RequestConfig config = RequestConfig.custom().setRedirectsEnabled(true).build(); try (CloseableHttpClient httpClient = DSpaceHttpClientFactory.getInstance().buildWithRequestConfig(config)) { - CloseableHttpResponse httpResponse = httpClient.execute(new HttpGet(url)); + URI uri = new URIBuilder(url).build(); + CloseableHttpResponse httpResponse = httpClient.execute(new HttpGet(uri)); int statusCode = httpResponse.getStatusLine().getStatusCode(); int maxRedirect = configurationService.getIntProperty("curate.checklinks.max-redirect", 0); if ((statusCode == HttpURLConnection.HTTP_MOVED_TEMP || statusCode == HttpURLConnection.HTTP_MOVED_PERM || @@ -153,6 +157,9 @@ protected int getResponseStatus(String url, int redirects) { } } return statusCode; + } catch (URISyntaxException e) { + log.error("Invalid URL: ", url, e); + return 0; } catch (IOException ioe) { // Must be a bad URL log.debug("Bad link: " + ioe.getMessage()); From cf4b0442aca19b1b4ef7521fd5f8f66561ca57ae Mon Sep 17 00:00:00 2001 From: zhaw-hsb Date: Mon, 26 Jan 2026 14:38:38 +0100 Subject: [PATCH 786/939] checkstyle removed trailing spaces (cherry picked from commit 1cb88279efcab123c62150a731ebfe038b2168fd) --- .../main/java/org/dspace/ctask/general/BasicLinkChecker.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java b/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java index 476c673ec95..3c116df13bd 100644 --- a/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java +++ b/dspace-api/src/main/java/org/dspace/ctask/general/BasicLinkChecker.java @@ -158,7 +158,7 @@ protected int getResponseStatus(String url, int redirects) { } return statusCode; } catch (URISyntaxException e) { - log.error("Invalid URL: ", url, e); + log.error("Invalid URL: ", url, e); return 0; } catch (IOException ioe) { // Must be a bad URL From b434d84271fbdbd4f0dd263e25ef1abf33cc8bd7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Feb 2026 02:18:21 +0000 Subject: [PATCH 787/939] Bump the build-tools group with 4 updates Bumps the build-tools group with 4 updates: [org.sonatype.central:central-publishing-maven-plugin](https://github.com/sonatype/central-publishing-maven-plugin), [org.codehaus.mojo:xml-maven-plugin](https://github.com/mojohaus/xml-maven-plugin), [org.codehaus.mojo:license-maven-plugin](https://github.com/mojohaus/license-maven-plugin) and [org.codehaus.mojo:buildnumber-maven-plugin](https://github.com/mojohaus/buildnumber-maven-plugin). Updates `org.sonatype.central:central-publishing-maven-plugin` from 0.9.0 to 0.10.0 - [Commits](https://github.com/sonatype/central-publishing-maven-plugin/commits) Updates `org.codehaus.mojo:xml-maven-plugin` from 1.2.0 to 1.2.1 - [Release notes](https://github.com/mojohaus/xml-maven-plugin/releases) - [Commits](https://github.com/mojohaus/xml-maven-plugin/compare/1.2.0...xml-maven-plugin-1.2.1) Updates `org.codehaus.mojo:license-maven-plugin` from 2.7.0 to 2.7.1 - [Release notes](https://github.com/mojohaus/license-maven-plugin/releases) - [Commits](https://github.com/mojohaus/license-maven-plugin/compare/2.7.0...2.7.1) Updates `org.codehaus.mojo:buildnumber-maven-plugin` from 3.2.1 to 3.3.0 - [Release notes](https://github.com/mojohaus/buildnumber-maven-plugin/releases) - [Commits](https://github.com/mojohaus/buildnumber-maven-plugin/compare/3.2.1...buildnumber-maven-plugin-3.3.0) --- updated-dependencies: - dependency-name: org.sonatype.central:central-publishing-maven-plugin dependency-version: 0.10.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools - dependency-name: org.codehaus.mojo:xml-maven-plugin dependency-version: 1.2.1 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: build-tools - dependency-name: org.codehaus.mojo:license-maven-plugin dependency-version: 2.7.1 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: build-tools - dependency-name: org.codehaus.mojo:buildnumber-maven-plugin dependency-version: 3.3.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: build-tools ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 2 +- pom.xml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index d0493a4c746..8bd941709d9 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -102,7 +102,7 @@ org.codehaus.mojo buildnumber-maven-plugin - 3.2.1 + 3.3.0 UNKNOWN_REVISION diff --git a/pom.xml b/pom.xml index ca643941936..09e75a04f54 100644 --- a/pom.xml +++ b/pom.xml @@ -358,7 +358,7 @@ org.sonatype.central central-publishing-maven-plugin - 0.9.0 + 0.10.0 @@ -473,7 +473,7 @@ org.codehaus.mojo xml-maven-plugin - 1.2.0 + 1.2.1 validate-ALL-xml-and-xsl @@ -681,7 +681,7 @@ org.codehaus.mojo license-maven-plugin - 2.7.0 + 2.7.1 false From 1efdd2715208e3039107e0f0291bff73c8d71446 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Feb 2026 02:18:38 +0000 Subject: [PATCH 788/939] Bump the fasterxml group with 4 updates Bumps the fasterxml group with 4 updates: [com.fasterxml:classmate](https://github.com/FasterXML/java-classmate), [com.fasterxml.jackson.core:jackson-annotations](https://github.com/FasterXML/jackson), [com.fasterxml.jackson.core:jackson-core](https://github.com/FasterXML/jackson-core) and [com.fasterxml.jackson.core:jackson-databind](https://github.com/FasterXML/jackson). Updates `com.fasterxml:classmate` from 1.7.1 to 1.7.3 - [Commits](https://github.com/FasterXML/java-classmate/compare/classmate-1.7.1...classmate-1.7.3) Updates `com.fasterxml.jackson.core:jackson-annotations` from 2.20 to 2.21 - [Commits](https://github.com/FasterXML/jackson/commits) Updates `com.fasterxml.jackson.core:jackson-core` from 2.20.1 to 2.21.0 - [Commits](https://github.com/FasterXML/jackson-core/compare/jackson-core-2.20.1...jackson-core-2.21.0) Updates `com.fasterxml.jackson.core:jackson-databind` from 2.20.1 to 2.21.0 - [Commits](https://github.com/FasterXML/jackson/commits) Updates `com.fasterxml.jackson.core:jackson-databind` from 2.20.1 to 2.21.0 - [Commits](https://github.com/FasterXML/jackson/commits) --- updated-dependencies: - dependency-name: com.fasterxml:classmate dependency-version: 1.7.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: fasterxml - dependency-name: com.fasterxml.jackson.core:jackson-annotations dependency-version: '2.21' dependency-type: direct:production update-type: version-update:semver-minor dependency-group: fasterxml - dependency-name: com.fasterxml.jackson.core:jackson-core dependency-version: 2.21.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: fasterxml - dependency-name: com.fasterxml.jackson.core:jackson-databind dependency-version: 2.21.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: fasterxml - dependency-name: com.fasterxml.jackson.core:jackson-databind dependency-version: 2.21.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: fasterxml ... Signed-off-by: dependabot[bot] --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index ca643941936..55ef2080e35 100644 --- a/pom.xml +++ b/pom.xml @@ -30,8 +30,8 @@ 3.11.1 2.31.0 - 2.20.1 - 2.20 + 2.21.0 + 2.21 1.3.2 2.3.1 2.3.9 @@ -1772,7 +1772,7 @@ com.fasterxml classmate - 1.7.1 + 1.7.3 com.fasterxml.jackson.core From 4ebae2132b6df9c04f73f069d806495659ed8747 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Feb 2026 02:19:48 +0000 Subject: [PATCH 789/939] Bump org.apache.bcel:bcel from 6.11.0 to 6.12.0 Bumps [org.apache.bcel:bcel](https://github.com/apache/commons-bcel) from 6.11.0 to 6.12.0. - [Changelog](https://github.com/apache/commons-bcel/blob/master/RELEASE-NOTES.txt) - [Commits](https://github.com/apache/commons-bcel/compare/rel/commons-bcel-6.11.0...rel/commons-bcel-6.12.0) --- updated-dependencies: - dependency-name: org.apache.bcel:bcel dependency-version: 6.12.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index d0493a4c746..8906beac67a 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -822,7 +822,7 @@ org.apache.bcel bcel - 6.11.0 + 6.12.0 test From ca4ff14c28b9576c5b332187cd53a9ebb6f2c5b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Feb 2026 02:20:29 +0000 Subject: [PATCH 790/939] Bump dnsjava:dnsjava from 3.6.3 to 3.6.4 Bumps [dnsjava:dnsjava](https://github.com/dnsjava/dnsjava) from 3.6.3 to 3.6.4. - [Release notes](https://github.com/dnsjava/dnsjava/releases) - [Changelog](https://github.com/dnsjava/dnsjava/blob/master/Changelog) - [Commits](https://github.com/dnsjava/dnsjava/compare/v3.6.3...v3.6.4) --- updated-dependencies: - dependency-name: dnsjava:dnsjava dependency-version: 3.6.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index d0493a4c746..137ab82d082 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -628,7 +628,7 @@ dnsjava dnsjava - 3.6.3 + 3.6.4 From 6fe8d4653007b8fa2b719d454a62e8f556245bac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Feb 2026 02:21:37 +0000 Subject: [PATCH 791/939] Bump org.postgresql:postgresql from 42.7.8 to 42.7.9 Bumps [org.postgresql:postgresql](https://github.com/pgjdbc/pgjdbc) from 42.7.8 to 42.7.9. - [Release notes](https://github.com/pgjdbc/pgjdbc/releases) - [Changelog](https://github.com/pgjdbc/pgjdbc/blob/master/CHANGELOG.md) - [Commits](https://github.com/pgjdbc/pgjdbc/compare/REL42.7.8...REL42.7.9) --- updated-dependencies: - dependency-name: org.postgresql:postgresql dependency-version: 42.7.9 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ca643941936..b5e50442051 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ 5.7.14 5.6.15.Final 6.2.5.Final - 42.7.8 + 42.7.9 8.11.4 3.11.1 From e7117be54c562ca1466c5b3d771c168216c991fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Feb 2026 02:52:59 +0000 Subject: [PATCH 792/939] build(deps): bump the amazon-s3 group with 2 updates Bumps the amazon-s3 group with 2 updates: software.amazon.awssdk:s3 and [software.amazon.awssdk.crt:aws-crt](https://github.com/awslabs/aws-crt-java). Updates `software.amazon.awssdk:s3` from 2.38.8 to 2.39.6 Updates `software.amazon.awssdk.crt:aws-crt` from 0.39.4 to 0.40.1 - [Release notes](https://github.com/awslabs/aws-crt-java/releases) - [Commits](https://github.com/awslabs/aws-crt-java/compare/v0.39.4...v0.40.1) --- updated-dependencies: - dependency-name: software.amazon.awssdk:s3 dependency-version: 2.39.6 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: amazon-s3 - dependency-name: software.amazon.awssdk.crt:aws-crt dependency-version: 0.40.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: amazon-s3 ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index d0493a4c746..1bfc30e660d 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -739,7 +739,7 @@ software.amazon.awssdk s3 - 2.38.8 + 2.41.14 software.amazon.awssdk @@ -755,7 +755,7 @@ software.amazon.awssdk.crt aws-crt - 0.39.4 + 0.42.2 From baa994f64dccdb4f33b2c97b44843c0b32b32052 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Feb 2026 03:01:56 +0000 Subject: [PATCH 793/939] Bump the apache-commons group across 1 directory with 4 updates Bumps the apache-commons group with 4 updates in the / directory: [commons-codec:commons-codec](https://github.com/apache/commons-codec), org.apache.commons:commons-dbcp2, org.apache.commons:commons-pool2 and [org.apache.commons:commons-text](https://github.com/apache/commons-text). Updates `commons-codec:commons-codec` from 1.20.0 to 1.21.0 - [Changelog](https://github.com/apache/commons-codec/blob/master/RELEASE-NOTES.txt) - [Commits](https://github.com/apache/commons-codec/compare/rel/commons-codec-1.20.0...rel/commons-codec-1.21.0) Updates `org.apache.commons:commons-dbcp2` from 2.13.0 to 2.14.0 Updates `org.apache.commons:commons-pool2` from 2.12.1 to 2.13.1 Updates `org.apache.commons:commons-text` from 1.14.0 to 1.15.0 - [Changelog](https://github.com/apache/commons-text/blob/master/RELEASE-NOTES.txt) - [Commits](https://github.com/apache/commons-text/compare/rel/commons-text-1.14.0...rel/commons-text-1.15.0) --- updated-dependencies: - dependency-name: commons-codec:commons-codec dependency-version: 1.21.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: apache-commons - dependency-name: org.apache.commons:commons-dbcp2 dependency-version: 2.14.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: apache-commons - dependency-name: org.apache.commons:commons-pool2 dependency-version: 2.13.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: apache-commons - dependency-name: org.apache.commons:commons-text dependency-version: 1.15.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: apache-commons ... Signed-off-by: dependabot[bot] --- pom.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index ca643941936..82aa88b92a5 100644 --- a/pom.xml +++ b/pom.xml @@ -1479,7 +1479,7 @@ commons-codec commons-codec - 1.20.0 + 1.21.0 org.apache.commons @@ -1494,7 +1494,7 @@ org.apache.commons commons-dbcp2 - 2.13.0 + 2.14.0 commons-io @@ -1521,12 +1521,12 @@ org.apache.commons commons-pool2 - 2.12.1 + 2.13.1 org.apache.commons commons-text - 1.14.0 + 1.15.0 commons-validator From 33686254bf94961f5c8b1925bdc07a1419107d77 Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Tue, 3 Feb 2026 21:25:08 +0100 Subject: [PATCH 794/939] Move same-collection check to inheritDefaultPolicies move method (cherry picked from commit cfff8040b3f0fcf079f87fef51451b12107c80b6) --- .../org/dspace/content/ItemServiceImpl.java | 10 ++++----- .../java/org/dspace/content/ItemTest.java | 21 +++++++++++++++++++ 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java index c1072969df2..744666baff9 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -1134,11 +1134,6 @@ public void adjustItemPolicies(Context context, Item item, Collection collection public void move(Context context, Item item, Collection from, Collection to) throws SQLException, AuthorizeException, IOException { - // If the two collections are the same, do nothing. - if (from.equals(to)) { - return; - } - // Use the normal move method, and default to not inherit permissions this.move(context, item, from, to, false); } @@ -1153,6 +1148,11 @@ public void move(Context context, Item item, Collection from, Collection to, boo authorizeService.authorizeAction(context, item, Constants.WRITE); } + // If the two collections are the same, do nothing. + if (from.equals(to)) { + return; + } + // Move the Item from one Collection to the other collectionService.addItem(context, to, item); collectionService.removeItem(context, from, item); diff --git a/dspace-api/src/test/java/org/dspace/content/ItemTest.java b/dspace-api/src/test/java/org/dspace/content/ItemTest.java index 00dbf2994d9..1f520926b98 100644 --- a/dspace-api/src/test/java/org/dspace/content/ItemTest.java +++ b/dspace-api/src/test/java/org/dspace/content/ItemTest.java @@ -1604,6 +1604,27 @@ public void testMoveSameCollection() throws Exception { verify(itemServiceSpy, times(0)).delete(context, it); } + /** + * Test of move with inherit default policies method, of class Item, where both Collections are the same. + */ + @Test + public void testMoveSameCollectionWithInheritDefaultPolicies() throws Exception { + context.turnOffAuthorisationSystem(); + while (it.getCollections().size() > 1) { + it.removeCollection(it.getCollections().get(0)); + } + + Collection collection = it.getCollections().get(0); + it.setOwningCollection(collection); + ItemService itemServiceSpy = spy(itemService); + + itemService.move(context, it, collection, collection, true); + context.restoreAuthSystemState(); + assertThat("testMoveSameCollection 0", it.getOwningCollection(), notNullValue()); + assertThat("testMoveSameCollection 1", it.getOwningCollection(), equalTo(collection)); + verify(itemServiceSpy, times(0)).delete(context, it); + } + /** * Test of hasUploadedFiles method, of class Item. */ From 52d4e83413db41bf248eba81d896fced53891e3c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Feb 2026 05:57:52 +0000 Subject: [PATCH 795/939] build(deps): bump the test-tools group with 7 updates Bumps the test-tools group with 7 updates: | Package | From | To | | --- | --- | --- | | [org.testcontainers:testcontainers-localstack](https://github.com/testcontainers/testcontainers-java) | `2.0.2` | `2.0.3` | | [io.netty:netty-buffer](https://github.com/netty/netty) | `4.2.7.Final` | `4.2.9.Final` | | [io.netty:netty-transport](https://github.com/netty/netty) | `4.2.7.Final` | `4.2.9.Final` | | [io.netty:netty-transport-native-unix-common](https://github.com/netty/netty) | `4.2.7.Final` | `4.2.9.Final` | | [io.netty:netty-common](https://github.com/netty/netty) | `4.2.7.Final` | `4.2.9.Final` | | [io.netty:netty-handler](https://github.com/netty/netty) | `4.2.7.Final` | `4.2.9.Final` | | [io.netty:netty-codec](https://github.com/netty/netty) | `4.2.7.Final` | `4.2.9.Final` | Updates `org.testcontainers:testcontainers-localstack` from 2.0.2 to 2.0.3 - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/2.0.2...2.0.3) Updates `io.netty:netty-buffer` from 4.2.7.Final to 4.2.9.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.7.Final...netty-4.2.9.Final) Updates `io.netty:netty-transport` from 4.2.7.Final to 4.2.9.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.7.Final...netty-4.2.9.Final) Updates `io.netty:netty-transport-native-unix-common` from 4.2.7.Final to 4.2.9.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.7.Final...netty-4.2.9.Final) Updates `io.netty:netty-common` from 4.2.7.Final to 4.2.9.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.7.Final...netty-4.2.9.Final) Updates `io.netty:netty-handler` from 4.2.7.Final to 4.2.9.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.7.Final...netty-4.2.9.Final) Updates `io.netty:netty-codec` from 4.2.7.Final to 4.2.9.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.7.Final...netty-4.2.9.Final) --- updated-dependencies: - dependency-name: org.testcontainers:testcontainers-localstack dependency-version: 2.0.3 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-buffer dependency-version: 4.2.9.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-transport dependency-version: 4.2.9.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-transport-native-unix-common dependency-version: 4.2.9.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-common dependency-version: 4.2.9.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-handler dependency-version: 4.2.9.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-codec dependency-version: 4.2.9.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index bf0d1b8800e..8d72f1f7f21 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -857,7 +857,7 @@ org.testcontainers testcontainers-localstack - 2.0.2 + 2.0.3 test @@ -877,32 +877,32 @@ io.netty netty-buffer - 4.2.7.Final + 4.2.9.Final io.netty netty-transport - 4.2.7.Final + 4.2.9.Final io.netty netty-transport-native-unix-common - 4.2.7.Final + 4.2.9.Final io.netty netty-common - 4.2.7.Final + 4.2.9.Final io.netty netty-handler - 4.2.7.Final + 4.2.9.Final io.netty netty-codec - 4.2.7.Final + 4.2.9.Final org.apache.velocity From f728ae00a31c624c6e231b9942f03adb6e6a71df Mon Sep 17 00:00:00 2001 From: Paurikova2 Date: Thu, 11 Dec 2025 13:40:42 +0100 Subject: [PATCH 796/939] added test for getVetsion, fix null pointer exception (cherry picked from commit 8a18ca6a79bd2433734088d00ff03170ba38ee59) --- .../versioning/VersionHistoryServiceImpl.java | 3 --- .../org/dspace/content/VersioningTest.java | 21 +++++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/versioning/VersionHistoryServiceImpl.java b/dspace-api/src/main/java/org/dspace/versioning/VersionHistoryServiceImpl.java index 96c39ac3a8e..493861df1c6 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/VersionHistoryServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/versioning/VersionHistoryServiceImpl.java @@ -109,9 +109,6 @@ public Version getVersion(Context context, VersionHistory versionHistory, Item i throws SQLException { Version v = versioningService.getVersion(context, item); if (v != null) { - ; - } - { if (versionHistory.equals(v.getVersionHistory())) { return v; } diff --git a/dspace-api/src/test/java/org/dspace/content/VersioningTest.java b/dspace-api/src/test/java/org/dspace/content/VersioningTest.java index c23fe783196..3723bae2e32 100644 --- a/dspace-api/src/test/java/org/dspace/content/VersioningTest.java +++ b/dspace-api/src/test/java/org/dspace/content/VersioningTest.java @@ -170,4 +170,25 @@ public void testVersionDelete() throws Exception { assertThat("Test_version_handle_delete", handleService.resolveToObject(context, handle), nullValue()); context.restoreAuthSystemState(); } + + @Test + public void testGetVersionWithNullPointerException() throws Exception { + context.turnOffAuthorisationSystem(); + + // Create item without version + Community community = communityService.create(null, context); + Collection col = collectionService.create(context, community); + WorkspaceItem is = workspaceItemService.create(context, col, false); + Item itemWithoutVersion = installItemService.installItem(context, is); + + VersionHistory versionHistory = versionHistoryService.findByItem(context, originalItem); + + try { + Version result = versionHistoryService.getVersion(context, versionHistory, itemWithoutVersion); + assertThat("getVersion should return null for item without version", result, nullValue()); + } catch (NullPointerException npe) { + fail("NullPointerException should not be thrown. Method should return null gracefully: " + npe.getMessage()); + } + context.restoreAuthSystemState(); + } } From 9cc2e58d90f749b08cb610a278c9a963a0ec6970 Mon Sep 17 00:00:00 2001 From: Paurikova2 Date: Thu, 11 Dec 2025 13:51:33 +0100 Subject: [PATCH 797/939] fix info message (cherry picked from commit 14d4f4fe9beca9faeb43671e899e24efeb0d4710) --- dspace-api/src/test/java/org/dspace/content/VersioningTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/test/java/org/dspace/content/VersioningTest.java b/dspace-api/src/test/java/org/dspace/content/VersioningTest.java index 3723bae2e32..fb971dab79f 100644 --- a/dspace-api/src/test/java/org/dspace/content/VersioningTest.java +++ b/dspace-api/src/test/java/org/dspace/content/VersioningTest.java @@ -187,7 +187,7 @@ public void testGetVersionWithNullPointerException() throws Exception { Version result = versionHistoryService.getVersion(context, versionHistory, itemWithoutVersion); assertThat("getVersion should return null for item without version", result, nullValue()); } catch (NullPointerException npe) { - fail("NullPointerException should not be thrown. Method should return null gracefully: " + npe.getMessage()); + fail("NullPointerException should not be thrown. Method should return null: " + npe.getMessage()); } context.restoreAuthSystemState(); } From 65d7c61febdec1cbbbeec4556fe0762a4e2d43a5 Mon Sep 17 00:00:00 2001 From: Paurikova2 Date: Thu, 11 Dec 2025 14:11:27 +0100 Subject: [PATCH 798/939] added restoreAuthSystemState to finally block (cherry picked from commit f22766a500553bfc87efe3ec2693a2421d4a6a3e) --- .../src/test/java/org/dspace/content/VersioningTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/test/java/org/dspace/content/VersioningTest.java b/dspace-api/src/test/java/org/dspace/content/VersioningTest.java index fb971dab79f..5045412969a 100644 --- a/dspace-api/src/test/java/org/dspace/content/VersioningTest.java +++ b/dspace-api/src/test/java/org/dspace/content/VersioningTest.java @@ -188,7 +188,8 @@ public void testGetVersionWithNullPointerException() throws Exception { assertThat("getVersion should return null for item without version", result, nullValue()); } catch (NullPointerException npe) { fail("NullPointerException should not be thrown. Method should return null: " + npe.getMessage()); + } finally { + context.restoreAuthSystemState(); } - context.restoreAuthSystemState(); } } From 198f2d6d1e29c5fa4e3c27abc10268eb9cea7bd2 Mon Sep 17 00:00:00 2001 From: Paurikova2 Date: Thu, 11 Dec 2025 15:08:56 +0100 Subject: [PATCH 799/939] removed empty lines because of the trailing whitespace (cherry picked from commit 396e8437f8f8b6347729d29a5b0a5d711f20f565) --- .../src/test/java/org/dspace/content/VersioningTest.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/content/VersioningTest.java b/dspace-api/src/test/java/org/dspace/content/VersioningTest.java index 5045412969a..a0a8b82ec15 100644 --- a/dspace-api/src/test/java/org/dspace/content/VersioningTest.java +++ b/dspace-api/src/test/java/org/dspace/content/VersioningTest.java @@ -174,15 +174,12 @@ public void testVersionDelete() throws Exception { @Test public void testGetVersionWithNullPointerException() throws Exception { context.turnOffAuthorisationSystem(); - // Create item without version Community community = communityService.create(null, context); Collection col = collectionService.create(context, community); WorkspaceItem is = workspaceItemService.create(context, col, false); Item itemWithoutVersion = installItemService.installItem(context, is); - VersionHistory versionHistory = versionHistoryService.findByItem(context, originalItem); - try { Version result = versionHistoryService.getVersion(context, versionHistory, itemWithoutVersion); assertThat("getVersion should return null for item without version", result, nullValue()); From 66b4d01c8618a55922f1bc49337e2eb795ae06f7 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 11 Feb 2026 21:47:25 +0100 Subject: [PATCH 800/939] Type date browse startsWith functionality - input date as start date, not date filter (#10056) * 121661: [Issue#10055] startsWith on date browse should return items starting from that date, not with that exact year https://github.com/DSpace/DSpace/issues/10055 * 121661: [Issue#10055, PR#10056] Use upperBound year when sorting by startsWith date descending, so a year implies => up to end of year, month implied => up to end of month --- .../java/org/dspace/browse/BrowseDAO.java | 2 + .../java/org/dspace/browse/BrowseEngine.java | 32 +++++-- .../java/org/dspace/browse/SolrBrowseDAO.java | 31 +++++++ .../app/rest/BrowsesResourceControllerIT.java | 85 ++++++++++++++++--- 4 files changed, 131 insertions(+), 19 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/browse/BrowseDAO.java b/dspace-api/src/main/java/org/dspace/browse/BrowseDAO.java index 03130e39e78..26983303a15 100644 --- a/dspace-api/src/main/java/org/dspace/browse/BrowseDAO.java +++ b/dspace-api/src/main/java/org/dspace/browse/BrowseDAO.java @@ -396,4 +396,6 @@ public interface BrowseDAO { public void setStartsWith(String startsWith); public String getStartsWith(); + + public void setDateStartsWith(String dateStartsWith); } diff --git a/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java b/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java index be7a34086a4..f7f5f2ff7df 100644 --- a/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java +++ b/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java @@ -203,12 +203,8 @@ private BrowseInfo browseByItem(BrowserScope bs) // get the table name that we are going to be getting our data from dao.setTable(browseIndex.getTableName()); - if (scope.getBrowseIndex() != null && OrderFormat.TITLE.equals(scope.getBrowseIndex().getDataType())) { - // For browsing by title, apply the same normalization applied to indexed titles - dao.setStartsWith(normalizeJumpToValue(scope.getStartsWith())); - } else { - dao.setStartsWith(StringUtils.lowerCase(scope.getStartsWith())); - } + // Set startsWith or dateStartsWith params on SolrBrowseDAO + addStartsWithParams(bs); // tell the browse query whether we are ascending or descending on the value dao.setAscending(scope.isAscending()); @@ -367,6 +363,30 @@ private BrowseInfo browseByItem(BrowserScope bs) } } + private void addStartsWithParams(BrowserScope bs) throws BrowseException { + if (StringUtils.isNotBlank(scope.getStartsWith())) { + boolean isDateBrowse = bs.getSortOption().getType().equals("date"); + if (!isDateBrowse) { + if (scope.getBrowseIndex() != null + && OrderFormat.TITLE.equals(scope.getBrowseIndex().getDataType())) { + // For browsing by title, apply the same normalization applied to indexed titles + dao.setStartsWith(normalizeJumpToValue(scope.getStartsWith())); + } else { + dao.setStartsWith(StringUtils.lowerCase(scope.getStartsWith())); + } + // clear the old date starts with + dao.setDateStartsWith(null); + } else { + // For "date" sort browses ({@code webui.itemlist.sort-option.*} config): + // sets a date specific filter where the startsWith query is the start date, + // eg `fq=bi_sort_*_sort:+["1940-02" TO + ]` + dao.setDateStartsWith(scope.getStartsWith().trim()); + // clear the old non date starts with + dao.setStartsWith(null); + } + } + } + /** * Browse the archive by single values (such as the name of an author). This * produces a BrowseInfo object that contains Strings as the results of diff --git a/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java b/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java index a0a7725fa13..32841e6c4b3 100644 --- a/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java +++ b/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java @@ -11,6 +11,7 @@ import static org.dspace.discovery.SearchUtils.RESOURCE_TYPE_FIELD; import java.io.Serializable; +import java.time.YearMonth; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -99,6 +100,8 @@ public int compare(Object o1, Object o2) { private String startsWith = null; + private String dateStartsWith = null; + /** * field to look for value in */ @@ -221,10 +224,33 @@ private DiscoverResult getSolrResponse() throws BrowseException { } else if (valuePartial) { query.addFilterQueries("{!field f=" + facetField + "_partial}" + value); } + if (StringUtils.isNotBlank(startsWith) && orderField != null) { query.addFilterQueries( "bi_" + orderField + "_sort:" + ClientUtils.escapeQueryChars(startsWith) + "*"); } + if (StringUtils.isNotBlank(dateStartsWith)) { + if (!ascending) { + String raw = dateStartsWith.trim(); + String upperBound; + if (raw.length() == 4) { // YYYY + upperBound = raw + "-12-31"; + } else if (raw.length() == 7) { // YYYY-MM + YearMonth ym = YearMonth.parse(raw); + upperBound = ym.atEndOfMonth().toString(); + } else { // YYYY-MM-DD + upperBound = raw; + } + query.addFilterQueries("bi_" + orderField + "_sort" + ": [* TO \"" + upperBound + "\"]"); + } else { + query.addFilterQueries("bi_" + orderField + "_sort" + ": [\"" + dateStartsWith + "\" TO *]"); + } + } + if (StringUtils.isNotBlank(startsWith) && StringUtils.isNotBlank(dateStartsWith)) { + log.warn(String.format("dateStartsWith %s and startsWith %s both given, only one should " + + "be given since different type of sort filterquery applied depending on which is not blank", + dateStartsWith, startsWith)); + } // filter on item to be sure to don't include any other object // indexed in the Discovery Search core query.addFilterQueries("search.resourcetype:" + IndexableItem.TYPE); @@ -466,6 +492,11 @@ public int getLimit() { return limit; } + @Override + public void setDateStartsWith(String dateStartsWith) { + this.dateStartsWith = dateStartsWith; + } + /* * (non-Javadoc) * diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java index 6b8113e5afa..0b9e355747b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java @@ -1883,8 +1883,8 @@ public void testBrowseByItemsStartsWith() throws Exception { // ---- BROWSES BY ITEM ---- //** WHEN ** //An anonymous user browses the items in the Browse by date issued endpoint - //with startsWith set to 199 - getClient().perform(get("/api/discover/browses/dateissued/items?startsWith=199") + //with startsWith set to 1990 + getClient().perform(get("/api/discover/browses/dateissued/items?startsWith=1990") .param("size", "2")) //** THEN ** @@ -1893,8 +1893,8 @@ public void testBrowseByItemsStartsWith() throws Exception { //We expect the content type to be "application/hal+json;charset=UTF-8" .andExpect(content().contentType(contentType)) - //We expect the totalElements to be the 2 items present in the repository - .andExpect(jsonPath("$.page.totalElements", is(2))) + //We expect the totalElements to be the 5 items from 1990 til now + .andExpect(jsonPath("$.page.totalElements", is(5))) //We expect to jump to page 1 of the index .andExpect(jsonPath("$.page.number", is(0))) .andExpect(jsonPath("$.page.size", is(2))) @@ -2055,8 +2055,8 @@ public void testBrowseByStartsWithAndPage() throws Exception { //** WHEN ** //An anonymous user browses the items in the Browse by date issued endpoint - //with startsWith set to 199 and Page to 1 - getClient().perform(get("/api/discover/browses/dateissued/items?startsWith=199") + //with startsWith set to 1990 and Page to 1 + getClient().perform(get("/api/discover/browses/dateissued/items?startsWith=1990") .param("size", "1").param("page", "1")) //** THEN ** @@ -2065,17 +2065,76 @@ public void testBrowseByStartsWithAndPage() throws Exception { //We expect the content type to be "application/hal+json;charset=UTF-8" .andExpect(content().contentType(contentType)) - //We expect the totalElements to be the 2 items present in the repository - .andExpect(jsonPath("$.page.totalElements", is(2))) + //We expect the totalElements to be the 5 items present in the repository from 1990 until now + .andExpect(jsonPath("$.page.totalElements", is(5))) //We expect to jump to page 1 of the index .andExpect(jsonPath("$.page.number", is(1))) .andExpect(jsonPath("$.page.size", is(1))) - .andExpect(jsonPath("$._links.self.href", containsString("startsWith=199"))) + .andExpect(jsonPath("$._links.self.href", containsString("startsWith=1990"))) - //Verify that the index jumps to the "Java" item. - .andExpect(jsonPath("$._embedded.items", - contains( - ItemMatcher.matchItemWithTitleAndDateIssued(item3, "Java", "1995-05-23") + //Verify that the returned item is 2nd (page 0 first item, page 1 second item) item from 1990 + // Items: Alan Turing - 1912; Blade Runner - 1982-06-25 || Python - 1990; + // Java - 1995-05-23; Zeta Reticuli - 2018-01-01; Moon - 2018-01-02; T-800 - 2029 + // 2nd since 1990: Java + .andExpect(jsonPath("$._embedded.items", + contains(ItemMatcher.matchItemWithTitleAndDateIssued(item3, + "Java", "1995-05-23") + ))); + + getClient().perform(get("/api/discover/browses/dateissued/items?startsWith=1990") + .param("size", "2").param("page", "1")) + //Verify that the returned item is 3rd&4th item from 1990 + // Items: Alan Turing - 1912; Blade Runner - 1982-06-25 || Python - 1990; + // Java - 1995-05-23; Zeta Reticuli - 2018-01-01; Moon - 2018-01-02; T-800 - 2029 + // => Zeta Reticuli & Moon + .andExpect(jsonPath("$._embedded.items", + contains(ItemMatcher.matchItemWithTitleAndDateIssued(item7, + "Zeta Reticuli", "2018-01-01"), + ItemMatcher.matchItemWithTitleAndDateIssued(item4, + "Moon", "2018-01-02") + ))); + + // Sort descending + getClient().perform(get("/api/discover/browses/dateissued/items?startsWith=1990&sort=default,DESC") + .param("size", "2").param("page", "0")) + //Verify that the returned items are from 1990 and below dates + // Items: Alan Turing - 1912; Blade Runner - 1982-06-25 || Python - 1990; + // Java - 1995-05-23; Zeta Reticuli - 2018-01-01; Moon - 2018-01-02; T-800 - 2029 + // => Python & Blade Runner + .andExpect(jsonPath("$._embedded.items", + contains(ItemMatcher.matchItemWithTitleAndDateIssued(item5, + "Python", "1990"), + ItemMatcher.matchItemWithTitleAndDateIssued(item2, + "Blade Runner", "1982-06-25") + ))); + + getClient().perform(get("/api/discover/browses/dateissued/items?startsWith=1990&sort=default,DESC") + .param("size", "1").param("page", "0")) + //Verify that the returned item is the one closest to 1990 but below its upperBound (1990-12-31) + .andExpect(jsonPath("$._embedded.items", + contains(ItemMatcher.matchItemWithTitleAndDateIssued(item5, + "Python", "1990") + ))); + + getClient().perform(get("/api/discover/browses/dateissued/items?startsWith=1990&sort=default,DESC") + .param("size", "3").param("page", "0")) + //Verify that the 3 returned items are from 1990 and below dates, + // with closest to upperBound 1990-12-31 as first + .andExpect(jsonPath("$._embedded.items", + contains(ItemMatcher.matchItemWithTitleAndDateIssued(item5, + "Python", "1990"), + ItemMatcher.matchItemWithTitleAndDateIssued(item2, + "Blade Runner", "1982-06-25"), + ItemMatcher.matchItemWithTitleAndDateIssued(item1, + "Alan Turing", "1912-06-23") + ))); + + getClient().perform(get("/api/discover/browses/dateissued/items?startsWith=1982-06&sort=default,DESC") + .param("size", "1").param("page", "0")) + //Verify that the returned item is the one closest to 1982-06 but below its upperBound (1982-06-30) + .andExpect(jsonPath("$._embedded.items", + contains(ItemMatcher.matchItemWithTitleAndDateIssued(item2, + "Blade Runner", "1982-06-25") ))); } From 490f907419a602b47cc151a2e2b225dfcf9ec947 Mon Sep 17 00:00:00 2001 From: Jesiel Viana Date: Mon, 9 Feb 2026 14:52:24 -0300 Subject: [PATCH 801/939] return 404 instead of 401 for deleted bitstreams --- .../java/org/dspace/app/rest/BitstreamRestController.java | 2 +- .../security/AuthorizeServicePermissionEvaluatorPlugin.java | 5 +++++ .../BitstreamMetadataReadPermissionEvaluatorPlugin.java | 3 +++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java index 1ff9f7520c1..57fc73dfc6f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java @@ -110,7 +110,7 @@ public ResponseEntity retrieve(@PathVariable UUID uuid, HttpServletResponse resp Bitstream bit = bitstreamService.find(context, uuid); EPerson currentUser = context.getCurrentUser(); - if (bit == null) { + if (bit == null || bit.isDeleted()) { response.sendError(HttpServletResponse.SC_NOT_FOUND); return null; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AuthorizeServicePermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AuthorizeServicePermissionEvaluatorPlugin.java index f0b44187c59..7e87a1a613e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AuthorizeServicePermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AuthorizeServicePermissionEvaluatorPlugin.java @@ -13,6 +13,7 @@ import org.dspace.app.rest.utils.ContextUtil; import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Bitstream; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.factory.ContentServiceFactory; @@ -86,6 +87,10 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t return true; } + if (dSpaceObject instanceof Bitstream && ((Bitstream) dSpaceObject).isDeleted()) { + return true; // Let downstream REST layer handle with 404 + } + if (dSpaceObject instanceof Item) { Item item = (Item) dSpaceObject; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/BitstreamMetadataReadPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/BitstreamMetadataReadPermissionEvaluatorPlugin.java index b4b08c668b0..d15412cd201 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/BitstreamMetadataReadPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/BitstreamMetadataReadPermissionEvaluatorPlugin.java @@ -73,6 +73,9 @@ public boolean hasPermission(Authentication authentication, Serializable targetI } public boolean metadataReadPermissionOnBitstream(Context context, Bitstream bitstream) throws SQLException { + if (bitstream.isDeleted()) { + return true; // Let downstream REST layer handle with 404 + } if (authorizeService.isAdmin(context, bitstream)) { // Is Admin on bitstream return true; From 6a781f636f1344f246d18886ed76997489ba8cc4 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 13 Feb 2026 09:49:22 -0600 Subject: [PATCH 802/939] Fix broken tests due to random order execution. When CreateMissingIdentifiersIT is run before WorkflowCurationIT, it was causing the latter to fail. (cherry picked from commit 816dfbe473effcdc4b6df210e79d3be6d6c7d3bb) --- .../org/dspace/ctask/general/CreateMissingIdentifiersIT.java | 2 ++ .../test/java/org/dspace/workflow/WorkflowCurationIT.java | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java b/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java index 3b50258a5a2..8ecddc6cd50 100644 --- a/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java +++ b/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java @@ -45,6 +45,7 @@ public void testPerform() // Must remove any cached named plugins before creating a new one CoreServiceFactory.getInstance().getPluginService().clearNamedPluginClasses(); // Define a new task dynamically + String[] prevTaskDef = configurationService.getArrayProperty(P_TASK_DEF); configurationService.setProperty(P_TASK_DEF, CreateMissingIdentifiers.class.getCanonicalName() + " = " + TASK_NAME); @@ -82,5 +83,6 @@ public void testPerform() curator.curate(context, item); int status = curator.getStatus(TASK_NAME); assertEquals("Curation should succeed", Curator.CURATE_SUCCESS, status); + configurationService.setProperty(P_TASK_DEF, prevTaskDef); } } diff --git a/dspace-api/src/test/java/org/dspace/workflow/WorkflowCurationIT.java b/dspace-api/src/test/java/org/dspace/workflow/WorkflowCurationIT.java index 66dd2cee807..dfe61a30b2b 100644 --- a/dspace-api/src/test/java/org/dspace/workflow/WorkflowCurationIT.java +++ b/dspace-api/src/test/java/org/dspace/workflow/WorkflowCurationIT.java @@ -22,6 +22,7 @@ import org.dspace.content.Community; import org.dspace.content.MetadataValue; import org.dspace.content.service.ItemService; +import org.dspace.core.LegacyPluginServiceImpl; import org.dspace.ctask.testing.MarkerTask; import org.dspace.eperson.EPerson; import org.dspace.util.DSpaceConfigurationInitializer; @@ -29,6 +30,7 @@ import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; @@ -46,6 +48,8 @@ public class WorkflowCurationIT extends AbstractIntegrationTestWithDatabase { @Inject private ItemService itemService; + @Autowired + private LegacyPluginServiceImpl legacyPluginService; /** * Basic smoke test of a curation task attached to a workflow step. @@ -56,6 +60,7 @@ public class WorkflowCurationIT public void curationTest() throws Exception { context.turnOffAuthorisationSystem(); + legacyPluginService.clearNamedPluginClasses(); //** GIVEN ** From fe222ad17679e64aadad73f202a2716319c08950 Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Sat, 29 Nov 2025 22:10:34 +0100 Subject: [PATCH 803/939] Update community indexing to include all ancestor communities, not just direct parents --- .../dspace/discovery/indexobject/CommunityIndexFactoryImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/CommunityIndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/CommunityIndexFactoryImpl.java index e9281960183..9f4b558cce7 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/CommunityIndexFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/CommunityIndexFactoryImpl.java @@ -126,7 +126,7 @@ public List getLocations(Context context, IndexableCommunity indexableDS final Community target = indexableDSpaceObject.getIndexedObject(); List locations = new ArrayList<>(); // build list of community ids - List communities = target.getParentCommunities(); + List communities = communityService.getAllParents(context, target); // now put those into strings for (Community community : communities) { From 892d61137419073b63de379fe6804ecc4d95a2e7 Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Sat, 29 Nov 2025 22:14:56 +0100 Subject: [PATCH 804/939] Replace separate community/collection admin checks with unified isComColAdmin() call --- .../org/dspace/discovery/SolrServicePrivateItemPlugin.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServicePrivateItemPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServicePrivateItemPlugin.java index db543141e13..aab1176d5af 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServicePrivateItemPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServicePrivateItemPlugin.java @@ -45,9 +45,8 @@ public void additionalSearchParameters(Context context, DiscoverQuery discoveryQ solrQuery.addFilterQuery("NOT(discoverable:false)"); return; } - if (!authorizeService.isCommunityAdmin(context) && !authorizeService.isCollectionAdmin(context)) { + if (!authorizeService.isComColAdmin(context)) { solrQuery.addFilterQuery("NOT(discoverable:false)"); - } } catch (SQLException ex) { log.error(LogHelper.getHeader(context, "Error looking up authorization rights of current user", From 7427e4c1de2ae8f4cfb2f988f670b9b92a5b9b09 Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Sat, 29 Nov 2025 22:26:09 +0100 Subject: [PATCH 805/939] Refactor permission indexing to store only direct object permissions and update Solr filter query logic to resolve inherited permissions via the location ancestor field; add authorization-based endpoints for Item, Collection, and Community. --- .../authorize/AuthorizeServiceImpl.java | 146 +++++++++------ .../authorize/service/AuthorizeService.java | 58 +++++- .../dspace/content/CollectionServiceImpl.java | 34 +--- .../org/dspace/content/ItemServiceImpl.java | 28 ++- .../content/service/CollectionService.java | 21 ++- .../dspace/content/service/ItemService.java | 10 +- .../org/dspace/discovery/DiscoverQuery.java | 59 +++++++ .../org/dspace/discovery/SearchService.java | 2 + .../org/dspace/discovery/SolrServiceImpl.java | 52 ++++++ .../SolrServiceResourceRestrictionPlugin.java | 166 +++++++++++------- .../dspace/content/service/ItemServiceIT.java | 20 +-- .../authorization/impl/EditItemFeature.java | 2 +- .../authorization/impl/SubmitFeature.java | 2 +- .../repository/CollectionRestRepository.java | 39 ++-- .../repository/CommunityRestRepository.java | 24 ++- .../rest/repository/ItemRestRepository.java | 24 +++ dspace/config/spring/api/discovery.xml | 2 - 17 files changed, 485 insertions(+), 204 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java index 7198e28d03b..202d5d42aa0 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java @@ -729,15 +729,15 @@ public List getPoliciesActionFilterExceptRpType(Context c, DSpac /** * Checks that the context's current user is a community admin in the site by querying the solr database. + * This query doesn't use authorization inheritance because direct community admin is enough to perform this check. * * @param context context with the current user * @return true if the current user is a community admin in the site * false when this is not the case, or an exception occurred - * @throws java.sql.SQLException passed through. */ @Override - public boolean isCommunityAdmin(Context context) throws SQLException { - return performCheck(context, RESOURCE_TYPE_FIELD + ":" + IndexableCommunity.TYPE); + public boolean isCommunityAdmin(Context context) { + return performCheck(context, RESOURCE_TYPE_FIELD + ":" + IndexableCommunity.TYPE, false); } /** @@ -746,11 +746,10 @@ public boolean isCommunityAdmin(Context context) throws SQLException { * @param context context with the current user * @return true if the current user is a collection admin in the site * false when this is not the case, or an exception occurred - * @throws java.sql.SQLException passed through. */ @Override - public boolean isCollectionAdmin(Context context) throws SQLException { - return performCheck(context, RESOURCE_TYPE_FIELD + ":" + IndexableCollection.TYPE); + public boolean isCollectionAdmin(Context context) { + return performCheck(context, RESOURCE_TYPE_FIELD + ":" + IndexableCollection.TYPE, true); } /** @@ -759,26 +758,26 @@ public boolean isCollectionAdmin(Context context) throws SQLException { * @param context context with the current user * @return true if the current user is an item admin in the site * false when this is not the case, or an exception occurred - * @throws java.sql.SQLException passed through. */ @Override - public boolean isItemAdmin(Context context) throws SQLException { - return performCheck(context, RESOURCE_TYPE_FIELD + ":" + IndexableItem.TYPE); + public boolean isItemAdmin(Context context) { + return performCheck(context, RESOURCE_TYPE_FIELD + ":" + IndexableItem.TYPE, true); } /** * Checks that the context's current user is a community or collection admin in the site. + * This query doesn't use authorization inheritance because direct community/collection admin is enough to + * perform this check. * * @param context context with the current user * @return true if the current user is a community or collection admin in the site * false when this is not the case, or an exception occurred - * @throws java.sql.SQLException passed through. */ @Override - public boolean isComColAdmin(Context context) throws SQLException { + public boolean isComColAdmin(Context context) { return performCheck(context, "(" + RESOURCE_TYPE_FIELD + ":" + IndexableCommunity.TYPE + " OR " + - RESOURCE_TYPE_FIELD + ":" + IndexableCollection.TYPE + ")"); + RESOURCE_TYPE_FIELD + ":" + IndexableCollection.TYPE + ")", false); } /** @@ -793,11 +792,29 @@ public boolean isComColAdmin(Context context) throws SQLException { */ @Override public List findAdminAuthorizedCommunity(Context context, String query, int offset, int limit) - throws SearchServiceException, SQLException { + throws SearchServiceException { + return findAuthorizedCommunityByAction(context, query, Constants.ADMIN, offset, limit); + } + + /** + * Finds communities for which the logged in user has the rights specified by the action parameter. + * + * @param context the context whose user is checked against + * @param query the optional extra query + * @param action the action to check for + * @param offset the offset for pagination + * @param limit the amount of dso's to return + * @return a list of communities for which the logged in user has the rights specified by the action + * @throws SearchServiceException + */ + @Override + public List findAuthorizedCommunityByAction(Context context, String query, int action, int offset, + int limit) + throws SearchServiceException { List communities = new ArrayList<>(); query = formatCustomQuery(query); DiscoverResult discoverResult = getDiscoverResult(context, query + RESOURCE_TYPE_FIELD + ":" + - IndexableCommunity.TYPE, + IndexableCommunity.TYPE, action, true, offset, limit, null, null); for (IndexableObject solrCollections : discoverResult.getIndexableObjects()) { Community community = ((IndexableCommunity) solrCollections).getIndexedObject(); @@ -816,10 +833,25 @@ public List findAdminAuthorizedCommunity(Context context, String quer */ @Override public long countAdminAuthorizedCommunity(Context context, String query) - throws SearchServiceException, SQLException { + throws SearchServiceException { + return countAuthorizedCommunityByAction(context, query, Constants.ADMIN); + } + + /** + * Counts communities for which the current user has the rights specified by the action parameter. + * + * @param context context with the current user + * @param query the query for which to filter the results more + * @param action the action to check for + * @return the matching communities + * @throws SearchServiceException + */ + @Override + public long countAuthorizedCommunityByAction(Context context, String query, int action) + throws SearchServiceException { query = formatCustomQuery(query); DiscoverResult discoverResult = getDiscoverResult(context, query + RESOURCE_TYPE_FIELD + ":" + - IndexableCommunity.TYPE, + IndexableCommunity.TYPE, action, true, null, 0, null, null); return discoverResult.getTotalSearchResults(); } @@ -836,7 +868,25 @@ public long countAdminAuthorizedCommunity(Context context, String query) */ @Override public List findAdminAuthorizedCollection(Context context, String query, int offset, int limit) - throws SearchServiceException, SQLException { + throws SearchServiceException { + return findAuthorizedCollectionByAction(context, query, Constants.ADMIN, offset, limit); + } + + /** + * Finds collections for which the logged in user has the rights specified by the action parameter. + * + * @param context the context whose user is checked against + * @param query the optional extra query + * @param action the action to check for + * @param offset the offset for pagination + * @param limit the amount of dso's to return + * @return a list of collections for which the logged in user has the rights specified by the action + * @throws SearchServiceException + */ + @Override + public List findAuthorizedCollectionByAction(Context context, String query, + int action, int offset, int limit) + throws SearchServiceException { List collections = new ArrayList<>(); if (context.getCurrentUser() == null) { return collections; @@ -844,7 +894,7 @@ public List findAdminAuthorizedCollection(Context context, String qu query = formatCustomQuery(query); DiscoverResult discoverResult = getDiscoverResult(context, query + RESOURCE_TYPE_FIELD + ":" + - IndexableCollection.TYPE, + IndexableCollection.TYPE, action, true, offset, limit, CollectionService.SOLR_SORT_FIELD, SORT_ORDER.asc); for (IndexableObject solrCollections : discoverResult.getIndexableObjects()) { Collection collection = ((IndexableCollection) solrCollections).getIndexedObject(); @@ -863,31 +913,43 @@ public List findAdminAuthorizedCollection(Context context, String qu */ @Override public long countAdminAuthorizedCollection(Context context, String query) - throws SearchServiceException, SQLException { + throws SearchServiceException { + return countAuthorizedCollectionByAction(context, query, Constants.ADMIN); + } + + /** + * Counts collections for which the current user has the rights specified by the action parameter. + * + * @param context context with the current user + * @param query the query for which to filter the results more + * @param action the action to check for + * @return the matching collections + * @throws SearchServiceException + */ + @Override + public long countAuthorizedCollectionByAction(Context context, String query, int action) + throws SearchServiceException { query = formatCustomQuery(query); DiscoverResult discoverResult = getDiscoverResult(context, query + RESOURCE_TYPE_FIELD + ":" + - IndexableCollection.TYPE, - null, 0, null, null); + IndexableCollection.TYPE, action, + true, null, 0, null, null); return discoverResult.getTotalSearchResults(); } @Override public boolean isAccountManager(Context context) { - try { - return (canCommunityAdminManageAccounts() && isCommunityAdmin(context) - || canCollectionAdminManageAccounts() && isCollectionAdmin(context)); - } catch (SQLException e) { - throw new RuntimeException(e); - } + return (canCommunityAdminManageAccounts() && isCommunityAdmin(context) + || canCollectionAdminManageAccounts() && isCollectionAdmin(context)); } - private boolean performCheck(Context context, String query) throws SQLException { + private boolean performCheck(Context context, String query, boolean inheritAuthorizations) { if (context.getCurrentUser() == null) { return false; } try { - DiscoverResult discoverResult = getDiscoverResult(context, query, null, null, null, null); + DiscoverResult discoverResult = getDiscoverResult(context, query, Constants.ADMIN, inheritAuthorizations, + null, 0, null, null); if (discoverResult.getTotalSearchResults() > 0) { return true; } @@ -899,16 +961,11 @@ private boolean performCheck(Context context, String query) throws SQLException return false; } - private DiscoverResult getDiscoverResult(Context context, String query, Integer offset, Integer limit, - String sortField, SORT_ORDER sortOrder) - throws SearchServiceException, SQLException { - String groupQuery = getGroupToQuery(groupService.allMemberGroups(context, context.getCurrentUser())); + private DiscoverResult getDiscoverResult(Context context, String query, int action, boolean inheritAuthorizations, + Integer offset, Integer limit, String sortField, SORT_ORDER sortOrder) + throws SearchServiceException { DiscoverQuery discoverQuery = new DiscoverQuery(); - if (!this.isAdmin(context)) { - query = query + " AND (" + - "admin:e" + context.getCurrentUser().getID() + groupQuery + ")"; - } discoverQuery.setQuery(query); if (offset != null) { discoverQuery.setStart(offset); @@ -919,23 +976,12 @@ private DiscoverResult getDiscoverResult(Context context, String query, Integer if (sortField != null && sortOrder != null) { discoverQuery.setSortField(sortField, sortOrder); } + discoverQuery.addRequiredAuthorization(action); + discoverQuery.setInheritAuthorizations(inheritAuthorizations); return searchService.search(context, discoverQuery); } - private String getGroupToQuery(List groups) { - StringBuilder groupQuery = new StringBuilder(); - - if (groups != null) { - for (Group group: groups) { - groupQuery.append(" OR admin:g"); - groupQuery.append(group.getID()); - } - } - - return groupQuery.toString(); - } - private String formatCustomQuery(String query) { if (StringUtils.isBlank(query)) { return ""; diff --git a/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java b/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java index e0a94833d76..12fae93c3e5 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java +++ b/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java @@ -546,6 +546,20 @@ void switchPoliciesAction(Context context, DSpaceObject dso, int fromAction, int List findAdminAuthorizedCommunity(Context context, String query, int offset, int limit) throws SearchServiceException, SQLException; + /** + * Finds communities for which the logged in user has the rights specified by the action parameter. + * + * @param context the context whose user is checked against + * @param query the optional extra query + * @param action the action to check for + * @param offset the offset for pagination + * @param limit the amount of dso's to return + * @return a list of communities for which the logged in user has the rights specified by the action + * @throws SearchServiceException + */ + List findAuthorizedCommunityByAction(Context context, String query, int action, int offset, int limit) + throws SearchServiceException, SQLException; + /** * Counts communities for which the current user is admin, AND which match the query. * @@ -558,6 +572,18 @@ List findAdminAuthorizedCommunity(Context context, String query, int long countAdminAuthorizedCommunity(Context context, String query) throws SearchServiceException, SQLException; + /** + * Counts communities for which the current user has the rights specified by the action parameter. + * + * @param context context with the current user + * @param query the query for which to filter the results more + * @param action the action to check for + * @return the matching communities + * @throws SearchServiceException + */ + long countAuthorizedCommunityByAction(Context context, String query, int action) + throws SearchServiceException; + /** * Finds collections for which the current user is admin, AND which match the query. * @@ -567,10 +593,24 @@ long countAdminAuthorizedCommunity(Context context, String query) * @param limit used for pagination of the results * @return the matching collections * @throws SearchServiceException - * @throws SQLException */ List findAdminAuthorizedCollection(Context context, String query, int offset, int limit) - throws SearchServiceException, SQLException; + throws SearchServiceException; + + /** + * Finds collections for which the current user has the rights specified by the action parameter. + * + * @param context context with the current user + * @param query the query for which to filter the results more + * @param action the action to check for + * @param offset used for pagination of the results + * @param limit used for pagination of the results + * @return the matching collections + * @throws SearchServiceException + */ + List findAuthorizedCollectionByAction(Context context, String query, int action, int offset, + int limit) + throws SearchServiceException; /** * Counts collections for which the current user is admin, AND which match the query. @@ -582,7 +622,19 @@ List findAdminAuthorizedCollection(Context context, String query, in * @throws SQLException */ long countAdminAuthorizedCollection(Context context, String query) - throws SearchServiceException, SQLException; + throws SearchServiceException; + + /** + * Counts collections for which the current user has the rights specified by the action parameter. + * + * @param context context with the current user + * @param query the query for which to filter the results more + * @param action the action to check for + * @return the number of matching collections + * @throws SearchServiceException + */ + long countAuthorizedCollectionByAction(Context context, String query, int action) + throws SearchServiceException; /** * Returns true if the current user can manage accounts. diff --git a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java index beef87582ea..a22f2acccea 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -1033,7 +1033,7 @@ public String getDefaultReadGroupName(Collection collection, String typeOfGroupS @Override public List findCollectionsWithSubmit(String q, Context context, Community community, - int offset, int limit) throws SQLException, SearchServiceException { + int offset, int limit) throws SearchServiceException { List collections = new ArrayList<>(); DiscoverQuery discoverQuery = new DiscoverQuery(); @@ -1050,8 +1050,8 @@ public List findCollectionsWithSubmit(String q, Context context, Com } @Override - public int countCollectionsWithSubmit(String q, Context context, Community community) - throws SQLException, SearchServiceException { + public int countCollectionsWithSubmit(Context context, String q, Community community) + throws SearchServiceException { DiscoverQuery discoverQuery = new DiscoverQuery(); discoverQuery.setMaxResults(0); @@ -1073,29 +1073,12 @@ public int countCollectionsWithSubmit(String q, Context context, Community commu * terms. The terms are used to make also a prefix query on SOLR * so it can be used to implement an autosuggest feature over the collection name * @return discovery search result objects - * @throws SQLException if something goes wrong * @throws SearchServiceException if search error */ private DiscoverResult retrieveCollectionsWithSubmit(Context context, DiscoverQuery discoverQuery, String entityType, Community community, String q) - throws SQLException, SearchServiceException { - - StringBuilder query = new StringBuilder(); - EPerson currentUser = context.getCurrentUser(); - if (!authorizeService.isAdmin(context)) { - String userId = ""; - if (currentUser != null) { - userId = currentUser.getID().toString(); - } - query.append("submit:(e").append(userId); + throws SearchServiceException { - Set groups = groupService.allMemberGroupsSet(context, currentUser); - for (Group group : groups) { - query.append(" OR g").append(group.getID()); - } - query.append(")"); - discoverQuery.addFilterQueries(query.toString()); - } if (Objects.nonNull(community)) { discoverQuery.addFilterQueries("location.comm:" + community.getID().toString()); } @@ -1109,13 +1092,14 @@ private DiscoverResult retrieveCollectionsWithSubmit(Context context, DiscoverQu .append(escapedQuery).append("*").append(")"); discoverQuery.setQuery(buildQuery.toString()); } + discoverQuery.addRequiredAuthorization(Constants.ADD); DiscoverResult resp = searchService.search(context, discoverQuery); return resp; } @Override - public List findCollectionsWithSubmit(String q, Context context, Community community, String entityType, - int offset, int limit) throws SQLException, SearchServiceException { + public List findCollectionsWithSubmit(Context context, String q, Community community, String entityType, + int offset, int limit) throws SearchServiceException { List collections = new ArrayList<>(); DiscoverQuery discoverQuery = new DiscoverQuery(); discoverQuery.setDSpaceObjectFilter(IndexableCollection.TYPE); @@ -1132,8 +1116,8 @@ public List findCollectionsWithSubmit(String q, Context context, Com } @Override - public int countCollectionsWithSubmit(String q, Context context, Community community, String entityType) - throws SQLException, SearchServiceException { + public int countCollectionsWithSubmit(Context context, String q, Community community, String entityType) + throws SearchServiceException { DiscoverQuery discoverQuery = new DiscoverQuery(); discoverQuery.setMaxResults(0); discoverQuery.setDSpaceObjectFilter(IndexableCollection.TYPE); diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java index 744666baff9..825b452d425 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -22,7 +22,6 @@ import java.util.UUID; import java.util.function.Supplier; import java.util.stream.Collectors; -import java.util.stream.Stream; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; @@ -1254,43 +1253,38 @@ public boolean canEdit(Context context, Item item) throws SQLException { * * @param context DSpace context * @param discoverQuery + * @param q query string * @return discovery search result objects - * @throws SQLException if something goes wrong * @throws SearchServiceException if search error */ - private DiscoverResult retrieveItemsWithEdit(Context context, DiscoverQuery discoverQuery) - throws SQLException, SearchServiceException { - EPerson currentUser = context.getCurrentUser(); - if (!authorizeService.isAdmin(context)) { - String userId = currentUser != null ? "e" + currentUser.getID().toString() : "e"; - Stream groupIds = groupService.allMemberGroupsSet(context, currentUser).stream() - .map(group -> "g" + group.getID()); - String query = Stream.concat(Stream.of(userId), groupIds) - .collect(Collectors.joining(" OR ", "edit:(", ")")); - discoverQuery.addFilterQueries(query); + private DiscoverResult retrieveItemsWithEdit(Context context, DiscoverQuery discoverQuery, String q) + throws SearchServiceException { + if (StringUtils.isNotBlank(q)) { + discoverQuery.setQuery(q); } + discoverQuery.addRequiredAuthorization(Constants.WRITE); return searchService.search(context, discoverQuery); } @Override - public List findItemsWithEdit(Context context, int offset, int limit) - throws SQLException, SearchServiceException { + public List findItemsWithEdit(Context context, String q, int offset, int limit) + throws SearchServiceException { DiscoverQuery discoverQuery = new DiscoverQuery(); discoverQuery.setDSpaceObjectFilter(IndexableItem.TYPE); discoverQuery.setStart(offset); discoverQuery.setMaxResults(limit); - DiscoverResult resp = retrieveItemsWithEdit(context, discoverQuery); + DiscoverResult resp = retrieveItemsWithEdit(context, discoverQuery, q); return resp.getIndexableObjects().stream() .map(solrItems -> ((IndexableItem) solrItems).getIndexedObject()) .collect(Collectors.toList()); } @Override - public int countItemsWithEdit(Context context) throws SQLException, SearchServiceException { + public int countItemsWithEdit(Context context, String q) throws SearchServiceException { DiscoverQuery discoverQuery = new DiscoverQuery(); discoverQuery.setMaxResults(0); discoverQuery.setDSpaceObjectFilter(IndexableItem.TYPE); - DiscoverResult resp = retrieveItemsWithEdit(context, discoverQuery); + DiscoverResult resp = retrieveItemsWithEdit(context, discoverQuery, q); return (int) resp.getTotalSearchResults(); } diff --git a/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java b/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java index 5e55c725691..80ca415ef22 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java @@ -391,11 +391,11 @@ Group createDefaultReadGroup(Context context, Collection collection, String type * NOTE: for better performance, this method retrieves its results from an * index (cache) and does not query the database directly. * This means that results may be stale or outdated until https://github.com/DSpace/DSpace/issues/2853 is resolved" - * + * + * @param context DSpace Context * @param q limit the returned collection to those with metadata values matching the query terms. * The terms are used to make also a prefix query on SOLR so it can be used to implement * an autosuggest feature over the collection name - * @param context DSpace Context * @param community parent community * @param entityType limit the returned collection to those related to given entity type * @param offset the position of the first result to return @@ -404,7 +404,7 @@ Group createDefaultReadGroup(Context context, Collection collection, String type * @throws SQLException if something goes wrong * @throws SearchServiceException if search error */ - public List findCollectionsWithSubmit(String q, Context context, Community community, + public List findCollectionsWithSubmit(Context context, String q, Community community, String entityType, int offset, int limit) throws SQLException, SearchServiceException; /** @@ -422,11 +422,10 @@ public List findCollectionsWithSubmit(String q, Context context, Com * @param offset the position of the first result to return * @param limit paging limit * @return discovery search result objects - * @throws SQLException if something goes wrong * @throws SearchServiceException if search error */ public List findCollectionsWithSubmit(String q, Context context, Community community, - int offset, int limit) throws SQLException, SearchServiceException; + int offset, int limit) throws SearchServiceException; /** * Counts the number of Collection for which the current user has 'submit' privileges. @@ -434,17 +433,17 @@ public List findCollectionsWithSubmit(String q, Context context, Com * and does not query the database directly. * This means that results may be stale or outdated until * https://github.com/DSpace/DSpace/issues/2853 is resolved." - * + * + * @param context DSpace Context * @param q limit the returned collection to those with metadata values matching the query terms. * The terms are used to make also a prefix query on SOLR so it can be used to implement * an autosuggest feature over the collection name - * @param context DSpace Context * @param community parent community * @return total collections found * @throws SQLException if something goes wrong * @throws SearchServiceException if search error */ - public int countCollectionsWithSubmit(String q, Context context, Community community) + public int countCollectionsWithSubmit(Context context, String q, Community community) throws SQLException, SearchServiceException; /** @@ -453,18 +452,18 @@ public int countCollectionsWithSubmit(String q, Context context, Community commu * and does not query the database directly. * This means that results may be stale or outdated until * https://github.com/DSpace/DSpace/issues/2853 is resolved." - * + * + * @param context DSpace Context * @param q limit the returned collection to those with metadata values matching the query terms. * The terms are used to make also a prefix query on SOLR so it can be used to implement * an autosuggest feature over the collection name - * @param context DSpace Context * @param community parent community * @param entityType limit the returned collection to those related to given entity type * @return total collections found * @throws SQLException if something goes wrong * @throws SearchServiceException if search error */ - public int countCollectionsWithSubmit(String q, Context context, Community community, String entityType) + public int countCollectionsWithSubmit(Context context, String q, Community community, String entityType) throws SQLException, SearchServiceException; /** diff --git a/dspace-api/src/main/java/org/dspace/content/service/ItemService.java b/dspace-api/src/main/java/org/dspace/content/service/ItemService.java index a307e7ceffa..35c01e74ade 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/ItemService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/ItemService.java @@ -889,23 +889,23 @@ public Iterator findByLastModifiedSince(Context context, Date last) /** * finds all items for which the current user has editing rights * @param context DSpace context object + * @param q search query * @param offset page offset * @param limit page size limit * @return list of items for which the current user has editing rights - * @throws SQLException * @throws SearchServiceException */ - public List findItemsWithEdit(Context context, int offset, int limit) - throws SQLException, SearchServiceException; + List findItemsWithEdit(Context context, String q, int offset, int limit) + throws SearchServiceException; /** * counts all items for which the current user has editing rights * @param context DSpace context object + * @param q search query * @return list of items for which the current user has editing rights - * @throws SQLException * @throws SearchServiceException */ - public int countItemsWithEdit(Context context) throws SQLException, SearchServiceException; + int countItemsWithEdit(Context context, String q) throws SearchServiceException; /** * Check if the supplied item is an inprogress submission diff --git a/dspace-api/src/main/java/org/dspace/discovery/DiscoverQuery.java b/dspace-api/src/main/java/org/dspace/discovery/DiscoverQuery.java index e133ad0ed17..185c768a057 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/DiscoverQuery.java +++ b/dspace-api/src/main/java/org/dspace/discovery/DiscoverQuery.java @@ -72,6 +72,14 @@ public enum SORT_ORDER { private String discoveryConfigurationName; + /** + * The required authorizations user should have for the objects returned by the query. + * The READ authorization (Constants.READ) is always required and does not need to be added here. + */ + private List requiredAuthorization; + + private boolean inheritAuthorizations = true; + public DiscoverQuery() { //Initialize all our lists this.filterQueries = new ArrayList<>(); @@ -83,6 +91,7 @@ public DiscoverQuery() { this.hitHighlighting = new HashMap<>(); //Use a linked hashmap since sometimes insertion order might matter this.properties = new LinkedHashMap<>(); + this.requiredAuthorization = new ArrayList<>(); } @@ -411,4 +420,54 @@ public String getDiscoveryConfigurationName() { public void setDiscoveryConfigurationName(String discoveryConfigurationName) { this.discoveryConfigurationName = discoveryConfigurationName; } + + /** + * Return the required authorization user should have for the objects returned by this query + * + * @return the required authorizations + */ + public List getRequiredAuthorizations() { + return requiredAuthorization; + } + + /** + * Add a required authorization user should have for the objects returned by this query. + * The READ authorization (Constants.READ) is always required and does not need to be added here. + * + * @param action + * the required action + */ + public void addRequiredAuthorization(int action) { + this.requiredAuthorization.add(action); + } + + /** + * Remove a required authorization user should have for the objects returned by this query + * + * @param authorizationAction + * the required action + */ + public void removeRequiredAuthorization(int authorizationAction) { + this.requiredAuthorization.removeIf(action -> action == authorizationAction); + } + + /** + * Return whether authorizations should be inherited from parent objects + * + * @return true if authorizations should be inherited, false otherwise + */ + public boolean isInheritAuthorizationsEnabled() { + return inheritAuthorizations; + } + + /** + * Set whether authorizations should be inherited from parent objects + * + * @param inheritAuthorizations + * true if authorizations should be inherited, false otherwise + */ + public void setInheritAuthorizations(boolean inheritAuthorizations) { + this.inheritAuthorizations = inheritAuthorizations; + } + } diff --git a/dspace-api/src/main/java/org/dspace/discovery/SearchService.java b/dspace-api/src/main/java/org/dspace/discovery/SearchService.java index cb945648e7c..7f4c85c3a86 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SearchService.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SearchService.java @@ -90,6 +90,8 @@ DiscoverFilterQuery toFilterQuery(Context context, String field, String operator List getRelatedItems(Context context, Item item, DiscoveryMoreLikeThisConfiguration moreLikeThisConfiguration); + String createLocationQueryForAdministrableDSOs(String epersonAndGroupClause); + /** * Method to create a Query that includes all * communities and collections a user may administrate. diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java index b832921ad87..acfd37ef5e6 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java @@ -597,6 +597,58 @@ protected boolean requiresIndexing(String uniqueId, Date lastModified) return reindexItem || !inIndex; } + /** + * Retrieves from Solr the list of administrable communities and collections for the + * current user based on a clause containing the e-person and group IDs. + * Builds and returns the "location" query part for these DSO's. + * + * @param epersonAndGroupClause A Solr filter clause containing one or more IDs combined with OR, + * e.g. {@code "eUUIDe1 OR gUUIDg2 OR gUUIDg3 OR ..."}. + * + * @return An empty string if no administrable DSO exists, or a string in the form + * {@code "location:(mUUID1 OR lUUID2 ... )"} when there are administrable DSO's. + */ + @Override + public String createLocationQueryForAdministrableDSOs(String epersonAndGroupClause) { + StringBuilder locationQuery = new StringBuilder(); + try { + + SolrQuery solrQuery = new SolrQuery(); + + String query = "*:*"; + solrQuery.setQuery(query); + solrQuery.addField(SearchUtils.RESOURCE_ID_FIELD); + solrQuery.addField(SearchUtils.RESOURCE_TYPE_FIELD); + solrQuery.addFilterQuery("(" + SearchUtils.RESOURCE_TYPE_FIELD + ":" + IndexableCommunity.TYPE + " OR " + + SearchUtils.RESOURCE_TYPE_FIELD + ":" + IndexableCollection.TYPE + ")"); + solrQuery.addFilterQuery("admin:(" + epersonAndGroupClause + ")"); + solrQuery.setRows(Integer.MAX_VALUE); + + QueryResponse solrQueryResponse = solrSearchCore.getSolr().query(solrQuery, + solrSearchCore.REQUEST_METHOD); + if (solrQueryResponse != null) { + List containerUUIDs = new ArrayList<>(); + for (SolrDocument doc : solrQueryResponse.getResults()) { + String type = (String) doc.getFieldValue(SearchUtils.RESOURCE_TYPE_FIELD); + String uniqueID = (String) doc.getFieldValue(SearchUtils.RESOURCE_ID_FIELD); + if (IndexableCommunity.TYPE.equals(type)) { + containerUUIDs.add("m" + uniqueID); + } else if (IndexableCollection.TYPE.equals(type)) { + containerUUIDs.add("l" + uniqueID); + } + } + if (!containerUUIDs.isEmpty()) { + locationQuery.append("location:("); + locationQuery.append(String.join(" OR ", containerUUIDs)); + return locationQuery.append(")").toString(); + } + } + } catch (Exception e) { + log.error("Failed to retrieve administrable communities and collections from Solr:", e); + } + return ""; + } + @Override public String createLocationQueryForAdministrableItems(Context context) throws SQLException { diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceResourceRestrictionPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceResourceRestrictionPlugin.java index d19616a85e1..db5b03e24a3 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceResourceRestrictionPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceResourceRestrictionPlugin.java @@ -18,12 +18,8 @@ import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.AuthorizeService; import org.dspace.authorize.service.ResourcePolicyService; -import org.dspace.content.Collection; -import org.dspace.content.Community; import org.dspace.content.DSpaceObject; import org.dspace.content.InProgressSubmission; -import org.dspace.content.Item; -import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; import org.dspace.core.Constants; @@ -36,14 +32,14 @@ import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.eperson.service.GroupService; -import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.xmlworkflow.storedcomponents.ClaimedTask; import org.dspace.xmlworkflow.storedcomponents.PoolTask; import org.springframework.beans.factory.annotation.Autowired; /** * Restriction plugin that ensures that indexes all the resource policies. - * When a search is performed extra filter queries are added to retrieve only results to which the user has READ access + * When a search is performed extra filter queries are added to retrieve only results to which the user has the + * required authorization. * * @author Kevin Van de Velde (kevin at atmire dot com) * @author Mark Diggory (markd at atmire dot com) @@ -64,6 +60,8 @@ public class SolrServiceResourceRestrictionPlugin implements SolrServiceIndexPlu protected GroupService groupService; @Autowired(required = true) protected ResourcePolicyService resourcePolicyService; + @Autowired + protected SearchService searchService; @Override public void additionalIndex(Context context, IndexableObject idxObj, SolrInputDocument document) { @@ -83,50 +81,32 @@ public void additionalIndex(Context context, IndexableObject idxObj, SolrInputDo } if (dso != null) { try { - List policies = authorizeService.getPoliciesActionFilter(context, dso, Constants.READ); - for (ResourcePolicy resourcePolicy : policies) { - if (resourcePolicyService.isDateValid(resourcePolicy)) { - String fieldValue; - if (resourcePolicy.getGroup() != null) { - //We have a group add it to the value - fieldValue = "g" + resourcePolicy.getGroup().getID(); - } else { - //We have an eperson add it to the value - fieldValue = "e" + resourcePolicy.getEPerson().getID(); - - } - - document.addField("read", fieldValue); - } - - //remove the policy from the cache to save memory - context.uncacheEntity(resourcePolicy); - } - // also index ADMIN policies as ADMIN permissions provides READ access - // going up through the hierarchy for communities, collections and items - while (dso != null) { - if (dso instanceof Community || dso instanceof Collection || dso instanceof Item) { - List policiesAdmin = authorizeService - .getPoliciesActionFilter(context, dso, Constants.ADMIN); - for (ResourcePolicy resourcePolicy : policiesAdmin) { - if (resourcePolicyService.isDateValid(resourcePolicy)) { - String fieldValue; - if (resourcePolicy.getGroup() != null) { - // We have a group add it to the value - fieldValue = "g" + resourcePolicy.getGroup().getID(); - } else { - // We have an eperson add it to the value - fieldValue = "e" + resourcePolicy.getEPerson().getID(); - } - document.addField("read", fieldValue); - document.addField("admin", fieldValue); + // Index read, submit, edit and admin permissions + int[] actionsToIndex = new int[] { Constants.READ, Constants.WRITE, Constants.ADD, Constants.ADMIN }; + + for (int action : actionsToIndex) { + String indexedActionName = getIndexedActionName(action); + List policies = authorizeService.getPoliciesActionFilter(context, dso, action); + for (ResourcePolicy resourcePolicy : policies) { + if (resourcePolicyService.isDateValid(resourcePolicy)) { + String fieldValue; + // Avoid NPE in cases where the policy does not have group or eperson + if (resourcePolicy.getGroup() == null && resourcePolicy.getEPerson() == null) { + continue; } - - // remove the policy from the cache to save memory - context.uncacheEntity(resourcePolicy); + if (resourcePolicy.getGroup() != null) { + //We have a group add it to the value + fieldValue = "g" + resourcePolicy.getGroup().getID(); + } else { + //We have an eperson add it to the value + fieldValue = "e" + resourcePolicy.getEPerson().getID(); + } + document.addField(indexedActionName, fieldValue); } + + //remove the policy from the cache to save memory + context.uncacheEntity(resourcePolicy); } - dso = ContentServiceFactory.getInstance().getDSpaceObjectService(dso).getParentObject(context, dso); } } catch (SQLException e) { log.error(LogHelper.getHeader(context, "Error while indexing resource policies", @@ -140,36 +120,66 @@ public void additionalIndex(Context context, IndexableObject idxObj, SolrInputDo public void additionalSearchParameters(Context context, DiscoverQuery discoveryQuery, SolrQuery solrQuery) { try { if (!authorizeService.isAdmin(context)) { - StringBuilder resourceQuery = new StringBuilder(); - //Always add the anonymous group id to the query - Group anonymousGroup = groupService.findByName(context, Group.ANONYMOUS); - String anonGroupId = ""; - if (anonymousGroup != null) { - anonGroupId = anonymousGroup.getID().toString(); - } - resourceQuery.append("read:(g" + anonGroupId); + EPerson currentUser = context.getCurrentUser(); + StringBuilder epersonAndGroupClause = new StringBuilder(); if (currentUser != null) { - resourceQuery.append(" OR e").append(currentUser.getID()); + epersonAndGroupClause.append("e").append(currentUser.getID()); } - //Retrieve all the groups the current user is a member of ! Set groups = groupService.allMemberGroupsSet(context, currentUser); for (Group group : groups) { - resourceQuery.append(" OR g").append(group.getID()); + if (epersonAndGroupClause.length() > 0) { + epersonAndGroupClause.append(" OR g").append(group.getID()); + } else { + epersonAndGroupClause.append("g").append(group.getID()); + } } - resourceQuery.append(")"); + StringBuilder resourceQuery = new StringBuilder(); - String locations = DSpaceServicesFactory.getInstance() - .getServiceManager() - .getServiceByName(SearchService.class.getName(), - SearchService.class) - .createLocationQueryForAdministrableItems(context); + List actions = discoveryQuery.getRequiredAuthorizations(); + /* + * The `actions` list specifies the permissions required beyond the default "read" permission. + * It should not include "read" because checking for "read" is always implicit. + * + * The query is constructed as follows: + * - If no actions are provided, it checks only for "read" or "admin" permissions. + * - If "admin" is in the `actions` list, it checks only for admin permissions. + * - Otherwise, it checks for both "read" and the other specified actions. + * + * The resulting query follows this structure: (read AND action) OR admin. + */ + if (actions.isEmpty()) { + // If no actions are included, we only check for read permissions + resourceQuery.append("(read:(").append(epersonAndGroupClause).append("))").append( " OR ") + .append("admin:(").append(epersonAndGroupClause).append(")"); + } else if (actions.contains(Constants.ADMIN)) { + // If the actions array contains the admin action, we only check for admin permissions + resourceQuery.append("admin:(").append(epersonAndGroupClause).append(")"); + } else { + // If the actions array contains other actions, we check for read permissions and the actions passed + resourceQuery.append("(read:(").append(epersonAndGroupClause).append(")"); + for (int action : actions) { + String actionName = getIndexedActionName(action); + resourceQuery.append(" AND ").append(actionName).append(":(").append(epersonAndGroupClause) + .append(")"); + } + resourceQuery.append(")"); + resourceQuery.append(" OR ").append("admin:(") + .append(epersonAndGroupClause).append(")"); + } - if (StringUtils.isNotBlank(locations)) { - resourceQuery.append(" OR "); - resourceQuery.append(locations); + // Add to the query the locations the user has administrative rights on to cover the cases of + // inherited permissions only if the inherit authorizations flag is enabled + if (discoveryQuery.isInheritAuthorizationsEnabled()) { + String locations = searchService + .createLocationQueryForAdministrableDSOs(epersonAndGroupClause.toString()); + + if (StringUtils.isNotBlank(locations)) { + resourceQuery.append(" OR "); + resourceQuery.append(locations); + } } solrQuery.addFilterQuery(resourceQuery.toString()); @@ -178,4 +188,26 @@ public void additionalSearchParameters(Context context, DiscoverQuery discoveryQ log.error(LogHelper.getHeader(context, "Error while adding resource policy information to query", ""), e); } } + + /** + * Get the action name used for solr indexing for the given action id + * + * @param action action id + * @return solr action name used for indexing + */ + private String getIndexedActionName(int action) { + + switch (action) { + case Constants.READ: + return "read"; + case Constants.WRITE: + return "edit"; + case Constants.ADD: + return "submit"; + case Constants.ADMIN: + return "admin"; + default: + return Constants.actionText[action].toLowerCase(); + } + } } diff --git a/dspace-api/src/test/java/org/dspace/content/service/ItemServiceIT.java b/dspace-api/src/test/java/org/dspace/content/service/ItemServiceIT.java index d31acd85451..bc4e4359dfd 100644 --- a/dspace-api/src/test/java/org/dspace/content/service/ItemServiceIT.java +++ b/dspace-api/src/test/java/org/dspace/content/service/ItemServiceIT.java @@ -669,8 +669,8 @@ public void testDeleteItemWithMultipleVersions() throws Exception { @Test public void testFindItemsWithEditNoRights() throws Exception { context.setCurrentUser(eperson); - List result = itemService.findItemsWithEdit(context, 0, 10); - int count = itemService.countItemsWithEdit(context); + List result = itemService.findItemsWithEdit(context, "", 0, 10); + int count = itemService.countItemsWithEdit(context, ""); assertThat(result.size(), equalTo(0)); assertThat(count, equalTo(0)); } @@ -682,8 +682,8 @@ public void testFindAndCountItemsWithEditEPerson() throws Exception { .withAction(Constants.WRITE) .build(); context.setCurrentUser(eperson); - List result = itemService.findItemsWithEdit(context, 0, 10); - int count = itemService.countItemsWithEdit(context); + List result = itemService.findItemsWithEdit(context, "", 0, 10); + int count = itemService.countItemsWithEdit(context, ""); assertThat(result.size(), equalTo(1)); assertThat(count, equalTo(1)); } @@ -695,8 +695,8 @@ public void testFindAndCountItemsWithAdminEPerson() throws Exception { .withAction(Constants.ADMIN) .build(); context.setCurrentUser(eperson); - List result = itemService.findItemsWithEdit(context, 0, 10); - int count = itemService.countItemsWithEdit(context); + List result = itemService.findItemsWithEdit(context, "", 0, 10); + int count = itemService.countItemsWithEdit(context, ""); assertThat(result.size(), equalTo(1)); assertThat(count, equalTo(1)); } @@ -714,8 +714,8 @@ public void testFindAndCountItemsWithEditGroup() throws Exception { .withAction(Constants.WRITE) .build(); context.setCurrentUser(eperson); - List result = itemService.findItemsWithEdit(context, 0, 10); - int count = itemService.countItemsWithEdit(context); + List result = itemService.findItemsWithEdit(context, "", 0, 10); + int count = itemService.countItemsWithEdit(context, ""); assertThat(result.size(), equalTo(1)); assertThat(count, equalTo(1)); } @@ -733,8 +733,8 @@ public void testFindAndCountItemsWithAdminGroup() throws Exception { .withAction(Constants.ADMIN) .build(); context.setCurrentUser(eperson); - List result = itemService.findItemsWithEdit(context, 0, 10); - int count = itemService.countItemsWithEdit(context); + List result = itemService.findItemsWithEdit(context, "", 0, 10); + int count = itemService.countItemsWithEdit(context, ""); assertThat(result.size(), equalTo(1)); assertThat(count, equalTo(1)); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/EditItemFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/EditItemFeature.java index 5c605daaf40..d938cd5a335 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/EditItemFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/EditItemFeature.java @@ -40,7 +40,7 @@ public class EditItemFeature implements AuthorizationFeature { @Override public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException, SearchServiceException { if (object instanceof SiteRest) { - return itemService.countItemsWithEdit(context) > 0; + return itemService.countItemsWithEdit(context, "") > 0; } else if (object instanceof ItemRest) { Item item = (Item) utils.getDSpaceAPIObjectFromRest(context, object); return authService.authorizeActionBoolean(context, item, Constants.WRITE); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/SubmitFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/SubmitFeature.java index 3793928fb0f..599bdb64117 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/SubmitFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/SubmitFeature.java @@ -43,7 +43,7 @@ public class SubmitFeature implements AuthorizationFeature { public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException, SearchServiceException { if (object instanceof SiteRest) { // Check whether the user has permission to add to any collection - return collectionService.countCollectionsWithSubmit("", context, null) > 0; + return collectionService.countCollectionsWithSubmit(context, "", null) > 0; } else if (object instanceof CollectionRest) { // Check whether the user has permission to add to the given collection Collection collection = (Collection) utils.getDSpaceAPIObjectFromRest(context, object); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java index ba3163a4447..ebfbbdb3132 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java @@ -185,7 +185,7 @@ public Page findSubmitAuthorizedByCommunity( List collections = cs.findCollectionsWithSubmit(q, context, com, Math.toIntExact(pageable.getOffset()), Math.toIntExact(pageable.getPageSize())); - int tot = cs.countCollectionsWithSubmit(q, context, com); + int tot = cs.countCollectionsWithSubmit(context, q, com); return converter.toRestPage(collections, pageable, tot , utils.obtainProjection()); } catch (SQLException | SearchServiceException e) { throw new RuntimeException(e.getMessage(), e); @@ -200,7 +200,7 @@ public Page findSubmitAuthorized(@Parameter(value = "query") Str List collections = cs.findCollectionsWithSubmit(q, context, null, Math.toIntExact(pageable.getOffset()), Math.toIntExact(pageable.getPageSize())); - int tot = cs.countCollectionsWithSubmit(q, context, null); + int tot = cs.countCollectionsWithSubmit(context, q, null); return converter.toRestPage(collections, pageable, tot, utils.obtainProjection()); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); @@ -211,14 +211,33 @@ public Page findSubmitAuthorized(@Parameter(value = "query") Str @SearchRestMethod(name = "findAdminAuthorized") public Page findAdminAuthorized ( Pageable pageable, @Parameter(value = "query") String query) { + return findAuthorized(pageable, Constants.ADMIN, query); + } + + /** + * Returns Collections for which the current user has 'edit' privileges. + * + * @param pageable The pagination information + * @param query The query used in the lookup + * @return + */ + @PreAuthorize("hasAuthority('AUTHENTICATED')") + @SearchRestMethod(name = "findEditAuthorized") + public Page findEditAuthorized ( + Pageable pageable, @Parameter(value = "query") String query) { + return findAuthorized(pageable, Constants.WRITE, query); + } + + private Page findAuthorized(Pageable pageable, int action, String query) { try { Context context = obtainContext(); - List collections = authorizeService.findAdminAuthorizedCollection(context, query, + List collections = authorizeService.findAuthorizedCollectionByAction(context, query, + action, Math.toIntExact(pageable.getOffset()), Math.toIntExact(pageable.getPageSize())); - long tot = authorizeService.countAdminAuthorizedCollection(context, query); - return converter.toRestPage(collections, pageable, tot , utils.obtainProjection()); - } catch (SearchServiceException | SQLException e) { + long tot = authorizeService.countAuthorizedCollectionByAction(context, query, action); + return converter.toRestPage(collections, pageable, tot, utils.obtainProjection()); + } catch (SearchServiceException e) { throw new RuntimeException(e.getMessage(), e); } } @@ -245,10 +264,10 @@ public Page findSubmitAuthorizedByEntityType( if (entityType == null) { throw new ResourceNotFoundException("There was no entityType found with label: " + entityTypeLabel); } - List collections = cs.findCollectionsWithSubmit(query, context, null, entityTypeLabel, + List collections = cs.findCollectionsWithSubmit(context, query,null, entityTypeLabel, Math.toIntExact(pageable.getOffset()), Math.toIntExact(pageable.getPageSize())); - int tot = cs.countCollectionsWithSubmit(query, context, null, entityTypeLabel); + int tot = cs.countCollectionsWithSubmit(context, query,null, entityTypeLabel); return converter.toRestPage(collections, pageable, tot, utils.obtainProjection()); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); @@ -282,10 +301,10 @@ public Page findSubmitAuthorizedByCommunityAndEntityType( throw new ResourceNotFoundException( CommunityRest.CATEGORY + "." + CommunityRest.NAME + " with id: " + communityUuid + " not found"); } - List collections = cs.findCollectionsWithSubmit(query, context, community, entityTypeLabel, + List collections = cs.findCollectionsWithSubmit(context, query, community, entityTypeLabel, Math.toIntExact(pageable.getOffset()), Math.toIntExact(pageable.getPageSize())); - int total = cs.countCollectionsWithSubmit(query, context, community, entityTypeLabel); + int total = cs.countCollectionsWithSubmit(context, query, community, entityTypeLabel); return converter.toRestPage(collections, pageable, total, utils.obtainProjection()); } catch (SQLException | SearchServiceException e) { throw new RuntimeException(e.getMessage(), e); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java index 927b6a0b98f..cacda5ade7b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java @@ -38,6 +38,7 @@ import org.dspace.content.Community; import org.dspace.content.service.BitstreamService; import org.dspace.content.service.CommunityService; +import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.discovery.DiscoverQuery; import org.dspace.discovery.DiscoverResult; @@ -220,12 +221,31 @@ public Page findAllTop(Pageable pageable) { @SearchRestMethod(name = "findAdminAuthorized") public Page findAdminAuthorized ( Pageable pageable, @Parameter(value = "query") String query) { + return findAuthorized(pageable, Constants.ADMIN, query); + } + + @PreAuthorize("hasAuthority('AUTHENTICATED')") + @SearchRestMethod(name = "findEditAuthorized") + public Page findEditAuthorized ( + Pageable pageable, @Parameter(value = "query") String query) { + return findAuthorized(pageable, Constants.WRITE, query); + } + + @PreAuthorize("hasAuthority('AUTHENTICATED')") + @SearchRestMethod(name = "findAddAuthorized") + public Page findAddAuthorized ( + Pageable pageable, @Parameter(value = "query") String query) { + return findAuthorized(pageable, Constants.ADD, query); + } + + private Page findAuthorized(Pageable pageable, int action, String query) { try { Context context = obtainContext(); - List communities = authorizeService.findAdminAuthorizedCommunity(context, query, + List communities = authorizeService.findAuthorizedCommunityByAction(context, query, + action, Math.toIntExact(pageable.getOffset()), Math.toIntExact(pageable.getPageSize())); - long tot = authorizeService.countAdminAuthorizedCommunity(context, query); + long tot = authorizeService.countAuthorizedCommunityByAction(context, query, action); return converter.toRestPage(communities, pageable, tot , utils.obtainProjection()); } catch (SearchServiceException | SQLException e) { throw new RuntimeException(e.getMessage(), e); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java index 16ce8629d17..55ce0ccde01 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java @@ -22,6 +22,8 @@ import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dspace.app.rest.Parameter; +import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.converter.MetadataConverter; import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; @@ -45,6 +47,7 @@ import org.dspace.content.service.RelationshipTypeService; import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Context; +import org.dspace.discovery.SearchServiceException; import org.dspace.util.UUIDUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -357,6 +360,27 @@ public Bundle addBundleToItem(Context context, Item item, BundleRest bundleRest) return bundle; } + /** + * Method to find the items for which the current user has editing rights. + * + * @param query Query string + * @param pageable Pagination information + * @return Page of Items (REST representation) for which the current user has editing rights + * @throws SearchServiceException + */ + @PreAuthorize("hasAuthority('AUTHENTICATED')") + @SearchRestMethod(name = "findEditAuthorized") + public Page findEditAuthorized(@Parameter(value = "query") String query, + Pageable pageable) + throws SearchServiceException { + Context context = obtainContext(); + List items = itemService.findItemsWithEdit(context, query, + Math.toIntExact(pageable.getOffset()), + Math.toIntExact(pageable.getPageSize())); + int tot = itemService.countItemsWithEdit(context, query); + return converter.toRestPage(items, pageable, tot, utils.obtainProjection()); + } + @Override protected ItemRest createAndReturn(Context context, List stringList) throws AuthorizeException, SQLException, RepositoryMethodNotImplementedException { diff --git a/dspace/config/spring/api/discovery.xml b/dspace/config/spring/api/discovery.xml index c5486ad84e6..d84da23a8d6 100644 --- a/dspace/config/spring/api/discovery.xml +++ b/dspace/config/spring/api/discovery.xml @@ -30,8 +30,6 @@ - - From c8b88884b60bca781e2ba0f8663e1d9ab4e1d0c2 Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Sat, 29 Nov 2025 22:29:51 +0100 Subject: [PATCH 806/939] Introduce integration tests for the new authorization-based endpoints for Item, Collection, and Community --- .../app/rest/CollectionRestRepositoryIT.java | 362 +++++++++++++ .../app/rest/CommunityRestRepositoryIT.java | 481 ++++++++++++++++++ .../dspace/app/rest/ItemRestRepositoryIT.java | 408 +++++++++++++++ 3 files changed, 1251 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java index 1e18723ca90..26eda3b06c9 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java @@ -56,6 +56,7 @@ import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.test.MetadataPatchSuite; +import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.AuthorizeService; import org.dspace.authorize.service.ResourcePolicyService; import org.dspace.builder.CollectionBuilder; @@ -3314,6 +3315,109 @@ public void addColAdminGroupToCheckReindexingTest() throws Exception { .andExpect(jsonPath("$.page.totalElements", is(1))); } + @Test + public void addParentComAdminGroupToCheckAdminPropagationTest() throws Exception { + addParentComAdminGroupToCheckGenericPropagationTest("findAdminAuthorized"); + } + + @Test + public void addParentComAdminGroupToCheckEditPropagationTest() throws Exception { + addParentComAdminGroupToCheckGenericPropagationTest("findEditAuthorized"); + } + + public void addParentComAdminGroupToCheckGenericPropagationTest(String method) throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("MyTest") + .build(); + + context.restoreAuthSystemState(); + + String epersonToken = getAuthToken(eperson.getEmail(), password); + getClient(epersonToken).perform(get("/api/core/collections/search/" + method) + .param("query", "MyTest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded").doesNotExist()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + AtomicReference idRef = new AtomicReference<>(); + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(post("/api/core/communities/" + parentCommunity.getID() + "/adminGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRef.set( + UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))) + ); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(post("/api/eperson/groups/" + idRef.get() + "/epersons") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + eperson.getID() + )); + + getClient(epersonToken).perform(get("/api/core/collections/search/" + method) + .param("query", "MyTest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.collections", Matchers.contains(CollectionMatcher + .matchProperties(col1.getName(), col1.getID(), col1.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + } + + @Test + public void removeParentComAdminPolicyToCheckAdminPropagationTest() throws Exception { + removeParentComAdminPolicyToCheckGenericPropagationTest("findAdminAuthorized"); + } + + @Test + public void removeParentComAdminPolicyToCheckEditPropagationTest() throws Exception { + removeParentComAdminPolicyToCheckGenericPropagationTest("findEditAuthorized"); + } + + public void removeParentComAdminPolicyToCheckGenericPropagationTest(String method) throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + ResourcePolicy policy = ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) + .withDspaceObject(parentCommunity).withAction(Constants.ADMIN) + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("MyTest") + .build(); + + context.restoreAuthSystemState(); + + String epersonToken = getAuthToken(eperson.getEmail(), password); + getClient(epersonToken).perform(get("/api/core/collections/search/" + method) + .param("query", "MyTest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.collections", Matchers.contains(CollectionMatcher + .matchProperties(col1.getName(), col1.getID(), col1.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(delete("/api/authz/resourcepolicies/" + policy.getID())) + .andExpect(status().is(204)); + + getClient(epersonToken).perform(get("/api/core/collections/search/" + method) + .param("query", "MyTest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded").doesNotExist()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + } + @Test public void findAuthorizedCollectionsByEntityType() throws Exception { context.turnOffAuthorisationSystem(); @@ -3652,4 +3756,262 @@ public void findSubmitAuthorizedByCommunityAndEntityTypeNotFoundTest() throws Ex .andExpect(status().isNotFound()); } + @Test + public void findEditAuthorizedUnauthorizedTest() throws Exception { + getClient().perform(get("/api/core/collections/search/findEditAuthorized")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findEditAuthorizedResourcePolicyTest() throws Exception { + context.turnOffAuthorisationSystem(); + Community comm1 = CommunityBuilder.createCommunity(context).withName("Community 1").build(); + + EPerson hasDirectEditRights = EPersonBuilder.createEPerson(context) + .withEmail("has@editrights.com").withPassword(password) + .build(); + EPerson hasDirectAdminRights = EPersonBuilder.createEPerson(context) + .withEmail("has@adminrights.com").withPassword(password) + .build(); + Collection byResourcePolicy = CollectionBuilder.createCollection(context, comm1) + .withName("direct edit rights for eperson") + .build(); + Collection uneditable = CollectionBuilder.createCollection(context, comm1) + .withName("uneditable collection") + .build(); + ResourcePolicy policy = ResourcePolicyBuilder.createResourcePolicy(context, hasDirectEditRights, null) + .withDspaceObject(byResourcePolicy).withAction(WRITE) + .build(); + policy = ResourcePolicyBuilder.createResourcePolicy(context, hasDirectAdminRights, null) + .withDspaceObject(byResourcePolicy).withAction(Constants.ADMIN) + .build(); + context.restoreAuthSystemState(); + + String tokenHasDirectEditRightsToken = getAuthToken(hasDirectEditRights.getEmail(), password); + String tokenHasDirectAdminRightsToken = getAuthToken(hasDirectAdminRights.getEmail(), password); + + getClient(tokenHasDirectEditRightsToken).perform(get("/api/core/collections/search/findEditAuthorized")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.collections", + Matchers.contains(CollectionMatcher.matchCollection(byResourcePolicy)))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + getClient(tokenHasDirectAdminRightsToken).perform(get("/api/core/collections/search/findEditAuthorized")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.collections", + Matchers.contains(CollectionMatcher.matchCollection(byResourcePolicy)))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + } + + @Test + public void findEditAuthorizedAdminPropagationTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + /* + DSO structure: + root + ├── subcomm1 + ├── subcomm1collA (collection) + └── subcomm2subcomm3 (community) + ├── subcomm2subcomm3collB (collection) + └── subcomm2 + └── subcomm2coll + */ + EPerson rootAdmin = EPersonBuilder.createEPerson(context) + .withEmail("root@admin.com").withPassword(password).build(); + EPerson subcomm1Admin = EPersonBuilder.createEPerson(context) + .withEmail("subcomm1@admin.com").withPassword(password).build(); + EPerson subcomm2Admin = EPersonBuilder.createEPerson(context) + .withEmail("subcomm2@admin.com").withPassword(password).build(); + EPerson subcomm1collA_Admin = EPersonBuilder.createEPerson(context) + .withEmail("subcomm1collA@admin.com").withPassword(password).build(); + EPerson subcomm1collB_Admin = EPersonBuilder.createEPerson(context) + .withEmail("subcomm1collB@admin.com").withPassword(password).build(); + EPerson subcomm2collAdmin = EPersonBuilder.createEPerson(context) + .withEmail("subcomm2coll@admin.com").withPassword(password).build(); + + Community root = CommunityBuilder.createCommunity(context) + .withAdminGroup(rootAdmin) + .withName("root") + .build(); + Community subcomm1 = CommunityBuilder.createSubCommunity(context, root) + .withAdminGroup(subcomm1Admin) + .withName("subcomm1") + .build(); + Community subcomm1subcom3 = CommunityBuilder.createSubCommunity(context, subcomm1) + .withName("subcomm1subcom3") + .build(); + Community subcomm2 = CommunityBuilder.createSubCommunity(context, root) + .withAdminGroup(subcomm2Admin) + .withName("subcomm2") + .build(); + Collection subcomm1collA = CollectionBuilder.createCollection(context, subcomm1) + .withAdminGroup(subcomm1collA_Admin) + .withName("subcomm1collA") + .build(); + Collection subcomm2subcomm3collB = CollectionBuilder.createCollection(context, subcomm1subcom3) + .withAdminGroup(subcomm1collB_Admin) + .withName("subcomm2subcomm3collB") + .build(); + Collection subcomm2coll = CollectionBuilder.createCollection(context, subcomm2) + .withAdminGroup(subcomm2collAdmin) + .withName("subcomm2coll") + .build(); + context.restoreAuthSystemState(); + + String siteAdminToken = getAuthToken(admin.getEmail(), password); + String rootAdminToken = getAuthToken(rootAdmin.getEmail(), password); + String subcomm1AdminToken = getAuthToken(subcomm1Admin.getEmail(), password); + String subcomm2AdminToken = getAuthToken(subcomm2Admin.getEmail(), password); + + getClient(siteAdminToken).perform(get("/api/core/collections/search/findEditAuthorized")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.collections", + Matchers.containsInAnyOrder( + CollectionMatcher.matchCollection(subcomm1collA), + CollectionMatcher.matchCollection(subcomm2subcomm3collB), + CollectionMatcher.matchCollection(subcomm2coll) + ))) + .andExpect(jsonPath("$.page.totalElements", is(3))); + + getClient(rootAdminToken).perform(get("/api/core/collections/search/findEditAuthorized")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.collections", + Matchers.containsInAnyOrder( + CollectionMatcher.matchCollection(subcomm1collA), + CollectionMatcher.matchCollection(subcomm2subcomm3collB), + CollectionMatcher.matchCollection(subcomm2coll) + ))) + .andExpect(jsonPath("$.page.totalElements", is(3))); + + getClient(subcomm1AdminToken).perform(get("/api/core/collections/search/findEditAuthorized")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.collections", + Matchers.containsInAnyOrder( + CollectionMatcher.matchCollection(subcomm1collA), + CollectionMatcher.matchCollection(subcomm2subcomm3collB) + ))) + .andExpect(jsonPath("$.page.totalElements", is(2))); + + getClient(subcomm2AdminToken).perform(get("/api/core/collections/search/findEditAuthorized")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.collections", + Matchers.containsInAnyOrder( + CollectionMatcher.matchCollection(subcomm2coll) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + } + + @Test + public void findEditAuthorizedCollectionsWithQueryTest() throws Exception { + findGenericAuthorizedCollectionsWithQueryTest("findEditAuthorized"); + } + + @Test + public void findReadAuthorizedCollectionsWithQueryTest() throws Exception { + findGenericAuthorizedCollectionsWithQueryTest("findAdminAuthorized"); + } + + public void findGenericAuthorizedCollectionsWithQueryTest(String method) throws Exception { + + context.turnOffAuthorisationSystem(); + + EPerson eperson2 = EPersonBuilder.createEPerson(context) + .withEmail("eperson2@mail.com") + .withPassword(password) + .build(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Community child2 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community Two") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Sample collection") + .withAdminGroup(eperson) + .build(); + Collection col2 = CollectionBuilder.createCollection(context, child1) + .withName("Test collection") + .build(); + Collection col3 = CollectionBuilder.createCollection(context, child2) + .withName("Collection of sample items") + .withAdminGroup(eperson) + .build(); + context.restoreAuthSystemState(); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + // Test simple query matches + getClient(tokenEPerson).perform(get("/api/core/collections/search/" + method) + .param("query", "collection")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.collections", Matchers.containsInAnyOrder( + CollectionMatcher.matchProperties(col1.getName(), col1.getID(), col1.getHandle()), + CollectionMatcher.matchProperties(col3.getName(), col3.getID(), col3.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(2))); + + // Test insensitive matches + getClient(tokenEPerson).perform(get("/api/core/collections/search/" + method) + .param("query", "COLLECTION")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.collections", Matchers.containsInAnyOrder( + CollectionMatcher.matchProperties(col1.getName(), col1.getID(), col1.getHandle()), + CollectionMatcher.matchProperties(col3.getName(), col3.getID(), col3.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(2))); + + // Test collection unathorized for eperson is not returned + getClient(tokenEPerson).perform(get("/api/core/collections/search/" + method) + .param("query", "test")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + // Test eperson with no authorized collections + String tokenEPerson2 = getAuthToken(eperson2.getEmail(), password); + getClient(tokenEPerson2).perform(get("/api/core/collections/search/" + method) + .param("query", "collection")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + // Test admin gets all authorized collections + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/core/collections/search/" + method) + .param("query", "sample")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.collections", Matchers.containsInAnyOrder( + CollectionMatcher.matchProperties(col1.getName(), col1.getID(), col1.getHandle()), + CollectionMatcher.matchProperties(col3.getName(), col3.getID(), col3.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(2))); + + // Test query with unsorted query words + getClient(tokenAdmin).perform(get("/api/core/collections/search/" + method) + .param("query", "items sample")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.collections", Matchers.contains( + CollectionMatcher.matchProperties(col3.getName(), col3.getID(), col3.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + + // Test collection not authorized for eperson is returned for admin + getClient(tokenAdmin).perform(get("/api/core/collections/search/" + method) + .param("query", "test")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.collections", Matchers.containsInAnyOrder( + CollectionMatcher.matchProperties(col2.getName(), col2.getID(), col2.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java index c2b005a2ea6..6cc25f22ce9 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java @@ -50,6 +50,7 @@ import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.test.MetadataPatchSuite; +import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.AuthorizeService; import org.dspace.authorize.service.ResourcePolicyService; import org.dspace.builder.CollectionBuilder; @@ -2757,4 +2758,484 @@ public void addComAdminGroupToCheckReindexingTest() throws Exception { .andExpect(jsonPath("$.page.totalElements", is(1))); } + @Test + public void addParentComAdminGroupToCheckAdminPropagationTest() throws Exception { + addParentComAdminGroupToCheckGenericPropagationTest("findAdminAuthorized"); + } + + @Test + public void addParentComAdminGroupToCheckEditPropagationTest() throws Exception { + addParentComAdminGroupToCheckGenericPropagationTest("findEditAuthorized"); + } + + @Test + public void addParentComAdminGroupToCheckAddPropagationTest() throws Exception { + addParentComAdminGroupToCheckGenericPropagationTest("findAddAuthorized"); + } + + public void addParentComAdminGroupToCheckGenericPropagationTest(String method) throws Exception { + context.turnOffAuthorisationSystem(); + + Community rootCommunity = CommunityBuilder.createCommunity(context) + .withName("Root Community") + .build(); + + Community subCommunity = CommunityBuilder.createSubCommunity(context, rootCommunity) + .withName("MyTestCom") + .build(); + + context.restoreAuthSystemState(); + + String epersonToken = getAuthToken(eperson.getEmail(), password); + getClient(epersonToken).perform(get("/api/core/communities/search/" + method) + .param("query", "MyTestCom")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded").doesNotExist()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + AtomicReference idRef = new AtomicReference<>(); + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(post("/api/core/communities/" + rootCommunity.getID() + "/adminGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRef.set( + UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))) + ); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(post("/api/eperson/groups/" + idRef.get() + "/epersons") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + eperson.getID() + )); + + getClient(epersonToken).perform(get("/api/core/communities/search/" + method) + .param("query", "MyTestCom")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.communities", Matchers.contains(CommunityMatcher + .matchProperties(subCommunity.getName(), + subCommunity.getID(), + subCommunity.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + } + + @Test + public void removeParentComAdminPolicyToCheckAdminPropagationTest() throws Exception { + removeParentComAdminPolicyToCheckGenericPropagationTest("findAdminAuthorized"); + } + + @Test + public void removeParentComAdminPolicyToCheckEditPropagationTest() throws Exception { + removeParentComAdminPolicyToCheckGenericPropagationTest("findEditAuthorized"); + } + + @Test + public void removeParentComAdminPolicyToCheckAddPropagationTest() throws Exception { + removeParentComAdminPolicyToCheckGenericPropagationTest("findAddAuthorized"); + } + + public void removeParentComAdminPolicyToCheckGenericPropagationTest(String method) throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Root Community") + .build(); + + ResourcePolicy policy = ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) + .withDspaceObject(parentCommunity).withAction(Constants.ADMIN) + .build(); + + Community subCommunity = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("MyTestCom") + .build(); + context.restoreAuthSystemState(); + + String epersonToken = getAuthToken(eperson.getEmail(), password); + getClient(epersonToken).perform(get("/api/core/communities/search/" + method) + .param("query", "MyTestCom")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.communities", Matchers.contains(CommunityMatcher + .matchProperties(subCommunity.getName(), + subCommunity.getID(), + subCommunity.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(delete("/api/authz/resourcepolicies/" + policy.getID())) + .andExpect(status().is(204)); + + getClient(epersonToken).perform(get("/api/core/communities/search/" + method) + .param("query", "MyTestCom")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded").doesNotExist()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + } + + @Test + public void findEditAuthorizedUnauthorizedTest() throws Exception { + getClient().perform(get("/api/core/communities/search/findEditAuthorized")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findEditAuthorizedResourcePolicyTest() throws Exception { + context.turnOffAuthorisationSystem(); + Community comm1 = CommunityBuilder.createCommunity(context).withName("Community 1").build(); + + EPerson hasDirectEditRights = EPersonBuilder.createEPerson(context) + .withEmail("has@editrights.com").withPassword(password) + .build(); + EPerson hasDirectAdminRights = EPersonBuilder.createEPerson(context) + .withEmail("has@adminrights.com").withPassword(password) + .build(); + Community byResourcePolicy = CommunityBuilder.createCommunity(context) + .withName("direct edit rights for eperson").build(); + Community uneditable = CommunityBuilder.createCommunity(context) + .withName("uneditable community") + .build(); + ResourcePolicy policy = ResourcePolicyBuilder.createResourcePolicy(context, hasDirectEditRights, null) + .withDspaceObject(byResourcePolicy).withAction(Constants.WRITE) + .build(); + policy = ResourcePolicyBuilder.createResourcePolicy(context, hasDirectAdminRights, null) + .withDspaceObject(byResourcePolicy).withAction(Constants.ADMIN) + .build(); + context.restoreAuthSystemState(); + + String tokenHasDirectEditRightsToken = getAuthToken(hasDirectEditRights.getEmail(), password); + String tokenHasDirectAdminRightsToken = getAuthToken(hasDirectAdminRights.getEmail(), password); + + getClient(tokenHasDirectEditRightsToken).perform(get("/api/core/communities/search/findEditAuthorized")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.communities", + Matchers.contains(CommunityMatcher.matchCommunity(byResourcePolicy)))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + getClient(tokenHasDirectAdminRightsToken).perform(get("/api/core/communities/search/findEditAuthorized")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.communities", + Matchers.contains(CommunityMatcher.matchCommunity(byResourcePolicy)))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + } + + @Test + public void findEditAuthorizedAdminPropagationTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + /* + DSO structure: + root + └── subcomm1 + └── subcomm1subcomm2 + */ + EPerson rootAdmin = EPersonBuilder.createEPerson(context) + .withEmail("root@admin.com").withPassword(password).build(); + EPerson subcomm1Admin = EPersonBuilder.createEPerson(context) + .withEmail("subcomm1@admin.com").withPassword(password).build(); + EPerson subcomm2Admin = EPersonBuilder.createEPerson(context) + .withEmail("subcomm2@admin.com").withPassword(password).build(); + + Community root = CommunityBuilder.createCommunity(context) + .withAdminGroup(rootAdmin) + .withName("root") + .build(); + Community subcomm1 = CommunityBuilder.createSubCommunity(context, root) + .withAdminGroup(subcomm1Admin) + .withName("subcomm1") + .build(); + Community subcomm1subcomm2 = CommunityBuilder.createSubCommunity(context, subcomm1) + .withAdminGroup(subcomm2Admin) + .withName("subcomm1subcomm2") + .build(); + context.restoreAuthSystemState(); + + String siteAdminToken = getAuthToken(admin.getEmail(), password); + String rootAdminToken = getAuthToken(rootAdmin.getEmail(), password); + String subcomm1AdminToken = getAuthToken(subcomm1Admin.getEmail(), password); + String subcomm2AdminToken = getAuthToken(subcomm2Admin.getEmail(), password); + + getClient(siteAdminToken).perform(get("/api/core/communities/search/findEditAuthorized")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.communities", + Matchers.containsInAnyOrder( + CommunityMatcher.matchCommunity(root), + CommunityMatcher.matchCommunity(subcomm1), + CommunityMatcher.matchCommunity(subcomm1subcomm2) + ))) + .andExpect(jsonPath("$.page.totalElements", is(3))); + + getClient(rootAdminToken).perform(get("/api/core/communities/search/findEditAuthorized")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.communities", + Matchers.containsInAnyOrder( + CommunityMatcher.matchCommunity(root), + CommunityMatcher.matchCommunity(subcomm1), + CommunityMatcher.matchCommunity(subcomm1subcomm2) + ))) + .andExpect(jsonPath("$.page.totalElements", is(3))); + + getClient(subcomm1AdminToken).perform(get("/api/core/communities/search/findEditAuthorized")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.communities", + Matchers.containsInAnyOrder( + CommunityMatcher.matchCommunity(subcomm1), + CommunityMatcher.matchCommunity(subcomm1subcomm2) + ))) + .andExpect(jsonPath("$.page.totalElements", is(2))); + + getClient(subcomm2AdminToken).perform(get("/api/core/communities/search/findEditAuthorized")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.communities", + Matchers.containsInAnyOrder( + CommunityMatcher.matchCommunity(subcomm1subcomm2) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + + } + + @Test + public void findAddAuthorizedUnauthorizedTest() throws Exception { + getClient().perform(get("/api/core/communities/search/findAddAuthorized")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findAddAuthorizedResourcePolicyTest() throws Exception { + context.turnOffAuthorisationSystem(); + Community comm1 = CommunityBuilder.createCommunity(context).withName("Community 1").build(); + + EPerson hasDirectEditRights = EPersonBuilder.createEPerson(context) + .withEmail("has@editrights.com").withPassword(password) + .build(); + EPerson hasDirectAdminRights = EPersonBuilder.createEPerson(context) + .withEmail("has@adminrights.com").withPassword(password) + .build(); + Community byResourcePolicy = CommunityBuilder.createCommunity(context) + .withName("direct add rights for eperson").build(); + Community uneditable = CommunityBuilder.createCommunity(context) + .withName("no add community") + .build(); + ResourcePolicy policy = ResourcePolicyBuilder.createResourcePolicy(context, hasDirectEditRights, null) + .withDspaceObject(byResourcePolicy).withAction(Constants.ADD) + .build(); + policy = ResourcePolicyBuilder.createResourcePolicy(context, hasDirectAdminRights, null) + .withDspaceObject(byResourcePolicy).withAction(Constants.ADMIN) + .build(); + context.restoreAuthSystemState(); + + String tokenHasDirectAddRightsToken = getAuthToken(hasDirectEditRights.getEmail(), password); + String tokenHasDirectAdminRightsToken = getAuthToken(hasDirectAdminRights.getEmail(), password); + + getClient(tokenHasDirectAddRightsToken).perform(get("/api/core/communities/search/findAddAuthorized")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.communities", + Matchers.contains(CommunityMatcher.matchCommunity(byResourcePolicy)))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + getClient(tokenHasDirectAdminRightsToken).perform(get("/api/core/communities/search/findAddAuthorized")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.communities", + Matchers.contains(CommunityMatcher.matchCommunity(byResourcePolicy)))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + } + + @Test + public void findAddAuthorizedAdminPropagationTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + /* + DSO structure: + root + └── subcomm1 + └── subcomm2 + */ + EPerson rootAdmin = EPersonBuilder.createEPerson(context) + .withEmail("root@admin.com").withPassword(password).build(); + EPerson subcomm1Admin = EPersonBuilder.createEPerson(context) + .withEmail("subcomm1@admin.com").withPassword(password).build(); + EPerson subcomm2Admin = EPersonBuilder.createEPerson(context) + .withEmail("subcomm2@admin.com").withPassword(password).build(); + + Community root = CommunityBuilder.createCommunity(context) + .withAdminGroup(rootAdmin) + .withName("root") + .build(); + Community subcomm1 = CommunityBuilder.createSubCommunity(context, root) + .withAdminGroup(subcomm1Admin) + .withName("subcomm1") + .build(); + Community subcomm2 = CommunityBuilder.createSubCommunity(context, subcomm1) + .withAdminGroup(subcomm2Admin) + .withName("subcomm2") + .build(); + context.restoreAuthSystemState(); + + String siteAdminToken = getAuthToken(admin.getEmail(), password); + String rootAdminToken = getAuthToken(rootAdmin.getEmail(), password); + String subcomm1AdminToken = getAuthToken(subcomm1Admin.getEmail(), password); + String subcomm2AdminToken = getAuthToken(subcomm2Admin.getEmail(), password); + + getClient(siteAdminToken).perform(get("/api/core/communities/search/findAddAuthorized")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.communities", + Matchers.containsInAnyOrder( + CommunityMatcher.matchCommunity(root), + CommunityMatcher.matchCommunity(subcomm1), + CommunityMatcher.matchCommunity(subcomm2) + ))) + .andExpect(jsonPath("$.page.totalElements", is(3))); + + getClient(rootAdminToken).perform(get("/api/core/communities/search/findAddAuthorized")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.communities", + Matchers.containsInAnyOrder( + CommunityMatcher.matchCommunity(root), + CommunityMatcher.matchCommunity(subcomm1), + CommunityMatcher.matchCommunity(subcomm2) + ))) + .andExpect(jsonPath("$.page.totalElements", is(3))); + + getClient(subcomm1AdminToken).perform(get("/api/core/communities/search/findAddAuthorized")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.communities", + Matchers.containsInAnyOrder( + CommunityMatcher.matchCommunity(subcomm1), + CommunityMatcher.matchCommunity(subcomm2) + ))) + .andExpect(jsonPath("$.page.totalElements", is(2))); + + getClient(subcomm2AdminToken).perform(get("/api/core/communities/search/findAddAuthorized")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.communities", + Matchers.containsInAnyOrder( + CommunityMatcher.matchCommunity(subcomm2) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + } + + @Test + public void findEditAuthorizedCommunitiesWithQueryTest() throws Exception { + findGenericAuthorizedCommunitiesWithQueryTest("findEditAuthorized"); + } + + @Test + public void findAddAuthorizedCommunitiesWithQueryTest() throws Exception { + findGenericAuthorizedCommunitiesWithQueryTest("findAddAuthorized"); + } + + @Test + public void findReadAuthorizedCommunitiesWithQueryTest() throws Exception { + findGenericAuthorizedCommunitiesWithQueryTest("findAdminAuthorized"); + } + + public void findGenericAuthorizedCommunitiesWithQueryTest(String method) throws Exception { + + context.turnOffAuthorisationSystem(); + + EPerson eperson2 = EPersonBuilder.createEPerson(context) + .withEmail("eperson2@mail.com") + .withPassword(password) + .build(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Community child2 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community Two") + .build(); + Community com1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sample community") + .withAdminGroup(eperson) + .build(); + Community com2 = CommunityBuilder.createSubCommunity(context, child1) + .withName("Test community") + .build(); + Community com3 = CommunityBuilder.createSubCommunity(context, child2) + .withName("community of sample items") + .withAdminGroup(eperson) + .build(); + context.restoreAuthSystemState(); + + // Test simple query + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(get("/api/core/communities/search/" + method) + .param("query", "community")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.communities", Matchers.containsInAnyOrder( + CommunityMatcher.matchProperties(com1.getName(), com1.getID(), com1.getHandle()), + CommunityMatcher.matchProperties(com3.getName(), com3.getID(), com3.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(2))); + + // Test case insensitive query + getClient(tokenEPerson).perform(get("/api/core/communities/search/" + method) + .param("query", "COMMUNITY")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.communities", Matchers.containsInAnyOrder( + CommunityMatcher.matchProperties(com1.getName(), com1.getID(), com1.getHandle()), + CommunityMatcher.matchProperties(com3.getName(), com3.getID(), com3.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(2))); + + // Test word for unauthorized community + getClient(tokenEPerson).perform(get("/api/core/communities/search/" + method) + .param("query", "test")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + // Test eperson with no authorized communities + String tokenEPerson2 = getAuthToken(eperson2.getEmail(), password); + getClient(tokenEPerson2).perform(get("/api/core/communities/search/" + method) + .param("query", "community")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + // Test as admin + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/core/communities/search/" + method) + .param("query", "sample")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.communities", Matchers.containsInAnyOrder( + CommunityMatcher.matchProperties(com1.getName(), com1.getID(), com1.getHandle()), + CommunityMatcher.matchProperties(com3.getName(), com3.getID(), com3.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(2))); + + // Test more specific unsorted query words + getClient(tokenAdmin).perform(get("/api/core/communities/search/" + method) + .param("query", "items sample")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.communities", Matchers.contains( + CommunityMatcher.matchProperties(com3.getName(), com3.getID(), com3.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + + // Test add retrieve the col not authorized to community admin user + getClient(tokenAdmin).perform(get("/api/core/communities/search/" + method) + .param("query", "test")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.communities", Matchers.containsInAnyOrder( + CommunityMatcher.matchProperties(com2.getName(), com2.getID(), com2.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java index 48e70a8d5f1..a77938eeb32 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java @@ -26,6 +26,8 @@ import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; +import static org.springframework.data.rest.webmvc.RestMediaTypes.TEXT_URI_LIST_VALUE; +import static org.springframework.http.MediaType.parseMediaType; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; @@ -54,6 +56,7 @@ import org.dspace.app.rest.matcher.CollectionMatcher; import org.dspace.app.rest.matcher.HalMatcher; import org.dspace.app.rest.matcher.ItemMatcher; +import org.dspace.app.rest.model.GroupRest; import org.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.model.MetadataRest; import org.dspace.app.rest.model.MetadataValueRest; @@ -63,6 +66,7 @@ import org.dspace.app.rest.repository.ItemRestRepository; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.test.MetadataPatchSuite; +import org.dspace.authorize.ResourcePolicy; import org.dspace.builder.BitstreamBuilder; import org.dspace.builder.BundleBuilder; import org.dspace.builder.CollectionBuilder; @@ -4758,4 +4762,408 @@ public void findAccessStatusForItemTest() throws Exception { .andExpect(jsonPath("$.status", notNullValue())); } + @Test + public void findEditAuthorizedUnauthorizedTest() throws Exception { + getClient().perform(get("/api/core/items/search/findEditAuthorized")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findEditAuthorizedResourcePolicyTest() throws Exception { + context.turnOffAuthorisationSystem(); + Community comm1 = CommunityBuilder.createCommunity(context).withName("Community 1").build(); + Collection col1 = CollectionBuilder.createCollection(context, comm1).withName("Collection 1").build(); + + EPerson hasDirectEditRights = EPersonBuilder.createEPerson(context) + .withEmail("has@editrights.com").withPassword(password) + .build(); + EPerson hasDirectAdminRights = EPersonBuilder.createEPerson(context) + .withEmail("has@adminrights.com").withPassword(password) + .build(); + Item byResourcePolicy = ItemBuilder.createItem(context, col1) + .withTitle("direct edit rights for eperson") + .build(); + Item uneditable = ItemBuilder.createItem(context, col1) + .withTitle("uneditable item") + .build(); + ResourcePolicy policy = ResourcePolicyBuilder.createResourcePolicy(context, hasDirectEditRights, null) + .withDspaceObject(byResourcePolicy).withAction(WRITE) + .build(); + policy = ResourcePolicyBuilder.createResourcePolicy(context, hasDirectAdminRights, null) + .withDspaceObject(byResourcePolicy).withAction(Constants.ADMIN) + .build(); + context.restoreAuthSystemState(); + + String tokenHasDirectEditRightsToken = getAuthToken(hasDirectEditRights.getEmail(), password); + String tokenHasDirectAdminRightsToken = getAuthToken(hasDirectAdminRights.getEmail(), password); + + getClient(tokenHasDirectEditRightsToken).perform(get("/api/core/items/search/findEditAuthorized")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.items", + Matchers.contains(ItemMatcher.matchItemProperties(byResourcePolicy)))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + getClient(tokenHasDirectAdminRightsToken).perform(get("/api/core/items/search/findEditAuthorized")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.items", + Matchers.contains(ItemMatcher.matchItemProperties(byResourcePolicy)))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + } + + @Test + public void findEditAuthorizedAdminPropagationTest() throws Exception { + /* + Cases: + - items in collection with admin rights + - items in collection in community with admin rights + */ + + context.turnOffAuthorisationSystem(); + + /* + DSO structure: + root + ├── subcomm1 + ├── subcomm1collA + ├── subcomm1collAitemX + ├── subcomm1collAitemY + ├── subcomm1collB + └── subcomm1collBitem + └── subcomm2 + └── subcomm2coll + └── subcomm2collitem + */ + EPerson rootAdmin = EPersonBuilder.createEPerson(context) + .withEmail("root@admin.com").withPassword(password).build(); + EPerson subcomm1Admin = EPersonBuilder.createEPerson(context) + .withEmail("subcomm1@admin.com").withPassword(password).build(); + EPerson subcomm2Admin = EPersonBuilder.createEPerson(context) + .withEmail("subcomm2@admin.com").withPassword(password).build(); + EPerson subcomm1collA_Admin = EPersonBuilder.createEPerson(context) + .withEmail("subcomm1collA@admin.com").withPassword(password).build(); + EPerson subcomm1collB_Admin = EPersonBuilder.createEPerson(context) + .withEmail("subcomm1collB@admin.com").withPassword(password).build(); + EPerson subcomm2collAdmin = EPersonBuilder.createEPerson(context) + .withEmail("subcomm2coll@admin.com").withPassword(password).build(); + + Community root = CommunityBuilder.createCommunity(context) + .withAdminGroup(rootAdmin) + .withName("root") + .build(); + Community subcomm1 = CommunityBuilder.createSubCommunity(context, root) + .withAdminGroup(subcomm1Admin) + .withName("subcomm1") + .build(); + Community subcomm2 = CommunityBuilder.createSubCommunity(context, root) + .withAdminGroup(subcomm2Admin) + .withName("subcomm2") + .build(); + Collection subcomm1collA = CollectionBuilder.createCollection(context, subcomm1) + .withAdminGroup(subcomm1collA_Admin) + .withName("subcomm1collA") + .build(); + Collection subcomm1collB = CollectionBuilder.createCollection(context, subcomm1) + .withAdminGroup(subcomm1collB_Admin) + .withName("subcomm1collB") + .build(); + Collection subcomm2coll = CollectionBuilder.createCollection(context, subcomm2) + .withAdminGroup(subcomm2collAdmin) + .withName("subcomm2coll") + .build(); + Item subcomm1collAitemX = ItemBuilder.createItem(context, subcomm1collA).withTitle("subcomm1collAitemX") + .build(); + Item subcomm1collAitemY = ItemBuilder.createItem(context, subcomm1collA).withTitle("subcomm1collAitemY") + .build(); + Item subcomm1collBitem = ItemBuilder.createItem(context, subcomm1collB).withTitle("subcomm1collBitem") + .build(); + Item subcomm2collitem = ItemBuilder.createItem(context, subcomm2coll).withTitle("subcomm2collitem") + .build(); + context.restoreAuthSystemState(); + + String siteAdminToken = getAuthToken(admin.getEmail(), password); + String rootAdminToken = getAuthToken(rootAdmin.getEmail(), password); + String subcomm1AdminToken = getAuthToken(subcomm1Admin.getEmail(), password); + String subcomm2AdminToken = getAuthToken(subcomm2Admin.getEmail(), password); + String subcomm1collA_AdminToken = getAuthToken(subcomm1collA_Admin.getEmail(), password); + String subcomm1collB_AdminToken = getAuthToken(subcomm1collB_Admin.getEmail(), password); + String subcomm2collAdminToken = getAuthToken(subcomm2collAdmin.getEmail(), password); + + getClient(siteAdminToken).perform(get("/api/core/items/search/findEditAuthorized")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.items", + Matchers.containsInAnyOrder( + ItemMatcher.matchItemProperties(subcomm1collAitemX), + ItemMatcher.matchItemProperties(subcomm1collAitemY), + ItemMatcher.matchItemProperties(subcomm1collBitem), + ItemMatcher.matchItemProperties(subcomm2collitem) + ))) + .andExpect(jsonPath("$.page.totalElements", is(4))); + + getClient(rootAdminToken).perform(get("/api/core/items/search/findEditAuthorized")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.items", + Matchers.containsInAnyOrder( + ItemMatcher.matchItemProperties(subcomm1collAitemX), + ItemMatcher.matchItemProperties(subcomm1collAitemY), + ItemMatcher.matchItemProperties(subcomm1collBitem), + ItemMatcher.matchItemProperties(subcomm2collitem) + ))) + .andExpect(jsonPath("$.page.totalElements", is(4))); + + getClient(subcomm1AdminToken).perform(get("/api/core/items/search/findEditAuthorized")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.items", + Matchers.containsInAnyOrder( + ItemMatcher.matchItemProperties(subcomm1collAitemX), + ItemMatcher.matchItemProperties(subcomm1collAitemY), + ItemMatcher.matchItemProperties(subcomm1collBitem) + ))) + .andExpect(jsonPath("$.page.totalElements", is(3))); + + getClient(subcomm2AdminToken).perform(get("/api/core/items/search/findEditAuthorized")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.items", + Matchers.containsInAnyOrder( + ItemMatcher.matchItemProperties(subcomm2collitem) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + + getClient(subcomm1collA_AdminToken).perform(get("/api/core/items/search/findEditAuthorized")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.items", + Matchers.containsInAnyOrder( + ItemMatcher.matchItemProperties(subcomm1collAitemX), + ItemMatcher.matchItemProperties(subcomm1collAitemY) + ))) + .andExpect(jsonPath("$.page.totalElements", is(2))); + + getClient(subcomm1collB_AdminToken).perform(get("/api/core/items/search/findEditAuthorized")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.items", + Matchers.containsInAnyOrder( + ItemMatcher.matchItemProperties(subcomm1collBitem) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + + getClient(subcomm2collAdminToken).perform(get("/api/core/items/search/findEditAuthorized")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.items", + Matchers.containsInAnyOrder( + ItemMatcher.matchItemProperties(subcomm2collitem) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + } + + @Test + public void addParentComAdminGroupToCheckReindexingTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("col1") + .build(); + + Item item = ItemBuilder.createItem(context, col1) + .withTitle("MyTest") + .build(); + + context.restoreAuthSystemState(); + + String epersonToken = getAuthToken(eperson.getEmail(), password); + getClient(epersonToken).perform(get("/api/core/items/search/findEditAuthorized") + .param("query", "MyTest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded").doesNotExist()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + AtomicReference idRef = new AtomicReference<>(); + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(post("/api/core/communities/" + parentCommunity.getID() + "/adminGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRef.set( + UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))) + ); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(post("/api/eperson/groups/" + idRef.get() + "/epersons") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + eperson.getID() + )); + + getClient(epersonToken).perform(get("/api/core/items/search/findEditAuthorized") + .param("query", "MyTest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.items", Matchers.contains(ItemMatcher + .matchItemProperties(item) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + } + + @Test + public void removeParentComAdminPolicyToCheckEditPropagationTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + ResourcePolicy policy = ResourcePolicyBuilder.createResourcePolicy(context, eperson, null) + .withDspaceObject(parentCommunity).withAction(Constants.ADMIN) + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("col1") + .build(); + + Item item = ItemBuilder.createItem(context, col1) + .withTitle("MyTest") + .build(); + + context.restoreAuthSystemState(); + + String epersonToken = getAuthToken(eperson.getEmail(), password); + getClient(epersonToken).perform(get("/api/core/items/search/findEditAuthorized") + .param("query", "MyTest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.items", Matchers.contains(ItemMatcher + .matchItemProperties(item) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(delete("/api/authz/resourcepolicies/" + policy.getID())) + .andExpect(status().is(204)); + + getClient(epersonToken).perform(get("/api/core/items/search/findEditAuthorized") + .param("query", "MyTest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded").doesNotExist()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + } + + @Test + public void findEditAuthorizedItemsWithQueryTest() throws Exception { + findGenericAuthorizedItemsWithQueryTest("findEditAuthorized"); + } + + public void findGenericAuthorizedItemsWithQueryTest(String method) throws Exception { + + context.turnOffAuthorisationSystem(); + + EPerson eperson2 = EPersonBuilder.createEPerson(context) + .withEmail("eperson2@mail.com") + .withPassword(password) + .build(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Community child2 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community Two") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Sample collection") + .withAdminGroup(eperson) + .build(); + Collection col2 = CollectionBuilder.createCollection(context, child1) + .withName("col2") + .build(); + Collection col3 = CollectionBuilder.createCollection(context, child2) + .withName("col3") + .withAdminGroup(eperson) + .build(); + Item item1 = ItemBuilder.createItem(context, col1) + .withTitle("Sample item") + .build(); + Item item2 = ItemBuilder.createItem(context, col2) + .withTitle("Test item") + .build(); + Item item3 = ItemBuilder.createItem(context, col3) + .withTitle("Item of sample bitstreams") + .build(); + + context.restoreAuthSystemState(); + + // Test simple query + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(get("/api/core/items/search/" + method) + .param("query", "item")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.items", Matchers.containsInAnyOrder( + ItemMatcher.matchItemProperties(item1), + ItemMatcher.matchItemProperties(item3) + ))) + .andExpect(jsonPath("$.page.totalElements", is(2))); + + // Test case insensitive + getClient(tokenEPerson).perform(get("/api/core/items/search/" + method) + .param("query", "ITEM")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.items", Matchers.containsInAnyOrder( + ItemMatcher.matchItemProperties(item1), + ItemMatcher.matchItemProperties(item3) + ))) + .andExpect(jsonPath("$.page.totalElements", is(2))); + + // Test word for unauthorized item + getClient(tokenEPerson).perform(get("/api/core/items/search/" + method) + .param("query", "test")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + // Test eperson with no authorized items + String tokenEPerson2 = getAuthToken(eperson2.getEmail(), password); + getClient(tokenEPerson2).perform(get("/api/core/items/search/" + method) + .param("query", "community")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + // Test query as admin + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/core/items/search/" + method) + .param("query", "sample")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.items", Matchers.containsInAnyOrder( + ItemMatcher.matchItemProperties(item1), + ItemMatcher.matchItemProperties(item3) + ))) + .andExpect(jsonPath("$.page.totalElements", is(2))); + + // Test unsorted query words + getClient(tokenAdmin).perform(get("/api/core/items/search/" + method) + .param("query", "bitstreams sample")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.items", Matchers.contains( + ItemMatcher.matchItemProperties(item3) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + + // Test item not authorized for eperson is returned for admin + getClient(tokenAdmin).perform(get("/api/core/items/search/" + method) + .param("query", "test")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.items", Matchers.containsInAnyOrder( + ItemMatcher.matchItemProperties(item2) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + + } + } From edfe917fae64eebe9d942a32fe6119ea2c421643 Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Thu, 4 Dec 2025 15:02:44 +0100 Subject: [PATCH 807/939] Adapt getSubmitAuthorizedTypes to use inherited permissions --- .../dspace/content/EntityTypeServiceImpl.java | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/EntityTypeServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/EntityTypeServiceImpl.java index 7df892cd56f..92a7b62e720 100644 --- a/dspace-api/src/main/java/org/dspace/content/EntityTypeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/EntityTypeServiceImpl.java @@ -16,6 +16,7 @@ import java.util.Set; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.solr.client.solrj.SolrQuery; import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.client.solrj.response.FacetField; @@ -28,6 +29,7 @@ import org.dspace.content.service.EntityTypeService; import org.dspace.core.Constants; import org.dspace.core.Context; +import org.dspace.discovery.SearchService; import org.dspace.discovery.SolrSearchCore; import org.dspace.discovery.indexobject.IndexableCollection; import org.dspace.eperson.EPerson; @@ -49,6 +51,9 @@ public class EntityTypeServiceImpl implements EntityTypeService { @Autowired protected SolrSearchCore solrSearchCore; + @Autowired + protected SearchService searchService; + @Override public EntityType findByEntityType(Context context, String entityType) throws SQLException { return entityTypeDAO.findByEntityType(context, entityType); @@ -126,26 +131,34 @@ public List getSubmitAuthorizedTypes(Context context) throws SQLException, SolrServerException, IOException { List types = new ArrayList<>(); StringBuilder query = null; - EPerson currentUser = context.getCurrentUser(); if (!authorizeService.isAdmin(context)) { - String userId = ""; + EPerson currentUser = context.getCurrentUser(); + StringBuilder epersonAndGroupClause = new StringBuilder(); if (currentUser != null) { - userId = currentUser.getID().toString(); - query = new StringBuilder(); - query.append("submit:(e").append(userId); + epersonAndGroupClause.append("e").append(currentUser.getID()); } - + //Retrieve all the groups the current user is a member of Set groups = groupService.allMemberGroupsSet(context, currentUser); for (Group group : groups) { - if (query == null) { - query = new StringBuilder(); - query.append("submit:(g"); + if (epersonAndGroupClause.length() > 0) { + epersonAndGroupClause.append(" OR g").append(group.getID()); } else { - query.append(" OR g"); + epersonAndGroupClause.append("g").append(group.getID()); } - query.append(group.getID()); } - query.append(")"); + + if (epersonAndGroupClause.length() == 0) { + // No user or groups, no authorized types + return new ArrayList<>(); + } + query = new StringBuilder(); + query.append("submit:(").append(epersonAndGroupClause).append(")"); + query.append(" OR ").append("admin:(").append(epersonAndGroupClause).append(")"); + String locations = searchService.createLocationQueryForAdministrableDSOs(epersonAndGroupClause.toString()); + if (StringUtils.isNotBlank(locations)) { + query.append(" OR "); + query.append(locations); + } } SolrQuery sQuery = new SolrQuery("*:*"); From 14d64e88a74d51d8410169a44766b798d2c91d92 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Feb 2026 09:52:26 +0000 Subject: [PATCH 808/939] build(deps): bump the test-tools group with 6 updates Bumps the test-tools group with 6 updates: | Package | From | To | | --- | --- | --- | | [io.netty:netty-buffer](https://github.com/netty/netty) | `4.2.9.Final` | `4.2.10.Final` | | [io.netty:netty-transport](https://github.com/netty/netty) | `4.2.9.Final` | `4.2.10.Final` | | [io.netty:netty-transport-native-unix-common](https://github.com/netty/netty) | `4.2.9.Final` | `4.2.10.Final` | | [io.netty:netty-common](https://github.com/netty/netty) | `4.2.9.Final` | `4.2.10.Final` | | [io.netty:netty-handler](https://github.com/netty/netty) | `4.2.9.Final` | `4.2.10.Final` | | [io.netty:netty-codec](https://github.com/netty/netty) | `4.2.9.Final` | `4.2.10.Final` | Updates `io.netty:netty-buffer` from 4.2.9.Final to 4.2.10.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.9.Final...netty-4.2.10.Final) Updates `io.netty:netty-transport` from 4.2.9.Final to 4.2.10.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.9.Final...netty-4.2.10.Final) Updates `io.netty:netty-transport-native-unix-common` from 4.2.9.Final to 4.2.10.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.9.Final...netty-4.2.10.Final) Updates `io.netty:netty-common` from 4.2.9.Final to 4.2.10.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.9.Final...netty-4.2.10.Final) Updates `io.netty:netty-handler` from 4.2.9.Final to 4.2.10.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.9.Final...netty-4.2.10.Final) Updates `io.netty:netty-codec` from 4.2.9.Final to 4.2.10.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.9.Final...netty-4.2.10.Final) --- updated-dependencies: - dependency-name: io.netty:netty-buffer dependency-version: 4.2.10.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-transport dependency-version: 4.2.10.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-transport-native-unix-common dependency-version: 4.2.10.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-common dependency-version: 4.2.10.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-handler dependency-version: 4.2.10.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-codec dependency-version: 4.2.10.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 5375686f194..9f8f236a05a 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -877,32 +877,32 @@ io.netty netty-buffer - 4.2.9.Final + 4.2.10.Final io.netty netty-transport - 4.2.9.Final + 4.2.10.Final io.netty netty-transport-native-unix-common - 4.2.9.Final + 4.2.10.Final io.netty netty-common - 4.2.9.Final + 4.2.10.Final io.netty netty-handler - 4.2.9.Final + 4.2.10.Final io.netty netty-codec - 4.2.9.Final + 4.2.10.Final org.apache.velocity From 7da4b55a7a09b1e776b270a6b8ad250d5c69bf29 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Feb 2026 09:56:50 +0000 Subject: [PATCH 809/939] build(deps): bump io.grpc:grpc-context from 1.78.0 to 1.79.0 Bumps [io.grpc:grpc-context](https://github.com/grpc/grpc-java) from 1.78.0 to 1.79.0. - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.78.0...v1.79.0) --- updated-dependencies: - dependency-name: io.grpc:grpc-context dependency-version: 1.79.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0d659bbccdc..d6e9e88a4dc 100644 --- a/pom.xml +++ b/pom.xml @@ -1719,7 +1719,7 @@ io.grpc grpc-context - 1.78.0 + 1.79.0 com.google.http-client From 3faf993f73523f1c753b4d893ea993f9f913454d Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 18 Feb 2026 16:56:21 -0600 Subject: [PATCH 810/939] Install Apache Ant via apt-get instead of manual download. Manual download has a tendency to fail randomly in automated Docker builds. (cherry picked from commit d2abf0f7a0ab28af8d2f8c46782f637db2cf641a) --- Dockerfile | 15 +++++---------- Dockerfile.cli | 15 +++++---------- Dockerfile.test | 15 +++++---------- 3 files changed, 15 insertions(+), 30 deletions(-) diff --git a/Dockerfile b/Dockerfile index 102f00abe1f..1651c43a5a3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -38,16 +38,11 @@ ARG TARGET_DIR=dspace-installer # COPY the /install directory from 'build' container to /dspace-src in this container COPY --from=build /install /dspace-src WORKDIR /dspace-src -# Create the initial install deployment using ANT -ENV ANT_VERSION=1.10.13 -ENV ANT_HOME=/tmp/ant-$ANT_VERSION -ENV PATH=$ANT_HOME/bin:$PATH -# Download and install 'ant' -RUN mkdir $ANT_HOME && \ - curl --silent --show-error --location --fail --retry 5 --output /tmp/apache-ant.tar.gz \ - https://archive.apache.org/dist/ant/binaries/apache-ant-${ANT_VERSION}-bin.tar.gz && \ - tar -zx --strip-components=1 -f /tmp/apache-ant.tar.gz -C $ANT_HOME && \ - rm /tmp/apache-ant.tar.gz +# Install Apache Ant +RUN apt-get update \ + && apt-get install -y --no-install-recommends ant \ + && apt-get purge -y --auto-remove \ + && rm -rf /var/lib/apt/lists/* # Run necessary 'ant' deploy scripts RUN ant init_installation update_configs update_code update_webapps diff --git a/Dockerfile.cli b/Dockerfile.cli index e208e3d9214..e0d6a18d951 100644 --- a/Dockerfile.cli +++ b/Dockerfile.cli @@ -34,16 +34,11 @@ ARG TARGET_DIR=dspace-installer # COPY the /install directory from 'build' container to /dspace-src in this container COPY --from=build /install /dspace-src WORKDIR /dspace-src -# Create the initial install deployment using ANT -ENV ANT_VERSION=1.10.13 -ENV ANT_HOME=/tmp/ant-$ANT_VERSION -ENV PATH=$ANT_HOME/bin:$PATH -# Download and install 'ant' -RUN mkdir $ANT_HOME && \ - curl --silent --show-error --location --fail --retry 5 --output /tmp/apache-ant.tar.gz \ - https://archive.apache.org/dist/ant/binaries/apache-ant-${ANT_VERSION}-bin.tar.gz && \ - tar -zx --strip-components=1 -f /tmp/apache-ant.tar.gz -C $ANT_HOME && \ - rm /tmp/apache-ant.tar.gz +# Install Apache Ant +RUN apt-get update \ + && apt-get install -y --no-install-recommends ant \ + && apt-get purge -y --auto-remove \ + && rm -rf /var/lib/apt/lists/* # Run necessary 'ant' deploy scripts RUN ant init_installation update_configs update_code diff --git a/Dockerfile.test b/Dockerfile.test index cc73c655b63..9779761ec89 100644 --- a/Dockerfile.test +++ b/Dockerfile.test @@ -37,16 +37,11 @@ ARG TARGET_DIR=dspace-installer # COPY the /install directory from 'build' container to /dspace-src in this container COPY --from=build /install /dspace-src WORKDIR /dspace-src -# Create the initial install deployment using ANT -ENV ANT_VERSION=1.10.12 -ENV ANT_HOME=/tmp/ant-$ANT_VERSION -ENV PATH=$ANT_HOME/bin:$PATH -# Download and install 'ant' -RUN mkdir $ANT_HOME && \ - curl --silent --show-error --location --fail --retry 5 --output /tmp/apache-ant.tar.gz \ - https://archive.apache.org/dist/ant/binaries/apache-ant-${ANT_VERSION}-bin.tar.gz && \ - tar -zx --strip-components=1 -f /tmp/apache-ant.tar.gz -C $ANT_HOME && \ - rm /tmp/apache-ant.tar.gz +# Install Apache Ant +RUN apt-get update \ + && apt-get install -y --no-install-recommends ant \ + && apt-get purge -y --auto-remove \ + && rm -rf /var/lib/apt/lists/* # Run necessary 'ant' deploy scripts RUN ant init_installation update_configs update_code update_webapps From 2cf99fd89c82f375c684be824fa0baa4ea6a9001 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 17:24:42 +0000 Subject: [PATCH 811/939] build(deps): bump org.postgresql:postgresql from 42.7.9 to 42.7.10 Bumps [org.postgresql:postgresql](https://github.com/pgjdbc/pgjdbc) from 42.7.9 to 42.7.10. - [Release notes](https://github.com/pgjdbc/pgjdbc/releases) - [Changelog](https://github.com/pgjdbc/pgjdbc/blob/master/CHANGELOG.md) - [Commits](https://github.com/pgjdbc/pgjdbc/compare/REL42.7.9...REL42.7.10) --- updated-dependencies: - dependency-name: org.postgresql:postgresql dependency-version: 42.7.10 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0d659bbccdc..cea1e14371e 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ 5.7.14 5.6.15.Final 6.2.5.Final - 42.7.9 + 42.7.10 8.11.4 3.11.1 From 2c496c3c843e7c3be2a4022264cdc77b40d7b5f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 17:27:25 +0000 Subject: [PATCH 812/939] build(deps): bump the build-tools group with 2 updates Bumps the build-tools group with 2 updates: [org.apache.maven.plugins:maven-compiler-plugin](https://github.com/apache/maven-compiler-plugin) and [org.apache.maven.plugins:maven-dependency-plugin](https://github.com/apache/maven-dependency-plugin). Updates `org.apache.maven.plugins:maven-compiler-plugin` from 3.14.1 to 3.15.0 - [Release notes](https://github.com/apache/maven-compiler-plugin/releases) - [Commits](https://github.com/apache/maven-compiler-plugin/compare/maven-compiler-plugin-3.14.1...maven-compiler-plugin-3.15.0) Updates `org.apache.maven.plugins:maven-dependency-plugin` from 3.9.0 to 3.10.0 - [Release notes](https://github.com/apache/maven-dependency-plugin/releases) - [Commits](https://github.com/apache/maven-dependency-plugin/compare/maven-dependency-plugin-3.9.0...maven-dependency-plugin-3.10.0) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-compiler-plugin dependency-version: 3.15.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools - dependency-name: org.apache.maven.plugins:maven-dependency-plugin dependency-version: 3.10.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: build-tools ... Signed-off-by: dependabot[bot] --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 0d659bbccdc..8685acbd857 100644 --- a/pom.xml +++ b/pom.xml @@ -141,7 +141,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.14.1 + 3.15.0 11 @@ -341,7 +341,7 @@ org.apache.maven.plugins maven-dependency-plugin - 3.9.0 + 3.10.0 org.apache.maven.plugins From 865b9084d308582d2aa0b11f7ea4e3e799d51ad4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 17:48:10 +0000 Subject: [PATCH 813/939] build(deps): bump the amazon-s3 group with 2 updates Bumps the amazon-s3 group with 2 updates: software.amazon.awssdk:s3 and [software.amazon.awssdk.crt:aws-crt](https://github.com/awslabs/aws-crt-java). Updates `software.amazon.awssdk:s3` from 2.41.14 to 2.41.24 Updates `software.amazon.awssdk.crt:aws-crt` from 0.42.2 to 0.43.1 - [Release notes](https://github.com/awslabs/aws-crt-java/releases) - [Commits](https://github.com/awslabs/aws-crt-java/compare/v0.42.2...v0.43.1) --- updated-dependencies: - dependency-name: software.amazon.awssdk:s3 dependency-version: 2.41.24 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: amazon-s3 - dependency-name: software.amazon.awssdk.crt:aws-crt dependency-version: 0.43.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: amazon-s3 ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 9f8f236a05a..244e26fe161 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -739,7 +739,7 @@ software.amazon.awssdk s3 - 2.41.14 + 2.41.28 software.amazon.awssdk @@ -755,7 +755,7 @@ software.amazon.awssdk.crt aws-crt - 0.42.2 + 0.43.1 From 278b857633c0e2b6f233b822da06890ff5a17c9e Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Fri, 20 Feb 2026 21:32:54 +0300 Subject: [PATCH 814/939] Pin jboss-logging version jboss-logging is a transitive dependency and it causes dependency convergence errors because various hibernate components pull in different versions. We should be pinning this to the same version used by the hibernate parent and disabling dependabot updates for it. See: https://github.com/hibernate/hibernate-orm/blob/5.6/gradle/libraries.gradle#L93 See: https://github.com/DSpace/DSpace/blob/main/.github/dependabot.yml --- pom.xml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 0d659bbccdc..507defcf476 100644 --- a/pom.xml +++ b/pom.xml @@ -1143,11 +1143,21 @@ ${hibernate-validator.version} - + org.jboss.logging jboss-logging - 3.6.1.Final + 3.4.3.Final From 00d72a1de9c21a79812685692d416b1014f0a41b Mon Sep 17 00:00:00 2001 From: Abhinav S <42234845+AbhinavS96@users.noreply.github.com> Date: Tue, 24 Feb 2026 17:41:48 +0000 Subject: [PATCH 815/939] Merge pull request #11995 from atmire/w2p-138764_fix-11880-openaire-xsl-crosswalk-contribute-7_x [Port dspace-7_x] Fix OAI-PMH: OpenAIRE XSL crosswalk: bogus resourcetype exposed if the repository uses dc.type.* fields with a qualifier --- dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl b/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl index 470a7c7211f..b387ead35c9 100644 --- a/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl +++ b/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl @@ -796,7 +796,7 @@ - + @@ -1429,7 +1429,7 @@ literature - + dataset From 0a52d1535607a4735876be8a5423fce85bcb5f0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Wed, 25 Feb 2026 12:44:27 +0000 Subject: [PATCH 816/939] remove unsed collection definition --- .../SubmissionDefinitionConverter.java | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionDefinitionConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionDefinitionConverter.java index a8454552046..65f43096df4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionDefinitionConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionDefinitionConverter.java @@ -7,24 +7,16 @@ */ package org.dspace.app.rest.converter; -import java.sql.SQLException; import java.util.LinkedList; import java.util.List; -import java.util.stream.Collectors; -import javax.servlet.http.HttpServletRequest; import org.apache.logging.log4j.Logger; -import org.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.model.SubmissionDefinitionRest; import org.dspace.app.rest.model.SubmissionSectionRest; import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.submit.DataProcessingStep; -import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.util.SubmissionConfig; -import org.dspace.app.util.SubmissionConfigReaderException; import org.dspace.app.util.SubmissionStepConfig; -import org.dspace.content.Collection; -import org.dspace.core.Context; import org.dspace.services.RequestService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; @@ -76,20 +68,6 @@ public SubmissionDefinitionRest convert(SubmissionConfig obj, Projection project } } - HttpServletRequest request = requestService.getCurrentRequest().getHttpServletRequest(); - Context context = null; - try { - context = ContextUtil.obtainContext(request); - List collections = panelConverter.getSubmissionConfigService() - .getCollectionsBySubmissionConfig(context, - obj.getSubmissionName()); - DSpaceConverter cc = converter.getConverter(Collection.class); - List collectionsRest = collections.stream().map((collection) -> - cc.convert(collection, projection)).collect(Collectors.toList()); - sd.setCollections(collectionsRest); - } catch (SQLException | IllegalStateException | SubmissionConfigReaderException e) { - log.error(e.getMessage(), e); - } sd.setPanels(panels); return sd; } From 6b2e1f938ba15f31a9b973700403128f714d9a69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Wed, 25 Feb 2026 12:46:52 +0000 Subject: [PATCH 817/939] empty submission definition collections --- .../dspace/app/rest/matcher/SubmissionDefinitionsMatcher.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SubmissionDefinitionsMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SubmissionDefinitionsMatcher.java index 398097db6fb..14d8e514381 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SubmissionDefinitionsMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SubmissionDefinitionsMatcher.java @@ -34,7 +34,7 @@ public static Matcher matchSubmissionDefinition(boolean isDefault, Strin */ public static Matcher matchFullEmbeds() { return matchEmbeds( - "collections[]", + "collections", "sections" ); } From 3b000d7c864bb8e183176afe2cfb285e592efdb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Wed, 25 Feb 2026 12:49:26 +0000 Subject: [PATCH 818/939] adjust test for fixing issue - 10750 --- .../org/dspace/app/rest/SubmissionDefinitionsControllerIT.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java index a4ef63afa6e..b2064cf21f7 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java @@ -190,8 +190,7 @@ public void findCollections() throws Exception { //Match only that a section exists with a submission configuration behind getClient(token).perform(get("/api/config/submissiondefinitions/traditional/collections") .param("projection", "full")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.page.totalElements", is(0))); + .andExpect(status().isNoContent()); } @Test From cdd5ca062709b37de1c2df20546b174ddacb8567 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 21:12:04 +0000 Subject: [PATCH 819/939] build(deps): bump docker/build-push-action from 5 to 6 Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 5 to 6. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v5...v6) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/reusable-docker-build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml index 86da7b9788f..94193a73e9c 100644 --- a/.github/workflows/reusable-docker-build.yml +++ b/.github/workflows/reusable-docker-build.yml @@ -148,7 +148,7 @@ jobs: - name: Build and push image to ${{ env.DOCKER_BUILD_REGISTRY }} if: ${{ ! matrix.isPr }} id: docker_build - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: build-contexts: | ${{ inputs.dockerfile_additional_contexts }} @@ -202,7 +202,7 @@ jobs: # NOTE: This step cannot be combined with the build above as it's a different type of output. - name: Build and push image to local TAR file if: ${{ matrix.arch == 'linux/amd64'}} - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: build-contexts: | ${{ inputs.dockerfile_additional_contexts }} From 7734a57e523d5698ecdf16b6151275074d3b8d4c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Mar 2026 14:33:23 +0100 Subject: [PATCH 820/939] build(deps): bump actions/checkout from 4 to 6 (#12021) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 4 ++-- .github/workflows/codescan.yml | 2 +- .github/workflows/docker.yml | 2 +- .github/workflows/port_merged_pull_request.yml | 2 +- .github/workflows/reusable-docker-build.yml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8b3cb1d017a..2c0141ac209 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -45,7 +45,7 @@ jobs: steps: # https://github.com/actions/checkout - name: Checkout codebase - uses: actions/checkout@v4 + uses: actions/checkout@v6 # https://github.com/actions/setup-java - name: Install JDK ${{ matrix.java }} @@ -87,7 +87,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 # Download artifacts from previous 'tests' job - name: Download coverage artifacts diff --git a/.github/workflows/codescan.yml b/.github/workflows/codescan.yml index 88931d478e6..e041233a7e3 100644 --- a/.github/workflows/codescan.yml +++ b/.github/workflows/codescan.yml @@ -35,7 +35,7 @@ jobs: steps: # https://github.com/actions/checkout - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 # https://github.com/actions/setup-java - name: Install JDK diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 07990074521..ac226a4582c 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -182,7 +182,7 @@ jobs: steps: # Checkout our codebase (to get access to Docker Compose scripts) - name: Checkout codebase - uses: actions/checkout@v4 + uses: actions/checkout@v6 # Download Docker image artifacts (which were just built by reusable-docker-build.yml) - name: Download Docker image artifacts uses: actions/download-artifact@v4 diff --git a/.github/workflows/port_merged_pull_request.yml b/.github/workflows/port_merged_pull_request.yml index 857f22755e4..6c63ffa8312 100644 --- a/.github/workflows/port_merged_pull_request.yml +++ b/.github/workflows/port_merged_pull_request.yml @@ -23,7 +23,7 @@ jobs: if: github.event.pull_request.merged steps: # Checkout code - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 # Port PR to other branch (ONLY if labeled with "port to") # See https://github.com/korthout/backport-action - name: Create backport pull requests diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml index 86da7b9788f..ecd667ec9fd 100644 --- a/.github/workflows/reusable-docker-build.yml +++ b/.github/workflows/reusable-docker-build.yml @@ -110,7 +110,7 @@ jobs: # https://github.com/actions/checkout - name: Checkout codebase - uses: actions/checkout@v4 + uses: actions/checkout@v6 # https://github.com/docker/login-action # NOTE: This login occurs for BOTH non-PRs or PRs. PRs *must* also login to access private images from GHCR From 327dd3ef9f016f3d72ebc64f168515d2e0aef5bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Mar 2026 14:33:49 +0100 Subject: [PATCH 821/939] build(deps): bump toshimaru/auto-author-assign from 2.1.0 to 3.0.1 (#12023) Bumps [toshimaru/auto-author-assign](https://github.com/toshimaru/auto-author-assign) from 2.1.0 to 3.0.1. - [Release notes](https://github.com/toshimaru/auto-author-assign/releases) - [Changelog](https://github.com/toshimaru/auto-author-assign/blob/main/CHANGELOG.md) - [Commits](https://github.com/toshimaru/auto-author-assign/compare/v2.1.0...v3.0.1) --- updated-dependencies: - dependency-name: toshimaru/auto-author-assign dependency-version: 3.0.1 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/pull_request_opened.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull_request_opened.yml b/.github/workflows/pull_request_opened.yml index bbac52af243..e2b6e8ba9c2 100644 --- a/.github/workflows/pull_request_opened.yml +++ b/.github/workflows/pull_request_opened.yml @@ -21,4 +21,4 @@ jobs: # Assign the PR to whomever created it. This is useful for visualizing assignments on project boards # See https://github.com/toshimaru/auto-author-assign - name: Assign PR to creator - uses: toshimaru/auto-author-assign@v2.1.0 + uses: toshimaru/auto-author-assign@v3.0.1 From 4a0f71210855173370f3e86a3b405b97e1b4f938 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Mar 2026 14:34:09 +0100 Subject: [PATCH 822/939] build(deps): bump the amazon-s3 group with 2 updates (#12025) Bumps the amazon-s3 group with 2 updates: software.amazon.awssdk:s3 and [software.amazon.awssdk.crt:aws-crt](https://github.com/awslabs/aws-crt-java). Updates `software.amazon.awssdk:s3` from 2.41.28 to 2.41.33 Updates `software.amazon.awssdk.crt:aws-crt` from 0.43.1 to 0.43.4 - [Release notes](https://github.com/awslabs/aws-crt-java/releases) - [Commits](https://github.com/awslabs/aws-crt-java/compare/v0.43.1...v0.43.4) --- updated-dependencies: - dependency-name: software.amazon.awssdk:s3 dependency-version: 2.41.33 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: amazon-s3 - dependency-name: software.amazon.awssdk.crt:aws-crt dependency-version: 0.43.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: amazon-s3 ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dspace-api/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 244e26fe161..27951088f9a 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -739,7 +739,7 @@ software.amazon.awssdk s3 - 2.41.28 + 2.41.33 software.amazon.awssdk @@ -755,7 +755,7 @@ software.amazon.awssdk.crt aws-crt - 0.43.1 + 0.43.4 From c416fb092e6a5be02a809810de189e10189904bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Mar 2026 13:57:27 +0000 Subject: [PATCH 823/939] build(deps): bump actions/setup-java from 4 to 5 Bumps [actions/setup-java](https://github.com/actions/setup-java) from 4 to 5. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-java dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 2 +- .github/workflows/codescan.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2c0141ac209..fbe21fa89f2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -49,7 +49,7 @@ jobs: # https://github.com/actions/setup-java - name: Install JDK ${{ matrix.java }} - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: ${{ matrix.java }} distribution: 'temurin' diff --git a/.github/workflows/codescan.yml b/.github/workflows/codescan.yml index e041233a7e3..ba56b683a27 100644 --- a/.github/workflows/codescan.yml +++ b/.github/workflows/codescan.yml @@ -39,7 +39,7 @@ jobs: # https://github.com/actions/setup-java - name: Install JDK - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 11 distribution: 'temurin' From 33498332362b8b5238349940eca81cc61d365135 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Mar 2026 13:58:49 +0000 Subject: [PATCH 824/939] build(deps): bump korthout/backport-action from 2 to 4 Bumps [korthout/backport-action](https://github.com/korthout/backport-action) from 2 to 4. - [Release notes](https://github.com/korthout/backport-action/releases) - [Commits](https://github.com/korthout/backport-action/compare/v2...v4) --- updated-dependencies: - dependency-name: korthout/backport-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/port_merged_pull_request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/port_merged_pull_request.yml b/.github/workflows/port_merged_pull_request.yml index 6c63ffa8312..676ad45ba26 100644 --- a/.github/workflows/port_merged_pull_request.yml +++ b/.github/workflows/port_merged_pull_request.yml @@ -27,7 +27,7 @@ jobs: # Port PR to other branch (ONLY if labeled with "port to") # See https://github.com/korthout/backport-action - name: Create backport pull requests - uses: korthout/backport-action@v2 + uses: korthout/backport-action@v4 with: # Trigger based on a "port to [branch]" label on PR # (This label must specify the branch name to port to) From c872c7c0378ba700c05129675b3385b99889df8f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 15:47:21 +0000 Subject: [PATCH 825/939] build(deps): bump the fasterxml group with 2 updates Bumps the fasterxml group with 2 updates: [com.fasterxml.jackson.core:jackson-core](https://github.com/FasterXML/jackson-core) and [com.fasterxml.jackson.core:jackson-databind](https://github.com/FasterXML/jackson). Updates `com.fasterxml.jackson.core:jackson-core` from 2.21.0 to 2.21.1 - [Commits](https://github.com/FasterXML/jackson-core/compare/jackson-core-2.21.0...jackson-core-2.21.1) Updates `com.fasterxml.jackson.core:jackson-databind` from 2.21.0 to 2.21.1 - [Commits](https://github.com/FasterXML/jackson/commits) Updates `com.fasterxml.jackson.core:jackson-databind` from 2.21.0 to 2.21.1 - [Commits](https://github.com/FasterXML/jackson/commits) --- updated-dependencies: - dependency-name: com.fasterxml.jackson.core:jackson-core dependency-version: 2.21.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: fasterxml - dependency-name: com.fasterxml.jackson.core:jackson-databind dependency-version: 2.21.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: fasterxml - dependency-name: com.fasterxml.jackson.core:jackson-databind dependency-version: 2.21.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: fasterxml ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9bd75b2618e..24c818e2879 100644 --- a/pom.xml +++ b/pom.xml @@ -30,7 +30,7 @@ 3.11.1 2.31.0 - 2.21.0 + 2.21.1 2.21 1.3.2 2.3.1 From c280dac0ba17abf92eb1f388e6caedcc9d73fe3e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 16:10:05 +0000 Subject: [PATCH 826/939] build(deps): bump Wandalen/wretry.action from 1.3.0 to 3.8.0 Bumps [Wandalen/wretry.action](https://github.com/wandalen/wretry.action) from 1.3.0 to 3.8.0. - [Release notes](https://github.com/wandalen/wretry.action/releases) - [Commits](https://github.com/wandalen/wretry.action/compare/v1.3.0...v3.8.0) --- updated-dependencies: - dependency-name: Wandalen/wretry.action dependency-version: 3.8.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2c0141ac209..11b155602f8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -99,7 +99,7 @@ jobs: # Retry action: https://github.com/marketplace/actions/retry-action # Codecov action: https://github.com/codecov/codecov-action - name: Upload coverage to Codecov.io - uses: Wandalen/wretry.action@v1.3.0 + uses: Wandalen/wretry.action@v3.8.0 with: action: codecov/codecov-action@v4 # Ensure codecov-action throws an error when it fails to upload From 99e8e21aaf3cc772c48d78f2371c511dd52d115b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 16:11:29 +0000 Subject: [PATCH 827/939] build(deps): bump actions/add-to-project from 1.0.0 to 1.0.2 Bumps [actions/add-to-project](https://github.com/actions/add-to-project) from 1.0.0 to 1.0.2. - [Release notes](https://github.com/actions/add-to-project/releases) - [Commits](https://github.com/actions/add-to-project/compare/v1.0.0...v1.0.2) --- updated-dependencies: - dependency-name: actions/add-to-project dependency-version: 1.0.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/issue_opened.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/issue_opened.yml b/.github/workflows/issue_opened.yml index 0a35a6a9504..c8c421d98f4 100644 --- a/.github/workflows/issue_opened.yml +++ b/.github/workflows/issue_opened.yml @@ -16,7 +16,7 @@ jobs: # Only add to project board if issue is flagged as "needs triage" or has no labels # NOTE: By default we flag new issues as "needs triage" in our issue template if: (contains(github.event.issue.labels.*.name, 'needs triage') || join(github.event.issue.labels.*.name) == '') - uses: actions/add-to-project@v1.0.0 + uses: actions/add-to-project@v1.0.2 # Note, the authentication token below is an ORG level Secret. # It must be created/recreated manually via a personal access token with admin:org, project, public_repo permissions # See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token#permissions-for-the-github_token From 726079a3249db45a94e29ca2eecc97194187b671 Mon Sep 17 00:00:00 2001 From: MMilosz Date: Mon, 2 Mar 2026 16:23:21 +0100 Subject: [PATCH 828/939] chore(submission): set repeatable=false for dc.description in bitstream-metadata #12039 (cherry picked from commit c301804561a6cedb4256a661fed262566856c638) --- dspace/config/submission-forms.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace/config/submission-forms.xml b/dspace/config/submission-forms.xml index ff4af24fb3e..e7df395f165 100644 --- a/dspace/config/submission-forms.xml +++ b/dspace/config/submission-forms.xml @@ -39,7 +39,7 @@ dc description - true + false textarea Enter a description for the file From 7221e16e8d466c9746e23f959b0773062dee9ac1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Mar 2026 22:42:15 +0000 Subject: [PATCH 829/939] build(deps): bump actions/download-artifact from 4 to 8 Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 8. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v4...v8) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '8' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 2 +- .github/workflows/docker.yml | 2 +- .github/workflows/reusable-docker-build.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 062f2c7c2f7..6b0f086e789 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -91,7 +91,7 @@ jobs: # Download artifacts from previous 'tests' job - name: Download coverage artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 # Now attempt upload to Codecov using its action. # NOTE: We use a retry action to retry the Codecov upload if it fails the first time. diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index ac226a4582c..96a577bf3e3 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -185,7 +185,7 @@ jobs: uses: actions/checkout@v6 # Download Docker image artifacts (which were just built by reusable-docker-build.yml) - name: Download Docker image artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: # Download all amd64 Docker images (TAR files) into the /tmp/docker directory pattern: docker-image-*-linux-amd64 diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml index 57d0f5dbb82..46c7ceb1cd9 100644 --- a/.github/workflows/reusable-docker-build.yml +++ b/.github/workflows/reusable-docker-build.yml @@ -246,7 +246,7 @@ jobs: - docker-build steps: - name: Download Docker build digests - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: path: /tmp/digests # Download digests for both AMD64 and ARM64 into same directory From 4b7465751cb1370c862c02119ac7302dfe43e899 Mon Sep 17 00:00:00 2001 From: abhinav Date: Tue, 3 Mar 2026 10:45:29 +0100 Subject: [PATCH 830/939] fix multiple empty datasite:sizes tags (cherry picked from commit 1a93f9a60b07acf5e58a24b0f3a9a320616d91db) --- .../crosswalks/oai/metadataFormats/oai_openaire.xsl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl b/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl index bc8a3ae2000..dd1ff556c63 100644 --- a/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl +++ b/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl @@ -851,13 +851,13 @@ - - + + - - + + From ecd76cffddec40f8f0d552e2dac9948cdde4bdf0 Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Fri, 20 Feb 2026 10:31:25 +0100 Subject: [PATCH 831/939] Update authorized Solr searches to escape queries and include dc.title_sort for queries with spaces (cherry picked from commit c1bcd0e7845b223b3ee136359a275880bf8d8004) --- .../authorize/AuthorizeServiceImpl.java | 4 ++ .../dspace/content/CollectionServiceImpl.java | 8 +--- .../org/dspace/content/ItemServiceImpl.java | 3 ++ .../org/dspace/discovery/SearchService.java | 9 ++++ .../org/dspace/discovery/SolrServiceImpl.java | 21 +++++++++ .../app/rest/CollectionRestRepositoryIT.java | 42 ++++++++++++++++-- .../app/rest/CommunityRestRepositoryIT.java | 41 +++++++++++++++-- .../dspace/app/rest/ItemRestRepositoryIT.java | 44 +++++++++++++++++-- 8 files changed, 156 insertions(+), 16 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java index 202d5d42aa0..4a1e2f86d1a 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java @@ -812,6 +812,7 @@ public List findAuthorizedCommunityByAction(Context context, String q int limit) throws SearchServiceException { List communities = new ArrayList<>(); + query = searchService.formatAutoCompleteQuery(query, "dc.title_sort"); query = formatCustomQuery(query); DiscoverResult discoverResult = getDiscoverResult(context, query + RESOURCE_TYPE_FIELD + ":" + IndexableCommunity.TYPE, action, true, @@ -849,6 +850,7 @@ public long countAdminAuthorizedCommunity(Context context, String query) @Override public long countAuthorizedCommunityByAction(Context context, String query, int action) throws SearchServiceException { + query = searchService.formatAutoCompleteQuery(query, "dc.title_sort"); query = formatCustomQuery(query); DiscoverResult discoverResult = getDiscoverResult(context, query + RESOURCE_TYPE_FIELD + ":" + IndexableCommunity.TYPE, action, true, @@ -892,6 +894,7 @@ public List findAuthorizedCollectionByAction(Context context, String return collections; } + query = searchService.formatAutoCompleteQuery(query, "dc.title_sort"); query = formatCustomQuery(query); DiscoverResult discoverResult = getDiscoverResult(context, query + RESOURCE_TYPE_FIELD + ":" + IndexableCollection.TYPE, action, true, @@ -929,6 +932,7 @@ public long countAdminAuthorizedCollection(Context context, String query) @Override public long countAuthorizedCollectionByAction(Context context, String query, int action) throws SearchServiceException { + query = searchService.formatAutoCompleteQuery(query, "dc.title_sort"); query = formatCustomQuery(query); DiscoverResult discoverResult = getDiscoverResult(context, query + RESOURCE_TYPE_FIELD + ":" + IndexableCollection.TYPE, action, diff --git a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java index a22f2acccea..d0ec8ccc0cc 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -28,7 +28,6 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; -import org.apache.solr.client.solrj.util.ClientUtils; import org.dspace.app.util.AuthorizeUtil; import org.dspace.authorize.AuthorizeConfiguration; import org.dspace.authorize.AuthorizeException; @@ -1086,11 +1085,8 @@ private DiscoverResult retrieveCollectionsWithSubmit(Context context, DiscoverQu discoverQuery.addFilterQueries("search.entitytype:" + entityType); } if (StringUtils.isNotBlank(q)) { - StringBuilder buildQuery = new StringBuilder(); - String escapedQuery = ClientUtils.escapeQueryChars(q); - buildQuery.append("(").append(escapedQuery).append(" OR dc.title_sort:*") - .append(escapedQuery).append("*").append(")"); - discoverQuery.setQuery(buildQuery.toString()); + q = searchService.formatAutoCompleteQuery(q, "dc.title_sort"); + discoverQuery.setQuery(q); } discoverQuery.addRequiredAuthorization(Constants.ADD); DiscoverResult resp = searchService.search(context, discoverQuery); diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java index 825b452d425..c40b412eacb 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -1260,6 +1260,9 @@ public boolean canEdit(Context context, Item item) throws SQLException { private DiscoverResult retrieveItemsWithEdit(Context context, DiscoverQuery discoverQuery, String q) throws SearchServiceException { if (StringUtils.isNotBlank(q)) { + // Although not all items will have a metadata dc.title, we use it for autocomplete because it is the + // most common. Ideally, we should use a field that all indexed items have + q = searchService.formatAutoCompleteQuery(q, "dc.title_sort"); discoverQuery.setQuery(q); } discoverQuery.addRequiredAuthorization(Constants.WRITE); diff --git a/dspace-api/src/main/java/org/dspace/discovery/SearchService.java b/dspace-api/src/main/java/org/dspace/discovery/SearchService.java index 7f4c85c3a86..986b2d23de2 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SearchService.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SearchService.java @@ -126,6 +126,15 @@ List getRelatedItems(Context context, Item item, */ String escapeQueryChars(String query); + /** + * Utility method to format an autocomplete query over a specific field. + * + * @param query to search for + * @param autocompleteField the field to use to autocomplete search, if null or empty no field is used + * @return the constructed solr query + */ + String formatAutoCompleteQuery(String query, String autocompleteField); + FacetYearRange getFacetYearRange(Context context, IndexableObject scope, DiscoverySearchFilterFacet facet, List filterQueries, DiscoverQuery parentQuery) throws SearchServiceException; diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java index acfd37ef5e6..09514504fed 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java @@ -1676,6 +1676,27 @@ public String escapeQueryChars(String query) { return ClientUtils.escapeQueryChars(query); } + /** + * Utility method to format an autocomplete query over a specific field. Combines the escaped query with a + * wildcard search over the specified {@code autocompleteField}. This field is typically non-tokenized and + * allows recovering searches containing spaces as a single value. + * + * @param query the user input to search for + * @param autocompleteField non-tokenized field used for wildcard autocomplete + * @return the constructed Solr query, or the original query if blank + */ + @Override + public String formatAutoCompleteQuery(String query, String autocompleteField) { + if (StringUtils.isNotBlank(query)) { + StringBuilder buildQuery = new StringBuilder(); + String escapedQuery = escapeQueryChars(query); + buildQuery.append("(").append(escapedQuery).append(" OR ").append(autocompleteField).append(":*") + .append(escapedQuery).append("*").append(")"); + return buildQuery.toString(); + } + return query; + } + @Override public FacetYearRange getFacetYearRange(Context context, IndexableObject scope, DiscoverySearchFilterFacet facet, List filterQueries, diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java index 26eda3b06c9..0fa71dd0675 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java @@ -3947,6 +3947,13 @@ public void findGenericAuthorizedCollectionsWithQueryTest(String method) throws .withName("Collection of sample items") .withAdminGroup(eperson) .build(); + Collection col4 = CollectionBuilder.createCollection(context, child2) + .withName("Testing autocomplete in collection") + .withAdminGroup(eperson2) + .build(); + Collection col5 = CollectionBuilder.createCollection(context, child2) + .withName("Title: subtitle (special characters)") + .build(); context.restoreAuthSystemState(); String tokenEPerson = getAuthToken(eperson.getEmail(), password); @@ -3977,11 +3984,29 @@ public void findGenericAuthorizedCollectionsWithQueryTest(String method) throws .andExpect(jsonPath("$.page.totalElements", is(0))); // Test eperson with no authorized collections + getClient(tokenEPerson).perform(get("/api/core/collections/search/" + method) + .param("query", "auto")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + String tokenEPerson2 = getAuthToken(eperson2.getEmail(), password); + // Test eperson2 gets only their authorized collection getClient(tokenEPerson2).perform(get("/api/core/collections/search/" + method) - .param("query", "collection")) + .param("query", "auto")) .andExpect(status().isOk()) - .andExpect(jsonPath("$.page.totalElements", is(0))); + .andExpect(jsonPath("$._embedded.collections", Matchers.contains( + CollectionMatcher.matchProperties(col4.getName(), col4.getID(), col4.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + + // Test query with multiple words + getClient(tokenEPerson2).perform(get("/api/core/collections/search/" + method) + .param("query", "testing auto")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.collections", Matchers.containsInAnyOrder( + CollectionMatcher.matchProperties(col4.getName(), col4.getID(), col4.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); // Test admin gets all authorized collections String tokenAdmin = getAuthToken(admin.getEmail(), password); @@ -4008,10 +4033,19 @@ public void findGenericAuthorizedCollectionsWithQueryTest(String method) throws .param("query", "test")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.collections", Matchers.containsInAnyOrder( - CollectionMatcher.matchProperties(col2.getName(), col2.getID(), col2.getHandle()) + CollectionMatcher.matchProperties(col2.getName(), col2.getID(), col2.getHandle()), + CollectionMatcher.matchProperties(col4.getName(), col4.getID(), col4.getHandle()) ))) - .andExpect(jsonPath("$.page.totalElements", is(1))); + .andExpect(jsonPath("$.page.totalElements", is(2))); + // Test query with special characters + getClient(tokenAdmin).perform(get("/api/core/collections/search/" + method) + .param("query", "title: subtitle (special")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.collections", Matchers.contains( + CollectionMatcher.matchProperties(col5.getName(), col5.getID(), col5.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java index 6cc25f22ce9..9440bb0edcc 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java @@ -3171,6 +3171,13 @@ public void findGenericAuthorizedCommunitiesWithQueryTest(String method) throws .withName("community of sample items") .withAdminGroup(eperson) .build(); + Community com4 = CommunityBuilder.createSubCommunity(context, child2) + .withName("Testing autocomplete in community") + .withAdminGroup(eperson2) + .build(); + Community com5 = CommunityBuilder.createSubCommunity(context, child2) + .withName("Title: subtitle (special characters)") + .build(); context.restoreAuthSystemState(); // Test simple query @@ -3201,11 +3208,29 @@ public void findGenericAuthorizedCommunitiesWithQueryTest(String method) throws .andExpect(jsonPath("$.page.totalElements", is(0))); // Test eperson with no authorized communities + getClient(tokenEPerson).perform(get("/api/core/communities/search/" + method) + .param("query", "auto")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + String tokenEPerson2 = getAuthToken(eperson2.getEmail(), password); + // Test eperson2 gets only their authorized community getClient(tokenEPerson2).perform(get("/api/core/communities/search/" + method) - .param("query", "community")) + .param("query", "auto")) .andExpect(status().isOk()) - .andExpect(jsonPath("$.page.totalElements", is(0))); + .andExpect(jsonPath("$._embedded.communities", Matchers.contains( + CommunityMatcher.matchProperties(com4.getName(), com4.getID(), com4.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + + // Test query with multiple words + getClient(tokenEPerson2).perform(get("/api/core/communities/search/" + method) + .param("query", "testing auto")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.communities", Matchers.containsInAnyOrder( + CommunityMatcher.matchProperties(com4.getName(), com4.getID(), com4.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); // Test as admin String tokenAdmin = getAuthToken(admin.getEmail(), password); @@ -3232,7 +3257,17 @@ public void findGenericAuthorizedCommunitiesWithQueryTest(String method) throws .param("query", "test")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.communities", Matchers.containsInAnyOrder( - CommunityMatcher.matchProperties(com2.getName(), com2.getID(), com2.getHandle()) + CommunityMatcher.matchProperties(com2.getName(), com2.getID(), com2.getHandle()), + CommunityMatcher.matchProperties(com4.getName(), com4.getID(), com4.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(2))); + + // Test query with special characters + getClient(tokenAdmin).perform(get("/api/core/communities/search/" + method) + .param("query", "title: subtitle")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.communities", Matchers.contains( + CommunityMatcher.matchProperties(com5.getName(), com5.getID(), com5.getHandle()) ))) .andExpect(jsonPath("$.page.totalElements", is(1))); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java index a77938eeb32..76470920a9f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java @@ -5089,6 +5089,10 @@ public void findGenericAuthorizedItemsWithQueryTest(String method) throws Except .withName("col3") .withAdminGroup(eperson) .build(); + Collection col4 = CollectionBuilder.createCollection(context, child2) + .withName("col4") + .withAdminGroup(eperson2) + .build(); Item item1 = ItemBuilder.createItem(context, col1) .withTitle("Sample item") .build(); @@ -5098,6 +5102,12 @@ public void findGenericAuthorizedItemsWithQueryTest(String method) throws Except Item item3 = ItemBuilder.createItem(context, col3) .withTitle("Item of sample bitstreams") .build(); + Item item4 = ItemBuilder.createItem(context, col4) + .withTitle("Testing autocomplete in items") + .build(); + Item item5 = ItemBuilder.createItem(context, col4) + .withTitle("Title: subtitle (special characters)") + .build(); context.restoreAuthSystemState(); @@ -5129,11 +5139,29 @@ public void findGenericAuthorizedItemsWithQueryTest(String method) throws Except .andExpect(jsonPath("$.page.totalElements", is(0))); // Test eperson with no authorized items + getClient(tokenEPerson).perform(get("/api/core/items/search/" + method) + .param("query", "auto")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + String tokenEPerson2 = getAuthToken(eperson2.getEmail(), password); + // Test eperson2 with one authorized item getClient(tokenEPerson2).perform(get("/api/core/items/search/" + method) - .param("query", "community")) + .param("query", "auto")) .andExpect(status().isOk()) - .andExpect(jsonPath("$.page.totalElements", is(0))); + .andExpect(jsonPath("$._embedded.items", Matchers.contains( + ItemMatcher.matchItemProperties(item4) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + + // Test query with multiple words + getClient(tokenEPerson2).perform(get("/api/core/items/search/" + method) + .param("query", "testing auto")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.items", Matchers.containsInAnyOrder( + ItemMatcher.matchItemProperties(item4) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); // Test query as admin String tokenAdmin = getAuthToken(admin.getEmail(), password); @@ -5160,7 +5188,17 @@ public void findGenericAuthorizedItemsWithQueryTest(String method) throws Except .param("query", "test")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.items", Matchers.containsInAnyOrder( - ItemMatcher.matchItemProperties(item2) + ItemMatcher.matchItemProperties(item2), + ItemMatcher.matchItemProperties(item4) + ))) + .andExpect(jsonPath("$.page.totalElements", is(2))); + + // Test special characters in query + getClient(tokenAdmin).perform(get("/api/core/items/search/" + method) + .param("query", "title: subtitle (special")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.items", Matchers.contains( + ItemMatcher.matchItemProperties(item5) ))) .andExpect(jsonPath("$.page.totalElements", is(1))); From 218d0360a87c29b5e7d2c553e40a9b28268ec34c Mon Sep 17 00:00:00 2001 From: Piaget Bouaka Donfack Date: Tue, 23 Sep 2025 12:45:50 +0200 Subject: [PATCH 832/939] [DURACOM-389] Import from external sources creates invalid metadata (cherry picked from commit 255b1dd15ef926b88361ab6c1ad3ec3ed7f6f30f) --- .../external/crossref/CrossRefAbstractProcessor.java | 8 ++++++++ .../contributor/SimpleJsonPathMetadataContributor.java | 3 +++ 2 files changed, 11 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAbstractProcessor.java b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAbstractProcessor.java index 99f1ee37a54..d54c70c3198 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAbstractProcessor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAbstractProcessor.java @@ -95,6 +95,14 @@ private String prettifyAbstract(String abstractValue) { sb.append("\n"); } sb.append("\n"); + } else if (StringUtils.equals(nodeName, "jats:p")) { + NodeList secElements = childElement.getChildNodes(); + for (int j = 0; j < secElements.getLength(); j++) { + Node secChildElement = secElements.item(j); + sb.append(secChildElement.getTextContent()); + sb.append("\n"); + } + sb.append("\n"); } } diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleJsonPathMetadataContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleJsonPathMetadataContributor.java index 590fc63283b..db3ba16dc42 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleJsonPathMetadataContributor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleJsonPathMetadataContributor.java @@ -146,6 +146,9 @@ public Collection contributeMetadata(String fullJson) { } } for (String value : metadataValue) { + if (StringUtils.isBlank(value)) { + continue; + } MetadatumDTO metadatumDto = new MetadatumDTO(); metadatumDto.setValue(value); metadatumDto.setElement(field.getElement()); From 92cf7e694a673b65a2cffa2f36d11430bf96616b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Mar 2026 21:13:34 +0100 Subject: [PATCH 833/939] build(deps): bump software.amazon.awssdk:s3 in the amazon-s3 group (#12066) Bumps the amazon-s3 group with 1 update: software.amazon.awssdk:s3. Updates `software.amazon.awssdk:s3` from 2.41.33 to 2.41.34 --- updated-dependencies: - dependency-name: software.amazon.awssdk:s3 dependency-version: 2.41.34 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: amazon-s3 ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 27951088f9a..f1744feb4cb 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -739,7 +739,7 @@ software.amazon.awssdk s3 - 2.41.33 + 2.42.1 software.amazon.awssdk From 9644e9c3430eb9e34317c17fa59301979f86d15c Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Wed, 29 Oct 2025 13:52:57 +0100 Subject: [PATCH 834/939] Optimize SQL query in findByEPerson to enhance performance when retrieving associated groups (cherry picked from commit 548c5e18bda6c876d8c313e9b3779de2b1085eb7) --- .../src/main/java/org/dspace/eperson/dao/impl/GroupDAOImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/GroupDAOImpl.java b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/GroupDAOImpl.java index 6aea9ecd8d6..40323d23e16 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/GroupDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/GroupDAOImpl.java @@ -92,7 +92,7 @@ public List findAll(Context context, int pageSize, int offset) throws SQL @Override public List findByEPerson(Context context, EPerson ePerson) throws SQLException { Query query = createQuery(context, - "from Group where (from EPerson e where e.id = :eperson_id) in elements(epeople)"); + "select distinct g from Group g join g.epeople ep where ep.id = :eperson_id"); query.setParameter("eperson_id", ePerson.getID()); query.setHint("org.hibernate.cacheable", Boolean.TRUE); From 955a6d03f16661fb6e48afa6d82f6c5a535b1847 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 17 Feb 2026 16:42:50 -0600 Subject: [PATCH 835/939] Replace webui.content_disposition_format with webui.content_disposition_inline. The latter is an allowlist instead of a blocklist. --- .../app/rest/BitstreamRestController.java | 65 ++++++++++--------- .../app/rest/BitstreamRestControllerIT.java | 60 ++++++++++------- dspace/config/dspace.cfg | 25 ++++--- 3 files changed, 82 insertions(+), 68 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java index 57fc73dfc6f..819a2b69e8f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java @@ -20,7 +20,6 @@ import javax.ws.rs.core.Response; import org.apache.catalina.connector.ClientAbortException; -import org.apache.commons.collections4.ListUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.converter.ConverterService; @@ -153,15 +152,17 @@ public ResponseEntity retrieve(@PathVariable UUID uuid, HttpServletResponse resp httpHeadersInitializer.withLastModified(lastModified); } - //Determine if we need to send the file as a download or if the browser can open it inline - //The file will be downloaded if its size is larger than the configured threshold, - //or if its mimetype/extension appears in the "webui.content_disposition_format" config - long dispositionThreshold = configurationService.getLongProperty("webui.content_disposition_threshold"); - if ((dispositionThreshold >= 0 && filesize > dispositionThreshold) - || checkFormatForContentDisposition(format)) { - httpHeadersInitializer.withDisposition(HttpHeadersInitializer.CONTENT_DISPOSITION_ATTACHMENT); - } else { + // Determine if we need to send the file as a download or if the browser can open it inline. + // By default, all files will be downloaded as that is more secure. File formats will only be opened inline + // if they are listed in the "webui.content_disposition_inline" config and size is less than the + // configured "webui.content_disposition_threshold" (default = 8MB) + long dispositionThreshold = configurationService.getLongProperty("webui.content_disposition_threshold", + 8388608); + if (checkFormatForContentDispositionInline(format) && + filesize <= dispositionThreshold) { httpHeadersInitializer.withDisposition(HttpHeadersInitializer.CONTENT_DISPOSITION_INLINE); + } else { + httpHeadersInitializer.withDisposition(HttpHeadersInitializer.CONTENT_DISPOSITION_ATTACHMENT); } //We have all the data we need, close the connection to the database so that it doesn't stay open during @@ -208,48 +209,48 @@ private boolean isNotAnErrorResponse(HttpServletResponse response) { } /** - * Check if a Bitstream of the specified format should always be downloaded (i.e. "content-disposition: attachment") - * or can be opened inline (i.e. "content-disposition: inline"). + * Check if a Bitstream of the specified format should be opened inline (i.e. "content-disposition: inline") + * instead of the default behavior of always downloading (i.e. "content-disposition: attachment"). *

    * NOTE that downloading via "attachment" is more secure, as the user's browser will not attempt to process or * display the file. But, downloading via "inline" may be seen as more user-friendly for common formats. * @param format BitstreamFormat - * @return true if always download ("attachment"). false if can be opened inline ("inline") + * @return true if format is configured to be opened inline ("inline"). false if always download ("attachment") */ - private boolean checkFormatForContentDisposition(BitstreamFormat format) { + private boolean checkFormatForContentDispositionInline(BitstreamFormat format) { // Undefined or Unknown formats should ALWAYS be downloaded for additional security. if (format == null || format.getSupportLevel() == BitstreamFormat.UNKNOWN) { - return true; + return false; } - // Load additional formats configured to require download - List configuredFormats = List.of(configurationService. - getArrayProperty("webui.content_disposition_format")); - - // If configuration includes "*", then all formats will always be downloaded. - if (configuredFormats.contains("*")) { - return true; + // Return false for BANNED inline formats. Some formats, especially XML / HTML / Javascript based formats, + // when loaded inline may be susceptible to XSS attacks. Therefore, we will refuse to allow those formats to be + // displayed inline for security purposes. + // NOTE: "+xml" in this list will match any MIME Type that ends in "+xml", as those are XML-based formats. + List bannedInlineFormats = List.of("text/html", "text/javascript", "text/xml", "application/xml", + "+xml"); + for (String bannedInlineFormat : bannedInlineFormats) { + // If our format MIME Type contains one of the banned inline formats, we refuse to display it inline + if (format.getMIMEType().contains(bannedInlineFormat)) { + return false; + } } - // Define a download list of formats which DSpace forces to ALWAYS be downloaded. - // These formats can embed JavaScript which may be run in the user's browser if the file is opened inline. - // Therefore, DSpace blocks opening these formats inline as it could be used for an XSS attack. - List downloadOnlyFormats = List.of("text/html", "text/javascript", "text/xml", "rdf"); - - // Combine our two lists - List formats = ListUtils.union(downloadOnlyFormats, configuredFormats); + // Load formats configured to allow inline display + List formats = List.of(configurationService. + getArrayProperty("webui.content_disposition_inline")); // See if the passed in format's MIME type or file extension is listed. - boolean download = formats.contains(format.getMIMEType()); - if (!download) { + boolean inline = formats.contains(format.getMIMEType()); + if (!inline) { for (String ext : format.getExtensions()) { if (formats.contains(ext)) { - download = true; + inline = true; break; } } } - return download; + return inline; } /** diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java index b4649ce4c5b..f48b48b341f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java @@ -1261,12 +1261,8 @@ public void closeInputStreamsDownloadWithCoverPage() throws Exception { @Test public void checkContentDispositionOfFormats() throws Exception { - configurationService.setProperty("webui.content_disposition_format", new String[] { - "text/richtext", - "text/xml", - "txt" - }); - + // This test verifies that, by default, common text formats will be downloaded instead of being served inline. + // The next two tests will verify behavior of non-default settings of "webui.content_disposition_inline" context.turnOffAuthorisationSystem(); Community community = CommunityBuilder.createCommunity(context).build(); Collection collection = CollectionBuilder.createCollection(context, community).build(); @@ -1288,18 +1284,26 @@ public void checkContentDispositionOfFormats() throws Exception { } context.restoreAuthSystemState(); - // these formats are configured and files should be downloaded + // Based on default configuration all files should be downloaded verifyBitstreamDownload(rtf, "text/richtext;charset=UTF-8", true); verifyBitstreamDownload(xml, "text/xml;charset=UTF-8", true); verifyBitstreamDownload(txt, "text/plain;charset=UTF-8", true); - // this format is not configured and should open inline - verifyBitstreamDownload(csv, "text/csv;charset=UTF-8", false); + verifyBitstreamDownload(csv, "text/csv;charset=UTF-8", true); } @Test - public void checkHardcodedContentDispositionFormats() throws Exception { - // This test is similar to the above test, but it verifies that our *hardcoded settings* for - // webui.content_disposition_format are protecting us from loading specific formats *inline*. + public void checkBannedContentDispositionInlineFormats() throws Exception { + configurationService.setProperty("webui.content_disposition_inline", new String[] { + "text/html", + "text/javascript", + "rdf", + "text/xml", + "image/svg+xml" + }); + + // This test is similar to the above test, but it verifies that if a site specifies + // a banned format (e.g. HTML, XML, etc) in their "webui.content_disposition_inline" setting + // DSpace will still protect them by refusing to load the format *inline*. context.turnOffAuthorisationSystem(); Community community = CommunityBuilder.createCommunity(context).build(); Collection collection = CollectionBuilder.createCollection(context, community).build(); @@ -1329,8 +1333,9 @@ public void checkHardcodedContentDispositionFormats() throws Exception { } context.restoreAuthSystemState(); - // By default, HTML, JS & XML should all download. This protects us from possible XSS attacks, as - // each of these formats can embed JavaScript which may execute when the file is loaded *inline*. + // By default, HTML, JS & XML should all download regardless of inline configuration. + // This protects us from possible XSS attacks, as each of these formats can embed JavaScript + // which may execute when the file is loaded *inline*. verifyBitstreamDownload(html, "text/html;charset=UTF-8", true); verifyBitstreamDownload(js, "text/javascript;charset=UTF-8", true); verifyBitstreamDownload(rdf, "application/rdf+xml;charset=UTF-8", true); @@ -1343,22 +1348,29 @@ public void checkHardcodedContentDispositionFormats() throws Exception { } @Test - public void checkWildcardContentDispositionFormats() throws Exception { - // Setting "*" should result in all formats being downloaded (nothing will be opened inline) - configurationService.setProperty("webui.content_disposition_format", "*"); - + public void checkContentDispositionInlineFormats() throws Exception { + // Set PDF and a few image formats to verify they will display inline. But leave off "text/plain" + configurationService.setProperty("webui.content_disposition_inline", new String[] { + "text/csv", + "application/pdf", + "image/jpeg", + "video/mpeg" + }); context.turnOffAuthorisationSystem(); Community community = CommunityBuilder.createCommunity(context).build(); Collection collection = CollectionBuilder.createCollection(context, community).build(); Item item = ItemBuilder.createItem(context, collection).build(); String content = "Test Content"; Bitstream csv; + Bitstream txt; Bitstream jpg; Bitstream mpg; Bitstream pdf; try (InputStream is = IOUtils.toInputStream(content, CharEncoding.UTF_8)) { csv = BitstreamBuilder.createBitstream(context, item, is) .withMimeType("text/csv").build(); + txt = BitstreamBuilder.createBitstream(context, item, is) + .withMimeType("text/plain").build(); jpg = BitstreamBuilder.createBitstream(context, item, is) .withMimeType("image/jpeg").build(); mpg = BitstreamBuilder.createBitstream(context, item, is) @@ -1368,11 +1380,13 @@ public void checkWildcardContentDispositionFormats() throws Exception { } context.restoreAuthSystemState(); - // All formats should be download only - verifyBitstreamDownload(csv, "text/csv;charset=UTF-8", true); - verifyBitstreamDownload(jpg, "image/jpeg;charset=UTF-8", true); - verifyBitstreamDownload(mpg, "video/mpeg;charset=UTF-8", true); - verifyBitstreamDownload(pdf, "application/pdf;charset=UTF-8", true); + // Only text/plain should download, while other formats should be served inline based on the configuration + verifyBitstreamDownload(csv, "text/csv;charset=UTF-8", false); + verifyBitstreamDownload(jpg, "image/jpeg;charset=UTF-8", false); + verifyBitstreamDownload(mpg, "video/mpeg;charset=UTF-8", false); + verifyBitstreamDownload(pdf, "application/pdf;charset=UTF-8", false); + // This is the only format not listed in the inline configuration, so it will be downloaded + verifyBitstreamDownload(txt, "text/plain;charset=UTF-8", true); } diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 0fcf0022bd1..783015efe72 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1376,21 +1376,20 @@ websvc.opensearch.max_num_of_items_per_request = 100 #### Content Inline Disposition Threshold #### # -# Set the max size of a bitstream that can be served inline -# Use -1 to force all bitstream to be served inline -webui.content_disposition_threshold = 8388608 - -#### Content Attachment Disposition Formats #### -# -# Set which mimetypes or file extensions will NOT be opened inline. -# Files with these mimetypes/extensions will always be downloaded, regardless of the threshold above. +# Set which mimetypes or file extensions are allowed to be opened inline in a user's browser. +# By default, all files will be downloaded, regardless of the threshold below, unless specified in this configuration. # NOTE: For security reasons, some file formats (e.g. HTML, XML, RDF, JS) will always be downloaded regardless -# of the settings here. This blocks these formats from executing embedded JavaScript when opened inline. -# For additional security, you may choose to set this to "*" to force all formats to always be downloaded -# (i.e. disables all formats from opening inline within the user's browser). +# of the settings here. This blocks these formats from executing embedded JavaScript when opened inline, protecting +# the site from potential XSS attacks. +# +# For example: enable PDF and common images formats to be opened in a user's browser. +#webui.content_disposition_inline = application/pdf, image/gif, image/jpeg, image/png + # -# By default, RTF is always downloaded because most browsers attempt to display it as plain text. -webui.content_disposition_format = text/richtext +# Set the max size (in bytes) of a bitstream that can be served inline. This setting only applies to formats +# specified in the "webui.content_disposition_inline" configuration above. +# Default = 8MB (8388608 bytes). Use -1 to ignore the size of file when serving it inline. +#webui.content_disposition_threshold = 8388608 #### Multi-file HTML document/site settings ##### # TODO: UNSUPPORTED in DSpace 7.0. May be re-added in a later release From abd7932e609bf8ce198a744c47a3f3c5aeccd59e Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 13 Mar 2026 13:55:09 -0500 Subject: [PATCH 836/939] Enable inline display by default for common, generally safe formats --- dspace/config/dspace.cfg | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 783015efe72..beb985eb472 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1382,8 +1382,9 @@ websvc.opensearch.max_num_of_items_per_request = 100 # of the settings here. This blocks these formats from executing embedded JavaScript when opened inline, protecting # the site from potential XSS attacks. # -# For example: enable PDF and common images formats to be opened in a user's browser. -#webui.content_disposition_inline = application/pdf, image/gif, image/jpeg, image/png +# For example: this setting defaults to enabling PDF and common image / audio / video formats to be opened +# (or potentially streamed) in a user's browser. +webui.content_disposition_inline = application/pdf, image/gif, image/jpeg, image/png, audio/mpeg, video/mpeg, video/mp4 # # Set the max size (in bytes) of a bitstream that can be served inline. This setting only applies to formats From 6c393799a89557f2e41a68511dba75f1359e11ac Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 24 Mar 2026 11:55:27 -0500 Subject: [PATCH 837/939] Fix bug with starting up localstack for S3 testing by pinning to 4.14.0 release. --- .../java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java index e2f68baf3ae..4478684abdb 100644 --- a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java +++ b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java @@ -71,7 +71,9 @@ * @author Luca Giamminonni (luca.giamminonni at 4science.com) */ public class S3BitStoreServiceIT extends AbstractIntegrationTestWithDatabase { - private static DockerImageName localstackName = DockerImageName.parse("localstack/localstack:stable"); + // Pin to version 4.1.4 of Docker image. Newer versions (starting with 2026-03-0) require an Auth Token. + // See https://blog.localstack.cloud/localstack-for-aws-release-2026-03-0 + private static DockerImageName localstackName = DockerImageName.parse("localstack/localstack:4.14.0"); @SuppressWarnings("resource") private static LocalStackContainer localstackContainer = new LocalStackContainer(localstackName).withServices("s3"); From 429df10fe7837d6c4155563adb4d59cdd2268d78 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 02:38:37 +0000 Subject: [PATCH 838/939] build(deps): bump pdfbox-version from 3.0.6 to 3.0.7 Bumps `pdfbox-version` from 3.0.6 to 3.0.7. Updates `org.apache.pdfbox:pdfbox` from 3.0.6 to 3.0.7 Updates `org.apache.pdfbox:fontbox` from 3.0.6 to 3.0.7 --- updated-dependencies: - dependency-name: org.apache.pdfbox:pdfbox dependency-version: 3.0.7 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.pdfbox:fontbox dependency-version: 3.0.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 24c818e2879..47d7d064e76 100644 --- a/pom.xml +++ b/pom.xml @@ -39,7 +39,7 @@ 9.4.58.v20250814 2.25.3 - 3.0.6 + 3.0.7 1.19.0 1.7.36 3.2.3 From 2149dd0454dcd3c205a74af62e1df0032b773475 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 02:39:33 +0000 Subject: [PATCH 839/939] build(deps): bump tika.version from 3.2.3 to 3.3.0 Bumps `tika.version` from 3.2.3 to 3.3.0. Updates `org.apache.tika:tika-core` from 3.2.3 to 3.3.0 - [Changelog](https://github.com/apache/tika/blob/main/CHANGES.txt) - [Commits](https://github.com/apache/tika/compare/3.2.3...3.3.0) Updates `org.apache.tika:tika-parsers-standard-package` from 3.2.3 to 3.3.0 --- updated-dependencies: - dependency-name: org.apache.tika:tika-core dependency-version: 3.3.0 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.apache.tika:tika-parsers-standard-package dependency-version: 3.3.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 24c818e2879..be658c6caa2 100644 --- a/pom.xml +++ b/pom.xml @@ -42,7 +42,7 @@ 3.0.6 1.19.0 1.7.36 - 3.2.3 + 3.3.0 From c234471aacb444909cae2ec5ad015a5f82587c2b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 02:47:03 +0000 Subject: [PATCH 840/939] build(deps): bump docker/build-push-action from 6 to 7 Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6 to 7. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v6...v7) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/reusable-docker-build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml index 46c7ceb1cd9..f35f1d80edd 100644 --- a/.github/workflows/reusable-docker-build.yml +++ b/.github/workflows/reusable-docker-build.yml @@ -148,7 +148,7 @@ jobs: - name: Build and push image to ${{ env.DOCKER_BUILD_REGISTRY }} if: ${{ ! matrix.isPr }} id: docker_build - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: build-contexts: | ${{ inputs.dockerfile_additional_contexts }} @@ -202,7 +202,7 @@ jobs: # NOTE: This step cannot be combined with the build above as it's a different type of output. - name: Build and push image to local TAR file if: ${{ matrix.arch == 'linux/amd64'}} - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: build-contexts: | ${{ inputs.dockerfile_additional_contexts }} From ed8e76e4067163c587842764d6db9865051dcd11 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 02:47:11 +0000 Subject: [PATCH 841/939] build(deps): bump docker/login-action from 3 to 4 Bumps [docker/login-action](https://github.com/docker/login-action) from 3 to 4. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/v3...v4) --- updated-dependencies: - dependency-name: docker/login-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/reusable-docker-build.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml index 46c7ceb1cd9..4f68779260b 100644 --- a/.github/workflows/reusable-docker-build.yml +++ b/.github/workflows/reusable-docker-build.yml @@ -116,7 +116,7 @@ jobs: # NOTE: This login occurs for BOTH non-PRs or PRs. PRs *must* also login to access private images from GHCR # during the build process - name: Login to ${{ env.DOCKER_BUILD_REGISTRY }} - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ${{ env.DOCKER_BUILD_REGISTRY }} username: ${{ github.repository_owner }} @@ -254,7 +254,7 @@ jobs: merge-multiple: true - name: Login to ${{ env.DOCKER_BUILD_REGISTRY }} - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ${{ env.DOCKER_BUILD_REGISTRY }} username: ${{ github.repository_owner }} @@ -317,7 +317,7 @@ jobs: # Login to source registry first, as this is where we are copying *from* - name: Login to ${{ env.DOCKER_BUILD_REGISTRY }} - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ${{ env.DOCKER_BUILD_REGISTRY }} username: ${{ github.repository_owner }} @@ -325,7 +325,7 @@ jobs: # Login to DockerHub, since this is where we are copying *to* - name: Login to DockerHub - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_ACCESS_TOKEN }} From ea7b5414fda84ab3b86a216d11630f6683093c49 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 02:47:19 +0000 Subject: [PATCH 842/939] build(deps): bump docker/setup-buildx-action from 3 to 4 Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3 to 4. - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/v3...v4) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/reusable-docker-build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml index 46c7ceb1cd9..05659d2e5c8 100644 --- a/.github/workflows/reusable-docker-build.yml +++ b/.github/workflows/reusable-docker-build.yml @@ -124,7 +124,7 @@ jobs: # https://github.com/docker/setup-buildx-action - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 # https://github.com/docker/metadata-action # Extract metadata used for Docker images in all build steps below @@ -261,7 +261,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Add Docker metadata for image id: meta From 5a19f6c6cbcfe25e57fbef7c4c2632d48e385487 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 02:47:33 +0000 Subject: [PATCH 843/939] build(deps): bump docker/metadata-action from 5 to 6 Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5 to 6. - [Release notes](https://github.com/docker/metadata-action/releases) - [Commits](https://github.com/docker/metadata-action/compare/v5...v6) --- updated-dependencies: - dependency-name: docker/metadata-action dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/reusable-docker-build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml index 46c7ceb1cd9..2d89485b8ea 100644 --- a/.github/workflows/reusable-docker-build.yml +++ b/.github/workflows/reusable-docker-build.yml @@ -130,7 +130,7 @@ jobs: # Extract metadata used for Docker images in all build steps below - name: Extract metadata (tags, labels) from GitHub for Docker image id: meta_build - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v6 with: images: ${{ env.DOCKER_BUILD_REGISTRY }}/${{ env.IMAGE_NAME }} tags: ${{ env.IMAGE_TAGS }} @@ -265,7 +265,7 @@ jobs: - name: Add Docker metadata for image id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v6 with: images: ${{ env.DOCKER_BUILD_REGISTRY }}/${{ env.IMAGE_NAME }} tags: ${{ env.IMAGE_TAGS }} @@ -309,7 +309,7 @@ jobs: # This recreates Docker tags for DockerHub - name: Add Docker metadata for image id: meta_dockerhub - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v6 with: images: ${{ env.IMAGE_NAME }} tags: ${{ env.IMAGE_TAGS }} From d59a67b7c317014fcded16a557043315952dbe0a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Apr 2026 10:20:03 +0200 Subject: [PATCH 844/939] build(deps): bump actions/upload-artifact from 4 to 7 (#12214) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 7. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4...v7) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 4 ++-- .github/workflows/reusable-docker-build.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6b0f086e789..c2d8d1198a0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -65,14 +65,14 @@ jobs: # (This artifact is downloadable at the bottom of any job's summary page) - name: Upload Results of ${{ matrix.type }} to Artifact if: ${{ failure() }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: ${{ matrix.type }} results path: ${{ matrix.resultsdir }} # Upload code coverage report to artifact, so that it can be shared with the 'codecov' job (see below) - name: Upload code coverage report to Artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: ${{ matrix.type }} coverage report path: 'dspace/target/site/jacoco-aggregate/jacoco.xml' diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml index 46c7ceb1cd9..e3e51c2e95a 100644 --- a/.github/workflows/reusable-docker-build.yml +++ b/.github/workflows/reusable-docker-build.yml @@ -179,7 +179,7 @@ jobs: # (The purpose of the combined manifest is to list both amd64 and arm64 builds under same tag) - name: Upload Docker build digest to artifact if: ${{ ! matrix.isPr }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: digests-${{ inputs.build_id }}-${{ env.ARCH_NAME }} path: /tmp/digests/* @@ -225,7 +225,7 @@ jobs: # This step is only done for AMD64, as that's the only image we use in our automated testing (at this time). - name: Upload local image TAR to artifact if: ${{ matrix.arch == 'linux/amd64'}} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: docker-image-${{ inputs.build_id }}-${{ env.ARCH_NAME }} path: /tmp/${{ inputs.build_id }}.tar From 97353bcb77f3a48e6ca9203b106c628d7a77458e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Apr 2026 10:21:06 +0200 Subject: [PATCH 845/939] build(deps-dev): bump org.apache.maven.plugins:maven-resources-plugin (#12196) Bumps the build-tools group with 1 update: [org.apache.maven.plugins:maven-resources-plugin](https://github.com/apache/maven-resources-plugin). Updates `org.apache.maven.plugins:maven-resources-plugin` from 3.4.0 to 3.5.0 - [Release notes](https://github.com/apache/maven-resources-plugin/releases) - [Commits](https://github.com/apache/maven-resources-plugin/compare/v3.4.0...maven-resources-plugin-3.5.0) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-resources-plugin dependency-version: 3.5.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: build-tools ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 24c818e2879..5a7e5bb9028 100644 --- a/pom.xml +++ b/pom.xml @@ -346,7 +346,7 @@ org.apache.maven.plugins maven-resources-plugin - 3.4.0 + 3.5.0 From 6bfadb6b77c56004da9fbf8342d3784541f67232 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Apr 2026 10:21:23 +0200 Subject: [PATCH 846/939] build(deps): bump commons-logging:commons-logging (#12203) Bumps the apache-commons group with 1 update: [commons-logging:commons-logging](https://github.com/apache/commons-logging). Updates `commons-logging:commons-logging` from 1.3.5 to 1.3.6 - [Changelog](https://github.com/apache/commons-logging/blob/master/RELEASE-NOTES.txt) - [Commits](https://github.com/apache/commons-logging/compare/rel/commons-logging-1.3.5...rel/commons-logging-1.3.6) --- updated-dependencies: - dependency-name: commons-logging:commons-logging dependency-version: 1.3.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: apache-commons ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5a7e5bb9028..e3d3530ba5f 100644 --- a/pom.xml +++ b/pom.xml @@ -1521,7 +1521,7 @@ commons-logging commons-logging - 1.3.5 + 1.3.6 org.apache.commons From d8ddc02fb8922bd43c5630468e7d0b8f101e6929 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Apr 2026 10:21:52 +0200 Subject: [PATCH 847/939] build(deps): bump the amazon-s3 group with 2 updates (#12202) Bumps the amazon-s3 group with 2 updates: software.amazon.awssdk:s3 and [software.amazon.awssdk.crt:aws-crt](https://github.com/awslabs/aws-crt-java). Updates `software.amazon.awssdk:s3` from 2.42.1 to 2.42.20 Updates `software.amazon.awssdk.crt:aws-crt` from 0.43.4 to 0.43.9 - [Release notes](https://github.com/awslabs/aws-crt-java/releases) - [Commits](https://github.com/awslabs/aws-crt-java/compare/v0.43.4...v0.43.9) --- updated-dependencies: - dependency-name: software.amazon.awssdk:s3 dependency-version: 2.42.20 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: amazon-s3 - dependency-name: software.amazon.awssdk.crt:aws-crt dependency-version: 0.43.9 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: amazon-s3 ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dspace-api/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index f1744feb4cb..92692336788 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -739,7 +739,7 @@ software.amazon.awssdk s3 - 2.42.1 + 2.42.20 software.amazon.awssdk @@ -755,7 +755,7 @@ software.amazon.awssdk.crt aws-crt - 0.43.4 + 0.43.9 From ba46f4864fa51deef726e8cb94059b8203b6bc31 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Apr 2026 10:22:10 +0200 Subject: [PATCH 848/939] build(deps): bump the fasterxml group with 2 updates (#12204) Bumps the fasterxml group with 2 updates: [com.fasterxml.jackson.core:jackson-core](https://github.com/FasterXML/jackson-core) and [com.fasterxml.jackson.core:jackson-databind](https://github.com/FasterXML/jackson). Updates `com.fasterxml.jackson.core:jackson-core` from 2.21.1 to 2.21.2 - [Commits](https://github.com/FasterXML/jackson-core/compare/jackson-core-2.21.1...jackson-core-2.21.2) Updates `com.fasterxml.jackson.core:jackson-databind` from 2.21.1 to 2.21.2 - [Commits](https://github.com/FasterXML/jackson/commits) Updates `com.fasterxml.jackson.core:jackson-databind` from 2.21.1 to 2.21.2 - [Commits](https://github.com/FasterXML/jackson/commits) --- updated-dependencies: - dependency-name: com.fasterxml.jackson.core:jackson-core dependency-version: 2.21.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: fasterxml - dependency-name: com.fasterxml.jackson.core:jackson-databind dependency-version: 2.21.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: fasterxml - dependency-name: com.fasterxml.jackson.core:jackson-databind dependency-version: 2.21.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: fasterxml ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e3d3530ba5f..34d339937a3 100644 --- a/pom.xml +++ b/pom.xml @@ -30,7 +30,7 @@ 3.11.1 2.31.0 - 2.21.1 + 2.21.2 2.21 1.3.2 2.3.1 From 168c55c76a810b847da9ca34b4059c8be6607fb3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Apr 2026 10:22:31 +0200 Subject: [PATCH 849/939] build(deps): bump the test-tools group with 7 updates (#12199) Bumps the test-tools group with 7 updates: | Package | From | To | | --- | --- | --- | | [org.testcontainers:testcontainers-localstack](https://github.com/testcontainers/testcontainers-java) | `2.0.3` | `2.0.4` | | [io.netty:netty-buffer](https://github.com/netty/netty) | `4.2.10.Final` | `4.2.11.Final` | | [io.netty:netty-transport](https://github.com/netty/netty) | `4.2.10.Final` | `4.2.11.Final` | | [io.netty:netty-transport-native-unix-common](https://github.com/netty/netty) | `4.2.10.Final` | `4.2.11.Final` | | [io.netty:netty-common](https://github.com/netty/netty) | `4.2.10.Final` | `4.2.11.Final` | | [io.netty:netty-handler](https://github.com/netty/netty) | `4.2.10.Final` | `4.2.11.Final` | | [io.netty:netty-codec](https://github.com/netty/netty) | `4.2.10.Final` | `4.2.11.Final` | Updates `org.testcontainers:testcontainers-localstack` from 2.0.3 to 2.0.4 - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/2.0.3...2.0.4) Updates `io.netty:netty-buffer` from 4.2.10.Final to 4.2.11.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.2.10.Final...netty-4.2.11.Final) Updates `io.netty:netty-transport` from 4.2.10.Final to 4.2.11.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.2.10.Final...netty-4.2.11.Final) Updates `io.netty:netty-transport-native-unix-common` from 4.2.10.Final to 4.2.11.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.2.10.Final...netty-4.2.11.Final) Updates `io.netty:netty-common` from 4.2.10.Final to 4.2.11.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.2.10.Final...netty-4.2.11.Final) Updates `io.netty:netty-handler` from 4.2.10.Final to 4.2.11.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.2.10.Final...netty-4.2.11.Final) Updates `io.netty:netty-codec` from 4.2.10.Final to 4.2.11.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.2.10.Final...netty-4.2.11.Final) --- updated-dependencies: - dependency-name: org.testcontainers:testcontainers-localstack dependency-version: 2.0.4 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-buffer dependency-version: 4.2.11.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-transport dependency-version: 4.2.11.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-transport-native-unix-common dependency-version: 4.2.11.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-common dependency-version: 4.2.11.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-handler dependency-version: 4.2.11.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-codec dependency-version: 4.2.11.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dspace-api/pom.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 92692336788..fbb51d6b7e3 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -857,7 +857,7 @@ org.testcontainers testcontainers-localstack - 2.0.3 + 2.0.4 test @@ -877,32 +877,32 @@ io.netty netty-buffer - 4.2.10.Final + 4.2.11.Final io.netty netty-transport - 4.2.10.Final + 4.2.11.Final io.netty netty-transport-native-unix-common - 4.2.10.Final + 4.2.11.Final io.netty netty-common - 4.2.10.Final + 4.2.11.Final io.netty netty-handler - 4.2.10.Final + 4.2.11.Final io.netty netty-codec - 4.2.10.Final + 4.2.11.Final org.apache.velocity From b1e7b4704a70ae6499903fc557ee713edf2b6125 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Apr 2026 10:22:39 +0200 Subject: [PATCH 850/939] build(deps): bump joda-time:joda-time from 2.14.0 to 2.14.1 (#12212) Bumps [joda-time:joda-time](https://github.com/JodaOrg/joda-time) from 2.14.0 to 2.14.1. - [Release notes](https://github.com/JodaOrg/joda-time/releases) - [Changelog](https://github.com/JodaOrg/joda-time/blob/main/RELEASE-NOTES.txt) - [Commits](https://github.com/JodaOrg/joda-time/compare/v2.14.0...v2.14.1) --- updated-dependencies: - dependency-name: joda-time:joda-time dependency-version: 2.14.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 34d339937a3..b585342152c 100644 --- a/pom.xml +++ b/pom.xml @@ -1546,7 +1546,7 @@ joda-time joda-time - 2.14.0 + 2.14.1 com.sun.mail From 847711a28642eadcef951d88ba914fbd70cfed8f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Apr 2026 10:22:43 +0200 Subject: [PATCH 851/939] build(deps): bump io.grpc:grpc-context from 1.79.0 to 1.80.0 (#12213) Bumps [io.grpc:grpc-context](https://github.com/grpc/grpc-java) from 1.79.0 to 1.80.0. - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.79.0...v1.80.0) --- updated-dependencies: - dependency-name: io.grpc:grpc-context dependency-version: 1.80.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b585342152c..8e0c6109d9a 100644 --- a/pom.xml +++ b/pom.xml @@ -1729,7 +1729,7 @@ io.grpc grpc-context - 1.79.0 + 1.80.0 com.google.http-client From 9aac8da4f1bb9fa02aa57310719144ba06a98d9c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Apr 2026 10:22:47 +0200 Subject: [PATCH 852/939] build(deps): bump jersey.version from 2.47 to 2.48 (#12205) Bumps `jersey.version` from 2.47 to 2.48. Updates `org.glassfish.jersey.core:jersey-client` from 2.47 to 2.48 Updates `org.glassfish.jersey.inject:jersey-hk2` from 2.47 to 2.48 Updates `org.glassfish.jersey.core:jersey-server` from 2.47 to 2.48 Updates `org.glassfish.jersey.containers:jersey-container-servlet` from 2.47 to 2.48 Updates `org.glassfish.jersey.media:jersey-media-json-jackson` from 2.47 to 2.48 Updates `org.glassfish.jersey.media:jersey-media-jaxb` from 2.47 to 2.48 Updates `org.glassfish.jersey.ext:jersey-spring5` from 2.47 to 2.48 --- updated-dependencies: - dependency-name: org.glassfish.jersey.core:jersey-client dependency-version: '2.48' dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.glassfish.jersey.inject:jersey-hk2 dependency-version: '2.48' dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.glassfish.jersey.core:jersey-server dependency-version: '2.48' dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.glassfish.jersey.containers:jersey-container-servlet dependency-version: '2.48' dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.glassfish.jersey.media:jersey-media-json-jackson dependency-version: '2.48' dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.glassfish.jersey.media:jersey-media-jaxb dependency-version: '2.48' dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.glassfish.jersey.ext:jersey-spring5 dependency-version: '2.48' dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8e0c6109d9a..480b3ffc8e4 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ https://jena.apache.org/documentation/migrate_jena2_jena3.html --> 2.13.0 - 2.47 + 2.48 UTF-8 From 51d4f8cb0353a94c732776e917737d78fa9529f3 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 13 Mar 2026 17:09:33 -0500 Subject: [PATCH 853/939] Update all Docker Compose scripts to allow for environment variable overrides from the shell. If override is not provided, the existing default value is still used. --- docker-compose-cli.yml | 4 ++-- docker-compose.yml | 12 ++++++------ .../main/docker-compose/cli.assetstore.yml | 2 +- dspace/src/main/docker-compose/cli.ingest.yml | 6 +++--- .../src/main/docker-compose/db.entities.yml | 2 +- dspace/src/main/docker-compose/db.restore.yml | 2 +- .../docker-compose/docker-compose-angular.yml | 19 ++++++++++--------- 7 files changed, 24 insertions(+), 23 deletions(-) diff --git a/docker-compose-cli.yml b/docker-compose-cli.yml index 77901c97618..8b47a012646 100644 --- a/docker-compose-cli.yml +++ b/docker-compose-cli.yml @@ -19,9 +19,9 @@ services: # dspace.dir: Must match with Dockerfile's DSPACE_INSTALL directory. dspace__P__dir: /dspace # db.url: Ensure we are using the 'dspacedb' image for our database - db__P__url: 'jdbc:postgresql://dspacedb:5432/dspace' + db__P__url: ${db__P__url:-jdbc:postgresql://dspacedb:5432/dspace} # solr.server: Ensure we are using the 'dspacesolr' image for Solr - solr__P__server: http://dspacesolr:8983/solr + solr__P__server: ${solr__P__server:-http://dspacesolr:8983/solr} volumes: # Keep DSpace assetstore directory between reboots - assetstore:/dspace/assetstore diff --git a/docker-compose.yml b/docker-compose.yml index 83474bde86a..ca56e369b6a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,16 +23,16 @@ services: # dspace__P__ui__P__url: http://localhost:4000 # Set SSR URL to the Docker container name so that UI can contact container directly in Production mode. # (This is necessary for docker-compose-angular.yml as it uses production mode by default) - dspace__P__server__P__ssr__P__url: http://dspace:8080/server - dspace__P__name: 'DSpace Started with Docker Compose' + dspace__P__server__P__ssr__P__url: ${dspace__P__server__P__ssr__P__url:-http://dspace:8080/server} + dspace__P__name: ${dspace__P__name:-DSpace Started with Docker Compose} # db.url: Ensure we are using the 'dspacedb' image for our database - db__P__url: 'jdbc:postgresql://dspacedb:5432/dspace' + db__P__url: ${db__P__url:-jdbc:postgresql://dspacedb:5432/dspace} # solr.server: Ensure we are using the 'dspacesolr' image for Solr - solr__P__server: http://dspacesolr:8983/solr + solr__P__server: ${solr__P__server:-http://dspacesolr:8983/solr} # proxies.trusted.ipranges: This setting is required for a REST API running in Docker to trust requests # from the host machine. This IP range MUST correspond to the 'dspacenet' subnet defined above. - proxies__P__trusted__P__ipranges: '172.23.0' - LOGGING_CONFIG: /dspace/config/log4j2-container.xml + proxies__P__trusted__P__ipranges: ${proxies__P__trusted__P__ipranges:-172.23.0} + LOGGING_CONFIG: ${LOGGING_CONFIG:-/dspace/config/log4j2-container.xml} image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace:${DSPACE_VER:-dspace-7_x-test}" build: context: . diff --git a/dspace/src/main/docker-compose/cli.assetstore.yml b/dspace/src/main/docker-compose/cli.assetstore.yml index 6563aa081eb..ab9527f8e4a 100644 --- a/dspace/src/main/docker-compose/cli.assetstore.yml +++ b/dspace/src/main/docker-compose/cli.assetstore.yml @@ -10,7 +10,7 @@ services: dspace-cli: environment: # This assetstore zip is available from https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data - - LOADASSETS=https://github.com/DSpace-Labs/AIP-Files/releases/download/demo-entities-data/assetstore.tar.gz + - LOADASSETS=${LOADASSETS:-https://github.com/DSpace-Labs/AIP-Files/releases/download/demo-entities-data/assetstore.tar.gz} entrypoint: - /bin/bash - '-c' diff --git a/dspace/src/main/docker-compose/cli.ingest.yml b/dspace/src/main/docker-compose/cli.ingest.yml index 6172147f195..b6b9886eb73 100644 --- a/dspace/src/main/docker-compose/cli.ingest.yml +++ b/dspace/src/main/docker-compose/cli.ingest.yml @@ -9,9 +9,9 @@ services: dspace-cli: environment: - - AIPZIP=https://github.com/DSpace-Labs/AIP-Files/raw/main/dogAndReport.zip - - ADMIN_EMAIL=test@test.edu - - AIPDIR=/tmp/aip-dir + - AIPZIP=${AIPZIP:-https://github.com/DSpace-Labs/AIP-Files/raw/main/dogAndReport.zip} + - ADMIN_EMAIL=${ADMIN_EMAIL:-test@test.edu} + - AIPDIR=${AIPDIR:-/tmp/aip-dir} entrypoint: - /bin/bash - '-c' diff --git a/dspace/src/main/docker-compose/db.entities.yml b/dspace/src/main/docker-compose/db.entities.yml index db28fef80b8..d1f17a4e04a 100644 --- a/dspace/src/main/docker-compose/db.entities.yml +++ b/dspace/src/main/docker-compose/db.entities.yml @@ -11,7 +11,7 @@ services: image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-dspace-7_x}-loadsql" environment: # This SQL is available from https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data - - LOADSQL=https://github.com/DSpace-Labs/AIP-Files/releases/download/demo-entities-data/dspace7-entities-data.sql + - LOADSQL=${LOADSQL:-https://github.com/DSpace-Labs/AIP-Files/releases/download/demo-entities-data/dspace7-entities-data.sql} dspace: ### OVERRIDE default 'entrypoint' in 'docker-compose.yml #### # Ensure that the database is ready BEFORE starting tomcat diff --git a/dspace/src/main/docker-compose/db.restore.yml b/dspace/src/main/docker-compose/db.restore.yml index 8584eee8272..9e8e1b5cda3 100644 --- a/dspace/src/main/docker-compose/db.restore.yml +++ b/dspace/src/main/docker-compose/db.restore.yml @@ -15,7 +15,7 @@ services: image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-dspace-7_x}-loadsql" environment: # Location where the dump SQL file will be available on the running container - - LOCALSQL=/tmp/pgdump.sql + - LOCALSQL=${LOCALSQL:-/tmp/pgdump.sql} volumes: # Volume which shares a local SQL file at "./pgdump.sql" to the running container # IF YOUR LOCAL FILE HAS A DIFFERENT NAME (or is in a different location), then change the "./pgdump.sql" diff --git a/dspace/src/main/docker-compose/docker-compose-angular.yml b/dspace/src/main/docker-compose/docker-compose-angular.yml index dcfc6190523..477d63096e1 100644 --- a/dspace/src/main/docker-compose/docker-compose-angular.yml +++ b/dspace/src/main/docker-compose/docker-compose-angular.yml @@ -18,16 +18,17 @@ services: depends_on: - dspace environment: - DSPACE_UI_SSL: 'false' - DSPACE_UI_HOST: dspace-angular - DSPACE_UI_PORT: '4000' - DSPACE_UI_NAMESPACE: / - DSPACE_REST_SSL: 'false' - DSPACE_REST_HOST: localhost - DSPACE_REST_PORT: 8080 - DSPACE_REST_NAMESPACE: /server + DSPACE_UI_SSL: ${DSPACE_UI_SSL:-false} + DSPACE_UI_HOST: ${DSPACE_UI_HOST:-dspace-angular} + DSPACE_UI_PORT: ${DSPACE_UI_PORT:-4000} + DSPACE_UI_NAMESPACE: ${DSPACE_UI_NAMESPACE:-/} + DSPACE_UI_BASEURL: ${DSPACE_UI_BASEURL:-http://localhost:4000} + DSPACE_REST_SSL: ${DSPACE_REST_SSL:-false} + DSPACE_REST_HOST: ${DSPACE_REST_HOST:-localhost} + DSPACE_REST_PORT: ${DSPACE_REST_PORT:-8080} + DSPACE_REST_NAMESPACE: ${DSPACE_REST_NAMESPACE:-/server} # Ensure SSR can use the 'dspace' Docker image directly (see docker-compose-rest.yml) - DSPACE_REST_SSRBASEURL: http://dspace:8080/server + DSPACE_REST_SSRBASEURL: ${DSPACE_REST_SSRBASEURL:-http://dspace:8080/server} image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-angular:${DSPACE_VER:-dspace-7_x-dist}" ports: - published: 4000 From 6bdac9da81740eb3d4c11786c59a6813709e5bfd Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Mon, 13 Apr 2026 08:58:33 +0200 Subject: [PATCH 854/939] Fix NPE in ORCID authentication when request is null (cherry picked from commit 4f3ae5f18201c30b2a2b257e144fed83019507b4) --- .../java/org/dspace/authenticate/OrcidAuthenticationBean.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/authenticate/OrcidAuthenticationBean.java b/dspace-api/src/main/java/org/dspace/authenticate/OrcidAuthenticationBean.java index a11bbfc867b..b5c238f3344 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/OrcidAuthenticationBean.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/OrcidAuthenticationBean.java @@ -120,7 +120,7 @@ public String loginPageURL(Context context, HttpServletRequest request, HttpServ @Override public boolean isUsed(Context context, HttpServletRequest request) { - return request.getAttribute(ORCID_AUTH_ATTRIBUTE) != null; + return request != null && request.getAttribute(ORCID_AUTH_ATTRIBUTE) != null; } @Override From ea11820af8c614f68f08a8e268b4e5aa328f7b24 Mon Sep 17 00:00:00 2001 From: JohnnyMendesC <177888064+JohnnyMendesC@users.noreply.github.com> Date: Wed, 27 Aug 2025 09:47:13 -0300 Subject: [PATCH 855/939] fix(#9663): Add configurable limit to CSV metadata import (cherry picked from commit 557c133ff021851ce2515ce76af91860e5f9edeb) --- .../dspace/app/bulkedit/MetadataImport.java | 10 +++ .../dspace/app/bulkedit/MetadataImportIT.java | 74 ++++++++++++++++++- dspace/config/modules/bulkedit.cfg | 7 +- 3 files changed, 86 insertions(+), 5 deletions(-) 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 7873f1b12ca..d6d00dfa3c3 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 @@ -358,6 +358,16 @@ public List runImport(Context c, boolean change, // Process each change rowCount = 1; + + int maxItems = configurationService.getIntProperty("bulkedit.import.max-items", 1000); + int numItems = toImport.size(); + if (numItems > maxItems && maxItems > 0) { + throw new MetadataImportException( + "Import contains " + numItems + " items, which exceeds the configured " + + "maximum of " + maxItems + ". You can change this limit by setting " + + "'bulkedit.import.max-items' in your local configuration."); + } + for (DSpaceCSVLine line : toImport) { // Resolve target references to other items populateRefAndRowMap(line, line.getID()); diff --git a/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java index de1dcc91c9a..158c3f23e6a 100644 --- a/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java +++ b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java @@ -10,6 +10,7 @@ import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertTrue; import static junit.framework.TestCase.fail; +import static org.junit.Assert.assertNotNull; import java.io.BufferedWriter; import java.io.File; @@ -43,6 +44,8 @@ import org.dspace.scripts.configuration.ScriptConfiguration; import org.dspace.scripts.factory.ScriptServiceFactory; import org.dspace.scripts.service.ScriptService; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; import org.junit.Before; import org.junit.Test; @@ -54,6 +57,8 @@ public class MetadataImportIT extends AbstractIntegrationTestWithDatabase { = EPersonServiceFactory.getInstance().getEPersonService(); private final RelationshipService relationshipService = ContentServiceFactory.getInstance().getRelationshipService(); + private final ConfigurationService configurationService + = DSpaceServicesFactory.getInstance().getConfigurationService(); private Collection collection; private Collection publicationCollection; @@ -305,4 +310,71 @@ public void performImportScript(String[] csv, boolean useTemplate) throws Except csvFile.delete(); } } -} + + @Test + public void metadataImportExceedsLimitTest() throws Exception { + configurationService.setProperty("bulkedit.import.max-items", 1); + String[] csv = {"id,collection,dc.title", + "+," + collection.getHandle() + ",\"Title 1\"", + "+," + collection.getHandle() + ",\"Title 2\""}; + File csvFile = File.createTempFile("dspace-test-import", "csv"); + try { + try (BufferedWriter out = new BufferedWriter( + new OutputStreamWriter(new FileOutputStream(csvFile), "UTF-8"))) { + for (String csvLine : csv) { + out.write(csvLine + "\n"); + } + } + String fileLocation = csvFile.getAbsolutePath(); + String[] args = new String[] {"metadata-import", "-f", fileLocation, "-e", eperson.getEmail(), "-s"}; + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + ScriptLauncher.handleScript( + args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); + + assertNotNull("The handler should contain an exception", + testDSpaceRunnableHandler.getException()); + + assertTrue("The exception cause should be a MetadataImportException", + testDSpaceRunnableHandler.getException().getCause() instanceof MetadataImportException); + + String exceptionMessage = testDSpaceRunnableHandler.getException().getCause().getMessage(); + assertTrue("The error message does not contain the expected text.", + exceptionMessage.contains("exceeds the configured maximum of 1")); + } finally { + csvFile.delete(); + } + } + + @Test + public void metadataImportWithItemCountBelowLimitTest() throws Exception { + configurationService.setProperty("bulkedit.import.max-items", 2); + String[] csv = {"id,collection,dc.title", + "+," + collection.getHandle() + ",\"Title 1\"", + "+," + collection.getHandle() + ",\"Title 2\""}; + performImportScript(csv); + Item importedItem1 = findItemByName("Title 1"); + Item importedItem2 = findItemByName("Title 2"); + assertNotNull("Should have imported Title 1", importedItem1); + assertNotNull("Should have imported Title 2", importedItem2); + } + + @Test + public void metadataImportWithLimitDisabledTest() throws Exception { + configurationService.setProperty("bulkedit.import.max-items", 0); + String[] csv = {"id,collection,dc.title", + "+," + collection.getHandle() + ",\"Title 1\"", + "+," + collection.getHandle() + ",\"Title 2\""}; + performImportScript(csv); + Item importedItem1 = findItemByName("Title 1"); + Item importedItem2 = findItemByName("Title 2"); + assertNotNull("Should have imported Title 1 with limit disabled", importedItem1); + assertNotNull("Should have imported Title 2 with limit disabled", importedItem2); + } + + @Test + public void metadataImportWithEmptyCSVTest() throws Exception { + String[] csv = {"id,collection,dc.title"}; + performImportScript(csv); + assertEquals(0, IteratorUtils.toList(itemService.findAll(context)).size()); + } +} \ No newline at end of file diff --git a/dspace/config/modules/bulkedit.cfg b/dspace/config/modules/bulkedit.cfg index e326e007f88..84a12926bc4 100644 --- a/dspace/config/modules/bulkedit.cfg +++ b/dspace/config/modules/bulkedit.cfg @@ -14,10 +14,9 @@ # The delimiter used to serarate authority data (defaults to a double colon ::) # bulkedit.authorityseparator = :: -# A hard limit of the number of items allowed to be edited in one go in the UI -# (does not apply to the command line version) -# TODO: UNSUPPORTED in DSpace 7.0 -# bulkedit.gui-item-limit = 20 +# A hard limit on the number of items allowed to be imported via the UI. +# To disable this limit, set this value to 0. Defaults to 1000. +bulkedit.import.max-items = 1000 # Metadata elements to exclude when exporting via the user interfaces, or when using the # command line version and not using the -a (all) option. From f66781bb6c12e87bb8c9dae13dbd2af583dced46 Mon Sep 17 00:00:00 2001 From: JohnnyMendesC <177888064+JohnnyMendesC@users.noreply.github.com> Date: Wed, 27 Aug 2025 14:05:17 -0300 Subject: [PATCH 856/939] refactor: Rename configuration property per review feedback (cherry picked from commit 4da59460955b1045eb038aab8eb86af5c2987eff) --- .../main/java/org/dspace/app/bulkedit/MetadataImport.java | 4 ++-- .../test/java/org/dspace/app/bulkedit/MetadataImportIT.java | 6 +++--- dspace/config/modules/bulkedit.cfg | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) 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 d6d00dfa3c3..fb5657a9e3c 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 @@ -359,13 +359,13 @@ public List runImport(Context c, boolean change, // Process each change rowCount = 1; - int maxItems = configurationService.getIntProperty("bulkedit.import.max-items", 1000); + int maxItems = configurationService.getIntProperty("bulkedit.import.max.items", 1000); int numItems = toImport.size(); if (numItems > maxItems && maxItems > 0) { throw new MetadataImportException( "Import contains " + numItems + " items, which exceeds the configured " + "maximum of " + maxItems + ". You can change this limit by setting " - + "'bulkedit.import.max-items' in your local configuration."); + + "'bulkedit.import.max.items' in your local configuration."); } for (DSpaceCSVLine line : toImport) { diff --git a/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java index 158c3f23e6a..9f28834eff3 100644 --- a/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java +++ b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java @@ -313,7 +313,7 @@ public void performImportScript(String[] csv, boolean useTemplate) throws Except @Test public void metadataImportExceedsLimitTest() throws Exception { - configurationService.setProperty("bulkedit.import.max-items", 1); + configurationService.setProperty("bulkedit.import.max.items", 1); String[] csv = {"id,collection,dc.title", "+," + collection.getHandle() + ",\"Title 1\"", "+," + collection.getHandle() + ",\"Title 2\""}; @@ -347,7 +347,7 @@ public void metadataImportExceedsLimitTest() throws Exception { @Test public void metadataImportWithItemCountBelowLimitTest() throws Exception { - configurationService.setProperty("bulkedit.import.max-items", 2); + configurationService.setProperty("bulkedit.import.max.items", 2); String[] csv = {"id,collection,dc.title", "+," + collection.getHandle() + ",\"Title 1\"", "+," + collection.getHandle() + ",\"Title 2\""}; @@ -360,7 +360,7 @@ public void metadataImportWithItemCountBelowLimitTest() throws Exception { @Test public void metadataImportWithLimitDisabledTest() throws Exception { - configurationService.setProperty("bulkedit.import.max-items", 0); + configurationService.setProperty("bulkedit.import.max.items", 0); String[] csv = {"id,collection,dc.title", "+," + collection.getHandle() + ",\"Title 1\"", "+," + collection.getHandle() + ",\"Title 2\""}; diff --git a/dspace/config/modules/bulkedit.cfg b/dspace/config/modules/bulkedit.cfg index 84a12926bc4..498d051bc6b 100644 --- a/dspace/config/modules/bulkedit.cfg +++ b/dspace/config/modules/bulkedit.cfg @@ -16,7 +16,7 @@ # A hard limit on the number of items allowed to be imported via the UI. # To disable this limit, set this value to 0. Defaults to 1000. -bulkedit.import.max-items = 1000 +bulkedit.import.max.items = 1000 # Metadata elements to exclude when exporting via the user interfaces, or when using the # command line version and not using the -a (all) option. From df75357b54deb918f5871f84b6629de90b281afc Mon Sep 17 00:00:00 2001 From: Nathan Buckingham Date: Mon, 25 Aug 2025 21:21:36 -0400 Subject: [PATCH 857/939] 133543: DSpaceCSV respect metadata.hide properties (cherry picked from commit ff372aa0b97ddecdd927416169640e38b2e754e5) --- .../org/dspace/app/bulkedit/DSpaceCSV.java | 50 ++++++--- .../app/util/MetadataExposureServiceImpl.java | 2 +- .../{DSpaceCSVTest.java => DSpaceCSVIT.java} | 104 +++++++++++++++++- dspace/config/modules/bulkedit.cfg | 3 + 4 files changed, 141 insertions(+), 18 deletions(-) rename dspace-api/src/test/java/org/dspace/app/bulkedit/{DSpaceCSVTest.java => DSpaceCSVIT.java} (65%) diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java index 3533a2397b3..89caa6c1528 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java @@ -25,6 +25,7 @@ import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; +import org.dspace.app.util.MetadataExposureServiceImpl; import org.dspace.authority.AuthorityValue; import org.dspace.authority.factory.AuthorityServiceFactory; import org.dspace.authority.service.AuthorityValueService; @@ -321,20 +322,7 @@ protected void init() { // Set the metadata fields to ignore ignore = new HashMap<>(); - // Specify default values - String[] defaultValues = - new String[] { - "dc.date.accessioned", "dc.date.available", "dc.date.updated", "dc.description.provenance" - }; - String[] toIgnoreArray = - DSpaceServicesFactory.getInstance() - .getConfigurationService() - .getArrayProperty("bulkedit.ignore-on-export", defaultValues); - for (String toIgnoreString : toIgnoreArray) { - if (!"".equals(toIgnoreString.trim())) { - ignore.put(toIgnoreString.trim(), toIgnoreString.trim()); - } - } + getConfiguredIgnoreFields(); } /** @@ -352,6 +340,40 @@ public boolean hasActions() { return false; } + /** + * Sets the ignored fields with 'bulkedit.ignore-on-export' + * + * Also adds 'metadata.hide.*' fields to ignored if 'bulkedit.ignore-on-export.include-metadata-hide' is true + */ + private void getConfiguredIgnoreFields() { + // Specify default values + String[] defaultValues = + new String[] { + "dc.date.accessioned", "dc.date.available", "dc.date.updated", "dc.description.provenance" + }; + String[] toIgnoreArray = + DSpaceServicesFactory.getInstance() + .getConfigurationService() + .getArrayProperty("bulkedit.ignore-on-export", defaultValues); + + boolean ignoreHiddenMetadata = DSpaceServicesFactory.getInstance().getConfigurationService() + .getBooleanProperty("bulkedit.ignore-on-export.include-metadata-hide", true); + if (ignoreHiddenMetadata) { + List hiddenMetadata = DSpaceServicesFactory.getInstance().getConfigurationService() + .getPropertyKeys(MetadataExposureServiceImpl.CONFIG_PREFIX); + for (String hiddenMetadataKey : hiddenMetadata) { + String key = hiddenMetadataKey.split(MetadataExposureServiceImpl.CONFIG_PREFIX)[1]; + ignore.put(key.trim(), key.trim()); + } + } + + for (String toIgnoreString : toIgnoreArray) { + if (!"".equals(toIgnoreString.trim())) { + ignore.put(toIgnoreString.trim(), toIgnoreString.trim()); + } + } + } + /** * Set the value separator for multiple values stored in one csv value. * diff --git a/dspace-api/src/main/java/org/dspace/app/util/MetadataExposureServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/util/MetadataExposureServiceImpl.java index 681867371a0..9a281d65775 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/MetadataExposureServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/util/MetadataExposureServiceImpl.java @@ -63,7 +63,7 @@ public class MetadataExposureServiceImpl implements MetadataExposureService { protected Map> hiddenElementSets = null; protected Map>> hiddenElementMaps = null; - protected final String CONFIG_PREFIX = "metadata.hide."; + public static final String CONFIG_PREFIX = "metadata.hide."; @Autowired(required = true) protected AuthorizeService authorizeService; diff --git a/dspace-api/src/test/java/org/dspace/app/bulkedit/DSpaceCSVTest.java b/dspace-api/src/test/java/org/dspace/app/bulkedit/DSpaceCSVIT.java similarity index 65% rename from dspace-api/src/test/java/org/dspace/app/bulkedit/DSpaceCSVTest.java rename to dspace-api/src/test/java/org/dspace/app/bulkedit/DSpaceCSVIT.java index 21a1a67dde2..f4e1e7f2892 100644 --- a/dspace-api/src/test/java/org/dspace/app/bulkedit/DSpaceCSVTest.java +++ b/dspace-api/src/test/java/org/dspace/app/bulkedit/DSpaceCSVIT.java @@ -9,6 +9,9 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; import java.io.BufferedWriter; @@ -20,7 +23,15 @@ import org.apache.commons.io.FileUtils; import org.apache.logging.log4j.Logger; -import org.dspace.AbstractUnitTest; +import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.junit.Before; import org.junit.Test; @@ -29,11 +40,39 @@ * * @author Stuart Lewis */ -public class DSpaceCSVTest extends AbstractUnitTest { +public class DSpaceCSVIT extends AbstractIntegrationTestWithDatabase { /** * log4j category */ - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(DSpaceCSVTest.class); + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(DSpaceCSVIT.class); + + ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + + Item testItem; + + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + configurationService.addPropertyValue("metadata.hide.dc.subject", true); + + Collection parentCollection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Parent Collection") + .build(); + + testItem = ItemBuilder.createItem(context, parentCollection).withTitle("Test Item") + .withMetadata("dc", "description", "provenance", "provenance") + .withMetadata("dc", "subject", null, "hidden subject") + .build(); + + context.restoreAuthSystemState(); + } /** * Test the reading and parsing of CSV files @@ -147,4 +186,63 @@ public void testDSpaceCSV() { fail("IO Error while creating test CSV file"); } } + + /** + * Test the hidden metadata for csv is respected + * + */ + @Test + public void testHiddenDspaceCSV() throws Exception { + + DSpaceCSV dSpaceCSV = new DSpaceCSV(false); + + dSpaceCSV.addItem(testItem); + + List lines = dSpaceCSV.getCSVLines(); + + assertThat(lines.size(), equalTo(1)); + + DSpaceCSVLine line = lines.get(0); + + List subject = line.get("dc.subject"); + List provenance = line.get("dc.description.provenance"); + List title = line.get("dc.title"); + + assertNull(subject); + assertNull(provenance); + assertNotNull(title); + assertEquals("Test Item", title.get(0)); + + } + + /** + * Test the hidden metadata is still shown when force is applied. + * + */ + @Test + public void testHiddenDspaceForceCSV() throws Exception { + + DSpaceCSV dSpaceCSV = new DSpaceCSV(true); + + dSpaceCSV.addItem(testItem); + + List lines = dSpaceCSV.getCSVLines(); + + assertThat(lines.size(), equalTo(1)); + + DSpaceCSVLine line = lines.get(0); + + List subject = line.get("dc.subject"); + List provenance = line.get("dc.description.provenance"); + List title = line.get("dc.title"); + + assertNotNull(subject); + assertNotNull(provenance); + assertEquals("hidden subject", subject.get(0)); + assertEquals("provenance", provenance.get(0)); + assertNotNull(title); + assertEquals("Test Item", title.get(0)); + + } + } diff --git a/dspace/config/modules/bulkedit.cfg b/dspace/config/modules/bulkedit.cfg index 498d051bc6b..7a9685d09ac 100644 --- a/dspace/config/modules/bulkedit.cfg +++ b/dspace/config/modules/bulkedit.cfg @@ -44,3 +44,6 @@ bulkedit.change.commit.count = 100 # Recommend to keep this at a feasible number, as exporting large amounts of items can be resource intensive # If not set, this will default to 500 items # bulkedit.export.max.items = 500 + +# Bulkedit setting to add metadata.hide fields to ignored fields defaults to true +# bulkedit.ignore-on-export.include-metadata-hide = true From bc28ded2f7533808167403d9ff89a7ff9104e7b0 Mon Sep 17 00:00:00 2001 From: Pascal-Nicolas Becker Date: Thu, 11 Dec 2025 12:11:54 +0100 Subject: [PATCH 858/939] Curator does not support items without handles The curation task CreateMissingIdentifiers should create all identifiers an item misses. Unfortunately it will not be started, if the item does not have a handle, as the curator falls back to handles, even if a uuid is given as identifier. This PR changes the Curator to check if an identifier is a uuid if no handle was found. I added a test to the CreateMissingIdentifiersIT. It would probably be better to add a test to the Curator, but the CreateMissingIdentifier task seems to be the most likely target for such a scenario. (cherry picked from commit 4a23b71758cf8d2fd3d0c1c3718f30b13d3452d1) --- .../dspace/curate/AbstractCurationTask.java | 26 +++++++- .../main/java/org/dspace/curate/Curator.java | 18 ++++++ .../general/CreateMissingIdentifiersIT.java | 60 +++++++++++++++++++ 3 files changed, 103 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/curate/AbstractCurationTask.java b/dspace-api/src/main/java/org/dspace/curate/AbstractCurationTask.java index fa16d273695..ca370bc68fe 100644 --- a/dspace-api/src/main/java/org/dspace/curate/AbstractCurationTask.java +++ b/dspace-api/src/main/java/org/dspace/curate/AbstractCurationTask.java @@ -11,8 +11,13 @@ import java.sql.SQLException; import java.util.Iterator; import java.util.List; +import java.util.UUID; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.app.util.factory.UtilServiceFactory; +import org.dspace.app.util.service.DSpaceObjectUtils; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.DSpaceObject; @@ -42,6 +47,8 @@ public abstract class AbstractCurationTask implements CurationTask { protected ItemService itemService; protected HandleService handleService; protected ConfigurationService configurationService; + protected DSpaceObjectUtils dspaceObjectUtils; + private static final Logger log = LogManager.getLogger(); @Override public void init(Curator curator, String taskId) throws IOException { @@ -51,6 +58,7 @@ public void init(Curator curator, String taskId) throws IOException { itemService = ContentServiceFactory.getInstance().getItemService(); handleService = HandleServiceFactory.getInstance().getHandleService(); configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + dspaceObjectUtils = UtilServiceFactory.getInstance().getDSpaceObjectUtils(); } @Override @@ -167,8 +175,24 @@ public int perform(Context ctx, String id) throws IOException { * @throws IOException if IO error */ protected DSpaceObject dereference(Context ctx, String id) throws IOException { + // the identifier can be a handle or uuid. Try handle first try { - return handleService.resolveToObject(ctx, id); + DSpaceObject dso = handleService.resolveToObject(ctx, id); + // if the id did not resolve to a handle, check if it is a uuid + if (dso == null) { + UUID uuid = null; + try { + uuid = UUID.fromString(id); + } catch (IllegalArgumentException iae) { + // no uuid, nothing to do here. + log.debug("ID {} is not a valid UUID", id); + } + if (uuid != null) { + dso = dspaceObjectUtils.findDSpaceObject(ctx, uuid); + } + } + // dso is either null or a DSpaceObject + return dso; } catch (SQLException sqlE) { throw new IOException(sqlE.getMessage(), sqlE); } diff --git a/dspace-api/src/main/java/org/dspace/curate/Curator.java b/dspace-api/src/main/java/org/dspace/curate/Curator.java index 4076fab5198..126f07aed22 100644 --- a/dspace-api/src/main/java/org/dspace/curate/Curator.java +++ b/dspace-api/src/main/java/org/dspace/curate/Curator.java @@ -15,9 +15,12 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.UUID; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dspace.app.util.factory.UtilServiceFactory; +import org.dspace.app.util.service.DSpaceObjectUtils; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.DSpaceObject; @@ -90,6 +93,7 @@ public static enum TxScope { protected TaskResolver resolver = new TaskResolver(); protected TxScope txScope = TxScope.OPEN; protected CommunityService communityService; + protected DSpaceObjectUtils dspaceObjectUtils; protected ItemService itemService; protected HandleService handleService; protected DSpaceRunnableHandler handler; @@ -109,6 +113,7 @@ public Curator(DSpaceRunnableHandler handler) { */ public Curator() { communityService = ContentServiceFactory.getInstance().getCommunityService(); + dspaceObjectUtils = UtilServiceFactory.getInstance().getDSpaceObjectUtils(); itemService = ContentServiceFactory.getInstance().getItemService(); handleService = HandleServiceFactory.getInstance().getHandleService(); resolver = new TaskResolver(); @@ -249,6 +254,19 @@ public void curate(Context c, String id) throws IOException { curationCtx.set(c); DSpaceObject dso = handleService.resolveToObject(c, id); + // check if the DSpace Object does not have a handle and must be resolved by its uuid + if (dso == null) { + UUID uuid = null; + try { + uuid = UUID.fromString(id); + } catch (IllegalArgumentException iae) { + // no uuid, nothing to do here. + log.debug("ID {} is not a valid UUID", id); + } + if (uuid != null) { + dso = dspaceObjectUtils.findDSpaceObject(c, uuid); + } + } if (dso != null) { curate(dso); } else { diff --git a/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java b/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java index 8ecddc6cd50..63157301b96 100644 --- a/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java +++ b/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java @@ -8,8 +8,12 @@ package org.dspace.ctask.general; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; @@ -19,6 +23,7 @@ import org.dspace.core.factory.CoreServiceFactory; import org.dspace.curate.Curator; import org.dspace.identifier.AbstractIdentifierProviderIT; +import org.dspace.identifier.IdentifierProvider; import org.dspace.identifier.VersionedHandleIdentifierProvider; import org.dspace.identifier.VersionedHandleIdentifierProviderWithCanonicalHandles; import org.dspace.services.ConfigurationService; @@ -85,4 +90,59 @@ public void testPerform() assertEquals("Curation should succeed", Curator.CURATE_SUCCESS, status); configurationService.setProperty(P_TASK_DEF, prevTaskDef); } + + @Test + public void testCreationOfMissingHandles() throws IOException { + // Must remove any cached named plugins before creating a new one + CoreServiceFactory.getInstance().getPluginService().clearNamedPluginClasses(); + // Define a new task dynamically + configurationService.setProperty(P_TASK_DEF, + CreateMissingIdentifiers.class.getCanonicalName() + " = " + TASK_NAME); + + // deactivate all identifier provider + List identifierProviders = identifierService.getProviders(); + List identifierProviderClasses = + identifierProviders.stream().map(Object::getClass).distinct().collect(Collectors.toList()); + for (Class identifierProviderClass : identifierProviderClasses) { + unregisterProvider(identifierProviderClass); + } + + try { + context.setCurrentUser(admin); + parentCommunity = CommunityBuilder.createCommunity(context) + .build(); + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .build(); + // create item and assert it did not got any handle + Item item = ItemBuilder.createItem(context, collection) + .build(); + assertNull("Internal error in createMissingIdentifiersIT: item should not have a handle", + item.getHandle()); + + // setup the curator + Curator curator = new Curator(); + curator.addTask(TASK_NAME); + + // register the default Handle Provider + registerProvider(VersionedHandleIdentifierProvider.class); + + /* + * Now, verify curate with default Handle Provider works + * (and that our re-registration of the default provider above was successful) + * Use the uuid as reference to the item as the curation system takes handles and uuid as cli arguments. + * Do not use the item reference, to be sure the curator and the curation task are able to work without + * handle. + */ + curator.curate(context, item.getID().toString()); + int status = curator.getStatus(TASK_NAME); + assertEquals("Curation should succeed", Curator.CURATE_SUCCESS, status); + // assure we got a handle + assertNotNull("Curation task CreateMissingIdentifiers, did not assign a handle.", item.getHandle()); + } finally { + // restore the identifierProviders for following tests + for (Class identifierProviderClass : identifierProviderClasses) { + registerProvider(identifierProviderClass); + } + } + } } From be1d8d2ababb91c52efec5a63f241f39a58ae9a0 Mon Sep 17 00:00:00 2001 From: Pascal-Nicolas Becker Date: Thu, 19 Feb 2026 16:47:24 +0100 Subject: [PATCH 859/939] Move code to find items by uuid or handle to DSpaceObjectUtil (cherry picked from commit bdc575ff2b65a632b10db463a7e2a1aca6e41828) --- .../app/util/DSpaceObjectUtilsImpl.java | 31 ++++++++++++++++ .../app/util/service/DSpaceObjectUtils.java | 13 +++++++ .../dspace/curate/AbstractCurationTask.java | 35 ++----------------- .../main/java/org/dspace/curate/Curator.java | 16 +-------- 4 files changed, 48 insertions(+), 47 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/util/DSpaceObjectUtilsImpl.java b/dspace-api/src/main/java/org/dspace/app/util/DSpaceObjectUtilsImpl.java index e3f2b0ea5fa..33621abd529 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/DSpaceObjectUtilsImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/util/DSpaceObjectUtilsImpl.java @@ -15,12 +15,15 @@ import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.DSpaceObjectService; import org.dspace.core.Context; +import org.dspace.handle.service.HandleService; import org.springframework.beans.factory.annotation.Autowired; public class DSpaceObjectUtilsImpl implements DSpaceObjectUtils { @Autowired private ContentServiceFactory contentServiceFactory; + @Autowired + private HandleService handleService; /** * Retrieve a DSpaceObject from its uuid. As this method need to iterate over all the different services that @@ -44,4 +47,32 @@ public DSpaceObject findDSpaceObject(Context context, UUID uuid) throws SQLExcep } return null; } + + /** + * Retrieve a DSpaceObject from its uuid or handle. As this method need to iterate over all the different services + * that support concrete class of DSpaceObject it has poor performance. Please consider the use of the direct + * service (ItemService, CommunityService, etc.) if you know in advance the type of DSpaceObject that you are + * looking for + * + * @param context DSpace context + * @param id the uuid or handle to lookup + * @return the DSpaceObject if any with the supplied uuid or handle + * @throws SQLException + */ + public DSpaceObject findDSpaceObject(Context context, String id) throws SQLException { + DSpaceObject dso = handleService.resolveToObject(context, id); + // if the id did not resolve to a handle, check if it is a uuid + if (dso == null) { + UUID uuid = null; + try { + uuid = UUID.fromString(id); + } catch (IllegalArgumentException iae) { + // nothing to do here. We check later fo empty uuids anyway + } + if (uuid != null) { + dso = findDSpaceObject(context, uuid); + } + } + return dso; + } } diff --git a/dspace-api/src/main/java/org/dspace/app/util/service/DSpaceObjectUtils.java b/dspace-api/src/main/java/org/dspace/app/util/service/DSpaceObjectUtils.java index e6a97004ef6..8088a4ca4de 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/service/DSpaceObjectUtils.java +++ b/dspace-api/src/main/java/org/dspace/app/util/service/DSpaceObjectUtils.java @@ -30,4 +30,17 @@ public interface DSpaceObjectUtils { * @throws SQLException */ public DSpaceObject findDSpaceObject(Context context, UUID uuid) throws SQLException; + + /** + * Retrieve a DSpaceObject from its uuid or handle. As this method need to iterate over all the different services + * that support concrete class of DSpaceObject it has poor performance. Please consider the use of the direct + * service (ItemService, CommunityService, etc.) if you know in advance the type of DSpaceObject that you are + * looking for + * + * @param context DSpace context + * @param id the uuid or handle to lookup + * @return the DSpaceObject if any with the supplied uuid or handle + * @throws SQLException + */ + public DSpaceObject findDSpaceObject(Context context, String id) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/curate/AbstractCurationTask.java b/dspace-api/src/main/java/org/dspace/curate/AbstractCurationTask.java index ca370bc68fe..4d59d46a804 100644 --- a/dspace-api/src/main/java/org/dspace/curate/AbstractCurationTask.java +++ b/dspace-api/src/main/java/org/dspace/curate/AbstractCurationTask.java @@ -11,7 +11,6 @@ import java.sql.SQLException; import java.util.Iterator; import java.util.List; -import java.util.UUID; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; @@ -161,41 +160,13 @@ protected void performItem(Item item) throws SQLException, IOException { @Override public int perform(Context ctx, String id) throws IOException { - DSpaceObject dso = dereference(ctx, id); - return (dso != null) ? perform(dso) : Curator.CURATE_FAIL; - } - - /** - * Returns a DSpaceObject for passed identifier, if it exists - * - * @param ctx DSpace context - * @param id canonical id of object - * @return dso - * DSpace object, or null if no object with id exists - * @throws IOException if IO error - */ - protected DSpaceObject dereference(Context ctx, String id) throws IOException { - // the identifier can be a handle or uuid. Try handle first + DSpaceObject dso = null; try { - DSpaceObject dso = handleService.resolveToObject(ctx, id); - // if the id did not resolve to a handle, check if it is a uuid - if (dso == null) { - UUID uuid = null; - try { - uuid = UUID.fromString(id); - } catch (IllegalArgumentException iae) { - // no uuid, nothing to do here. - log.debug("ID {} is not a valid UUID", id); - } - if (uuid != null) { - dso = dspaceObjectUtils.findDSpaceObject(ctx, uuid); - } - } - // dso is either null or a DSpaceObject - return dso; + dso = dspaceObjectUtils.findDSpaceObject(ctx, id); } catch (SQLException sqlE) { throw new IOException(sqlE.getMessage(), sqlE); } + return (dso != null) ? perform(dso) : Curator.CURATE_FAIL; } /** diff --git a/dspace-api/src/main/java/org/dspace/curate/Curator.java b/dspace-api/src/main/java/org/dspace/curate/Curator.java index 126f07aed22..91f984b72e5 100644 --- a/dspace-api/src/main/java/org/dspace/curate/Curator.java +++ b/dspace-api/src/main/java/org/dspace/curate/Curator.java @@ -15,7 +15,6 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.UUID; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -253,20 +252,7 @@ public void curate(Context c, String id) throws IOException { //Save the context on current execution thread curationCtx.set(c); - DSpaceObject dso = handleService.resolveToObject(c, id); - // check if the DSpace Object does not have a handle and must be resolved by its uuid - if (dso == null) { - UUID uuid = null; - try { - uuid = UUID.fromString(id); - } catch (IllegalArgumentException iae) { - // no uuid, nothing to do here. - log.debug("ID {} is not a valid UUID", id); - } - if (uuid != null) { - dso = dspaceObjectUtils.findDSpaceObject(c, uuid); - } - } + DSpaceObject dso = dspaceObjectUtils.findDSpaceObject(c,id); if (dso != null) { curate(dso); } else { From 055128290fb8e5fb1f39c2628e9c78b26d4554f7 Mon Sep 17 00:00:00 2001 From: Pascal-Nicolas Becker Date: Thu, 19 Feb 2026 16:47:53 +0100 Subject: [PATCH 860/939] Restore curation task configuration at the end of IT (cherry picked from commit 0cc9a4d89add6adba4fff2ae4e372b61e0197e2e) --- .../org/dspace/ctask/general/CreateMissingIdentifiersIT.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java b/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java index 63157301b96..5013f2cb669 100644 --- a/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java +++ b/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java @@ -96,6 +96,7 @@ public void testCreationOfMissingHandles() throws IOException { // Must remove any cached named plugins before creating a new one CoreServiceFactory.getInstance().getPluginService().clearNamedPluginClasses(); // Define a new task dynamically + String[] prevTaskDef = configurationService.getArrayProperty(P_TASK_DEF); configurationService.setProperty(P_TASK_DEF, CreateMissingIdentifiers.class.getCanonicalName() + " = " + TASK_NAME); @@ -143,6 +144,8 @@ public void testCreationOfMissingHandles() throws IOException { for (Class identifierProviderClass : identifierProviderClasses) { registerProvider(identifierProviderClass); } + // restore curation task configuration + configurationService.setProperty(P_TASK_DEF, prevTaskDef); } } } From a9d4f433d563fb0b41733790e2d0b401a98078b5 Mon Sep 17 00:00:00 2001 From: bram-atmire Date: Fri, 13 Mar 2026 15:49:43 +0100 Subject: [PATCH 861/939] Fix race condition in LegacyPluginServiceImpl named plugin cache The namedPluginClasses HashMap was accessed concurrently without synchronization, causing spurious "Name collision in named plugin" errors and potential HashMap corruption under concurrent load. - Replace HashMap with ConcurrentHashMap for thread-safe reads - Add synchronized to configureNamedPlugin() to prevent double init - Use putIfAbsent() in installNamedConfigs() to eliminate check-then-put race Fixes: https://github.com/DSpace/DSpace/issues/12117 Co-Authored-By: Claude Opus 4.6 (cherry picked from commit 7bacc317d669f24e637dab624b1019d2b5a04a1b) --- .../java/org/dspace/core/LegacyPluginServiceImpl.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/core/LegacyPluginServiceImpl.java b/dspace-api/src/main/java/org/dspace/core/LegacyPluginServiceImpl.java index e92ea137f31..e7c092e75c9 100644 --- a/dspace-api/src/main/java/org/dspace/core/LegacyPluginServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/core/LegacyPluginServiceImpl.java @@ -17,6 +17,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -219,10 +220,10 @@ private Object getAnonymousPlugin(String classname) // Map of named plugin classes, [intfc,name] -> class // Also contains intfc -> "marker" to mark when interface has been loaded. - private final Map namedPluginClasses = new HashMap<>(); + private final Map namedPluginClasses = new ConcurrentHashMap<>(); // load and cache configuration data for the given interface. - private void configureNamedPlugin(String iname) + private synchronized void configureNamedPlugin(String iname) throws ClassNotFoundException { int found = 0; @@ -307,11 +308,10 @@ private int installNamedConfigs(String iname, String classname, String names[]) int found = 0; for (int i = 0; i < names.length; ++i) { String key = iname + SEP + names[i]; - if (namedPluginClasses.containsKey(key)) { + String existing = namedPluginClasses.putIfAbsent(key, classname); + if (existing != null) { log.error("Name collision in named plugin, implementation class=\"" + classname + "\", name=\"" + names[i] + "\""); - } else { - namedPluginClasses.put(key, classname); } log.debug("Got Named Plugin, intfc=" + iname + ", name=" + names[i] + ", class=" + classname); ++found; From b0af037106020a8f46b0b5b7c2e8a4eab56de664 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 17 Apr 2026 16:32:32 -0500 Subject: [PATCH 862/939] Ensure docker-deploy waits for the backend to fully initialize before creating test content --- .github/workflows/docker.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 96a577bf3e3..b1862ae2e20 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -204,8 +204,11 @@ jobs: sleep 10 docker container ls # Create a test admin account. Load test data from a simple set of AIPs as defined in cli.ingest.yml + # NOTE: Before creating test data, we wait for the backend to become responsive by requesting it every 10 sec. + # Timeout after 5 minutes. This is done to ensure the backend is fully initialized before we create test data. - name: Load test data into Backend run: | + timeout 5m wget --retry-connrefused -t 0 --waitretry=10 http://127.0.0.1:8080/server/api docker compose -f docker-compose-cli.yml run --rm dspace-cli create-administrator -e test@test.edu -f admin -l user -p admin -c en docker compose -f docker-compose-cli.yml -f dspace/src/main/docker-compose/cli.ingest.yml run --rm dspace-cli # Verify backend started successfully. From 79a2de0a4c7e58147daa8caf2e15530fbed4ce34 Mon Sep 17 00:00:00 2001 From: Tina Schoenborn Date: Tue, 24 Feb 2026 17:34:22 +0100 Subject: [PATCH 863/939] add custom sql count query for signposting endpoint (cherry picked from commit e655bcf94746419288d97b50eebea1bdc6de66db) --- .../java/org/dspace/content/BundleServiceImpl.java | 4 ++++ .../main/java/org/dspace/content/dao/BundleDAO.java | 2 ++ .../org/dspace/content/dao/impl/BundleDAOImpl.java | 10 ++++++++++ .../java/org/dspace/content/service/BundleService.java | 8 ++++++++ .../signposting/service/impl/LinksetServiceImpl.java | 10 +++++++--- 5 files changed, 31 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java index 3ba90c8cc2a..8234846ed4f 100644 --- a/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java @@ -583,4 +583,8 @@ public Bundle findByLegacyId(Context context, int id) throws SQLException { public int countTotal(Context context) throws SQLException { return bundleDAO.countRows(context); } + + public int countBitstreams(Context context, Bundle bundle) throws SQLException { + return bundleDAO.countBitstreams(context, bundle); + } } diff --git a/dspace-api/src/main/java/org/dspace/content/dao/BundleDAO.java b/dspace-api/src/main/java/org/dspace/content/dao/BundleDAO.java index da7435d4664..99abd84eabc 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/BundleDAO.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/BundleDAO.java @@ -22,4 +22,6 @@ */ public interface BundleDAO extends DSpaceObjectLegacySupportDAO { int countRows(Context context) throws SQLException; + + int countBitstreams(Context context, Bundle bundle) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/BundleDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/BundleDAOImpl.java index 99163610849..ded13687aa7 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/BundleDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/BundleDAOImpl.java @@ -9,6 +9,7 @@ import java.sql.SQLException; +import jakarta.persistence.Query; import org.dspace.content.Bundle; import org.dspace.content.dao.BundleDAO; import org.dspace.core.AbstractHibernateDSODAO; @@ -31,4 +32,13 @@ protected BundleDAOImpl() { public int countRows(Context context) throws SQLException { return count(createQuery(context, "SELECT count(*) from Bundle")); } + + @Override + public int countBitstreams(Context context, Bundle bundle) throws SQLException { + Query query = createQuery( + context, "SELECT count(bi.id) from Bundle bu join bu.bitstreams bi where bu.id = :bundleID" + ); + query.setParameter("bundleID", bundle.getID()); + return count(query); + } } diff --git a/dspace-api/src/main/java/org/dspace/content/service/BundleService.java b/dspace-api/src/main/java/org/dspace/content/service/BundleService.java index 10d6613b2a2..31e0d79f490 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/BundleService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/BundleService.java @@ -146,4 +146,12 @@ public void moveBitstreamToBundle(Context context, Bundle targetBundle, Bitstre public void setOrder(Context context, Bundle bundle, UUID bitstreamIds[]) throws AuthorizeException, SQLException; int countTotal(Context context) throws SQLException; + + /** + * Returns the count of bitstreams for the given bundle, performance optimized. + * + * @param context DSpace Context + * @param bundle the bitstream bundle + */ + int countBitstreams(Context context, Bundle bundle) throws SQLException; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/service/impl/LinksetServiceImpl.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/service/impl/LinksetServiceImpl.java index 8ce992d1e88..a4434e14c36 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/service/impl/LinksetServiceImpl.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/signposting/service/impl/LinksetServiceImpl.java @@ -26,6 +26,7 @@ import org.dspace.content.Bundle; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; +import org.dspace.content.service.BundleService; import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; @@ -48,6 +49,9 @@ public class LinksetServiceImpl implements LinksetService { @Autowired protected ItemService itemService; + @Autowired + protected BundleService bundleService; + @Autowired private BitstreamMetadataReadPermissionEvaluatorPlugin bitstreamMetadataReadPermissionEvaluatorPlugin; @@ -86,7 +90,7 @@ public List createLinksetNodesForSingleLinkset( List linksetNodes = new ArrayList<>(); if (object.getType() == Constants.ITEM) { - int itemBitstreamsCount = countItemBitstreams((Item) object); + int itemBitstreamsCount = countItemBitstreams(context, (Item) object); // Do not include individual bitstream typed links if their number exceeds // the limit in the configuration. @@ -170,12 +174,12 @@ private Iterator getItemBitstreams(Context context, Item item) { } } - private int countItemBitstreams(Item item) { + private int countItemBitstreams(Context context, Item item) { try { int countBitstreams = 0; List bundles = itemService.getBundles(item, Constants.DEFAULT_BUNDLE_NAME); for (Bundle bundle: bundles) { - countBitstreams += bundle.getBitstreams().size(); + countBitstreams += bundleService.countBitstreams(context, bundle); } return countBitstreams; } catch (SQLException e) { From 659e15545bfbeade13b1bb465e0fbf756f01b114 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 23 Apr 2026 14:02:54 -0500 Subject: [PATCH 864/939] Replace jakarta.persistence.Query with javax.persistence.Query --- .../main/java/org/dspace/content/dao/impl/BundleDAOImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/BundleDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/BundleDAOImpl.java index ded13687aa7..08d6342e267 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/BundleDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/BundleDAOImpl.java @@ -8,8 +8,8 @@ package org.dspace.content.dao.impl; import java.sql.SQLException; +import javax.persistence.Query; -import jakarta.persistence.Query; import org.dspace.content.Bundle; import org.dspace.content.dao.BundleDAO; import org.dspace.core.AbstractHibernateDSODAO; From 92b0bf8b17d9cfefadc7108de09a5532dd407c0c Mon Sep 17 00:00:00 2001 From: Francesco Pio Scognamiglio Date: Wed, 15 May 2024 10:39:44 +0200 Subject: [PATCH 865/939] [DURACOM-437] add the mime.types file due to a known issue on the external library iiif-apis (cherry picked from commit 99340f8a3f5d68605b026483811facd2c10f2ebf) (cherry picked from commit f30117ea76a2a12a34b5ad8fc02852dd5d04f225) --- .../additions/src/main/resources/mime.types | 1863 +++++++++++++++++ 1 file changed, 1863 insertions(+) create mode 100644 dspace/modules/additions/src/main/resources/mime.types diff --git a/dspace/modules/additions/src/main/resources/mime.types b/dspace/modules/additions/src/main/resources/mime.types new file mode 100644 index 00000000000..6707e7e794f --- /dev/null +++ b/dspace/modules/additions/src/main/resources/mime.types @@ -0,0 +1,1863 @@ +# The mime.types file comes from the iiif-apis library https://github.com/dbmdz/iiif-apis/blob/main/src/main/resources/mime.types. +# The mime.types file is protected by the MIT license defined here https://github.com/dbmdz/iiif-apis/blob/main/LICENSE. + +# Due to this issue https://github.com/dbmdz/iiif-apis/issues/270 +# The mime.types file has been added to the DSpace sources. +# The mime.types file can be removed as soon as the ticket is closed. + + +# This file maps Internet media types to unique file extension(s). +# Although created for httpd, this file is used by many software systems +# and has been placed in the public domain for unlimited redisribution. +# +# The table below contains both registered and (common) unregistered types. +# A type that has no unique extension can be ignored -- they are listed +# here to guide configurations toward known types and to make it easier to +# identify "new" types. File extensions are also commonly used to indicate +# content languages and encodings, so choose them carefully. +# +# Internet media types should be registered as described in RFC 4288. +# The registry is at . +# +# MIME type (lowercased) Extensions +# ============================================ ========== +# application/1d-interleaved-parityfec +# application/3gpdash-qoe-report+xml +# application/3gpp-ims+xml +# application/a2l +# application/activemessage +# application/alto-costmap+json +# application/alto-costmapfilter+json +# application/alto-directory+json +# application/alto-endpointcost+json +# application/alto-endpointcostparams+json +# application/alto-endpointprop+json +# application/alto-endpointpropparams+json +# application/alto-error+json +# application/alto-networkmap+json +# application/alto-networkmapfilter+json +# application/aml +application/andrew-inset ez +# application/applefile +application/applixware aw +# application/atf +# application/atfx +application/atom+xml atom +application/atomcat+xml atomcat +# application/atomdeleted+xml +# application/atomicmail +application/atomsvc+xml atomsvc +# application/atxml +# application/auth-policy+xml +# application/bacnet-xdd+zip +# application/batch-smtp +# application/beep+xml +# application/calendar+json +# application/calendar+xml +# application/call-completion +# application/cals-1840 +# application/cbor +# application/ccmp+xml +application/ccxml+xml ccxml +# application/cdfx+xml +application/cdmi-capability cdmia +application/cdmi-container cdmic +application/cdmi-domain cdmid +application/cdmi-object cdmio +application/cdmi-queue cdmiq +# application/cdni +# application/cea +# application/cea-2018+xml +# application/cellml+xml +# application/cfw +# application/cms +# application/cnrp+xml +# application/coap-group+json +# application/commonground +# application/conference-info+xml +# application/cpl+xml +# application/csrattrs +# application/csta+xml +# application/cstadata+xml +# application/csvm+json +application/cu-seeme cu +# application/cybercash +# application/dash+xml +# application/dashdelta +application/davmount+xml davmount +# application/dca-rft +# application/dcd +# application/dec-dx +# application/dialog-info+xml +# application/dicom +# application/dii +# application/dit +# application/dns +application/docbook+xml dbk +# application/dskpp+xml +application/dssc+der dssc +application/dssc+xml xdssc +# application/dvcs +application/ecmascript ecma +# application/edi-consent +# application/edi-x12 +# application/edifact +# application/efi +# application/emergencycalldata.comment+xml +# application/emergencycalldata.deviceinfo+xml +# application/emergencycalldata.providerinfo+xml +# application/emergencycalldata.serviceinfo+xml +# application/emergencycalldata.subscriberinfo+xml +application/emma+xml emma +# application/emotionml+xml +# application/encaprtp +# application/epp+xml +application/epub+zip epub +# application/eshop +# application/example +application/exi exi +# application/fastinfoset +# application/fastsoap +# application/fdt+xml +# application/fits +application/font-tdpfr pfr +# application/framework-attributes+xml +# application/geo+json +application/gml+xml gml +application/gpx+xml gpx +application/gxf gxf +# application/gzip +# application/h224 +# application/held+xml +# application/http +application/hyperstudio stk +# application/ibe-key-request+xml +# application/ibe-pkg-reply+xml +# application/ibe-pp-data +# application/iges +# application/im-iscomposing+xml +# application/index +# application/index.cmd +# application/index.obj +# application/index.response +# application/index.vnd +application/inkml+xml ink inkml +# application/iotp +application/ipfix ipfix +# application/ipp +# application/isup +# application/its+xml +application/java-archive jar +application/java-serialized-object ser +application/java-vm class +application/javascript js +# application/jose +# application/jose+json +# application/jrd+json +application/json json +# application/json-patch+json +# application/json-seq +application/jsonml+json jsonml +# application/jwk+json +# application/jwk-set+json +# application/jwt +# application/kpml-request+xml +# application/kpml-response+xml +# application/ld+json +# application/lgr+xml +# application/link-format +# application/load-control+xml +application/lost+xml lostxml +# application/lostsync+xml +# application/lxf +application/mac-binhex40 hqx +application/mac-compactpro cpt +# application/macwriteii +application/mads+xml mads +application/marc mrc +application/marcxml+xml mrcx +application/mathematica ma nb mb +application/mathml+xml mathml +# application/mathml-content+xml +# application/mathml-presentation+xml +# application/mbms-associated-procedure-description+xml +# application/mbms-deregister+xml +# application/mbms-envelope+xml +# application/mbms-msk+xml +# application/mbms-msk-response+xml +# application/mbms-protection-description+xml +# application/mbms-reception-report+xml +# application/mbms-register+xml +# application/mbms-register-response+xml +# application/mbms-schedule+xml +# application/mbms-user-service-description+xml +application/mbox mbox +# application/media-policy-dataset+xml +# application/media_control+xml +application/mediaservercontrol+xml mscml +# application/merge-patch+json +application/metalink+xml metalink +application/metalink4+xml meta4 +application/mets+xml mets +# application/mf4 +# application/mikey +application/mods+xml mods +# application/moss-keys +# application/moss-signature +# application/mosskey-data +# application/mosskey-request +application/mp21 m21 mp21 +application/mp4 mp4s +# application/mpeg4-generic +# application/mpeg4-iod +# application/mpeg4-iod-xmt +# application/mrb-consumer+xml +# application/mrb-publish+xml +# application/msc-ivr+xml +# application/msc-mixer+xml +application/msword doc dot +application/mxf mxf +# application/nasdata +# application/news-checkgroups +# application/news-groupinfo +# application/news-transmission +# application/nlsml+xml +# application/nss +# application/ocsp-request +# application/ocsp-response +application/octet-stream bin dms lrf mar so dist distz pkg bpk dump elc deploy +application/oda oda +# application/odx +application/oebps-package+xml opf +application/ogg ogx +application/omdoc+xml omdoc +application/onenote onetoc onetoc2 onetmp onepkg +application/oxps oxps +# application/p2p-overlay+xml +# application/parityfec +application/patch-ops-error+xml xer +application/pdf pdf +# application/pdx +application/pgp-encrypted pgp +# application/pgp-keys +application/pgp-signature asc sig +application/pics-rules prf +# application/pidf+xml +# application/pidf-diff+xml +application/pkcs10 p10 +# application/pkcs12 +application/pkcs7-mime p7m p7c +application/pkcs7-signature p7s +application/pkcs8 p8 +application/pkix-attr-cert ac +application/pkix-cert cer +application/pkix-crl crl +application/pkix-pkipath pkipath +application/pkixcmp pki +application/pls+xml pls +# application/poc-settings+xml +application/postscript ai eps ps +# application/ppsp-tracker+json +# application/problem+json +# application/problem+xml +# application/provenance+xml +# application/prs.alvestrand.titrax-sheet +application/prs.cww cww +# application/prs.hpub+zip +# application/prs.nprend +# application/prs.plucker +# application/prs.rdf-xml-crypt +# application/prs.xsf+xml +application/pskc+xml pskcxml +# application/qsig +# application/raptorfec +# application/rdap+json +application/rdf+xml rdf +application/reginfo+xml rif +application/relax-ng-compact-syntax rnc +# application/remote-printing +# application/reputon+json +application/resource-lists+xml rl +application/resource-lists-diff+xml rld +# application/rfc+xml +# application/riscos +# application/rlmi+xml +application/rls-services+xml rs +application/rpki-ghostbusters gbr +application/rpki-manifest mft +application/rpki-roa roa +# application/rpki-updown +application/rsd+xml rsd +application/rss+xml rss +application/rtf rtf +# application/rtploopback +# application/rtx +# application/samlassertion+xml +# application/samlmetadata+xml +application/sbml+xml sbml +# application/scaip+xml +# application/scim+json +application/scvp-cv-request scq +application/scvp-cv-response scs +application/scvp-vp-request spq +application/scvp-vp-response spp +application/sdp sdp +# application/sep+xml +# application/sep-exi +# application/session-info +# application/set-payment +application/set-payment-initiation setpay +# application/set-registration +application/set-registration-initiation setreg +# application/sgml +# application/sgml-open-catalog +application/shf+xml shf +# application/sieve +# application/simple-filter+xml +# application/simple-message-summary +# application/simplesymbolcontainer +# application/slate +# application/smil +application/smil+xml smi smil +# application/smpte336m +# application/soap+fastinfoset +# application/soap+xml +application/sparql-query rq +application/sparql-results+xml srx +# application/spirits-event+xml +# application/sql +application/srgs gram +application/srgs+xml grxml +application/sru+xml sru +application/ssdl+xml ssdl +application/ssml+xml ssml +# application/tamp-apex-update +# application/tamp-apex-update-confirm +# application/tamp-community-update +# application/tamp-community-update-confirm +# application/tamp-error +# application/tamp-sequence-adjust +# application/tamp-sequence-adjust-confirm +# application/tamp-status-query +# application/tamp-status-response +# application/tamp-update +# application/tamp-update-confirm +application/tei+xml tei teicorpus +application/thraud+xml tfi +# application/timestamp-query +# application/timestamp-reply +application/timestamped-data tsd +# application/ttml+xml +# application/tve-trigger +# application/ulpfec +# application/urc-grpsheet+xml +# application/urc-ressheet+xml +# application/urc-targetdesc+xml +# application/urc-uisocketdesc+xml +# application/vcard+json +# application/vcard+xml +# application/vemmi +# application/vividence.scriptfile +# application/vnd.3gpp-prose+xml +# application/vnd.3gpp-prose-pc3ch+xml +# application/vnd.3gpp.access-transfer-events+xml +# application/vnd.3gpp.bsf+xml +# application/vnd.3gpp.mid-call+xml +application/vnd.3gpp.pic-bw-large plb +application/vnd.3gpp.pic-bw-small psb +application/vnd.3gpp.pic-bw-var pvb +# application/vnd.3gpp.sms +# application/vnd.3gpp.sms+xml +# application/vnd.3gpp.srvcc-ext+xml +# application/vnd.3gpp.srvcc-info+xml +# application/vnd.3gpp.state-and-event-info+xml +# application/vnd.3gpp.ussd+xml +# application/vnd.3gpp2.bcmcsinfo+xml +# application/vnd.3gpp2.sms +application/vnd.3gpp2.tcap tcap +# application/vnd.3lightssoftware.imagescal +application/vnd.3m.post-it-notes pwn +application/vnd.accpac.simply.aso aso +application/vnd.accpac.simply.imp imp +application/vnd.acucobol acu +application/vnd.acucorp atc acutc +application/vnd.adobe.air-application-installer-package+zip air +# application/vnd.adobe.flash.movie +application/vnd.adobe.formscentral.fcdt fcdt +application/vnd.adobe.fxp fxp fxpl +# application/vnd.adobe.partial-upload +application/vnd.adobe.xdp+xml xdp +application/vnd.adobe.xfdf xfdf +# application/vnd.aether.imp +# application/vnd.ah-barcode +application/vnd.ahead.space ahead +application/vnd.airzip.filesecure.azf azf +application/vnd.airzip.filesecure.azs azs +application/vnd.amazon.ebook azw +# application/vnd.amazon.mobi8-ebook +application/vnd.americandynamics.acc acc +application/vnd.amiga.ami ami +# application/vnd.amundsen.maze+xml +application/vnd.android.package-archive apk +# application/vnd.anki +application/vnd.anser-web-certificate-issue-initiation cii +application/vnd.anser-web-funds-transfer-initiation fti +application/vnd.antix.game-component atx +# application/vnd.apache.thrift.binary +# application/vnd.apache.thrift.compact +# application/vnd.apache.thrift.json +# application/vnd.api+json +application/vnd.apple.installer+xml mpkg +application/vnd.apple.mpegurl m3u8 +# application/vnd.arastra.swi +application/vnd.aristanetworks.swi swi +# application/vnd.artsquare +application/vnd.astraea-software.iota iota +application/vnd.audiograph aep +# application/vnd.autopackage +# application/vnd.avistar+xml +# application/vnd.balsamiq.bmml+xml +# application/vnd.balsamiq.bmpr +# application/vnd.bekitzur-stech+json +# application/vnd.biopax.rdf+xml +application/vnd.blueice.multipass mpm +# application/vnd.bluetooth.ep.oob +# application/vnd.bluetooth.le.oob +application/vnd.bmi bmi +application/vnd.businessobjects rep +# application/vnd.cab-jscript +# application/vnd.canon-cpdl +# application/vnd.canon-lips +# application/vnd.cendio.thinlinc.clientconf +# application/vnd.century-systems.tcp_stream +application/vnd.chemdraw+xml cdxml +# application/vnd.chess-pgn +application/vnd.chipnuts.karaoke-mmd mmd +application/vnd.cinderella cdy +# application/vnd.cirpack.isdn-ext +# application/vnd.citationstyles.style+xml +application/vnd.claymore cla +application/vnd.cloanto.rp9 rp9 +application/vnd.clonk.c4group c4g c4d c4f c4p c4u +application/vnd.cluetrust.cartomobile-config c11amc +application/vnd.cluetrust.cartomobile-config-pkg c11amz +# application/vnd.coffeescript +# application/vnd.collection+json +# application/vnd.collection.doc+json +# application/vnd.collection.next+json +# application/vnd.comicbook+zip +# application/vnd.commerce-battelle +application/vnd.commonspace csp +application/vnd.contact.cmsg cdbcmsg +# application/vnd.coreos.ignition+json +application/vnd.cosmocaller cmc +application/vnd.crick.clicker clkx +application/vnd.crick.clicker.keyboard clkk +application/vnd.crick.clicker.palette clkp +application/vnd.crick.clicker.template clkt +application/vnd.crick.clicker.wordbank clkw +application/vnd.criticaltools.wbs+xml wbs +application/vnd.ctc-posml pml +# application/vnd.ctct.ws+xml +# application/vnd.cups-pdf +# application/vnd.cups-postscript +application/vnd.cups-ppd ppd +# application/vnd.cups-raster +# application/vnd.cups-raw +# application/vnd.curl +application/vnd.curl.car car +application/vnd.curl.pcurl pcurl +# application/vnd.cyan.dean.root+xml +# application/vnd.cybank +application/vnd.dart dart +application/vnd.data-vision.rdz rdz +# application/vnd.debian.binary-package +application/vnd.dece.data uvf uvvf uvd uvvd +application/vnd.dece.ttml+xml uvt uvvt +application/vnd.dece.unspecified uvx uvvx +application/vnd.dece.zip uvz uvvz +application/vnd.denovo.fcselayout-link fe_launch +# application/vnd.desmume.movie +# application/vnd.dir-bi.plate-dl-nosuffix +# application/vnd.dm.delegation+xml +application/vnd.dna dna +# application/vnd.document+json +application/vnd.dolby.mlp mlp +# application/vnd.dolby.mobile.1 +# application/vnd.dolby.mobile.2 +# application/vnd.doremir.scorecloud-binary-document +application/vnd.dpgraph dpg +application/vnd.dreamfactory dfac +# application/vnd.drive+json +application/vnd.ds-keypoint kpxx +# application/vnd.dtg.local +# application/vnd.dtg.local.flash +# application/vnd.dtg.local.html +application/vnd.dvb.ait ait +# application/vnd.dvb.dvbj +# application/vnd.dvb.esgcontainer +# application/vnd.dvb.ipdcdftnotifaccess +# application/vnd.dvb.ipdcesgaccess +# application/vnd.dvb.ipdcesgaccess2 +# application/vnd.dvb.ipdcesgpdd +# application/vnd.dvb.ipdcroaming +# application/vnd.dvb.iptv.alfec-base +# application/vnd.dvb.iptv.alfec-enhancement +# application/vnd.dvb.notif-aggregate-root+xml +# application/vnd.dvb.notif-container+xml +# application/vnd.dvb.notif-generic+xml +# application/vnd.dvb.notif-ia-msglist+xml +# application/vnd.dvb.notif-ia-registration-request+xml +# application/vnd.dvb.notif-ia-registration-response+xml +# application/vnd.dvb.notif-init+xml +# application/vnd.dvb.pfr +application/vnd.dvb.service svc +# application/vnd.dxr +application/vnd.dynageo geo +# application/vnd.dzr +# application/vnd.easykaraoke.cdgdownload +# application/vnd.ecdis-update +application/vnd.ecowin.chart mag +# application/vnd.ecowin.filerequest +# application/vnd.ecowin.fileupdate +# application/vnd.ecowin.series +# application/vnd.ecowin.seriesrequest +# application/vnd.ecowin.seriesupdate +# application/vnd.emclient.accessrequest+xml +application/vnd.enliven nml +# application/vnd.enphase.envoy +# application/vnd.eprints.data+xml +application/vnd.epson.esf esf +application/vnd.epson.msf msf +application/vnd.epson.quickanime qam +application/vnd.epson.salt slt +application/vnd.epson.ssf ssf +# application/vnd.ericsson.quickcall +application/vnd.eszigno3+xml es3 et3 +# application/vnd.etsi.aoc+xml +# application/vnd.etsi.asic-e+zip +# application/vnd.etsi.asic-s+zip +# application/vnd.etsi.cug+xml +# application/vnd.etsi.iptvcommand+xml +# application/vnd.etsi.iptvdiscovery+xml +# application/vnd.etsi.iptvprofile+xml +# application/vnd.etsi.iptvsad-bc+xml +# application/vnd.etsi.iptvsad-cod+xml +# application/vnd.etsi.iptvsad-npvr+xml +# application/vnd.etsi.iptvservice+xml +# application/vnd.etsi.iptvsync+xml +# application/vnd.etsi.iptvueprofile+xml +# application/vnd.etsi.mcid+xml +# application/vnd.etsi.mheg5 +# application/vnd.etsi.overload-control-policy-dataset+xml +# application/vnd.etsi.pstn+xml +# application/vnd.etsi.sci+xml +# application/vnd.etsi.simservs+xml +# application/vnd.etsi.timestamp-token +# application/vnd.etsi.tsl+xml +# application/vnd.etsi.tsl.der +# application/vnd.eudora.data +application/vnd.ezpix-album ez2 +application/vnd.ezpix-package ez3 +# application/vnd.f-secure.mobile +# application/vnd.fastcopy-disk-image +application/vnd.fdf fdf +application/vnd.fdsn.mseed mseed +application/vnd.fdsn.seed seed dataless +# application/vnd.ffsns +# application/vnd.filmit.zfc +# application/vnd.fints +# application/vnd.firemonkeys.cloudcell +application/vnd.flographit gph +application/vnd.fluxtime.clip ftc +# application/vnd.font-fontforge-sfd +application/vnd.framemaker fm frame maker book +application/vnd.frogans.fnc fnc +application/vnd.frogans.ltf ltf +application/vnd.fsc.weblaunch fsc +application/vnd.fujitsu.oasys oas +application/vnd.fujitsu.oasys2 oa2 +application/vnd.fujitsu.oasys3 oa3 +application/vnd.fujitsu.oasysgp fg5 +application/vnd.fujitsu.oasysprs bh2 +# application/vnd.fujixerox.art-ex +# application/vnd.fujixerox.art4 +application/vnd.fujixerox.ddd ddd +application/vnd.fujixerox.docuworks xdw +application/vnd.fujixerox.docuworks.binder xbd +# application/vnd.fujixerox.docuworks.container +# application/vnd.fujixerox.hbpl +# application/vnd.fut-misnet +application/vnd.fuzzysheet fzs +application/vnd.genomatix.tuxedo txd +# application/vnd.geo+json +# application/vnd.geocube+xml +application/vnd.geogebra.file ggb +application/vnd.geogebra.tool ggt +application/vnd.geometry-explorer gex gre +application/vnd.geonext gxt +application/vnd.geoplan g2w +application/vnd.geospace g3w +# application/vnd.gerber +# application/vnd.globalplatform.card-content-mgt +# application/vnd.globalplatform.card-content-mgt-response +application/vnd.gmx gmx +application/vnd.google-earth.kml+xml kml +application/vnd.google-earth.kmz kmz +# application/vnd.gov.sk.e-form+xml +# application/vnd.gov.sk.e-form+zip +# application/vnd.gov.sk.xmldatacontainer+xml +application/vnd.grafeq gqf gqs +# application/vnd.gridmp +application/vnd.groove-account gac +application/vnd.groove-help ghf +application/vnd.groove-identity-message gim +application/vnd.groove-injector grv +application/vnd.groove-tool-message gtm +application/vnd.groove-tool-template tpl +application/vnd.groove-vcard vcg +# application/vnd.hal+json +application/vnd.hal+xml hal +application/vnd.handheld-entertainment+xml zmm +application/vnd.hbci hbci +# application/vnd.hcl-bireports +# application/vnd.hdt +# application/vnd.heroku+json +application/vnd.hhe.lesson-player les +application/vnd.hp-hpgl hpgl +application/vnd.hp-hpid hpid +application/vnd.hp-hps hps +application/vnd.hp-jlyt jlt +application/vnd.hp-pcl pcl +application/vnd.hp-pclxl pclxl +# application/vnd.httphone +application/vnd.hydrostatix.sof-data sfd-hdstx +# application/vnd.hyperdrive+json +# application/vnd.hzn-3d-crossword +# application/vnd.ibm.afplinedata +# application/vnd.ibm.electronic-media +application/vnd.ibm.minipay mpy +application/vnd.ibm.modcap afp listafp list3820 +application/vnd.ibm.rights-management irm +application/vnd.ibm.secure-container sc +application/vnd.iccprofile icc icm +# application/vnd.ieee.1905 +application/vnd.igloader igl +application/vnd.immervision-ivp ivp +application/vnd.immervision-ivu ivu +# application/vnd.ims.imsccv1p1 +# application/vnd.ims.imsccv1p2 +# application/vnd.ims.imsccv1p3 +# application/vnd.ims.lis.v2.result+json +# application/vnd.ims.lti.v2.toolconsumerprofile+json +# application/vnd.ims.lti.v2.toolproxy+json +# application/vnd.ims.lti.v2.toolproxy.id+json +# application/vnd.ims.lti.v2.toolsettings+json +# application/vnd.ims.lti.v2.toolsettings.simple+json +# application/vnd.informedcontrol.rms+xml +# application/vnd.informix-visionary +# application/vnd.infotech.project +# application/vnd.infotech.project+xml +# application/vnd.innopath.wamp.notification +application/vnd.insors.igm igm +application/vnd.intercon.formnet xpw xpx +application/vnd.intergeo i2g +# application/vnd.intertrust.digibox +# application/vnd.intertrust.nncp +application/vnd.intu.qbo qbo +application/vnd.intu.qfx qfx +# application/vnd.iptc.g2.catalogitem+xml +# application/vnd.iptc.g2.conceptitem+xml +# application/vnd.iptc.g2.knowledgeitem+xml +# application/vnd.iptc.g2.newsitem+xml +# application/vnd.iptc.g2.newsmessage+xml +# application/vnd.iptc.g2.packageitem+xml +# application/vnd.iptc.g2.planningitem+xml +application/vnd.ipunplugged.rcprofile rcprofile +application/vnd.irepository.package+xml irp +application/vnd.is-xpr xpr +application/vnd.isac.fcs fcs +application/vnd.jam jam +# application/vnd.japannet-directory-service +# application/vnd.japannet-jpnstore-wakeup +# application/vnd.japannet-payment-wakeup +# application/vnd.japannet-registration +# application/vnd.japannet-registration-wakeup +# application/vnd.japannet-setstore-wakeup +# application/vnd.japannet-verification +# application/vnd.japannet-verification-wakeup +application/vnd.jcp.javame.midlet-rms rms +application/vnd.jisp jisp +application/vnd.joost.joda-archive joda +# application/vnd.jsk.isdn-ngn +application/vnd.kahootz ktz ktr +application/vnd.kde.karbon karbon +application/vnd.kde.kchart chrt +application/vnd.kde.kformula kfo +application/vnd.kde.kivio flw +application/vnd.kde.kontour kon +application/vnd.kde.kpresenter kpr kpt +application/vnd.kde.kspread ksp +application/vnd.kde.kword kwd kwt +application/vnd.kenameaapp htke +application/vnd.kidspiration kia +application/vnd.kinar kne knp +application/vnd.koan skp skd skt skm +application/vnd.kodak-descriptor sse +application/vnd.las.las+xml lasxml +# application/vnd.liberty-request+xml +application/vnd.llamagraphics.life-balance.desktop lbd +application/vnd.llamagraphics.life-balance.exchange+xml lbe +application/vnd.lotus-1-2-3 123 +application/vnd.lotus-approach apr +application/vnd.lotus-freelance pre +application/vnd.lotus-notes nsf +application/vnd.lotus-organizer org +application/vnd.lotus-screencam scm +application/vnd.lotus-wordpro lwp +application/vnd.macports.portpkg portpkg +# application/vnd.mapbox-vector-tile +# application/vnd.marlin.drm.actiontoken+xml +# application/vnd.marlin.drm.conftoken+xml +# application/vnd.marlin.drm.license+xml +# application/vnd.marlin.drm.mdcf +# application/vnd.mason+json +# application/vnd.maxmind.maxmind-db +application/vnd.mcd mcd +application/vnd.medcalcdata mc1 +application/vnd.mediastation.cdkey cdkey +# application/vnd.meridian-slingshot +application/vnd.mfer mwf +application/vnd.mfmp mfm +# application/vnd.micro+json +application/vnd.micrografx.flo flo +application/vnd.micrografx.igx igx +# application/vnd.microsoft.portable-executable +# application/vnd.miele+json +application/vnd.mif mif +# application/vnd.minisoft-hp3000-save +# application/vnd.mitsubishi.misty-guard.trustweb +application/vnd.mobius.daf daf +application/vnd.mobius.dis dis +application/vnd.mobius.mbk mbk +application/vnd.mobius.mqy mqy +application/vnd.mobius.msl msl +application/vnd.mobius.plc plc +application/vnd.mobius.txf txf +application/vnd.mophun.application mpn +application/vnd.mophun.certificate mpc +# application/vnd.motorola.flexsuite +# application/vnd.motorola.flexsuite.adsi +# application/vnd.motorola.flexsuite.fis +# application/vnd.motorola.flexsuite.gotap +# application/vnd.motorola.flexsuite.kmr +# application/vnd.motorola.flexsuite.ttc +# application/vnd.motorola.flexsuite.wem +# application/vnd.motorola.iprm +application/vnd.mozilla.xul+xml xul +# application/vnd.ms-3mfdocument +application/vnd.ms-artgalry cil +# application/vnd.ms-asf +application/vnd.ms-cab-compressed cab +# application/vnd.ms-color.iccprofile +application/vnd.ms-excel xls xlm xla xlc xlt xlw +application/vnd.ms-excel.addin.macroenabled.12 xlam +application/vnd.ms-excel.sheet.binary.macroenabled.12 xlsb +application/vnd.ms-excel.sheet.macroenabled.12 xlsm +application/vnd.ms-excel.template.macroenabled.12 xltm +application/vnd.ms-fontobject eot +application/vnd.ms-htmlhelp chm +application/vnd.ms-ims ims +application/vnd.ms-lrm lrm +# application/vnd.ms-office.activex+xml +application/vnd.ms-officetheme thmx +# application/vnd.ms-opentype +# application/vnd.ms-package.obfuscated-opentype +application/vnd.ms-pki.seccat cat +application/vnd.ms-pki.stl stl +# application/vnd.ms-playready.initiator+xml +application/vnd.ms-powerpoint ppt pps pot +application/vnd.ms-powerpoint.addin.macroenabled.12 ppam +application/vnd.ms-powerpoint.presentation.macroenabled.12 pptm +application/vnd.ms-powerpoint.slide.macroenabled.12 sldm +application/vnd.ms-powerpoint.slideshow.macroenabled.12 ppsm +application/vnd.ms-powerpoint.template.macroenabled.12 potm +# application/vnd.ms-printdevicecapabilities+xml +# application/vnd.ms-printing.printticket+xml +# application/vnd.ms-printschematicket+xml +application/vnd.ms-project mpp mpt +# application/vnd.ms-tnef +# application/vnd.ms-windows.devicepairing +# application/vnd.ms-windows.nwprinting.oob +# application/vnd.ms-windows.printerpairing +# application/vnd.ms-windows.wsd.oob +# application/vnd.ms-wmdrm.lic-chlg-req +# application/vnd.ms-wmdrm.lic-resp +# application/vnd.ms-wmdrm.meter-chlg-req +# application/vnd.ms-wmdrm.meter-resp +application/vnd.ms-word.document.macroenabled.12 docm +application/vnd.ms-word.template.macroenabled.12 dotm +application/vnd.ms-works wps wks wcm wdb +application/vnd.ms-wpl wpl +application/vnd.ms-xpsdocument xps +# application/vnd.msa-disk-image +application/vnd.mseq mseq +# application/vnd.msign +# application/vnd.multiad.creator +# application/vnd.multiad.creator.cif +# application/vnd.music-niff +application/vnd.musician mus +application/vnd.muvee.style msty +application/vnd.mynfc taglet +# application/vnd.ncd.control +# application/vnd.ncd.reference +# application/vnd.nervana +# application/vnd.netfpx +application/vnd.neurolanguage.nlu nlu +# application/vnd.nintendo.nitro.rom +# application/vnd.nintendo.snes.rom +application/vnd.nitf ntf nitf +application/vnd.noblenet-directory nnd +application/vnd.noblenet-sealer nns +application/vnd.noblenet-web nnw +# application/vnd.nokia.catalogs +# application/vnd.nokia.conml+wbxml +# application/vnd.nokia.conml+xml +# application/vnd.nokia.iptv.config+xml +# application/vnd.nokia.isds-radio-presets +# application/vnd.nokia.landmark+wbxml +# application/vnd.nokia.landmark+xml +# application/vnd.nokia.landmarkcollection+xml +# application/vnd.nokia.n-gage.ac+xml +application/vnd.nokia.n-gage.data ngdat +application/vnd.nokia.n-gage.symbian.install n-gage +# application/vnd.nokia.ncd +# application/vnd.nokia.pcd+wbxml +# application/vnd.nokia.pcd+xml +application/vnd.nokia.radio-preset rpst +application/vnd.nokia.radio-presets rpss +application/vnd.novadigm.edm edm +application/vnd.novadigm.edx edx +application/vnd.novadigm.ext ext +# application/vnd.ntt-local.content-share +# application/vnd.ntt-local.file-transfer +# application/vnd.ntt-local.ogw_remote-access +# application/vnd.ntt-local.sip-ta_remote +# application/vnd.ntt-local.sip-ta_tcp_stream +application/vnd.oasis.opendocument.chart odc +application/vnd.oasis.opendocument.chart-template otc +application/vnd.oasis.opendocument.database odb +application/vnd.oasis.opendocument.formula odf +application/vnd.oasis.opendocument.formula-template odft +application/vnd.oasis.opendocument.graphics odg +application/vnd.oasis.opendocument.graphics-template otg +application/vnd.oasis.opendocument.image odi +application/vnd.oasis.opendocument.image-template oti +application/vnd.oasis.opendocument.presentation odp +application/vnd.oasis.opendocument.presentation-template otp +application/vnd.oasis.opendocument.spreadsheet ods +application/vnd.oasis.opendocument.spreadsheet-template ots +application/vnd.oasis.opendocument.text odt +application/vnd.oasis.opendocument.text-master odm +application/vnd.oasis.opendocument.text-template ott +application/vnd.oasis.opendocument.text-web oth +# application/vnd.obn +# application/vnd.oftn.l10n+json +# application/vnd.oipf.contentaccessdownload+xml +# application/vnd.oipf.contentaccessstreaming+xml +# application/vnd.oipf.cspg-hexbinary +# application/vnd.oipf.dae.svg+xml +# application/vnd.oipf.dae.xhtml+xml +# application/vnd.oipf.mippvcontrolmessage+xml +# application/vnd.oipf.pae.gem +# application/vnd.oipf.spdiscovery+xml +# application/vnd.oipf.spdlist+xml +# application/vnd.oipf.ueprofile+xml +# application/vnd.oipf.userprofile+xml +application/vnd.olpc-sugar xo +# application/vnd.oma-scws-config +# application/vnd.oma-scws-http-request +# application/vnd.oma-scws-http-response +# application/vnd.oma.bcast.associated-procedure-parameter+xml +# application/vnd.oma.bcast.drm-trigger+xml +# application/vnd.oma.bcast.imd+xml +# application/vnd.oma.bcast.ltkm +# application/vnd.oma.bcast.notification+xml +# application/vnd.oma.bcast.provisioningtrigger +# application/vnd.oma.bcast.sgboot +# application/vnd.oma.bcast.sgdd+xml +# application/vnd.oma.bcast.sgdu +# application/vnd.oma.bcast.simple-symbol-container +# application/vnd.oma.bcast.smartcard-trigger+xml +# application/vnd.oma.bcast.sprov+xml +# application/vnd.oma.bcast.stkm +# application/vnd.oma.cab-address-book+xml +# application/vnd.oma.cab-feature-handler+xml +# application/vnd.oma.cab-pcc+xml +# application/vnd.oma.cab-subs-invite+xml +# application/vnd.oma.cab-user-prefs+xml +# application/vnd.oma.dcd +# application/vnd.oma.dcdc +application/vnd.oma.dd2+xml dd2 +# application/vnd.oma.drm.risd+xml +# application/vnd.oma.group-usage-list+xml +# application/vnd.oma.lwm2m+json +# application/vnd.oma.lwm2m+tlv +# application/vnd.oma.pal+xml +# application/vnd.oma.poc.detailed-progress-report+xml +# application/vnd.oma.poc.final-report+xml +# application/vnd.oma.poc.groups+xml +# application/vnd.oma.poc.invocation-descriptor+xml +# application/vnd.oma.poc.optimized-progress-report+xml +# application/vnd.oma.push +# application/vnd.oma.scidm.messages+xml +# application/vnd.oma.xcap-directory+xml +# application/vnd.omads-email+xml +# application/vnd.omads-file+xml +# application/vnd.omads-folder+xml +# application/vnd.omaloc-supl-init +# application/vnd.onepager +# application/vnd.openblox.game+xml +# application/vnd.openblox.game-binary +# application/vnd.openeye.oeb +application/vnd.openofficeorg.extension oxt +# application/vnd.openxmlformats-officedocument.custom-properties+xml +# application/vnd.openxmlformats-officedocument.customxmlproperties+xml +# application/vnd.openxmlformats-officedocument.drawing+xml +# application/vnd.openxmlformats-officedocument.drawingml.chart+xml +# application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml +# application/vnd.openxmlformats-officedocument.drawingml.diagramcolors+xml +# application/vnd.openxmlformats-officedocument.drawingml.diagramdata+xml +# application/vnd.openxmlformats-officedocument.drawingml.diagramlayout+xml +# application/vnd.openxmlformats-officedocument.drawingml.diagramstyle+xml +# application/vnd.openxmlformats-officedocument.extended-properties+xml +# application/vnd.openxmlformats-officedocument.presentationml.commentauthors+xml +# application/vnd.openxmlformats-officedocument.presentationml.comments+xml +# application/vnd.openxmlformats-officedocument.presentationml.handoutmaster+xml +# application/vnd.openxmlformats-officedocument.presentationml.notesmaster+xml +# application/vnd.openxmlformats-officedocument.presentationml.notesslide+xml +application/vnd.openxmlformats-officedocument.presentationml.presentation pptx +# application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml +# application/vnd.openxmlformats-officedocument.presentationml.presprops+xml +application/vnd.openxmlformats-officedocument.presentationml.slide sldx +# application/vnd.openxmlformats-officedocument.presentationml.slide+xml +# application/vnd.openxmlformats-officedocument.presentationml.slidelayout+xml +# application/vnd.openxmlformats-officedocument.presentationml.slidemaster+xml +application/vnd.openxmlformats-officedocument.presentationml.slideshow ppsx +# application/vnd.openxmlformats-officedocument.presentationml.slideshow.main+xml +# application/vnd.openxmlformats-officedocument.presentationml.slideupdateinfo+xml +# application/vnd.openxmlformats-officedocument.presentationml.tablestyles+xml +# application/vnd.openxmlformats-officedocument.presentationml.tags+xml +application/vnd.openxmlformats-officedocument.presentationml.template potx +# application/vnd.openxmlformats-officedocument.presentationml.template.main+xml +# application/vnd.openxmlformats-officedocument.presentationml.viewprops+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.calcchain+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.connections+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.externallink+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.pivotcachedefinition+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.pivotcacherecords+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.pivottable+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.querytable+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.revisionheaders+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.revisionlog+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.sharedstrings+xml +application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx +# application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.sheetmetadata+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.tablesinglecells+xml +application/vnd.openxmlformats-officedocument.spreadsheetml.template xltx +# application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.usernames+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.volatiledependencies+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml +# application/vnd.openxmlformats-officedocument.theme+xml +# application/vnd.openxmlformats-officedocument.themeoverride+xml +# application/vnd.openxmlformats-officedocument.vmldrawing +# application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml +application/vnd.openxmlformats-officedocument.wordprocessingml.document docx +# application/vnd.openxmlformats-officedocument.wordprocessingml.document.glossary+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.fonttable+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml +application/vnd.openxmlformats-officedocument.wordprocessingml.template dotx +# application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.websettings+xml +# application/vnd.openxmlformats-package.core-properties+xml +# application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml +# application/vnd.openxmlformats-package.relationships+xml +# application/vnd.oracle.resource+json +# application/vnd.orange.indata +# application/vnd.osa.netdeploy +application/vnd.osgeo.mapguide.package mgp +# application/vnd.osgi.bundle +application/vnd.osgi.dp dp +application/vnd.osgi.subsystem esa +# application/vnd.otps.ct-kip+xml +# application/vnd.oxli.countgraph +# application/vnd.pagerduty+json +application/vnd.palm pdb pqa oprc +# application/vnd.panoply +# application/vnd.paos.xml +application/vnd.pawaafile paw +# application/vnd.pcos +application/vnd.pg.format str +application/vnd.pg.osasli ei6 +# application/vnd.piaccess.application-licence +application/vnd.picsel efif +application/vnd.pmi.widget wg +# application/vnd.poc.group-advertisement+xml +application/vnd.pocketlearn plf +application/vnd.powerbuilder6 pbd +# application/vnd.powerbuilder6-s +# application/vnd.powerbuilder7 +# application/vnd.powerbuilder7-s +# application/vnd.powerbuilder75 +# application/vnd.powerbuilder75-s +# application/vnd.preminet +application/vnd.previewsystems.box box +application/vnd.proteus.magazine mgz +application/vnd.publishare-delta-tree qps +application/vnd.pvi.ptid1 ptid +# application/vnd.pwg-multiplexed +# application/vnd.pwg-xhtml-print+xml +# application/vnd.qualcomm.brew-app-res +# application/vnd.quarantainenet +application/vnd.quark.quarkxpress qxd qxt qwd qwt qxl qxb +# application/vnd.quobject-quoxdocument +# application/vnd.radisys.moml+xml +# application/vnd.radisys.msml+xml +# application/vnd.radisys.msml-audit+xml +# application/vnd.radisys.msml-audit-conf+xml +# application/vnd.radisys.msml-audit-conn+xml +# application/vnd.radisys.msml-audit-dialog+xml +# application/vnd.radisys.msml-audit-stream+xml +# application/vnd.radisys.msml-conf+xml +# application/vnd.radisys.msml-dialog+xml +# application/vnd.radisys.msml-dialog-base+xml +# application/vnd.radisys.msml-dialog-fax-detect+xml +# application/vnd.radisys.msml-dialog-fax-sendrecv+xml +# application/vnd.radisys.msml-dialog-group+xml +# application/vnd.radisys.msml-dialog-speech+xml +# application/vnd.radisys.msml-dialog-transform+xml +# application/vnd.rainstor.data +# application/vnd.rapid +# application/vnd.rar +application/vnd.realvnc.bed bed +application/vnd.recordare.musicxml mxl +application/vnd.recordare.musicxml+xml musicxml +# application/vnd.renlearn.rlprint +application/vnd.rig.cryptonote cryptonote +application/vnd.rim.cod cod +application/vnd.rn-realmedia rm +application/vnd.rn-realmedia-vbr rmvb +application/vnd.route66.link66+xml link66 +# application/vnd.rs-274x +# application/vnd.ruckus.download +# application/vnd.s3sms +application/vnd.sailingtracker.track st +# application/vnd.sbm.cid +# application/vnd.sbm.mid2 +# application/vnd.scribus +# application/vnd.sealed.3df +# application/vnd.sealed.csf +# application/vnd.sealed.doc +# application/vnd.sealed.eml +# application/vnd.sealed.mht +# application/vnd.sealed.net +# application/vnd.sealed.ppt +# application/vnd.sealed.tiff +# application/vnd.sealed.xls +# application/vnd.sealedmedia.softseal.html +# application/vnd.sealedmedia.softseal.pdf +application/vnd.seemail see +application/vnd.sema sema +application/vnd.semd semd +application/vnd.semf semf +application/vnd.shana.informed.formdata ifm +application/vnd.shana.informed.formtemplate itp +application/vnd.shana.informed.interchange iif +application/vnd.shana.informed.package ipk +application/vnd.simtech-mindmapper twd twds +# application/vnd.siren+json +application/vnd.smaf mmf +# application/vnd.smart.notebook +application/vnd.smart.teacher teacher +# application/vnd.software602.filler.form+xml +# application/vnd.software602.filler.form-xml-zip +application/vnd.solent.sdkm+xml sdkm sdkd +application/vnd.spotfire.dxp dxp +application/vnd.spotfire.sfs sfs +# application/vnd.sss-cod +# application/vnd.sss-dtf +# application/vnd.sss-ntf +application/vnd.stardivision.calc sdc +application/vnd.stardivision.draw sda +application/vnd.stardivision.impress sdd +application/vnd.stardivision.math smf +application/vnd.stardivision.writer sdw vor +application/vnd.stardivision.writer-global sgl +application/vnd.stepmania.package smzip +application/vnd.stepmania.stepchart sm +# application/vnd.street-stream +# application/vnd.sun.wadl+xml +application/vnd.sun.xml.calc sxc +application/vnd.sun.xml.calc.template stc +application/vnd.sun.xml.draw sxd +application/vnd.sun.xml.draw.template std +application/vnd.sun.xml.impress sxi +application/vnd.sun.xml.impress.template sti +application/vnd.sun.xml.math sxm +application/vnd.sun.xml.writer sxw +application/vnd.sun.xml.writer.global sxg +application/vnd.sun.xml.writer.template stw +application/vnd.sus-calendar sus susp +application/vnd.svd svd +# application/vnd.swiftview-ics +application/vnd.symbian.install sis sisx +application/vnd.syncml+xml xsm +application/vnd.syncml.dm+wbxml bdm +application/vnd.syncml.dm+xml xdm +# application/vnd.syncml.dm.notification +# application/vnd.syncml.dmddf+wbxml +# application/vnd.syncml.dmddf+xml +# application/vnd.syncml.dmtnds+wbxml +# application/vnd.syncml.dmtnds+xml +# application/vnd.syncml.ds.notification +application/vnd.tao.intent-module-archive tao +application/vnd.tcpdump.pcap pcap cap dmp +# application/vnd.tmd.mediaflex.api+xml +# application/vnd.tml +application/vnd.tmobile-livetv tmo +application/vnd.trid.tpt tpt +application/vnd.triscape.mxs mxs +application/vnd.trueapp tra +# application/vnd.truedoc +# application/vnd.ubisoft.webplayer +application/vnd.ufdl ufd ufdl +application/vnd.uiq.theme utz +application/vnd.umajin umj +application/vnd.unity unityweb +application/vnd.uoml+xml uoml +# application/vnd.uplanet.alert +# application/vnd.uplanet.alert-wbxml +# application/vnd.uplanet.bearer-choice +# application/vnd.uplanet.bearer-choice-wbxml +# application/vnd.uplanet.cacheop +# application/vnd.uplanet.cacheop-wbxml +# application/vnd.uplanet.channel +# application/vnd.uplanet.channel-wbxml +# application/vnd.uplanet.list +# application/vnd.uplanet.list-wbxml +# application/vnd.uplanet.listcmd +# application/vnd.uplanet.listcmd-wbxml +# application/vnd.uplanet.signal +# application/vnd.uri-map +# application/vnd.valve.source.material +application/vnd.vcx vcx +# application/vnd.vd-study +# application/vnd.vectorworks +# application/vnd.vel+json +# application/vnd.verimatrix.vcas +# application/vnd.vidsoft.vidconference +application/vnd.visio vsd vst vss vsw +application/vnd.visionary vis +# application/vnd.vividence.scriptfile +application/vnd.vsf vsf +# application/vnd.wap.sic +# application/vnd.wap.slc +application/vnd.wap.wbxml wbxml +application/vnd.wap.wmlc wmlc +application/vnd.wap.wmlscriptc wmlsc +application/vnd.webturbo wtb +# application/vnd.wfa.p2p +# application/vnd.wfa.wsc +# application/vnd.windows.devicepairing +# application/vnd.wmc +# application/vnd.wmf.bootstrap +# application/vnd.wolfram.mathematica +# application/vnd.wolfram.mathematica.package +application/vnd.wolfram.player nbp +application/vnd.wordperfect wpd +application/vnd.wqd wqd +# application/vnd.wrq-hp3000-labelled +application/vnd.wt.stf stf +# application/vnd.wv.csp+wbxml +# application/vnd.wv.csp+xml +# application/vnd.wv.ssp+xml +# application/vnd.xacml+json +application/vnd.xara xar +application/vnd.xfdl xfdl +# application/vnd.xfdl.webform +# application/vnd.xmi+xml +# application/vnd.xmpie.cpkg +# application/vnd.xmpie.dpkg +# application/vnd.xmpie.plan +# application/vnd.xmpie.ppkg +# application/vnd.xmpie.xlim +application/vnd.yamaha.hv-dic hvd +application/vnd.yamaha.hv-script hvs +application/vnd.yamaha.hv-voice hvp +application/vnd.yamaha.openscoreformat osf +application/vnd.yamaha.openscoreformat.osfpvg+xml osfpvg +# application/vnd.yamaha.remote-setup +application/vnd.yamaha.smaf-audio saf +application/vnd.yamaha.smaf-phrase spf +# application/vnd.yamaha.through-ngn +# application/vnd.yamaha.tunnel-udpencap +# application/vnd.yaoweme +application/vnd.yellowriver-custom-menu cmp +application/vnd.zul zir zirz +application/vnd.zzazz.deck+xml zaz +application/voicexml+xml vxml +# application/vq-rtcpxr +# application/watcherinfo+xml +# application/whoispp-query +# application/whoispp-response +application/widget wgt +application/winhlp hlp +# application/wita +# application/wordperfect5.1 +application/wsdl+xml wsdl +application/wspolicy+xml wspolicy +application/x-7z-compressed 7z +application/x-abiword abw +application/x-ace-compressed ace +# application/x-amf +application/x-apple-diskimage dmg +application/x-authorware-bin aab x32 u32 vox +application/x-authorware-map aam +application/x-authorware-seg aas +application/x-bcpio bcpio +application/x-bittorrent torrent +application/x-blorb blb blorb +application/x-bzip bz +application/x-bzip2 bz2 boz +application/x-cbr cbr cba cbt cbz cb7 +application/x-cdlink vcd +application/x-cfs-compressed cfs +application/x-chat chat +application/x-chess-pgn pgn +# application/x-compress +application/x-conference nsc +application/x-cpio cpio +application/x-csh csh +application/x-debian-package deb udeb +application/x-dgc-compressed dgc +application/x-director dir dcr dxr cst cct cxt w3d fgd swa +application/x-doom wad +application/x-dtbncx+xml ncx +application/x-dtbook+xml dtb +application/x-dtbresource+xml res +application/x-dvi dvi +application/x-envoy evy +application/x-eva eva +application/x-font-bdf bdf +# application/x-font-dos +# application/x-font-framemaker +application/x-font-ghostscript gsf +# application/x-font-libgrx +application/x-font-linux-psf psf +application/x-font-pcf pcf +application/x-font-snf snf +# application/x-font-speedo +# application/x-font-sunos-news +application/x-font-type1 pfa pfb pfm afm +# application/x-font-vfont +application/x-freearc arc +application/x-futuresplash spl +application/x-gca-compressed gca +application/x-glulx ulx +application/x-gnumeric gnumeric +application/x-gramps-xml gramps +application/x-gtar gtar +# application/x-gzip +application/x-hdf hdf +application/x-install-instructions install +application/x-iso9660-image iso +application/x-java-jnlp-file jnlp +application/x-latex latex +application/x-lzh-compressed lzh lha +application/x-mie mie +application/x-mobipocket-ebook prc mobi +application/x-ms-application application +application/x-ms-shortcut lnk +application/x-ms-wmd wmd +application/x-ms-wmz wmz +application/x-ms-xbap xbap +application/x-msaccess mdb +application/x-msbinder obd +application/x-mscardfile crd +application/x-msclip clp +application/x-msdownload exe dll com bat msi +application/x-msmediaview mvb m13 m14 +application/x-msmetafile wmf wmz emf emz +application/x-msmoney mny +application/x-mspublisher pub +application/x-msschedule scd +application/x-msterminal trm +application/x-mswrite wri +application/x-netcdf nc cdf +application/x-nzb nzb +application/x-pkcs12 p12 pfx +application/x-pkcs7-certificates p7b spc +application/x-pkcs7-certreqresp p7r +application/x-rar-compressed rar +application/x-research-info-systems ris +application/x-sh sh +application/x-shar shar +application/x-shockwave-flash swf +application/x-silverlight-app xap +application/x-sql sql +application/x-stuffit sit +application/x-stuffitx sitx +application/x-subrip srt +application/x-sv4cpio sv4cpio +application/x-sv4crc sv4crc +application/x-t3vm-image t3 +application/x-tads gam +application/x-tar tar +application/x-tcl tcl +application/x-tex tex +application/x-tex-tfm tfm +application/x-texinfo texinfo texi +application/x-tgif obj +application/x-ustar ustar +application/x-wais-source src +# application/x-www-form-urlencoded +application/x-x509-ca-cert der crt +application/x-xfig fig +application/x-xliff+xml xlf +application/x-xpinstall xpi +application/x-xz xz +application/x-zmachine z1 z2 z3 z4 z5 z6 z7 z8 +# application/x400-bp +# application/xacml+xml +application/xaml+xml xaml +# application/xcap-att+xml +# application/xcap-caps+xml +application/xcap-diff+xml xdf +# application/xcap-el+xml +# application/xcap-error+xml +# application/xcap-ns+xml +# application/xcon-conference-info+xml +# application/xcon-conference-info-diff+xml +application/xenc+xml xenc +application/xhtml+xml xhtml xht +# application/xhtml-voice+xml +application/xml xml xsl +application/xml-dtd dtd +# application/xml-external-parsed-entity +# application/xml-patch+xml +# application/xmpp+xml +application/xop+xml xop +application/xproc+xml xpl +application/xslt+xml xslt +application/xspf+xml xspf +application/xv+xml mxml xhvml xvml xvm +application/yang yang +application/yin+xml yin +application/zip zip +# application/zlib +# audio/1d-interleaved-parityfec +# audio/32kadpcm +# audio/3gpp +# audio/3gpp2 +# audio/ac3 +audio/adpcm adp +# audio/amr +# audio/amr-wb +# audio/amr-wb+ +# audio/aptx +# audio/asc +# audio/atrac-advanced-lossless +# audio/atrac-x +# audio/atrac3 +audio/basic au snd +# audio/bv16 +# audio/bv32 +# audio/clearmode +# audio/cn +# audio/dat12 +# audio/dls +# audio/dsr-es201108 +# audio/dsr-es202050 +# audio/dsr-es202211 +# audio/dsr-es202212 +# audio/dv +# audio/dvi4 +# audio/eac3 +# audio/encaprtp +# audio/evrc +# audio/evrc-qcp +# audio/evrc0 +# audio/evrc1 +# audio/evrcb +# audio/evrcb0 +# audio/evrcb1 +# audio/evrcnw +# audio/evrcnw0 +# audio/evrcnw1 +# audio/evrcwb +# audio/evrcwb0 +# audio/evrcwb1 +# audio/evs +# audio/example +# audio/fwdred +# audio/g711-0 +# audio/g719 +# audio/g722 +# audio/g7221 +# audio/g723 +# audio/g726-16 +# audio/g726-24 +# audio/g726-32 +# audio/g726-40 +# audio/g728 +# audio/g729 +# audio/g7291 +# audio/g729d +# audio/g729e +# audio/gsm +# audio/gsm-efr +# audio/gsm-hr-08 +# audio/ilbc +# audio/ip-mr_v2.5 +# audio/isac +# audio/l16 +# audio/l20 +# audio/l24 +# audio/l8 +# audio/lpc +audio/midi mid midi kar rmi +# audio/mobile-xmf +audio/mp4 m4a mp4a +# audio/mp4a-latm +# audio/mpa +# audio/mpa-robust +audio/mpeg mpga mp2 mp2a mp3 m2a m3a +# audio/mpeg4-generic +# audio/musepack +audio/ogg oga ogg spx +# audio/opus +# audio/parityfec +# audio/pcma +# audio/pcma-wb +# audio/pcmu +# audio/pcmu-wb +# audio/prs.sid +# audio/qcelp +# audio/raptorfec +# audio/red +# audio/rtp-enc-aescm128 +# audio/rtp-midi +# audio/rtploopback +# audio/rtx +audio/s3m s3m +audio/silk sil +# audio/smv +# audio/smv-qcp +# audio/smv0 +# audio/sp-midi +# audio/speex +# audio/t140c +# audio/t38 +# audio/telephone-event +# audio/tone +# audio/uemclip +# audio/ulpfec +# audio/vdvi +# audio/vmr-wb +# audio/vnd.3gpp.iufp +# audio/vnd.4sb +# audio/vnd.audiokoz +# audio/vnd.celp +# audio/vnd.cisco.nse +# audio/vnd.cmles.radio-events +# audio/vnd.cns.anp1 +# audio/vnd.cns.inf1 +audio/vnd.dece.audio uva uvva +audio/vnd.digital-winds eol +# audio/vnd.dlna.adts +# audio/vnd.dolby.heaac.1 +# audio/vnd.dolby.heaac.2 +# audio/vnd.dolby.mlp +# audio/vnd.dolby.mps +# audio/vnd.dolby.pl2 +# audio/vnd.dolby.pl2x +# audio/vnd.dolby.pl2z +# audio/vnd.dolby.pulse.1 +audio/vnd.dra dra +audio/vnd.dts dts +audio/vnd.dts.hd dtshd +# audio/vnd.dvb.file +# audio/vnd.everad.plj +# audio/vnd.hns.audio +audio/vnd.lucent.voice lvp +audio/vnd.ms-playready.media.pya pya +# audio/vnd.nokia.mobile-xmf +# audio/vnd.nortel.vbk +audio/vnd.nuera.ecelp4800 ecelp4800 +audio/vnd.nuera.ecelp7470 ecelp7470 +audio/vnd.nuera.ecelp9600 ecelp9600 +# audio/vnd.octel.sbc +# audio/vnd.qcelp +# audio/vnd.rhetorex.32kadpcm +audio/vnd.rip rip +# audio/vnd.sealedmedia.softseal.mpeg +# audio/vnd.vmx.cvsd +# audio/vorbis +# audio/vorbis-config +audio/webm weba +audio/x-aac aac +audio/x-aiff aif aiff aifc +audio/x-caf caf +audio/x-flac flac +audio/x-matroska mka +audio/x-mpegurl m3u +audio/x-ms-wax wax +audio/x-ms-wma wma +audio/x-pn-realaudio ram ra +audio/x-pn-realaudio-plugin rmp +# audio/x-tta +audio/x-wav wav +audio/xm xm +chemical/x-cdx cdx +chemical/x-cif cif +chemical/x-cmdf cmdf +chemical/x-cml cml +chemical/x-csml csml +# chemical/x-pdb +chemical/x-xyz xyz +font/collection ttc +font/otf otf +# font/sfnt +font/ttf ttf +font/woff woff +font/woff2 woff2 +image/bmp bmp +image/cgm cgm +# image/dicom-rle +# image/emf +# image/example +# image/fits +image/g3fax g3 +image/gif gif +image/ief ief +# image/jls +# image/jp2 +image/jpeg jpeg jpg jpe +# image/jpm +# image/jpx +image/ktx ktx +# image/naplps +image/png png +image/prs.btif btif +# image/prs.pti +# image/pwg-raster +image/sgi sgi +image/svg+xml svg svgz +# image/t38 +image/tiff tiff tif +# image/tiff-fx +image/vnd.adobe.photoshop psd +# image/vnd.airzip.accelerator.azv +# image/vnd.cns.inf2 +image/vnd.dece.graphic uvi uvvi uvg uvvg +image/vnd.djvu djvu djv +image/vnd.dvb.subtitle sub +image/vnd.dwg dwg +image/vnd.dxf dxf +image/vnd.fastbidsheet fbs +image/vnd.fpx fpx +image/vnd.fst fst +image/vnd.fujixerox.edmics-mmr mmr +image/vnd.fujixerox.edmics-rlc rlc +# image/vnd.globalgraphics.pgb +# image/vnd.microsoft.icon +# image/vnd.mix +# image/vnd.mozilla.apng +image/vnd.ms-modi mdi +image/vnd.ms-photo wdp +image/vnd.net-fpx npx +# image/vnd.radiance +# image/vnd.sealed.png +# image/vnd.sealedmedia.softseal.gif +# image/vnd.sealedmedia.softseal.jpg +# image/vnd.svf +# image/vnd.tencent.tap +# image/vnd.valve.source.texture +image/vnd.wap.wbmp wbmp +image/vnd.xiff xif +# image/vnd.zbrush.pcx +image/webp webp +# image/wmf +image/x-3ds 3ds +image/x-cmu-raster ras +image/x-cmx cmx +image/x-freehand fh fhc fh4 fh5 fh7 +image/x-icon ico +image/x-mrsid-image sid +image/x-pcx pcx +image/x-pict pic pct +image/x-portable-anymap pnm +image/x-portable-bitmap pbm +image/x-portable-graymap pgm +image/x-portable-pixmap ppm +image/x-rgb rgb +image/x-tga tga +image/x-xbitmap xbm +image/x-xpixmap xpm +image/x-xwindowdump xwd +# message/cpim +# message/delivery-status +# message/disposition-notification +# message/example +# message/external-body +# message/feedback-report +# message/global +# message/global-delivery-status +# message/global-disposition-notification +# message/global-headers +# message/http +# message/imdn+xml +# message/news +# message/partial +message/rfc822 eml mime +# message/s-http +# message/sip +# message/sipfrag +# message/tracking-status +# message/vnd.si.simp +# message/vnd.wfa.wsc +# model/example +# model/gltf+json +model/iges igs iges +model/mesh msh mesh silo +model/vnd.collada+xml dae +model/vnd.dwf dwf +# model/vnd.flatland.3dml +model/vnd.gdl gdl +# model/vnd.gs-gdl +# model/vnd.gs.gdl +model/vnd.gtw gtw +# model/vnd.moml+xml +model/vnd.mts mts +# model/vnd.opengex +# model/vnd.parasolid.transmit.binary +# model/vnd.parasolid.transmit.text +# model/vnd.rosette.annotated-data-model +# model/vnd.valve.source.compiled-map +model/vnd.vtu vtu +model/vrml wrl vrml +model/x3d+binary x3db x3dbz +# model/x3d+fastinfoset +model/x3d+vrml x3dv x3dvz +model/x3d+xml x3d x3dz +# model/x3d-vrml +# multipart/alternative +# multipart/appledouble +# multipart/byteranges +# multipart/digest +# multipart/encrypted +# multipart/example +# multipart/form-data +# multipart/header-set +# multipart/mixed +# multipart/parallel +# multipart/related +# multipart/report +# multipart/signed +# multipart/voice-message +# multipart/x-mixed-replace +# text/1d-interleaved-parityfec +text/cache-manifest appcache +text/calendar ics ifb +text/css css +text/csv csv +# text/csv-schema +# text/directory +# text/dns +# text/ecmascript +# text/encaprtp +# text/enriched +# text/example +# text/fwdred +# text/grammar-ref-list +text/html html htm +# text/javascript +# text/jcr-cnd +# text/markdown +# text/mizar +text/n3 n3 +# text/parameters +# text/parityfec +text/plain txt text conf def list log in +# text/provenance-notation +# text/prs.fallenstein.rst +text/prs.lines.tag dsc +# text/prs.prop.logic +# text/raptorfec +# text/red +# text/rfc822-headers +text/richtext rtx +# text/rtf +# text/rtp-enc-aescm128 +# text/rtploopback +# text/rtx +text/sgml sgml sgm +# text/t140 +text/tab-separated-values tsv +text/troff t tr roff man me ms +text/turtle ttl +# text/ulpfec +text/uri-list uri uris urls +text/vcard vcard +# text/vnd.a +# text/vnd.abc +text/vnd.curl curl +text/vnd.curl.dcurl dcurl +text/vnd.curl.mcurl mcurl +text/vnd.curl.scurl scurl +# text/vnd.debian.copyright +# text/vnd.dmclientscript +text/vnd.dvb.subtitle sub +# text/vnd.esmertec.theme-descriptor +text/vnd.fly fly +text/vnd.fmi.flexstor flx +text/vnd.graphviz gv +text/vnd.in3d.3dml 3dml +text/vnd.in3d.spot spot +# text/vnd.iptc.newsml +# text/vnd.iptc.nitf +# text/vnd.latex-z +# text/vnd.motorola.reflex +# text/vnd.ms-mediapackage +# text/vnd.net2phone.commcenter.command +# text/vnd.radisys.msml-basic-layout +# text/vnd.si.uricatalogue +text/vnd.sun.j2me.app-descriptor jad +# text/vnd.trolltech.linguist +# text/vnd.wap.si +# text/vnd.wap.sl +text/vnd.wap.wml wml +text/vnd.wap.wmlscript wmls +text/x-asm s asm +text/x-c c cc cxx cpp h hh dic +text/x-fortran f for f77 f90 +text/x-java-source java +text/x-nfo nfo +text/x-opml opml +text/x-pascal p pas +text/x-setext etx +text/x-sfv sfv +text/x-uuencode uu +text/x-vcalendar vcs +text/x-vcard vcf +# text/xml +# text/xml-external-parsed-entity +# video/1d-interleaved-parityfec +video/3gpp 3gp +# video/3gpp-tt +video/3gpp2 3g2 +# video/bmpeg +# video/bt656 +# video/celb +# video/dv +# video/encaprtp +# video/example +video/h261 h261 +video/h263 h263 +# video/h263-1998 +# video/h263-2000 +video/h264 h264 +# video/h264-rcdo +# video/h264-svc +# video/h265 +# video/iso.segment +video/jpeg jpgv +# video/jpeg2000 +video/jpm jpm jpgm +video/mj2 mj2 mjp2 +# video/mp1s +# video/mp2p +# video/mp2t +video/mp4 mp4 mp4v mpg4 +# video/mp4v-es +video/mpeg mpeg mpg mpe m1v m2v +# video/mpeg4-generic +# video/mpv +# video/nv +video/ogg ogv +# video/parityfec +# video/pointer +video/quicktime qt mov +# video/raptorfec +# video/raw +# video/rtp-enc-aescm128 +# video/rtploopback +# video/rtx +# video/smpte292m +# video/ulpfec +# video/vc1 +# video/vnd.cctv +video/vnd.dece.hd uvh uvvh +video/vnd.dece.mobile uvm uvvm +# video/vnd.dece.mp4 +video/vnd.dece.pd uvp uvvp +video/vnd.dece.sd uvs uvvs +video/vnd.dece.video uvv uvvv +# video/vnd.directv.mpeg +# video/vnd.directv.mpeg-tts +# video/vnd.dlna.mpeg-tts +video/vnd.dvb.file dvb +video/vnd.fvt fvt +# video/vnd.hns.video +# video/vnd.iptvforum.1dparityfec-1010 +# video/vnd.iptvforum.1dparityfec-2005 +# video/vnd.iptvforum.2dparityfec-1010 +# video/vnd.iptvforum.2dparityfec-2005 +# video/vnd.iptvforum.ttsavc +# video/vnd.iptvforum.ttsmpeg2 +# video/vnd.motorola.video +# video/vnd.motorola.videop +video/vnd.mpegurl mxu m4u +video/vnd.ms-playready.media.pyv pyv +# video/vnd.nokia.interleaved-multimedia +# video/vnd.nokia.videovoip +# video/vnd.objectvideo +# video/vnd.radgamettools.bink +# video/vnd.radgamettools.smacker +# video/vnd.sealed.mpeg1 +# video/vnd.sealed.mpeg4 +# video/vnd.sealed.swf +# video/vnd.sealedmedia.softseal.mov +video/vnd.uvvu.mp4 uvu uvvu +video/vnd.vivo viv +# video/vp8 +video/webm webm +video/x-f4v f4v +video/x-fli fli +video/x-flv flv +video/x-m4v m4v +video/x-matroska mkv mk3d mks +video/x-mng mng +video/x-ms-asf asf asx +video/x-ms-vob vob +video/x-ms-wm wm +video/x-ms-wmv wmv +video/x-ms-wmx wmx +video/x-ms-wvx wvx +video/x-msvideo avi +video/x-sgi-movie movie +video/x-smv smv +x-conference/x-cooltalk ice From 0a08fd3853cb4dc294e408634428a349bad4a291 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 02:25:25 +0000 Subject: [PATCH 866/939] build(deps): bump the test-tools group with 7 updates Bumps the test-tools group with 7 updates: | Package | From | To | | --- | --- | --- | | [org.testcontainers:testcontainers-localstack](https://github.com/testcontainers/testcontainers-java) | `2.0.4` | `2.0.5` | | [io.netty:netty-buffer](https://github.com/netty/netty) | `4.2.11.Final` | `4.2.12.Final` | | [io.netty:netty-transport](https://github.com/netty/netty) | `4.2.11.Final` | `4.2.12.Final` | | [io.netty:netty-transport-native-unix-common](https://github.com/netty/netty) | `4.2.11.Final` | `4.2.12.Final` | | [io.netty:netty-common](https://github.com/netty/netty) | `4.2.11.Final` | `4.2.12.Final` | | [io.netty:netty-handler](https://github.com/netty/netty) | `4.2.11.Final` | `4.2.12.Final` | | [io.netty:netty-codec](https://github.com/netty/netty) | `4.2.11.Final` | `4.2.12.Final` | Updates `org.testcontainers:testcontainers-localstack` from 2.0.4 to 2.0.5 - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/2.0.4...2.0.5) Updates `io.netty:netty-buffer` from 4.2.11.Final to 4.2.12.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.2.11.Final...netty-4.2.12.Final) Updates `io.netty:netty-transport` from 4.2.11.Final to 4.2.12.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.2.11.Final...netty-4.2.12.Final) Updates `io.netty:netty-transport-native-unix-common` from 4.2.11.Final to 4.2.12.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.2.11.Final...netty-4.2.12.Final) Updates `io.netty:netty-common` from 4.2.11.Final to 4.2.12.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.2.11.Final...netty-4.2.12.Final) Updates `io.netty:netty-handler` from 4.2.11.Final to 4.2.12.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.2.11.Final...netty-4.2.12.Final) Updates `io.netty:netty-codec` from 4.2.11.Final to 4.2.12.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.2.11.Final...netty-4.2.12.Final) --- updated-dependencies: - dependency-name: org.testcontainers:testcontainers-localstack dependency-version: 2.0.5 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-buffer dependency-version: 4.2.12.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-transport dependency-version: 4.2.12.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-transport-native-unix-common dependency-version: 4.2.12.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-common dependency-version: 4.2.12.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-handler dependency-version: 4.2.12.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools - dependency-name: io.netty:netty-codec dependency-version: 4.2.12.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: test-tools ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index fbb51d6b7e3..e54a3267e9c 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -857,7 +857,7 @@ org.testcontainers testcontainers-localstack - 2.0.4 + 2.0.5 test @@ -877,32 +877,32 @@ io.netty netty-buffer - 4.2.11.Final + 4.2.12.Final io.netty netty-transport - 4.2.11.Final + 4.2.12.Final io.netty netty-transport-native-unix-common - 4.2.11.Final + 4.2.12.Final io.netty netty-common - 4.2.11.Final + 4.2.12.Final io.netty netty-handler - 4.2.11.Final + 4.2.12.Final io.netty netty-codec - 4.2.11.Final + 4.2.12.Final org.apache.velocity From f9468e6df913eed92e240d507fba97d3811b29b6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 02:25:47 +0000 Subject: [PATCH 867/939] build(deps): bump the amazon-s3 group with 2 updates Bumps the amazon-s3 group with 2 updates: software.amazon.awssdk:s3 and [software.amazon.awssdk.crt:aws-crt](https://github.com/awslabs/aws-crt-java). Updates `software.amazon.awssdk:s3` from 2.42.20 to 2.42.40 Updates `software.amazon.awssdk.crt:aws-crt` from 0.43.9 to 0.45.1 - [Release notes](https://github.com/awslabs/aws-crt-java/releases) - [Commits](https://github.com/awslabs/aws-crt-java/compare/v0.43.9...v0.45.1) --- updated-dependencies: - dependency-name: software.amazon.awssdk:s3 dependency-version: 2.42.40 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: amazon-s3 - dependency-name: software.amazon.awssdk.crt:aws-crt dependency-version: 0.45.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: amazon-s3 ... Signed-off-by: dependabot[bot] --- dspace-api/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index fbb51d6b7e3..f3b53bad28e 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -739,7 +739,7 @@ software.amazon.awssdk s3 - 2.42.20 + 2.42.40 software.amazon.awssdk @@ -755,7 +755,7 @@ software.amazon.awssdk.crt aws-crt - 0.43.9 + 0.45.1 From f9f66549f72d0b4abe35182d4cfc7c87d39ab81f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 02:26:12 +0000 Subject: [PATCH 868/939] build(deps): bump the apache-commons group with 3 updates Bumps the apache-commons group with 3 updates: [commons-codec:commons-codec](https://github.com/apache/commons-codec), org.apache.commons:commons-configuration2 and commons-io:commons-io. Updates `commons-codec:commons-codec` from 1.21.0 to 1.22.0 - [Changelog](https://github.com/apache/commons-codec/blob/master/RELEASE-NOTES.txt) - [Commits](https://github.com/apache/commons-codec/compare/rel/commons-codec-1.21.0...rel/commons-codec-1.22.0) Updates `org.apache.commons:commons-configuration2` from 2.13.0 to 2.14.0 Updates `commons-io:commons-io` from 2.21.0 to 2.22.0 --- updated-dependencies: - dependency-name: commons-codec:commons-codec dependency-version: 1.22.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: apache-commons - dependency-name: org.apache.commons:commons-configuration2 dependency-version: 2.14.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: apache-commons - dependency-name: commons-io:commons-io dependency-version: 2.22.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: apache-commons ... Signed-off-by: dependabot[bot] --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index e7e7f1569e8..84b63ecc0b6 100644 --- a/pom.xml +++ b/pom.xml @@ -1489,7 +1489,7 @@ commons-codec commons-codec - 1.21.0 + 1.22.0 org.apache.commons @@ -1499,7 +1499,7 @@ org.apache.commons commons-configuration2 - 2.13.0 + 2.14.0 org.apache.commons @@ -1509,7 +1509,7 @@ commons-io commons-io - 2.21.0 + 2.22.0 org.apache.commons From 1fb12c7c0ad50be7a33f657a5f050671ecffb89c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 02:28:45 +0000 Subject: [PATCH 869/939] build(deps): bump org.apache.james:apache-mime4j-core Bumps org.apache.james:apache-mime4j-core from 0.8.13 to 0.8.14. --- updated-dependencies: - dependency-name: org.apache.james:apache-mime4j-core dependency-version: 0.8.14 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e7e7f1569e8..474272eee1a 100644 --- a/pom.xml +++ b/pom.xml @@ -1335,7 +1335,7 @@ org.apache.james apache-mime4j-core - 0.8.13 + 0.8.14 From f2a2bd4b6317e489d839d9cc75c6cfcfd930235a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 02:28:56 +0000 Subject: [PATCH 870/939] build(deps): bump github/codeql-action from 3 to 4 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/codescan.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codescan.yml b/.github/workflows/codescan.yml index ba56b683a27..327bcf7b9e3 100644 --- a/.github/workflows/codescan.yml +++ b/.github/workflows/codescan.yml @@ -47,7 +47,7 @@ jobs: # Initializes the CodeQL tools for scanning. # https://github.com/github/codeql-action - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: # Codescan Javascript as well since a few JS files exist in REST API's interface languages: java, javascript @@ -56,8 +56,8 @@ jobs: # NOTE: Based on testing, this autobuild process works well for DSpace. A custom # DSpace build w/caching (like in build.yml) was about the same speed as autobuild. - name: Autobuild - uses: github/codeql-action/autobuild@v3 + uses: github/codeql-action/autobuild@v4 # Perform GitHub Code Scanning. - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 From 3682abf5bd384b20007f7aa658ca591f3bf1b12d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 02:29:15 +0000 Subject: [PATCH 871/939] build(deps): bump com.github.spotbugs:spotbugs-maven-plugin Bumps [com.github.spotbugs:spotbugs-maven-plugin](https://github.com/spotbugs/spotbugs-maven-plugin) from 4.9.8.2 to 4.9.8.3. - [Release notes](https://github.com/spotbugs/spotbugs-maven-plugin/releases) - [Commits](https://github.com/spotbugs/spotbugs-maven-plugin/compare/spotbugs-maven-plugin-4.9.8.2...spotbugs-maven-plugin-4.9.8.3) --- updated-dependencies: - dependency-name: com.github.spotbugs:spotbugs-maven-plugin dependency-version: 4.9.8.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e7e7f1569e8..d9fed393e9e 100644 --- a/pom.xml +++ b/pom.xml @@ -296,7 +296,7 @@ com.github.spotbugs spotbugs-maven-plugin - 4.9.8.2 + 4.9.8.3 Max Low From 3e25875e9e6460dbed084d531adfa2d89d71f153 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 02:29:19 +0000 Subject: [PATCH 872/939] build(deps): bump xom:xom from 1.3.9 to 1.4.0 Bumps [xom:xom](https://github.com/elharo/xom) from 1.3.9 to 1.4.0. - [Release notes](https://github.com/elharo/xom/releases) - [Commits](https://github.com/elharo/xom/compare/v1.3.9...v1.4.0) --- updated-dependencies: - dependency-name: xom:xom dependency-version: 1.4.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dspace-sword/pom.xml | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index 7b43bd3765d..3363a34e04e 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -81,7 +81,7 @@ xom xom - 1.3.9 + 1.4.0 diff --git a/pom.xml b/pom.xml index e7e7f1569e8..384ac947926 100644 --- a/pom.xml +++ b/pom.xml @@ -1814,7 +1814,7 @@ xom xom - 1.3.9 + 1.4.0 From 5ce08f392c9eafa4b257d98abbf8125f373ec884 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 02:29:26 +0000 Subject: [PATCH 873/939] build(deps): bump org.apache.ant:ant from 1.10.15 to 1.10.17 Bumps org.apache.ant:ant from 1.10.15 to 1.10.17. --- updated-dependencies: - dependency-name: org.apache.ant:ant dependency-version: 1.10.17 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e7e7f1569e8..25e30e3e563 100644 --- a/pom.xml +++ b/pom.xml @@ -1369,7 +1369,7 @@ org.apache.ant ant - 1.10.15 + 1.10.17 org.apache.jena From 9996db47c1155dbd9170bc335f5a792284665ec1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 02:30:04 +0000 Subject: [PATCH 874/939] build(deps): bump log4j.version from 2.25.3 to 2.25.4 Bumps `log4j.version` from 2.25.3 to 2.25.4. Updates `org.apache.logging.log4j:log4j-api` from 2.25.3 to 2.25.4 Updates `org.apache.logging.log4j:log4j-core` from 2.25.3 to 2.25.4 Updates `org.apache.logging.log4j:log4j-1.2-api` from 2.25.3 to 2.25.4 --- updated-dependencies: - dependency-name: org.apache.logging.log4j:log4j-api dependency-version: 2.25.4 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.logging.log4j:log4j-core dependency-version: 2.25.4 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.logging.log4j:log4j-1.2-api dependency-version: 2.25.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e7e7f1569e8..aa08114d3b9 100644 --- a/pom.xml +++ b/pom.xml @@ -38,7 +38,7 @@ 1.1.1 9.4.58.v20250814 - 2.25.3 + 2.25.4 3.0.7 1.19.0 1.7.36 From 157e2a203f9c2e63761df448a16ae9e46d9c5df3 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Mon, 4 May 2026 16:46:07 +0300 Subject: [PATCH 875/939] pom.xml: bump com.google.guava:guava to 33.6.0-jre (cherry picked from commit f82f3f3aaea7dba9ff2186bb84391f9a2ec11323) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e7e7f1569e8..995cb7f13a6 100644 --- a/pom.xml +++ b/pom.xml @@ -1802,7 +1802,7 @@ com.google.guava guava - 32.1.3-jre + 33.6.0-jre From 12b0d7e27498ae09e2754a61700a2faff2cf5d1e Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Wed, 6 May 2026 09:29:18 +0300 Subject: [PATCH 876/939] pom.xml: bump postgresql.driver.version to 42.7.11 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e7e7f1569e8..8d354544f4d 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ 5.7.14 5.6.15.Final 6.2.5.Final - 42.7.10 + 42.7.11 8.11.4 3.11.1 From 882dfaf12e4fd0358bd3d8fc8d909448cb22161c Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Wed, 6 May 2026 09:30:36 +0300 Subject: [PATCH 877/939] pom.xml: update comment about org.checkerframework:checker-qual Both com.google.guava:guava and org.apache.solr:solr-core depend on org.checkerframework:checker-qual, but org.postgresql:postgresql no longer does. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8d354544f4d..41bbde03a94 100644 --- a/pom.xml +++ b/pom.xml @@ -1804,7 +1804,7 @@ guava 32.1.3-jre - + org.checkerframework checker-qual From 87eb14498e22432fe3fe1cf7863f9767cba12158 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 20:14:51 +0000 Subject: [PATCH 878/939] build(deps): bump jaxen:jaxen from 2.0.0 to 2.0.1 Bumps [jaxen:jaxen](https://github.com/jaxen-xpath/jaxen) from 2.0.0 to 2.0.1. - [Release notes](https://github.com/jaxen-xpath/jaxen/releases) - [Commits](https://github.com/jaxen-xpath/jaxen/compare/v2.0.0...v2.0.1) --- updated-dependencies: - dependency-name: jaxen:jaxen dependency-version: 2.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8923ee7e0f0..0ee4bc7f0d5 100644 --- a/pom.xml +++ b/pom.xml @@ -1563,7 +1563,7 @@ jaxen jaxen - 2.0.0 + 2.0.1 org.jdom From efae0ed8dc370256111682913a8922a0abd25c06 Mon Sep 17 00:00:00 2001 From: Stefano Maffei <48757042+steph-ieffam@users.noreply.github.com> Date: Fri, 8 May 2026 15:55:56 +0200 Subject: [PATCH 879/939] [DURACOM-477] Fix slow loading times when caching counts for items (#12344) * [CST-25040] make cache count for items in collections/communities Asynch * [CST-25040] fix cache implementation * [CST-25040] fix cache implementation * [DURACOM-477] cleanup (cherry picked from commit 36b3f5c6cc577a8d5e0c66dd4c0787e28849b689) --- .../org/dspace/browse/ItemCountDAOSolr.java | 119 +++++------------- dspace/config/spring/api/core-services.xml | 2 +- dspace/solr/search/conf/schema.xml | 2 +- 3 files changed, 32 insertions(+), 91 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/browse/ItemCountDAOSolr.java b/dspace-api/src/main/java/org/dspace/browse/ItemCountDAOSolr.java index e4d0079fe20..723b49d63ae 100644 --- a/dspace-api/src/main/java/org/dspace/browse/ItemCountDAOSolr.java +++ b/dspace-api/src/main/java/org/dspace/browse/ItemCountDAOSolr.java @@ -7,121 +7,62 @@ */ package org.dspace.browse; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.solr.client.solrj.SolrClient; +import org.apache.solr.client.solrj.SolrQuery; +import org.apache.solr.client.solrj.response.QueryResponse; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.DSpaceObject; import org.dspace.core.Context; -import org.dspace.discovery.DiscoverFacetField; -import org.dspace.discovery.DiscoverQuery; -import org.dspace.discovery.DiscoverResult; -import org.dspace.discovery.DiscoverResult.FacetResult; -import org.dspace.discovery.SearchService; -import org.dspace.discovery.SearchServiceException; -import org.dspace.discovery.configuration.DiscoveryConfigurationParameters; +import org.dspace.discovery.SolrSearchCore; import org.dspace.discovery.indexobject.IndexableItem; import org.springframework.beans.factory.annotation.Autowired; /** * Discovery (Solr) driver implementing ItemCountDAO interface to look up item - * count information in communities and collections. Caching operations are - * intentionally not implemented because Solr already is our cache. + * count information in communities and collections. + *

    + * Counts are computed by querying Solr for archived, non-withdrawn, discoverable + * items using {@code location.comm} / {@code location.coll} filters. + * The query returns only {@code numFound} (rows=0), making it very fast. */ public class ItemCountDAOSolr implements ItemCountDAO { - /** - * Log4j logger - */ - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(ItemCountDAOSolr.class); - - /** - * Hold the communities item count obtained from SOLR after the first query. This only works - * well if the ItemCountDAO lifecycle is bound to the request lifecycle as - * it is now. If we switch to a Spring-based instantiation we should mark - * this bean as prototype - **/ - private Map communitiesCount = null; - - /** - * Hold the collection item count obtained from SOLR after the first query - **/ - private Map collectionsCount = null; + private static final Logger log = LogManager.getLogger(ItemCountDAOSolr.class); - /** - * Solr search service - */ @Autowired - protected SearchService searchService; + private SolrSearchCore solrSearchCore; - /** - * Get the count of the items in the given container. - * - * @param context DSpace context - * @param dso DspaceObject - * @return count - */ @Override public int getCount(Context context, DSpaceObject dso) { - loadCount(context); - Integer val = null; + String locationFilter; if (dso instanceof Collection) { - val = collectionsCount.get(dso.getID().toString()); + locationFilter = "location.coll:" + dso.getID().toString(); } else if (dso instanceof Community) { - val = communitiesCount.get(dso.getID().toString()); - } - - if (val != null) { - return val; + locationFilter = "location.comm:" + dso.getID().toString(); } else { return 0; } - } - /** - * make sure that the counts are actually fetched from Solr (if haven't been - * cached in a Map yet) - * - * @param context DSpace Context - */ - private void loadCount(Context context) { - if (communitiesCount != null || collectionsCount != null) { - return; - } - - communitiesCount = new HashMap<>(); - collectionsCount = new HashMap<>(); - - DiscoverQuery query = new DiscoverQuery(); - query.setFacetMinCount(1); - query.addFacetField(new DiscoverFacetField("location.comm", - DiscoveryConfigurationParameters.TYPE_STANDARD, -1, - DiscoveryConfigurationParameters.SORT.COUNT)); - query.addFacetField(new DiscoverFacetField("location.coll", - DiscoveryConfigurationParameters.TYPE_STANDARD, -1, - DiscoveryConfigurationParameters.SORT.COUNT)); - query.addFilterQueries("search.resourcetype:" + IndexableItem.TYPE); // count only items - query.addFilterQueries("NOT(discoverable:false)"); // only discoverable - query.addFilterQueries("withdrawn:false"); // only not withdrawn - query.addFilterQueries("archived:true"); // only archived - query.setMaxResults(0); - - DiscoverResult sResponse; try { - sResponse = searchService.search(context, query); - List commCount = sResponse.getFacetResult("location.comm"); - List collCount = sResponse.getFacetResult("location.coll"); - for (FacetResult c : commCount) { - communitiesCount.put(c.getAsFilterQuery(), (int) c.getCount()); - } - for (FacetResult c : collCount) { - collectionsCount.put(c.getAsFilterQuery(), (int) c.getCount()); + SolrClient solr = solrSearchCore.getSolr(); + if (solr == null) { + return 0; } - } catch (SearchServiceException e) { - log.error("Could not initialize Community/Collection Item Counts from Solr: ", e); + SolrQuery query = new SolrQuery("*:*"); + query.addFilterQuery(locationFilter); + query.addFilterQuery("search.resourcetype:" + IndexableItem.TYPE); + query.addFilterQuery("NOT(discoverable:false)"); + query.addFilterQuery("withdrawn:false"); + query.addFilterQuery("archived:true"); + query.setRows(0); + QueryResponse response = solr.query(query, solrSearchCore.REQUEST_METHOD); + return (int) response.getResults().getNumFound(); + } catch (Exception e) { + log.error("Error counting items in Solr for {}: ", dso.getID(), e); } + return 0; } } diff --git a/dspace/config/spring/api/core-services.xml b/dspace/config/spring/api/core-services.xml index be0e5977ede..3bde95cc07f 100644 --- a/dspace/config/spring/api/core-services.xml +++ b/dspace/config/spring/api/core-services.xml @@ -32,7 +32,7 @@ - + diff --git a/dspace/solr/search/conf/schema.xml b/dspace/solr/search/conf/schema.xml index 5c79519159a..1caaf1efe1a 100644 --- a/dspace/solr/search/conf/schema.xml +++ b/dspace/solr/search/conf/schema.xml @@ -276,7 +276,7 @@ - + From cf1a2d7b3c828add6208e1e5772363a471c8e4d1 Mon Sep 17 00:00:00 2001 From: milanmajchrak Date: Thu, 16 Apr 2026 14:06:28 +0200 Subject: [PATCH 880/939] Fix SWORDv2 deposit with embargo for non-admin submitters (cherry picked from commit cf6e027e0314bc6b11f3796e360ecd4415749b54) --- .../packager/AbstractMETSIngester.java | 20 +++++++++ .../java/org/dspace/app/sword2/Swordv2IT.java | 42 ++++++++++++++++++ .../org/dspace/app/sword2/example-embargo.zip | Bin 0 -> 11982 bytes .../CollectionDepositManagerDSpace.java | 3 ++ .../sword2/SwordMETSContentIngester.java | 9 ++++ 5 files changed, 74 insertions(+) create mode 100644 dspace-server-webapp/src/test/resources/org/dspace/app/sword2/example-embargo.zip diff --git a/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java index 0ab5ac71cda..68383f1d9e7 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java @@ -25,6 +25,9 @@ import org.apache.logging.log4j.Logger; import org.dspace.app.client.DSpaceHttpClientFactory; 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.BitstreamFormat; import org.dspace.content.Bundle; @@ -127,6 +130,10 @@ public abstract class AbstractMETSIngester extends AbstractPackageIngester { protected final ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + + protected final AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); + + /** *

    * An instance of ZipMdrefManager holds the state needed to retrieve the @@ -763,11 +770,24 @@ protected void addBitstreams(Context context, Item item, bitstream.setSequenceID(Integer.parseInt(seqID)); } + // Get TYPE_SUBMISSION policies before removing them in the `manifest.crosswalkBitstream` method. + List bitstreamPolicies = + authorizeService.findPoliciesByDSOAndType(context, bitstream, ResourcePolicy.TYPE_SUBMISSION); + // crosswalk this bitstream's administrative metadata located in // METS manifest (or referenced externally) manifest.crosswalkBitstream(context, params, bitstream, mfileID, mdRefCallback); + // Only add the saved TYPE_SUBMISSION policies if the crosswalk actually removed them to prevent duplicates. + if (!bitstreamPolicies.isEmpty()) { + List remainingSubmissionPolicies = + authorizeService.findPoliciesByDSOAndType(context, bitstream, ResourcePolicy.TYPE_SUBMISSION); + if (remainingSubmissionPolicies.isEmpty()) { + authorizeService.addPolicies(context, bitstreamPolicies, bitstream); + } + } + // is this the primary bitstream? if (primaryID != null && mfileID.equals(primaryID)) { bundle.setPrimaryBitstreamID(bitstream); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/sword2/Swordv2IT.java b/dspace-server-webapp/src/test/java/org/dspace/app/sword2/Swordv2IT.java index f24a09c8e56..ba9f6c40520 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/sword2/Swordv2IT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/sword2/Swordv2IT.java @@ -190,6 +190,48 @@ public void mediaResourceUnauthorizedTest() throws Exception { assertEquals(response.getStatusCode(), HttpStatus.UNAUTHORIZED); } + /** + * There should not be any Internal Server/Authorization error when uploading a new Item with embargo + * The embargo is defined in the `mets.xml` of the `example-embargo.zip` file + */ + @Test + public void depositItemWithEmbargo() throws Exception { + context.turnOffAuthorisationSystem(); + // Create a top level community and one Collection + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + // Make sure our Collection allows the "eperson" user to submit into it + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test SWORDv2 Collection") + .withSubmitterGroup(eperson) + .build(); + // Above changes MUST be committed to the database for SWORDv2 to see them. + context.commit(); + context.restoreAuthSystemState(); + + // Add file + LinkedMultiValueMap multipart = new LinkedMultiValueMap<>(); + multipart.add("file", new FileSystemResource(Path.of("src", "test", "resources", + "org", "dspace", "app", "sword2", "example-embargo.zip"))); + // Add required headers + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.MULTIPART_FORM_DATA); + headers.setContentDisposition(ContentDisposition.attachment().filename("example-embargo.zip").build()); + headers.set("Packaging", "http://purl.org/net/sword/package/METSDSpaceSIP"); + headers.setAccept(List.of(MediaType.APPLICATION_ATOM_XML)); + + + // Send POST to upload Zip file via SWORD + ResponseEntity response = postResponseAsString(COLLECTION_PATH + "/" + collection.getHandle(), + eperson.getEmail(), password, + new HttpEntity<>(multipart, headers)); + + // Expect a 201 CREATED response with ATOM "entry" content returned + assertEquals(HttpStatus.CREATED, response.getStatusCode()); + assertEquals(ATOM_ENTRY_CONTENT_TYPE, response.getHeaders().getContentType().toString()); + } + /** * This tests four different SWORDv2 actions, as these all require starting with a new deposit. * 1. Depositing a new item via SWORD (via POST /collections/[collection-uuid]) diff --git a/dspace-server-webapp/src/test/resources/org/dspace/app/sword2/example-embargo.zip b/dspace-server-webapp/src/test/resources/org/dspace/app/sword2/example-embargo.zip new file mode 100644 index 0000000000000000000000000000000000000000..ff1273679b88fb5292974a21d6a35b74ec0d42b7 GIT binary patch literal 11982 zcmZ{~V{jz^6Qv#Acw*bOor!JRwsm7pjEQaAoH&`-$;7su+$8(H-&eJ}RlD0=)u+0< z&j0h{QI>;*!TINT>tnS0q&IK}&(GqrM(|`Nr`rD^8&_>`KP0JV0T;>BQ$s~UW!Qn) zlaSC88gjNk1YtGk0hU-etiIYQv4X05&?k(WY&R=ErMbFd5PB6w$!fKF6mSVInYaD3 z4t#gcry8Md1{Pb(Gd!j)@0#dm(ci3Jm+I<=LExb##d($zrIu#7tN3n;hjD1P0HiuEQ7?-W94JP`1tsCUnKEiW*`wC zAN!daSVOSU>Cw^2$)#KNK?^=}dOM1lyUt@^O%8mVzld=OR0TUS$ zfANF-vUYzgh?`=lvA-lJ5E&s2%8L&U&P&=$U*27$x#j&cKI#5ik}ULqDo35B_T~TA zi@4Vklby!yu>W5SwpJ&hb3&ZK?kVfQ0a~)^0$Wp+?KxhS?!Oae96Vejj!yUK^LHe> z?CiD~raH%KqCCUkp97?YCVO7j!clQCO49Papu_g^S++bY%U+-C^zXV|;9jIs{v!}b=UX<5)9Kho1!cAM;>;5oLXR` zXyyJU0l8jz&3@&Xy?o^he3YAg-`gSXnioJ~Cj?^$L8RI_C6@$$pL!(nzN|DKbaBbG z+3w^Wb#QBHjcKMqx9Q>Jn*~GH@$$`q{fJ0Aa~KB$FpEL{o5Lex6n9E;MwkTquAB%R z=U6|vL8_}*y~MjmXE|IHkV+IBujVKy3<3~D&TNzQsbkpj=FJqbtBBQFmuSu|1dV)! zZs(YsB!#NIs1pEhS#s!fTW4ztia8r(y(J-=CY#i}w6D>XEX_MezI( zvPHr#IT_|F_SMPS4VKpg4@(Y_d{bG-Q@3#<4iYY`NRm5-p(uY2;~!M_N*7sPX`+vO zJ1V15OO6C&6MplDSwNsjW8#a|Z_!H!BDt|xozsj}w({S=%@>Nr<9cRS&a z^YGGF$2awm1WUJ#vttzG; z`Hiq?pI1=EBBI(@kPX!lbrBY_>l%c#3oG-Xog;1rV69)#1B6BFwCY9Qyi4#AC#IW|P_ScSmiVvDpRkj! zN;pNGmb+#nNWsjh(X!|8NQp(@u@}?rtt9)ueSMsg4?T}ZMZC)sehxefdk!KQW$^m5fI?(!GuK(Q&Vfak6GEL2u;xQ`h^m!1#X6M9-M76(%2*ToK?#XXBndmD@+x}p zUrY#rmR=!o0B29A5Pol{XzVg@D0=hed-L&IyYXwjyjdJSvHb^gV{OgQ31l<$qN0FJ=DKq&!d3m2AyUUYg~9nzp1sX@zi?Ng znF1EsDSGTpS`G`#g)fj~yNP;oDs)l`9{Iy{l95a*`)apg$nc-%6@;r%qOB&s718TY zD@C)r3hsAz%qq|_~EDJ5>-dM6XHD~RaY9Ax?= zzn5~UBnZP!kO|)018%PM6<~0`rZsclQyAI?;dpVLI&$I6V`}381s^P{3yHH`#s_G-l0GeH z9{gq(;xU=90$9in2O%v<*BcxN$U6P7f(}UHX#J4_Jt?My9aLOzSG4A$&OCB zazwHsAEB$c1qVAu(0nldv^v-iaLLbc8^bsm%m z7W$^ndvQZsGaB`r8`O#SOTmA*AE#o^tyQ2{cJi8onozjj#r?S7nrPBQ+XPX7HlHbo zN!h;zjp9`l+ARmG8OKAlF+5L7Nb{k$b}ku^D+S{(@2~_Vao&;Rxshdl0e*FFZZTsF zc}Xy_46Rb_S^k)zda?*^TyAVu4RL6u9b&8uk*wPv9QaTGnlMMy4uxD~PA_(>M%jlZXQdcK z0gG@yNrJ@FCH-T;6cL2+r3Y5gvyMu94B{Jh= z_o{(O9Q4q%hTRUJ*#kqZX&~f65%>ip`qWPWY+CHR(EbyrZ^MDc=0i%zGBGsiLvvT{ zQ9$@og?1mOK!&Vu#3Y{y@MXaSy=n9a`1(o(JuxAipo^G?@ zys~3;8DS(TT>H;_zGRXbXO+K@@^x{5MMyDiNwydJMp@CS(U2Yc2bvw)un;H*IfV8H zt3JxSKC0scLPtgw<3_4*f_!W4)^J&;gCwx(FO$r+IDM4@rCTkZ7J^PFn|*IYMb5=@ zUs*{$+2pnC&-UGIM1WR`?Co6JJ{ zSC4o>hd0A;$25;9F zoy}t9P?Tsuik+GNwnjt>vLvP5STt|0K;I-%1yRr|WcdF|a9_r{f4aAUyOYiRXs(Em z0KJ_5Q%#K@MeR;Mh?q)W+2B};17^d~Xjcn-KQ~U2<>)J~zAjnCV4MGI`pZ~$QQzZ} zP1yF#x5>$);Jf>vV*+-X2r?agzyXdL26Rx;dGow12+jczPi576Je(|1zxM=2 z9)BL7flz@~$&yPejdy4@nn?^;3_eWRY7qKT3Ao8#IK3HdIF#}vJdOR!TxP|Oj0EIv zH0)Dvz=dVUH_S|A5tt?U_^a_Ns;B4TB=Af^9p8dCqd(j`$S;e?%~Bztces^rKbS4S zUmh}M{BPB&axl2pm%`b~E^N|Gy1CXW2ZP;3a^;x9Puq-(MTK=nDX#1w3cu`XfcP6J z%YBWHS%vgvMACuwOxoJ5EWd7~G+Gjy??TT~zkU;z=~)d_0t(0b4RCDIB9BSDB?N=?T0(6n{@KtxGQ`lFrsUp7uc*8L{JXDz|q z=={6^#lqi{iz5mYn7*=@8=6?fY=W?|uH_v(VS{8JVRKX6ENJF_r!DS|L^1429zds; z$5!}Izp`0uZPJn5M&W5TEZH|<dD zHTO@%u^`m(@%F@Ol3pyo^ca1B4Y7<)wt0|CzQiShF-$XdJj#;^-%{V%G&+fY4WemV ztIXJUT+2(_q5WUXd9cK-LwjnQWy2#p=$A5HP0kYfG zs*(oW!q^sUQjh?Lrj8q_U4lB-Gfp`3gH1dfR|&}+L*h(yKs6IVgacO2sIi9%-(=~kH zMuo{a6tzKA*ZL0f5JD!u*jL0~b-vUKJm;J!BCzmA85<<8sm^BQ&is^BTiIDK?!ebH zl!#}RO7=v>aCEn49cl5Ix;j`OqU|uJ^r{h;^4#WW@6|qPy0ubt@pqCIq2$xRtrK?s z&dXcEf7F`mD-K*6LvZ+8Kok<3R38Z`Z->tmvR#BrqoqvJW-0HUZ_p>{k@wK?NjzF!;U;Sql*ct0ReCT)u?{J3Ewt&3C(n>`LMICgVj!vHxD59=gdf~Wx?4xM5;xMp#fqE%!ZjW$sjUz9xy zJP-S*nxobWw(KmBjGL2il9PsgoB++PB8Yop~Vl3{e@qsr4`pwQu0qwG(dMk_nx#Ol>7TyMo;aTYD_4&2au(Mi#E zFcb;yfhRtAcJf73VMSswF!RJb$a8b z1b%rBXaaGHyfw!C&l zL?rq-lW*eAW~L6fB0h(-eLfj%IpSB5uv!^A2=kT%ZgGt3_o{t4o@IY0f>h*B$vj}9 zoL|i}2WTG0TJmJ5$xuKf?kqv`U4&wu9bY6>LoBMH0O#6077DP~jSndsk^Ag&)<%h5 zp?O3TVrel^xP*Qa3!O`9(cxjbK`9kXbBFKE{EXbz;w2p3!Bl1H0FL71r7)Gm`U)sD zxyF^UZT0S25<&bB7d1ZSJK2QLA?qTg#olR`C&Gu`^G*haJ~|mv)tXn(2WpEqd}pr( zqj#^iqsL>b&0q#re=%TD0Y6;~b>)g9NJ&an$~^uc=D0!0CMtE0$0sgq)>i_tR7`-swIzpfOIw zg>eHg`ZL#vcg>u7I&p_d=O~CY&?wFwI!#;`dmL*cFrrS12$vzqmo2&!jsMvEk;I&G za2`sfQ1yoMfqMNVR*9&{l}q{U<)+J*%!Lx1fD4<=HeVG_HNmk$NXU;~SHKF^Eb0!g zy^efgPx3e;51(8Sg4A<1|D=VO8vQnE*k6E5hx6&jMf2_b-OI&n2}dX6O`Ajqu~;Uo zd;inGrZ1)g)hWG9Vqj&i^|xs4y*m>vNCHG98t;Zev zos1x)ih`YRE#j`FvR}CQC!`P=h~u%COOAUb#`|1m1$wfL^bRWTSgAM#?RQ1b#0z@n zCM_LuvE3_y=^|;W`gycX(ceo1<|~+dwB-iRWyTd+&Q!@ z3EO=_SDpN0V^+)3A2NK8q@;qs0K;&oSQCf&8NCj8DELBKwnQn6QUc7XJDNx29H#Ym z3%mqQJN>-c*Mq99#B-js)SMlrleCY^seQtJ7n3 z(gGkaKWx@3fxF#w`Y!&incT;n-}R=7y_PE%o#)Jhm!r_zQ#IuFmdupVCH_aGbnEsN zr%uC%53JwC+pKLJlBqvz?Dz;>6#b(*@Ok3v@rbAxv(PhbW`DHAPenmPcz05OoUWGR zPxNj?X06K+iuaX)g$mUA3x<7}UjH_@L4P4C`t*hh+E%b8uJ3MKVimjKty297vta$5 zS&T1`=&v*Y4y#(Uns@cr`a}tBq3a?~((3rw@!_s|_G7T(8FzkZE+|2`TOshJu@l=T zIXO}@mt+G)w?EvT7G119Y}4+*t4?5SIy#qu-1S~Rbf6MAuc{=%CenvB;C*9Xu^Hg z_UBAp$)k?#$d9ohggr6<2Z_K;M&Pv6g0sopF5f`82Fbn@gB#P)cB#|=R} zq;(2;VBl8LdcD2?6{{yXH*=1xfSj*Akuh1G8>VCLww8lf0M3c=KX)rydj{E|s>_iN z;2njS!B&_WPO~-EeJ5g$Uo-A-VHy078=j?etr~C8;Cl8@s+EKEUZN@cArrw(UYO7m zX1nLnhWHEZ1mtgb8FZ{NREqixm^L(_-R9?GHx0b%jOh#YP_-f?tSPV^qTba$jNiHO zfT&}Z;1XU436nI+xWa)~ATwZ{X*p^$Q@0TOT_yp#eJb0P$lOGiwhzCo*GI#qqcIw8 zDkE_$qrk)0?lKpmY2TC&q4US)lDM;)*xwF2_$cWdmGJEi=eiw?``<@>oaf{%BMW{J zV~MXyN%y;YTHZe@*x^a6b?D;VrUFeP21|$y&My~A?d`-Cnf{h}<+_GHivMgZ_r3C!v<^0#Jei+JBVsQkN!NQ%l&YwUQy)Kx z3p*uuwi|7Xuh7qhX`AfPN1{P6$@>Se+L~3vQM)1?%Mn!)9O)xY_#i8)zYkvzSj}8I)pr!N9WO|US=osEyUX_J z{VenNVl0_^a`Pi!UVX0JD4XQOYnijBhmdq#v?DXc$`OkBibk}e;ye}r3OZ`pY51$kedm5V;P4(lCW*^qh8MpaeQNybixA?My zdV*AcWt0_w8Q$6e!g_Ne$t1|*JYo$z3ge?5B@=OkiI^l24knU_&si%1$38Ev-^klh zsC!;_yS|?sHVioHgCr~Ej!NB$=G|4~MIePdGw;MPB_l1u9DN~gn`vkW7_3j`DgKt( z+<2ovVA&LdyhR#naL{16idCfErs?(ja~Pe!iL=0}K9VjPx;GYf{!}WR)@isGQ_X#c z;KtZ6G_KQlj)S**tn$puEHT^lzT#0$!`HqAQ2&H4p?#M&BD}e9K~4ruqId-Ja+#bA zdZbvS5at%H#Q59V87@w@Cf5tgYsPO(gy*}2*Y)`TrX@U5dGwYMZ!i?_rmWV1I?=Oz zY~QwraI_c3X6-SF7+5@uoKI*Pou=8qXH!V^-tq|?U!A6W+33UkxUSmCx#T0*IwUv;TIqqA0Hv%Sku80v7gJ8Z(eB4U;Ju z)jaIbs4j6g;6deA18P5(4IChm*bH;z-w<;3>4*I3WbAO~#pcM0S3_1UY!}6!=bN9# zSVK8An`ov`%QX%;;)2lqDj$OAUjsj~>$Ap%NxiCQdblY^*S^NTZYw^fMz_3O%iAC@ z|ERvzQZ_zdvL2{xkLMFx2eoa+%X27YL?PYO2<#O8$w| zZd+>I`nA99Ooaw+!rRE*cduhvG;H#FSqBUOb}M(wQxa?ectR|A zs^7m;95b2C9fnwqU`pSCrB}H`q&5czZD;Ym=XXism3G+vQKHjq%>p^ad4{>!fXrFM zgu9#^C8JAXll-RT{t!HdOQqjwPu(W25Akaa(P45t?h0`Q_Czp8BB^y}Ek6pnr2L0x z-w-rnR!^z4Tf}$gio}ryB@tpJ;-=dagvq}B3Jl}(Wo+LKL3swx=ED%99oNS-br8=y ztLnaY+F!c%i~1p;{#&}(w{w&f=W>>~9+ibl zVSgosxJ~4Rd`_&$pS{h&C-8!aw>im;7nU;O%a5#wYwuOKPZ-d)XP$JY%uj|NIPJ?5 zU^#)B^?yi_?dmls6{^sr7Up-dWAVIn*lk2Io~$Puumb9T;;$fD^mn?0LaGy#vTIc~ z`UEXdQBO%+?mqetN|7=u?BV?(9)gX&&+(1x)RcU$3G086X7l*LTQV{m*2SfaZo|s- zq;KEDrqv?peai4KxY{`P*wi;mYs#pWN~ugIn{;!@ynoi)!ba$v@>I3H@v*Y*h@BPH z)|q>m&sS_XxtOXKDZJzqllJ)mvcuAZQ}`)Cv<}<+XvNw2XNU!>u0Ri(PoR-2T6t!r z>9EA+JX9-E5veWqu{GI`{6N)8_DO;#e8Nk*R90m90otaz@K(NxSgMY-*9UaVn?LMuZfDZ6=LprX!04#rayJE;)h>*m-{0w>bNH zx8&RPKZ_bR_L-Ib!aYB4G*kFJVo{gNAtup80x&<^wWZ!3i_bCqt-xkO zHa+jdy9i>oO?MWOYi91&;3x{tSc0zf z6eTLcbz00y*yjZyzOnqMnYeac)XRX8AM1~~dF0s|LxU*7uQN7^c6@`MnU&syKp^;4 zjagJmteG0)ob!)E4?^sc8kxUBlZfuqn9)vssrD@DWI`prte4lt88vX@4CLFovT83i z9yV(+TN)s9o8yVRt0J;r0i#5lfr}N!x0A&V7=k8Ftj@hrYqhmX6lGA`L(?GTFivBYQG)}bj`@EUztMw`&I8VGT-TX%#kTY-AVZ* zhiZOeYNlbEEm5VXQ8aHcL_9m!DNAZuZa3Eq1;WM`lsXPCm|sZFtui}dk=@oJ7S?Vr zq=~|Yqk*?P*KE(jZIku+%QRu@WrZIws?SBcSmqw#x33q7bUJa9cYg%N*c>{6mo538 zq-M3gV6Gt$Mye!aECR;9M(R;HRCNrZ(Ta6BUaA+l{mHynkbq-3wk>cgwHn{TgYO}qQC=E8f`&s64>Ax|@L+;MSg_GfzhGT*Ye zDIbnjFKUqU5}=oyT@9{P{ZXv%MDm(1=1eT{#eScc!S_vP;I}VzSTRRu$2l*o8fI(G zD=!eXSf`FB@elu1hSbkikNQ%AT>duQCDs5I^B+{z86D*OS$@hd0yF4#_3C?eqfERO$p8c{}%j)taiBlE+;+!V4;VPAhE`2OamF-3V$Zs@VCi+@3qkurc+ zC}74d-bj%@P-p~ISCtf3sY|nay$MA@R8gDlMmIBy^Q$3IorjcXAMQ#QuKf%2_@I=V zK^Oh*xSFhA5fSiFCd|O`?r--?OFvQ4ki%KXFSPCmK>LFho=CDU=Ka?w7T!m3_1B+` zC8S=NppY+|IOzPwD6-nQSz%Idky&xi+HlpgQipoYYRloF;~Zv(t%+?%k2j3Q!^Q>A zr^1PmO~*jc>pWNM@^dnwEpXem^@@4uCkm5wEIz}$NZ69-+?Q(BAUJX(+Z>@9B4Nci zIHveNJd%fb|Kwo^W4Bhs9&Yi1yc=Hh+3=k@=yTAvHPF80UyA0%0_c>e!kf=q5j`A~ zG}`P%EHCM})7dR`+1z=U_@SqxNZa((e}TUn+HF|>!>+;FsquMXP;KC_DY zL>DAmk(B^dS=%aWvfiSkGZ@1F(}y5IOo39-+MCa>FLxR1#qpLj&+hfMWj^SF`*=A5 zi$~*n*Xw9gTftx^F5_u(hN7qcU|eaR+q!I+^XUa^c;i!K$_2MQWuXBC(7L54nV3>x zba-?W6=?c!Ipe8vWsDlkfFg8vDL5lrR#eXCBlbh_Opf3Bj1?8>Vi{2C6qE%LvaF}! zm?1@p)7nuacx=Op$}j-27=}@}5R_pvUC5kI!SUa<+yJ2820fjr%|Rs;eb~9d<@GgK zoNQlVA~lJ57A<^E1$HWAto&SN6D$Z)VHKZAgjN309Nq%%Qz?Rcj`r{_m6FP`kXD0U zRtZJLxh$HBpp67q2cImFvqqjFH7ntk$!@KHm_=E-wB#PKHb}-BPW3cd(wHlRf&>vC z>1C!88{;7=0=H$9c6=Bmrd_aBq|$H-AbEh?p$-lr^{V%U+l@lef`cSWj*G;zv4wtb zSdWEg3G6KxglXSG5J3N9UDWS93@bGK?-A~F%yo1E+9+6aluJ)!83E1*D+z7NAvXD( z;DT}*3I(~X5%H28n!jGT`$_I#UkggNC{a?S*>DtT3gUUh$~sFTp#U;|B^h^RdLZ8f z!3W|lVBQe-+t!lA2hkYMUM?yby+brT_K!SGmP_GP%D^9*8DqHuF85TcC)WkoND+2r zXp71SQd3qnUq1X2rQ&T%K@<_8&@oNF$RKC9>P@q4`ps7&?*b9NYy5v2>3=3p-ryuQl;Pl=3 z$|C9~`vxlm_(T%&V{e9w-!LWQvx(Gu#rqSuuGy>K1@4EPrR>_0D}N}Ee+&#$h=VEERR)h$g2m zSI(cQDmSr)iQF?gnNq!|U3@C%=8rlN>u3GpKp6P?E)g`hcUK5;!m>_-I#3dry4jW_3g-)M|4FZ z3wpU~P#X!qQ`OJ&eVagLU1v_*m`W;NsgA|@L;1;9-tLu%?WsQ=Z&sXQ-@7xe`4<|O zOB*(m148h*mpwrQ({zkug8VcJrocKxmzx1>EnG4+wDYKmq5zwECf> zjsB2!-1w5ZCqSNJ!cjf5Byu<3P_d%hCIw9Ii8Gze7;hV;)}i}7Pp`18(F`(SqX5N*+b7$^BK}J97G_wa({WJWpIf8h1Y`e2GL0%md5SBM7F{3>nRmQ*(j{dgjC5hlO=OgKKdx0H?u zQhW_P`%ELb%W`$9pccO2T-NkIz(UsG17>gj44~148s=3+)_+4Q%Yj2+fc?KQ^8X}> u{}jppH~$Z){C_w9-!jO5UEu$}nd*Nf5@k7Pz<)mw|7o}Xs07o0xBme$gFCqZ literal 0 HcmV?d00001 diff --git a/dspace-swordv2/src/main/java/org/dspace/sword2/CollectionDepositManagerDSpace.java b/dspace-swordv2/src/main/java/org/dspace/sword2/CollectionDepositManagerDSpace.java index 7f77c00f0d9..afc35cfaa11 100644 --- a/dspace-swordv2/src/main/java/org/dspace/sword2/CollectionDepositManagerDSpace.java +++ b/dspace-swordv2/src/main/java/org/dspace/sword2/CollectionDepositManagerDSpace.java @@ -151,6 +151,9 @@ public DepositReceipt createNew(String collectionUri, Deposit deposit, sc.commit(); return receipt; + } catch (SwordAuthException e) { + log.error("caught exception:", e); + throw e; } catch (DSpaceSwordException e) { log.error("caught exception:", e); throw new SwordServerException( diff --git a/dspace-swordv2/src/main/java/org/dspace/sword2/SwordMETSContentIngester.java b/dspace-swordv2/src/main/java/org/dspace/sword2/SwordMETSContentIngester.java index a77caa655b5..45f0f315aa2 100644 --- a/dspace-swordv2/src/main/java/org/dspace/sword2/SwordMETSContentIngester.java +++ b/dspace-swordv2/src/main/java/org/dspace/sword2/SwordMETSContentIngester.java @@ -10,10 +10,12 @@ import java.io.File; import org.apache.logging.log4j.Logger; +import org.dspace.authorize.AuthorizeException; import org.dspace.content.Collection; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.WorkspaceItem; +import org.dspace.content.crosswalk.CrosswalkException; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.packager.PackageIngester; import org.dspace.content.packager.PackageParameters; @@ -28,6 +30,7 @@ import org.swordapp.server.SwordAuthException; import org.swordapp.server.SwordError; import org.swordapp.server.SwordServerException; +import org.swordapp.server.UriRegistry; public class SwordMETSContentIngester extends AbstractSwordContentIngester { /** @@ -181,6 +184,12 @@ public DepositResult ingestToCollection(Context context, Deposit deposit, } catch (RuntimeException re) { log.error("caught exception: ", re); throw re; + } catch (AuthorizeException e) { + log.error("caught exception: ", e); + throw new SwordAuthException(e); + } catch (CrosswalkException e) { + log.error("caught exception: ", e); + throw new SwordError(UriRegistry.ERROR_BAD_REQUEST, e.getMessage(), e); } catch (Exception e) { log.error("caught exception: ", e); throw new DSpaceSwordException(e); From 993801ab69ddab3d657d921bae725daecccddd1e Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Sat, 7 Mar 2026 09:37:39 +0100 Subject: [PATCH 881/939] Avoid exception during OAI update caused by uncached entities (cherry picked from commit c3ac49dc64135ab48b34ef3766fcb5981cd94fbb) --- .../main/java/org/dspace/xoai/app/XOAI.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 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 16c16b0115b..0c6d3e64c38 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 @@ -190,9 +190,9 @@ private int index(Date last) throws DSpaceSolrIndexerException, IOException { .findInArchiveOrWithdrawnDiscoverableModifiedSince(context, last); Iterator nonDiscoverableChangedItems = itemService .findInArchiveOrWithdrawnNonDiscoverableModifiedSince(context, last); + int total = this.index(discoverableChangedItems, true) + this.index(nonDiscoverableChangedItems, true); Iterator possiblyChangedItems = getItemsWithPossibleChangesBefore(last); - return this.index(discoverableChangedItems) + this.index(nonDiscoverableChangedItems) - + this.index(possiblyChangedItems); + return total + this.index(possiblyChangedItems, false); } catch (SQLException ex) { throw new DSpaceSolrIndexerException(ex.getMessage(), ex); } @@ -262,7 +262,7 @@ private int indexAll() throws DSpaceSolrIndexerException { null); Iterator nonDiscoverableItems = itemService .findInArchiveOrWithdrawnNonDiscoverableModifiedSince(context, null); - return this.index(discoverableItems) + this.index(nonDiscoverableItems); + return this.index(discoverableItems, true) + this.index(nonDiscoverableItems, true); } catch (SQLException ex) { throw new DSpaceSolrIndexerException(ex.getMessage(), ex); } @@ -305,7 +305,7 @@ private boolean checkIfVisibleInOAI(Item item) throws IOException { } } - private int index(Iterator iterator) throws DSpaceSolrIndexerException { + private int index(Iterator iterator, boolean uncacheEntities) throws DSpaceSolrIndexerException { try { int i = 0; int batchSize = configurationService.getIntProperty("oai.import.batch.size", 1000); @@ -334,10 +334,12 @@ private int index(Iterator iterator) throws DSpaceSolrIndexerException { server.add(list); server.commit(); list.clear(); - try { - context.uncacheEntities(); - } catch (SQLException ex) { - log.error("Error uncaching entities", ex); + if (uncacheEntities) { + try { + context.uncacheEntities(); + } catch (SQLException ex) { + log.error("Error uncaching entities", ex); + } } } } From 949c36ee65c44c67594fe0088508899f5f0650f7 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 24 Apr 2026 16:05:51 -0500 Subject: [PATCH 882/939] Bug Fix: Add missing Entity-related metadata fields required by virtual-metadata.xml --- dspace/config/dspace.cfg | 2 +- dspace/config/registries/journal-types.xml | 37 +++++++++++++++++++ .../config/registries/schema-person-types.xml | 9 +++++ 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 dspace/config/registries/journal-types.xml diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index beb985eb472..7e45645983b 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -944,7 +944,7 @@ registry.metadata.load = schema-publicationVolume-types.xml registry.metadata.load = openaire4-types.xml registry.metadata.load = dspace-types.xml registry.metadata.load = iiif-types.xml - +registry.metadata.load = journal-types.xml #---------------------------------------------------------------# #-----------------UI-Related CONFIGURATIONS---------------------# diff --git a/dspace/config/registries/journal-types.xml b/dspace/config/registries/journal-types.xml new file mode 100644 index 00000000000..c68bee1ce6c --- /dev/null +++ b/dspace/config/registries/journal-types.xml @@ -0,0 +1,37 @@ + + + + + + DSpace Journal Types + + + + + + journal + http://dspace.org/journal + + + + journal + title + + The title of the Journal related to this object + + + + + journalvolume + http://dspace.org/journalvolume + + + + journalvolume + identifier + name + The identifier name for the Journal Volume related to this object + + + diff --git a/dspace/config/registries/schema-person-types.xml b/dspace/config/registries/schema-person-types.xml index 3a8f79732d4..9a3c494de49 100644 --- a/dspace/config/registries/schema-person-types.xml +++ b/dspace/config/registries/schema-person-types.xml @@ -156,4 +156,13 @@ Full name variant + + + person + contributor + other + + + \ No newline at end of file From 69c66944daee5ae742a922ffac3e4bdb74b0b2f5 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 24 Apr 2026 16:07:16 -0500 Subject: [PATCH 883/939] Bug Fix: RoleDisseminator must add EPerson email to "Name" attribute in METS because that's what RoleIngester.ingestDocument() expects to be in the "Name" --- .../java/org/dspace/content/packager/RoleDisseminator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/packager/RoleDisseminator.java b/dspace-api/src/main/java/org/dspace/content/packager/RoleDisseminator.java index 15e9f1b1449..48901b5ce5c 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/RoleDisseminator.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/RoleDisseminator.java @@ -310,8 +310,8 @@ protected void writeGroup(Context context, DSpaceObject relatedObject, Group gro for (EPerson member : group.getMembers()) { writer.writeEmptyElement(MEMBER); writer.writeAttribute(ID, String.valueOf(member.getID())); - if (null != member.getName()) { - writer.writeAttribute(NAME, member.getName()); + if (null != member.getEmail()) { + writer.writeAttribute(NAME, member.getEmail()); } } writer.writeEndElement(); From 4024e719539fa4b456b3191ebe0ae7b389657b9f Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 24 Apr 2026 16:13:53 -0500 Subject: [PATCH 884/939] Bug Fix: Improve handling of missing or empty Bitstreams. While this shouldn't happen much in production, it may happen in dev/test environments. Ensure entire export and import doesn't fail when a single empty/missing Bitstream is encountered. Instead of an exception, log a warning. --- .../LicenseStreamDisseminationCrosswalk.java | 8 +++- .../packager/AbstractMETSDisseminator.java | 43 +++++++++++++++---- .../packager/AbstractMETSIngester.java | 20 ++++++--- 3 files changed, 57 insertions(+), 14 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/LicenseStreamDisseminationCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/LicenseStreamDisseminationCrosswalk.java index 46858747870..b1854bfd85a 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/LicenseStreamDisseminationCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/LicenseStreamDisseminationCrosswalk.java @@ -8,6 +8,7 @@ package org.dspace.content.crosswalk; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.sql.SQLException; @@ -56,7 +57,12 @@ public void disseminate(Context context, DSpaceObject dso, OutputStream out) Bitstream licenseBs = PackageUtils.findDepositLicense(context, (Item) dso); if (licenseBs != null) { - Utils.copy(bitstreamService.retrieve(context, licenseBs), out); + try (final InputStream bitInputStream = bitstreamService.retrieve(context, licenseBs)) { + Utils.copy(bitInputStream, out); + } catch (Exception e) { + log.warn("Could not retrieve license file for Item with UUID={}. " + + "Leaving it out of generated package. Error{}", dso.getID(), e.getMessage()); + } } } } diff --git a/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSDisseminator.java b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSDisseminator.java index fd50ec8023e..2161a222e1f 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSDisseminator.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSDisseminator.java @@ -456,17 +456,37 @@ protected void addBitstreamsToZip(Context context, DSpaceObject dso, // contents are unchanged ze.setTime(DEFAULT_MODIFIED_DATE); } - ze.setSize(auth ? bitstream.getSizeBytes() : 0); - zip.putNextEntry(ze); + + long bitstreamSize = 0; + // If user is authorized to read this bitstream, attempt to retrieve it. if (auth) { - InputStream input = bitstreamService.retrieve(context, bitstream); - Utils.copy(input, zip); - input.close(); + try (final InputStream bitstreamInput = bitstreamService.retrieve(context, bitstream)) { + // Save bitstream size into Zip entry & put entry in Zip file. + bitstreamSize = bitstream.getSizeBytes(); + ze.setSize(bitstreamSize); + zip.putNextEntry(ze); + + // Copy bitstream contents to Zip file + Utils.copy(bitstreamInput, zip); + } catch (Exception e) { + log.warn("Adding zero-length file for Bitstream, uuid={}." + + " Bitstream is unable to be retrieved from assetstore." + + " Error={}", bitstream.getID(), e.getMessage()); + } } else { - log.warn("Adding zero-length file for Bitstream, uuid=" - + String.valueOf(bitstream.getID()) - + ", not authorized for READ."); + log.warn("Adding zero-length file for Bitstream, uuid={}" + + ", not authorized for READ.", bitstream.getID()); + } + + // If bitstreamSize is still zero, that means either we didn't have READ privileges + // or the bitstream could not be retrieved from storage. Either way, write a zero-length + // file into our Zip entry in place of the bitstream. + if (bitstreamSize == 0) { + ze.setSize(0); + zip.putNextEntry(ze); } + + // Close our zip entry zip.closeEntry(); } else if (unauth != null && unauth.equalsIgnoreCase("skip")) { log.warn("Skipping Bitstream, uuid=" + String @@ -629,6 +649,13 @@ protected MdSec makeMdSec(Context context, DSpaceObject dso, Class mdSecClass, ByteArrayOutputStream disseminateOutput = new ByteArrayOutputStream(); sxwalk.disseminate(context, dso, disseminateOutput); disseminateOutput.close(); + + // If our disseminated output has zero size, exit immediately (i.e. return a null mdSec). + // Likely, the outputstream failed to be created, so we cannot include it in this package. + if (disseminateOutput.size() == 0) { + return null; + } + // Convert output to an inputstream, so we can write to manifest or Zip file ByteArrayInputStream crosswalkedStream = new ByteArrayInputStream( disseminateOutput.toByteArray()); diff --git a/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java index 68383f1d9e7..122f1399208 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java @@ -19,6 +19,7 @@ import java.util.zip.ZipFile; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.io.input.NullInputStream; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; @@ -748,6 +749,14 @@ protected void addBitstreams(Context context, Item item, // externally, if it is an externally referenced file) InputStream fileStream = getFileInputStream(pkgFile, params, path); + // Before proceeding we must ensure we have a non-empty input stream + // NOTE: If getFileInputStream encounters a zero-sized file, then it returns NullInputStream + if (fileStream == null || fileStream instanceof NullInputStream) { + log.warn("Empty InputStream encountered for Bitstream with ID={} in zip file={}. " + + "Skipping adding this empty bitstream to Item={}", mfileID, pkgFile, item.getID()); + continue; + } + // retrieve bundle name from manifest String bundleName = METSManifest.getBundleName(mfile); @@ -1324,7 +1333,7 @@ public String getObjectHandle(METSManifest manifest) * zip) * @param params Parameters passed to METSIngester * @param path the File path (either path in Zip package or a URL) - * @return the InputStream for the file + * @return the InputStream for the file, or NullInputStream if a zero-sized entry is encountered * @throws MetadataValidationException if validation error * @throws IOException if IO error */ @@ -1357,12 +1366,13 @@ protected static InputStream getFileInputStream(File pkgFile, // Retrieve the manifest file entry by name ZipEntry manifestEntry = zipPackage.getEntry(path); - // Get inputStream associated with this file - if (manifestEntry != null) { + if (manifestEntry.getSize() > 0) { + // Get inputStream associated with this file return zipPackage.getInputStream(manifestEntry); } else { - throw new MetadataValidationException("Manifest file references file '" - + path + "' not included in the zip."); + log.warn("Zero-sized file entry={} found in zip file={}. Returning empty InputStream.", + path, pkgFile); + return new NullInputStream(); } } } From 1fa3696aa9c75944023bc0032689d5e4f2ad44ec Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 24 Apr 2026 16:15:34 -0500 Subject: [PATCH 885/939] Minor fix to exception message. Include the metadata schema name in the message text. --- .../content/crosswalk/CrosswalkMetadataValidator.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/CrosswalkMetadataValidator.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/CrosswalkMetadataValidator.java index b1be458a255..1bcc1c9ba9f 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/CrosswalkMetadataValidator.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/CrosswalkMetadataValidator.java @@ -107,9 +107,12 @@ public MetadataField checkMetadata(Context context, String schema, String elemen e.printStackTrace(); } } else if (!fieldChoice.equals("ignore")) { - throw new CrosswalkException( - "The '" + element + "." + qualifier + "' element has not been defined in this DSpace " + - "instance. "); + throw new CrosswalkException(String.format( + "The '%s.%s%s' element has not been defined in this DSpace instance.", + mdSchema.getName(), + element, + qualifier == null ? "" : ("." + qualifier) + )); } } } From 5b66f8743f817703427731a2311aa5e47141bae4 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 12 May 2026 12:20:48 +0200 Subject: [PATCH 886/939] DSpace#12470 Force each JWT handler to fall back to documented defaults (cherry picked from commit 54b7b574416ac3bbf6791dfd2ee653f35939ea4e) --- .../app/rest/security/jwt/JWTTokenHandler.java | 13 ++++++++----- .../app/rest/security/jwt/LoginJWTTokenHandler.java | 11 +++++++++++ .../security/jwt/ShortLivedJWTTokenHandler.java | 10 ++++++++++ 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java index 6beab1aa853..5ea9af25dac 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java @@ -65,7 +65,7 @@ public abstract class JWTTokenHandler { private List jwtClaimProviders; @Autowired - private ConfigurationService configurationService; + protected ConfigurationService configurationService; @Autowired private EPersonClaimProvider ePersonClaimProvider; @@ -79,6 +79,13 @@ public abstract class JWTTokenHandler { private String generatedJwtKey; private String generatedEncryptionKey; + /** + * Get the default expiration period for this handler if not + * defined in configuration. + * @return default expiration period if not explicitly defined in configuration + */ + public abstract long getExpirationPeriod(); + /** * Get the configuration property key for the token secret. * @return the configuration property key @@ -219,10 +226,6 @@ public String getJwtKey() { return secret; } - public long getExpirationPeriod() { - return configurationService.getLongProperty(getTokenExpirationConfigurationKey(), 1800000); - } - public boolean isEncryptionEnabled() { return configurationService.getBooleanProperty(getEncryptionEnabledConfigurationKey(), false); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/LoginJWTTokenHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/LoginJWTTokenHandler.java index 1fad8416580..46877b03c3c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/LoginJWTTokenHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/LoginJWTTokenHandler.java @@ -15,6 +15,17 @@ */ @Component public class LoginJWTTokenHandler extends JWTTokenHandler { + + /** + * Default expiration period for login tokens in milliseconds + */ + private static final long DEFAULT_EXPIRATION_PERIOD = 1800000; + + @Override + public long getExpirationPeriod() { + return configurationService.getLongProperty(getTokenExpirationConfigurationKey(), DEFAULT_EXPIRATION_PERIOD); + } + @Override protected String getTokenSecretConfigurationKey() { return "jwt.login.token.secret"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java index fc4ab39407a..177e84ed5a8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandler.java @@ -28,6 +28,11 @@ @Component public class ShortLivedJWTTokenHandler extends JWTTokenHandler { + /** + * Default expiration period for short-lived tokens in milliseconds + */ + private static final long DEFAULT_EXPIRATION_PERIOD = 2000; + /** * Determine if current JWT is valid for the given EPerson object. * To be valid, current JWT *must* have been signed by the EPerson and not be expired. @@ -67,6 +72,11 @@ protected EPerson updateSessionSalt(final Context context, final Date previousLo return context.getCurrentUser(); } + @Override + public long getExpirationPeriod() { + return configurationService.getLongProperty(getTokenExpirationConfigurationKey(), DEFAULT_EXPIRATION_PERIOD); + } + @Override protected String getTokenSecretConfigurationKey() { return "jwt.shortLived.token.secret"; From 158e511c60b8c6ff5548906e4c269ed9def44527 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 12 May 2026 12:28:56 +0200 Subject: [PATCH 887/939] DSpace#12470 Doc and comment default expiration periods in authentication.cfg This further proves the forcing of each handler to set an expiration period is working and sets accurate expectations for implementers. (cherry picked from commit d4cb66f1c6f83c3998aefd4db76677e25f846294) --- dspace/config/modules/authentication.cfg | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/dspace/config/modules/authentication.cfg b/dspace/config/modules/authentication.cfg index 41c28df1d7c..253035fe3e5 100644 --- a/dspace/config/modules/authentication.cfg +++ b/dspace/config/modules/authentication.cfg @@ -78,8 +78,9 @@ jwt.login.encryption.enabled = false # of some performance, this setting WILL ONLY BE used when encrypting the jwt. jwt.login.compression.enabled = true -# Expiration time of a token in milliseconds -jwt.login.token.expiration = 1800000 +# Expiration time of a login token in milliseconds +# Default: 1800000 (30 minutes) +#jwt.login.token.expiration = 1800000 #---------------------------------------------------------------# #---Stateless JWT Authentication for downloads of bitstreams----# @@ -103,5 +104,6 @@ jwt.shortLived.encryption.enabled = false # of some performance, this setting WILL ONLY BE used when encrypting the jwt. jwt.shortLived.compression.enabled = true -# Expiration time of a token in milliseconds -jwt.shortLived.token.expiration = 2000 +# Expiration time of a short-lived token in milliseconds +# Default: 2000 (2 seconds) +#jwt.shortLived.token.expiration = 2000 From 6a193db82583a1f4f3149d1153dba782833ef97e Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 12 May 2026 13:08:13 +0200 Subject: [PATCH 888/939] DSpace#12470 Remove unnecessary stubbing in short lived JWT test (cherry picked from commit ae45cecee456be71ae2e89220803e918d76ce682) --- .../app/rest/security/jwt/ShortLivedJWTTokenHandlerTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandlerTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandlerTest.java index 70497218652..ab3307fdaf9 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandlerTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandlerTest.java @@ -76,8 +76,7 @@ public void testJWTEncrypted() throws Exception { //temporary set a negative expiration time so the token is invalid immediately @Test public void testExpiredToken() throws Exception { - when(configurationService.getLongProperty("jwt.shortLived.token.expiration", 1800000)) - .thenReturn(-99999999L); + when(shortLivedJWTTokenHandler.getExpirationPeriod()).thenReturn(-99999999L); when(ePersonClaimProvider.getEPerson(any(Context.class), any(JWTClaimsSet.class))).thenReturn(ePerson); Date previous = new Date(new Date().getTime() - 10000000000L); String token = shortLivedJWTTokenHandler From af5a8fba7f5b07ffd1dc78823cab8add3d7e403c Mon Sep 17 00:00:00 2001 From: Mark Patton Date: Mon, 17 Nov 2025 10:58:43 -0500 Subject: [PATCH 889/939] Handle creation of thumbnails for multi-image TIFF files (cherry picked from commit f28641d910e1133fbbdeb528491c70594f9190ea) --- .../org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java index 7543410a796..28bfc72dc11 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java @@ -105,7 +105,7 @@ public File getThumbnailFile(File f, boolean verbose) ConvertCmd cmd = new ConvertCmd(); IMOperation op = new IMOperation(); op.autoOrient(); - op.addImage(f.getAbsolutePath()); + op.addImage(f.getAbsolutePath() + "[0]"); op.thumbnail(configurationService.getIntProperty("thumbnail.maxwidth", DEFAULT_WIDTH), configurationService.getIntProperty("thumbnail.maxheight", DEFAULT_HEIGHT)); op.addImage(f2.getAbsolutePath()); From 93b4b10a315a2c71bee9e4c5a7453dc1e46c45ea Mon Sep 17 00:00:00 2001 From: Istvan Vig Date: Mon, 9 Mar 2026 16:13:33 +0100 Subject: [PATCH 890/939] Controlled vocabulary for accented characters (cherry picked from commit e346526f3a5d0a2be4cb10c2f16cd07c2bd67f9b) --- .../dspace/content/authority/DSpaceControlledVocabulary.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java index 3f7f926c07a..f31d0c3bc51 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java @@ -60,8 +60,9 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements HierarchicalAuthority { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(); - protected static String xpathTemplate = "//node[contains(translate(@label,'ABCDEFGHIJKLMNOPQRSTUVWXYZ'," + - "'abcdefghijklmnopqrstuvwxyz'),%s)]"; + protected static String xpathTemplate = "//node[contains(translate(@label," + + "'ABCDEFGHIJKLMNOPQRSTUVWXYZÁÀÂÄÃÅĄĂĆČÇĎĐÉÈÊËĘĚÍÌÎÏŁĽŃŇÑÓÒÔÖÕŐŔŘŚŠŞŤŢÚÙÛÜŮŰÝŸŹŽŻ'," + + "'abcdefghijklmnopqrstuvwxyzáàâäãåąăćčçďđéèêëęěíìîïłľńňñóòôöõőŕřśšşťţúùûüůűýÿźžż'),%s)]"; protected static String idTemplate = "//node[@id = %s]"; protected static String idTemplateQuoted = "//node[@id = '%s']"; protected static String labelTemplate = "//node[@label = %s]"; From 633c194ac1e8765e5afb8ca12042fdcc0bdbcf96 Mon Sep 17 00:00:00 2001 From: Istvan Vig Date: Wed, 11 Mar 2026 16:07:35 +0100 Subject: [PATCH 891/939] Fixes https://github.com/bme-omikk/repozitorium-config/issues/214 The fix is in the dspace/modules/additions folder. (cherry picked from commit c40868bdf9489fae34c3c4358876a0d1832daac7) --- .../authority/DSpaceControlledVocabulary.java | 5 +- .../authority/DSpaceControlledVocabulary.java | 496 ++++++++++++++++++ .../modules/server/src/main/webapp/.gitignore | 0 3 files changed, 498 insertions(+), 3 deletions(-) create mode 100644 dspace/modules/additions/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java delete mode 100644 dspace/modules/server/src/main/webapp/.gitignore diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java index f31d0c3bc51..3f7f926c07a 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java @@ -60,9 +60,8 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements HierarchicalAuthority { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(); - protected static String xpathTemplate = "//node[contains(translate(@label," + - "'ABCDEFGHIJKLMNOPQRSTUVWXYZÁÀÂÄÃÅĄĂĆČÇĎĐÉÈÊËĘĚÍÌÎÏŁĽŃŇÑÓÒÔÖÕŐŔŘŚŠŞŤŢÚÙÛÜŮŰÝŸŹŽŻ'," + - "'abcdefghijklmnopqrstuvwxyzáàâäãåąăćčçďđéèêëęěíìîïłľńňñóòôöõőŕřśšşťţúùûüůűýÿźžż'),%s)]"; + protected static String xpathTemplate = "//node[contains(translate(@label,'ABCDEFGHIJKLMNOPQRSTUVWXYZ'," + + "'abcdefghijklmnopqrstuvwxyz'),%s)]"; protected static String idTemplate = "//node[@id = %s]"; protected static String idTemplateQuoted = "//node[@id = '%s']"; protected static String labelTemplate = "//node[@label = %s]"; diff --git a/dspace/modules/additions/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java b/dspace/modules/additions/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java new file mode 100644 index 00000000000..36ac58f4b66 --- /dev/null +++ b/dspace/modules/additions/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java @@ -0,0 +1,496 @@ +/** + * 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.content.authority; + +import java.io.File; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.Logger; +import org.dspace.core.SelfNamedPlugin; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; + +/** + * ChoiceAuthority source that reads the hierarchical vocabularies + * from {@code ${dspace.dir}/config/controlled-vocabularies/*.xml} and turns + * them into autocompleting authorities. + * + *

    Configuration: This MUST be configured as a self-named plugin, e.g.: {@code + * plugin.selfnamed.org.dspace.content.authority.ChoiceAuthority = + * org.dspace.content.authority.DSpaceControlledVocabulary + * } + * + *

    It AUTOMATICALLY configures a plugin instance for each XML file in the + * controlled vocabularies directory. The name of the plugin is the basename of + * the file; e.g., {@code ${dspace.dir}/config/controlled-vocabularies/nsi.xml} + * would generate a plugin called "nsi". + * + *

    Each configured plugin comes with three configuration options: + *

      + *
    • {@code vocabulary.plugin._plugin_.hierarchy.store = + * # Store entire hierarchy along with selected value. Default: TRUE}
    • + *
    • {@code vocabulary.plugin._plugin_.hierarchy.suggest = + * # Display entire hierarchy in the suggestion list. Default: TRUE}
    • + *
    • {@code vocabulary.plugin._plugin_.delimiter = "" + * # Delimiter to use when building hierarchy strings. Default: "::"}
    • + *
    + * } + * + * @author Michael B. Klein + */ + +public class DSpaceControlledVocabulary extends SelfNamedPlugin implements HierarchicalAuthority { + + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(); + protected static String xpathTemplate = "//node[contains(translate(@label," + + "'ABCDEFGHIJKLMNOPQRSTUVWXYZÁÀÂÄÃÅĄĂĆČÇĎĐÉÈÊËĘĚÍÌÎÏŁĽŃŇÑÓÒÔÖÕŐŔŘŚŠŞŤŢÚÙÛÜŮŰÝŸŹŽŻ'," + + "'abcdefghijklmnopqrstuvwxyzáàâäãåąăćčçďđéèêëęěíìîïłľńňñóòôöõőŕřśšşťţúùûüůűýÿźžż'),%s)]"; + protected static String idTemplate = "//node[@id = %s]"; + protected static String idTemplateQuoted = "//node[@id = '%s']"; + protected static String labelTemplate = "//node[@label = %s]"; + protected static String idParentTemplate = "//node[@id = '%s']/parent::isComposedBy/parent::node"; + protected static String rootTemplate = "/node"; + protected static String idAttribute = "id"; + protected static String labelAttribute = "label"; + protected static String[] pluginNames = null; + protected String vocabularyName = null; + protected InputSource vocabulary = null; + protected Boolean suggestHierarchy = false; + protected Boolean storeHierarchy = true; + protected String hierarchyDelimiter = "::"; + protected Integer preloadLevel = 1; + protected String valueAttribute = labelAttribute; + protected String valueTemplate = labelTemplate; + + public DSpaceControlledVocabulary() { + super(); + } + + @Override + public boolean storeAuthorityInMetadata() { + // For backward compatibility controlled vocabularies don't store the node id in + // the metadatavalue + return false; + } + + public static String[] getPluginNames() { + if (pluginNames == null) { + initPluginNames(); + } + + return ArrayUtils.clone(pluginNames); + } + + private static synchronized void initPluginNames() { + if (pluginNames == null) { + class xmlFilter implements java.io.FilenameFilter { + @Override + public boolean accept(File dir, String name) { + return name.endsWith(".xml"); + } + } + String vocabulariesPath = DSpaceServicesFactory.getInstance().getConfigurationService() + .getProperty("dspace.dir") + + File.separator + "config" + + File.separator + "controlled-vocabularies"; + String[] xmlFiles = (new File(vocabulariesPath)).list(new xmlFilter()); + List names = new ArrayList<>(); + for (String filename : xmlFiles) { + names.add((new File(filename)).getName().replace(".xml", "")); + } + pluginNames = names.toArray(new String[names.size()]); + log.info("Got plugin names = " + Arrays.deepToString(pluginNames)); + } + } + + protected void init(String locale) { + if (vocabulary == null) { + ConfigurationService config = DSpaceServicesFactory.getInstance().getConfigurationService(); + + log.info("Initializing " + this.getClass().getName()); + vocabularyName = this.getPluginInstanceName(); + String vocabulariesPath = config.getProperty("dspace.dir") + File.separator + "config" + + File.separator + "controlled-vocabularies" + File.separator; + String configurationPrefix = "vocabulary.plugin." + vocabularyName; + storeHierarchy = config.getBooleanProperty(configurationPrefix + ".hierarchy.store", storeHierarchy); + boolean storeIDs = config.getBooleanProperty(configurationPrefix + ".storeIDs", false); + suggestHierarchy = config.getBooleanProperty(configurationPrefix + ".hierarchy.suggest", suggestHierarchy); + preloadLevel = config.getIntProperty(configurationPrefix + ".hierarchy.preloadLevel", preloadLevel); + String configuredDelimiter = config.getProperty(configurationPrefix + ".delimiter"); + if (configuredDelimiter != null) { + hierarchyDelimiter = configuredDelimiter.replaceAll("(^\"|\"$)", ""); + } + if (storeIDs) { + valueAttribute = idAttribute; + valueTemplate = idTemplate; + } + + String filename = vocabulariesPath + vocabularyName + ".xml"; + if (StringUtils.isNotEmpty(locale)) { + String localizedFilename = vocabulariesPath + vocabularyName + "_" + locale + ".xml"; + if (Paths.get(localizedFilename).toFile().exists()) { + filename = localizedFilename; + } + } + log.info("Loading " + filename); + vocabulary = new InputSource(filename); + } + } + + protected String buildString(Node node) { + if (node.getNodeType() == Node.DOCUMENT_NODE || ( + node.getParentNode() != null && + node.getParentNode().getNodeType() == Node.DOCUMENT_NODE)) { + return (""); + } else { + String parentValue = buildString(node.getParentNode()); + Node currentNodeValue = node.getAttributes().getNamedItem(valueAttribute); + if (currentNodeValue != null) { + String currentValue = currentNodeValue.getNodeValue(); + if (parentValue.equals("")) { + return currentValue; + } else { + return (parentValue + this.hierarchyDelimiter + currentValue); + } + } else { + return (parentValue); + } + } + } + + @Override + public Choices getMatches(String text, int start, int limit, String locale) { + init(locale); + log.debug("Getting matches for '" + text + "'"); + String[] textHierarchy = text.split(hierarchyDelimiter, -1); + StringBuilder xpathExpressionBuilder = new StringBuilder(); + for (int i = 0; i < textHierarchy.length; i++) { + xpathExpressionBuilder.append(String.format(xpathTemplate, "$var" + i)); + } + String xpathExpression = xpathExpressionBuilder.toString(); + XPath xpath = XPathFactory.newInstance().newXPath(); + xpath.setXPathVariableResolver(variableName -> { + String varName = variableName.getLocalPart(); + if (varName.startsWith("var")) { + int index = Integer.parseInt(varName.substring(3)); + return textHierarchy[index].toLowerCase(); + } + throw new IllegalArgumentException("Unexpected variable: " + varName); + }); + int total; + List choices; + try { + NodeList results = (NodeList) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODESET); + total = results.getLength(); + choices = getChoicesFromNodeList(results, start, limit); + } catch (XPathExpressionException e) { + log.warn(e.getMessage(), e); + return new Choices(true); + } + return new Choices(choices.toArray(new Choice[choices.size()]), start, total, Choices.CF_AMBIGUOUS, + total > start + limit); + } + + @Override + public Choices getBestMatch(String text, String locale) { + init(locale); + log.debug("Getting best matches for {}'", text); + String[] textHierarchy = text.split(hierarchyDelimiter, -1); + StringBuilder xpathExpressionBuilder = new StringBuilder(); + for (int i = 0; i < textHierarchy.length; i++) { + xpathExpressionBuilder.append(String.format(valueTemplate, "$var" + i)); + } + String xpathExpression = xpathExpressionBuilder.toString(); + XPath xpath = XPathFactory.newInstance().newXPath(); + xpath.setXPathVariableResolver(variableName -> { + String varName = variableName.getLocalPart(); + if (varName.startsWith("var")) { + int index = Integer.parseInt(varName.substring(3)); + return textHierarchy[index]; + } + throw new IllegalArgumentException("Unexpected variable: " + varName); + }); + List choices; + try { + NodeList results = (NodeList) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODESET); + choices = getChoicesFromNodeList(results, 0, 1); + } catch (XPathExpressionException e) { + log.warn(e.getMessage(), e); + return new Choices(true); + } + return new Choices(choices.toArray(new Choice[choices.size()]), 0, choices.size(), Choices.CF_AMBIGUOUS, false); + } + + @Override + public String getLabel(String key, String locale) { + return getNodeValue(key, locale, this.suggestHierarchy); + } + + @Override + public String getValue(String key, String locale) { + return getNodeValue(key, locale, this.storeHierarchy); + } + + @Override + public Choice getChoice(String authKey, String locale) { + Node node; + try { + node = getNode(authKey, locale); + } catch (XPathExpressionException e) { + return null; + } + return createChoiceFromNode(node); + } + + @Override + public boolean isHierarchical() { + init(null); + return true; + } + + @Override + public Choices getTopChoices(String authorityName, int start, int limit, String locale) { + init(locale); + String xpathExpression = rootTemplate; + return getChoicesByXpath(xpathExpression, start, limit); + } + + @Override + public Choices getChoicesByParent(String authorityName, String parentId, int start, int limit, String locale) { + init(locale); + String xpathExpression = String.format(idTemplateQuoted, parentId); + return getChoicesByXpath(xpathExpression, start, limit); + } + + @Override + public Choice getParentChoice(String authorityName, String childId, String locale) { + init(locale); + try { + String xpathExpression = String.format(idParentTemplate, childId); + Choice choice = createChoiceFromNode(getNodeFromXPath(xpathExpression)); + return choice; + } catch (XPathExpressionException e) { + log.error(e.getMessage(), e); + return null; + } + } + + @Override + public Integer getPreloadLevel() { + init(null); + return preloadLevel; + } + + private boolean isRootElement(Node node) { + return node != null && node.getOwnerDocument().getDocumentElement().equals(node); + } + + private Node getNode(String key, String locale) throws XPathExpressionException { + init(locale); + String xpathExpression = String.format(idTemplateQuoted, key); + Node node = getNodeFromXPath(xpathExpression); + return node; + } + + private Node getNodeFromXPath(String xpathExpression) throws XPathExpressionException { + XPath xpath = XPathFactory.newInstance().newXPath(); + Node node = (Node) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODE); + return node; + } + + private List getChoicesFromNodeList(NodeList results, int start, int limit) { + List choices = new ArrayList<>(); + for (int i = 0; i < results.getLength(); i++) { + if (i < start) { + continue; + } + if (choices.size() == limit) { + break; + } + Node node = results.item(i); + Choice choice = new Choice(getAuthority(node), getLabel(node), getValue(node), + isSelectable(node)); + choice.extras = addOtherInformation(getParent(node), getNote(node), getChildren(node), getAuthority(node)); + choices.add(choice); + } + return choices; + } + + private Map addOtherInformation(String parentCurr, String noteCurr, + List childrenCurr, String authorityCurr) { + Map extras = new HashMap<>(); + if (StringUtils.isNotBlank(parentCurr)) { + extras.put("parent", parentCurr); + } + if (StringUtils.isNotBlank(noteCurr)) { + extras.put("note", noteCurr); + } + if (childrenCurr.isEmpty()) { + extras.put("hasChildren", "false"); + } else { + extras.put("hasChildren", "true"); + } + extras.put("id", authorityCurr); + return extras; + } + + private String getNodeValue(String key, String locale, boolean useHierarchy) { + try { + Node node = getNode(key, locale); + if (Objects.isNull(node)) { + return null; + } + if (useHierarchy) { + return this.buildString(node); + } else { + return node.getAttributes().getNamedItem(valueAttribute).getNodeValue(); + } + } catch (XPathExpressionException e) { + return (""); + } + } + + private String getLabel(Node node) { + String hierarchy = this.buildString(node); + if (this.suggestHierarchy) { + return hierarchy; + } else { + return node.getAttributes().getNamedItem("label").getNodeValue(); + } + } + + private String getValue(Node node) { + String hierarchy = this.buildString(node); + if (this.storeHierarchy) { + return hierarchy; + } else { + return node.getAttributes().getNamedItem(valueAttribute).getNodeValue(); + } + } + + private String getNote(Node node) { + NodeList childNodes = node.getChildNodes(); + for (int ci = 0; ci < childNodes.getLength(); ci++) { + Node firstChild = childNodes.item(ci); + if (firstChild != null && "hasNote".equals(firstChild.getNodeName())) { + String nodeValue = firstChild.getTextContent(); + if (StringUtils.isNotBlank(nodeValue)) { + return nodeValue; + } + } + } + return null; + } + + private List getChildren(Node node) { + List children = new ArrayList<>(); + NodeList childNodes = node.getChildNodes(); + for (int ci = 0; ci < childNodes.getLength(); ci++) { + Node firstChild = childNodes.item(ci); + if (firstChild != null && "isComposedBy".equals(firstChild.getNodeName())) { + for (int cii = 0; cii < firstChild.getChildNodes().getLength(); cii++) { + Node childN = firstChild.getChildNodes().item(cii); + if (childN != null && "node".equals(childN.getNodeName())) { + Node childIdAttr = childN.getAttributes().getNamedItem("id"); + if (null != childIdAttr) { + children.add(childIdAttr.getNodeValue()); + } + } + } + break; + } + } + return children; + } + + private boolean isSelectable(Node node) { + Node selectableAttr = node.getAttributes().getNamedItem("selectable"); + if (null != selectableAttr) { + return Boolean.parseBoolean(selectableAttr.getNodeValue()); + } else { // Default is true + return true; + } + } + + private String getParent(Node node) { + Node parentN = node.getParentNode(); + if (parentN != null) { + parentN = parentN.getParentNode(); + if (parentN != null && !isRootElement(parentN)) { + return buildString(parentN); + } + } + return null; + } + + private String getAuthority(Node node) { + Node idAttr = node.getAttributes().getNamedItem("id"); + if (null != idAttr) { // 'id' is optional + return idAttr.getNodeValue(); + } else { + return null; + } + } + + private Choices getChoicesByXpath(String xpathExpression, int start, int limit) { + List choices = new ArrayList<>(); + XPath xpath = XPathFactory.newInstance().newXPath(); + try { + Node parentNode = (Node) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODE); + int count = 0; + if (parentNode != null) { + NodeList childNodes = (NodeList) xpath.evaluate(".//isComposedBy", parentNode, XPathConstants.NODE); + if (null != childNodes) { + for (int i = 0; i < childNodes.getLength(); i++) { + Node childNode = childNodes.item(i); + if (childNode != null && "node".equals(childNode.getNodeName())) { + if (count < start || choices.size() >= limit) { + count++; + continue; + } + count++; + choices.add(createChoiceFromNode(childNode)); + } + } + } + return new Choices(choices.toArray(new Choice[choices.size()]), start, count, + Choices.CF_AMBIGUOUS, false); + } + } catch (XPathExpressionException e) { + log.warn(e.getMessage(), e); + return new Choices(true); + } + return new Choices(false); + } + + private Choice createChoiceFromNode(Node node) { + if (node != null && !isRootElement(node)) { + Choice choice = new Choice(getAuthority(node), getLabel(node), getValue(node), + isSelectable(node)); + choice.extras = addOtherInformation(getParent(node), getNote(node),getChildren(node), getAuthority(node)); + return choice; + } + return null; + } + +} diff --git a/dspace/modules/server/src/main/webapp/.gitignore b/dspace/modules/server/src/main/webapp/.gitignore deleted file mode 100644 index e69de29bb2d..00000000000 From 7e4fda2c5a5c1ba4f8614fd29b6c0e464e6cbf1a Mon Sep 17 00:00:00 2001 From: Istvan Vig Date: Tue, 24 Mar 2026 19:45:22 +0100 Subject: [PATCH 892/939] rebase on current main; refactor to use all unicode characters (cherry picked from commit 18427d009156f67251c692dab35d321ec337ed84) --- .../authority/DSpaceControlledVocabulary.java | 15 +- .../authority/DSpaceControlledVocabulary.java | 496 ------------------ .../modules/server/src/main/webapp/.gitignore | 0 3 files changed, 13 insertions(+), 498 deletions(-) delete mode 100644 dspace/modules/additions/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java create mode 100644 dspace/modules/server/src/main/webapp/.gitignore diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java index 3f7f926c07a..746e0579dcf 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java @@ -60,8 +60,19 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements HierarchicalAuthority { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(); - protected static String xpathTemplate = "//node[contains(translate(@label,'ABCDEFGHIJKLMNOPQRSTUVWXYZ'," + - "'abcdefghijklmnopqrstuvwxyz'),%s)]"; + protected static final String xpathTemplate; + static { + StringBuilder upper = new StringBuilder(); + StringBuilder lower = new StringBuilder(); + for (int cp = 'A'; cp <= Character.MAX_CODE_POINT; cp++) { + if (Character.isLetter(cp) && Character.isUpperCase(cp)) { + int lcp = Character.toLowerCase(cp); + upper.appendCodePoint(cp); + lower.appendCodePoint(lcp); + } + } + xpathTemplate = "//node[contains(translate(@label,'" + upper + "','" + lower + "'),%s)]"; + } protected static String idTemplate = "//node[@id = %s]"; protected static String idTemplateQuoted = "//node[@id = '%s']"; protected static String labelTemplate = "//node[@label = %s]"; diff --git a/dspace/modules/additions/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java b/dspace/modules/additions/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java deleted file mode 100644 index 36ac58f4b66..00000000000 --- a/dspace/modules/additions/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java +++ /dev/null @@ -1,496 +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.content.authority; - -import java.io.File; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import javax.xml.xpath.XPath; -import javax.xml.xpath.XPathConstants; -import javax.xml.xpath.XPathExpressionException; -import javax.xml.xpath.XPathFactory; - -import org.apache.commons.lang3.ArrayUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.Logger; -import org.dspace.core.SelfNamedPlugin; -import org.dspace.services.ConfigurationService; -import org.dspace.services.factory.DSpaceServicesFactory; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.InputSource; - -/** - * ChoiceAuthority source that reads the hierarchical vocabularies - * from {@code ${dspace.dir}/config/controlled-vocabularies/*.xml} and turns - * them into autocompleting authorities. - * - *

    Configuration: This MUST be configured as a self-named plugin, e.g.: {@code - * plugin.selfnamed.org.dspace.content.authority.ChoiceAuthority = - * org.dspace.content.authority.DSpaceControlledVocabulary - * } - * - *

    It AUTOMATICALLY configures a plugin instance for each XML file in the - * controlled vocabularies directory. The name of the plugin is the basename of - * the file; e.g., {@code ${dspace.dir}/config/controlled-vocabularies/nsi.xml} - * would generate a plugin called "nsi". - * - *

    Each configured plugin comes with three configuration options: - *

      - *
    • {@code vocabulary.plugin._plugin_.hierarchy.store = - * # Store entire hierarchy along with selected value. Default: TRUE}
    • - *
    • {@code vocabulary.plugin._plugin_.hierarchy.suggest = - * # Display entire hierarchy in the suggestion list. Default: TRUE}
    • - *
    • {@code vocabulary.plugin._plugin_.delimiter = "" - * # Delimiter to use when building hierarchy strings. Default: "::"}
    • - *
    - * } - * - * @author Michael B. Klein - */ - -public class DSpaceControlledVocabulary extends SelfNamedPlugin implements HierarchicalAuthority { - - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(); - protected static String xpathTemplate = "//node[contains(translate(@label," + - "'ABCDEFGHIJKLMNOPQRSTUVWXYZÁÀÂÄÃÅĄĂĆČÇĎĐÉÈÊËĘĚÍÌÎÏŁĽŃŇÑÓÒÔÖÕŐŔŘŚŠŞŤŢÚÙÛÜŮŰÝŸŹŽŻ'," + - "'abcdefghijklmnopqrstuvwxyzáàâäãåąăćčçďđéèêëęěíìîïłľńňñóòôöõőŕřśšşťţúùûüůűýÿźžż'),%s)]"; - protected static String idTemplate = "//node[@id = %s]"; - protected static String idTemplateQuoted = "//node[@id = '%s']"; - protected static String labelTemplate = "//node[@label = %s]"; - protected static String idParentTemplate = "//node[@id = '%s']/parent::isComposedBy/parent::node"; - protected static String rootTemplate = "/node"; - protected static String idAttribute = "id"; - protected static String labelAttribute = "label"; - protected static String[] pluginNames = null; - protected String vocabularyName = null; - protected InputSource vocabulary = null; - protected Boolean suggestHierarchy = false; - protected Boolean storeHierarchy = true; - protected String hierarchyDelimiter = "::"; - protected Integer preloadLevel = 1; - protected String valueAttribute = labelAttribute; - protected String valueTemplate = labelTemplate; - - public DSpaceControlledVocabulary() { - super(); - } - - @Override - public boolean storeAuthorityInMetadata() { - // For backward compatibility controlled vocabularies don't store the node id in - // the metadatavalue - return false; - } - - public static String[] getPluginNames() { - if (pluginNames == null) { - initPluginNames(); - } - - return ArrayUtils.clone(pluginNames); - } - - private static synchronized void initPluginNames() { - if (pluginNames == null) { - class xmlFilter implements java.io.FilenameFilter { - @Override - public boolean accept(File dir, String name) { - return name.endsWith(".xml"); - } - } - String vocabulariesPath = DSpaceServicesFactory.getInstance().getConfigurationService() - .getProperty("dspace.dir") + - File.separator + "config" + - File.separator + "controlled-vocabularies"; - String[] xmlFiles = (new File(vocabulariesPath)).list(new xmlFilter()); - List names = new ArrayList<>(); - for (String filename : xmlFiles) { - names.add((new File(filename)).getName().replace(".xml", "")); - } - pluginNames = names.toArray(new String[names.size()]); - log.info("Got plugin names = " + Arrays.deepToString(pluginNames)); - } - } - - protected void init(String locale) { - if (vocabulary == null) { - ConfigurationService config = DSpaceServicesFactory.getInstance().getConfigurationService(); - - log.info("Initializing " + this.getClass().getName()); - vocabularyName = this.getPluginInstanceName(); - String vocabulariesPath = config.getProperty("dspace.dir") + File.separator + "config" + - File.separator + "controlled-vocabularies" + File.separator; - String configurationPrefix = "vocabulary.plugin." + vocabularyName; - storeHierarchy = config.getBooleanProperty(configurationPrefix + ".hierarchy.store", storeHierarchy); - boolean storeIDs = config.getBooleanProperty(configurationPrefix + ".storeIDs", false); - suggestHierarchy = config.getBooleanProperty(configurationPrefix + ".hierarchy.suggest", suggestHierarchy); - preloadLevel = config.getIntProperty(configurationPrefix + ".hierarchy.preloadLevel", preloadLevel); - String configuredDelimiter = config.getProperty(configurationPrefix + ".delimiter"); - if (configuredDelimiter != null) { - hierarchyDelimiter = configuredDelimiter.replaceAll("(^\"|\"$)", ""); - } - if (storeIDs) { - valueAttribute = idAttribute; - valueTemplate = idTemplate; - } - - String filename = vocabulariesPath + vocabularyName + ".xml"; - if (StringUtils.isNotEmpty(locale)) { - String localizedFilename = vocabulariesPath + vocabularyName + "_" + locale + ".xml"; - if (Paths.get(localizedFilename).toFile().exists()) { - filename = localizedFilename; - } - } - log.info("Loading " + filename); - vocabulary = new InputSource(filename); - } - } - - protected String buildString(Node node) { - if (node.getNodeType() == Node.DOCUMENT_NODE || ( - node.getParentNode() != null && - node.getParentNode().getNodeType() == Node.DOCUMENT_NODE)) { - return (""); - } else { - String parentValue = buildString(node.getParentNode()); - Node currentNodeValue = node.getAttributes().getNamedItem(valueAttribute); - if (currentNodeValue != null) { - String currentValue = currentNodeValue.getNodeValue(); - if (parentValue.equals("")) { - return currentValue; - } else { - return (parentValue + this.hierarchyDelimiter + currentValue); - } - } else { - return (parentValue); - } - } - } - - @Override - public Choices getMatches(String text, int start, int limit, String locale) { - init(locale); - log.debug("Getting matches for '" + text + "'"); - String[] textHierarchy = text.split(hierarchyDelimiter, -1); - StringBuilder xpathExpressionBuilder = new StringBuilder(); - for (int i = 0; i < textHierarchy.length; i++) { - xpathExpressionBuilder.append(String.format(xpathTemplate, "$var" + i)); - } - String xpathExpression = xpathExpressionBuilder.toString(); - XPath xpath = XPathFactory.newInstance().newXPath(); - xpath.setXPathVariableResolver(variableName -> { - String varName = variableName.getLocalPart(); - if (varName.startsWith("var")) { - int index = Integer.parseInt(varName.substring(3)); - return textHierarchy[index].toLowerCase(); - } - throw new IllegalArgumentException("Unexpected variable: " + varName); - }); - int total; - List choices; - try { - NodeList results = (NodeList) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODESET); - total = results.getLength(); - choices = getChoicesFromNodeList(results, start, limit); - } catch (XPathExpressionException e) { - log.warn(e.getMessage(), e); - return new Choices(true); - } - return new Choices(choices.toArray(new Choice[choices.size()]), start, total, Choices.CF_AMBIGUOUS, - total > start + limit); - } - - @Override - public Choices getBestMatch(String text, String locale) { - init(locale); - log.debug("Getting best matches for {}'", text); - String[] textHierarchy = text.split(hierarchyDelimiter, -1); - StringBuilder xpathExpressionBuilder = new StringBuilder(); - for (int i = 0; i < textHierarchy.length; i++) { - xpathExpressionBuilder.append(String.format(valueTemplate, "$var" + i)); - } - String xpathExpression = xpathExpressionBuilder.toString(); - XPath xpath = XPathFactory.newInstance().newXPath(); - xpath.setXPathVariableResolver(variableName -> { - String varName = variableName.getLocalPart(); - if (varName.startsWith("var")) { - int index = Integer.parseInt(varName.substring(3)); - return textHierarchy[index]; - } - throw new IllegalArgumentException("Unexpected variable: " + varName); - }); - List choices; - try { - NodeList results = (NodeList) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODESET); - choices = getChoicesFromNodeList(results, 0, 1); - } catch (XPathExpressionException e) { - log.warn(e.getMessage(), e); - return new Choices(true); - } - return new Choices(choices.toArray(new Choice[choices.size()]), 0, choices.size(), Choices.CF_AMBIGUOUS, false); - } - - @Override - public String getLabel(String key, String locale) { - return getNodeValue(key, locale, this.suggestHierarchy); - } - - @Override - public String getValue(String key, String locale) { - return getNodeValue(key, locale, this.storeHierarchy); - } - - @Override - public Choice getChoice(String authKey, String locale) { - Node node; - try { - node = getNode(authKey, locale); - } catch (XPathExpressionException e) { - return null; - } - return createChoiceFromNode(node); - } - - @Override - public boolean isHierarchical() { - init(null); - return true; - } - - @Override - public Choices getTopChoices(String authorityName, int start, int limit, String locale) { - init(locale); - String xpathExpression = rootTemplate; - return getChoicesByXpath(xpathExpression, start, limit); - } - - @Override - public Choices getChoicesByParent(String authorityName, String parentId, int start, int limit, String locale) { - init(locale); - String xpathExpression = String.format(idTemplateQuoted, parentId); - return getChoicesByXpath(xpathExpression, start, limit); - } - - @Override - public Choice getParentChoice(String authorityName, String childId, String locale) { - init(locale); - try { - String xpathExpression = String.format(idParentTemplate, childId); - Choice choice = createChoiceFromNode(getNodeFromXPath(xpathExpression)); - return choice; - } catch (XPathExpressionException e) { - log.error(e.getMessage(), e); - return null; - } - } - - @Override - public Integer getPreloadLevel() { - init(null); - return preloadLevel; - } - - private boolean isRootElement(Node node) { - return node != null && node.getOwnerDocument().getDocumentElement().equals(node); - } - - private Node getNode(String key, String locale) throws XPathExpressionException { - init(locale); - String xpathExpression = String.format(idTemplateQuoted, key); - Node node = getNodeFromXPath(xpathExpression); - return node; - } - - private Node getNodeFromXPath(String xpathExpression) throws XPathExpressionException { - XPath xpath = XPathFactory.newInstance().newXPath(); - Node node = (Node) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODE); - return node; - } - - private List getChoicesFromNodeList(NodeList results, int start, int limit) { - List choices = new ArrayList<>(); - for (int i = 0; i < results.getLength(); i++) { - if (i < start) { - continue; - } - if (choices.size() == limit) { - break; - } - Node node = results.item(i); - Choice choice = new Choice(getAuthority(node), getLabel(node), getValue(node), - isSelectable(node)); - choice.extras = addOtherInformation(getParent(node), getNote(node), getChildren(node), getAuthority(node)); - choices.add(choice); - } - return choices; - } - - private Map addOtherInformation(String parentCurr, String noteCurr, - List childrenCurr, String authorityCurr) { - Map extras = new HashMap<>(); - if (StringUtils.isNotBlank(parentCurr)) { - extras.put("parent", parentCurr); - } - if (StringUtils.isNotBlank(noteCurr)) { - extras.put("note", noteCurr); - } - if (childrenCurr.isEmpty()) { - extras.put("hasChildren", "false"); - } else { - extras.put("hasChildren", "true"); - } - extras.put("id", authorityCurr); - return extras; - } - - private String getNodeValue(String key, String locale, boolean useHierarchy) { - try { - Node node = getNode(key, locale); - if (Objects.isNull(node)) { - return null; - } - if (useHierarchy) { - return this.buildString(node); - } else { - return node.getAttributes().getNamedItem(valueAttribute).getNodeValue(); - } - } catch (XPathExpressionException e) { - return (""); - } - } - - private String getLabel(Node node) { - String hierarchy = this.buildString(node); - if (this.suggestHierarchy) { - return hierarchy; - } else { - return node.getAttributes().getNamedItem("label").getNodeValue(); - } - } - - private String getValue(Node node) { - String hierarchy = this.buildString(node); - if (this.storeHierarchy) { - return hierarchy; - } else { - return node.getAttributes().getNamedItem(valueAttribute).getNodeValue(); - } - } - - private String getNote(Node node) { - NodeList childNodes = node.getChildNodes(); - for (int ci = 0; ci < childNodes.getLength(); ci++) { - Node firstChild = childNodes.item(ci); - if (firstChild != null && "hasNote".equals(firstChild.getNodeName())) { - String nodeValue = firstChild.getTextContent(); - if (StringUtils.isNotBlank(nodeValue)) { - return nodeValue; - } - } - } - return null; - } - - private List getChildren(Node node) { - List children = new ArrayList<>(); - NodeList childNodes = node.getChildNodes(); - for (int ci = 0; ci < childNodes.getLength(); ci++) { - Node firstChild = childNodes.item(ci); - if (firstChild != null && "isComposedBy".equals(firstChild.getNodeName())) { - for (int cii = 0; cii < firstChild.getChildNodes().getLength(); cii++) { - Node childN = firstChild.getChildNodes().item(cii); - if (childN != null && "node".equals(childN.getNodeName())) { - Node childIdAttr = childN.getAttributes().getNamedItem("id"); - if (null != childIdAttr) { - children.add(childIdAttr.getNodeValue()); - } - } - } - break; - } - } - return children; - } - - private boolean isSelectable(Node node) { - Node selectableAttr = node.getAttributes().getNamedItem("selectable"); - if (null != selectableAttr) { - return Boolean.parseBoolean(selectableAttr.getNodeValue()); - } else { // Default is true - return true; - } - } - - private String getParent(Node node) { - Node parentN = node.getParentNode(); - if (parentN != null) { - parentN = parentN.getParentNode(); - if (parentN != null && !isRootElement(parentN)) { - return buildString(parentN); - } - } - return null; - } - - private String getAuthority(Node node) { - Node idAttr = node.getAttributes().getNamedItem("id"); - if (null != idAttr) { // 'id' is optional - return idAttr.getNodeValue(); - } else { - return null; - } - } - - private Choices getChoicesByXpath(String xpathExpression, int start, int limit) { - List choices = new ArrayList<>(); - XPath xpath = XPathFactory.newInstance().newXPath(); - try { - Node parentNode = (Node) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODE); - int count = 0; - if (parentNode != null) { - NodeList childNodes = (NodeList) xpath.evaluate(".//isComposedBy", parentNode, XPathConstants.NODE); - if (null != childNodes) { - for (int i = 0; i < childNodes.getLength(); i++) { - Node childNode = childNodes.item(i); - if (childNode != null && "node".equals(childNode.getNodeName())) { - if (count < start || choices.size() >= limit) { - count++; - continue; - } - count++; - choices.add(createChoiceFromNode(childNode)); - } - } - } - return new Choices(choices.toArray(new Choice[choices.size()]), start, count, - Choices.CF_AMBIGUOUS, false); - } - } catch (XPathExpressionException e) { - log.warn(e.getMessage(), e); - return new Choices(true); - } - return new Choices(false); - } - - private Choice createChoiceFromNode(Node node) { - if (node != null && !isRootElement(node)) { - Choice choice = new Choice(getAuthority(node), getLabel(node), getValue(node), - isSelectable(node)); - choice.extras = addOtherInformation(getParent(node), getNote(node),getChildren(node), getAuthority(node)); - return choice; - } - return null; - } - -} diff --git a/dspace/modules/server/src/main/webapp/.gitignore b/dspace/modules/server/src/main/webapp/.gitignore new file mode 100644 index 00000000000..e69de29bb2d From 8cba843b9204a7ae4ec6f061d5ca61f600e6457d Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 25 Mar 2026 17:39:47 +0100 Subject: [PATCH 893/939] requestItemService.findbyBitstream for more efficient bitstream deletion (cherry picked from commit 85e76a9193f835824775778bad216ac10ad5c555) stripped out the new 'requestitem access token' feature, kept some small improvements (interface method scope, DAO findByItem query) --- .../requestitem/RequestItemServiceImpl.java | 7 +++ .../app/requestitem/dao/RequestItemDAO.java | 16 ++++++- .../dao/impl/RequestItemDAOImpl.java | 17 +++++++- .../service/RequestItemService.java | 43 +++++++++++++------ .../dspace/content/BitstreamServiceImpl.java | 8 ++-- 5 files changed, 70 insertions(+), 21 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemServiceImpl.java index b915cfedd34..009bbde7957 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemServiceImpl.java @@ -11,6 +11,8 @@ import java.util.Date; import java.util.Iterator; import java.util.List; +import java.util.TimeZone; +import java.util.UUID; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -96,6 +98,11 @@ public Iterator findByItem(Context context, Item item) throws SQLEx return requestItemDAO.findByItem(context, item); } + @Override + public Iterator findByBitstreamId(Context context, UUID bitstreamId) throws SQLException { + return requestItemDAO.findByBitstreamId(context, bitstreamId); + } + @Override public void update(Context context, RequestItem requestItem) { try { diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/dao/RequestItemDAO.java b/dspace-api/src/main/java/org/dspace/app/requestitem/dao/RequestItemDAO.java index b36ae58e0ca..9c6954fe6be 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/dao/RequestItemDAO.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/dao/RequestItemDAO.java @@ -9,6 +9,7 @@ import java.sql.SQLException; import java.util.Iterator; +import java.util.UUID; import org.dspace.app.requestitem.RequestItem; import org.dspace.content.Item; @@ -26,7 +27,7 @@ */ public interface RequestItemDAO extends GenericDAO { /** - * Fetch a request named by its unique token (passed in emails). + * Fetch a request named by its unique approval token (passed in emails). * * @param context the current DSpace context. * @param token uniquely identifies the request. @@ -36,4 +37,17 @@ public interface RequestItemDAO extends GenericDAO { public RequestItem findByToken(Context context, String token) throws SQLException; public Iterator findByItem(Context context, Item item) throws SQLException; + + /** + * Retrieve all requests (as iterator) for a given bitstream UUID + * A UUID parameter is used here rather than Bitstream object, to make it usable + * in situations even when a bitstream object no longer exists, but orphaned + * entries need to be found by their (previous) bitstream UUID. + * + * @param context current DSpace context + * @param bitstreamId the bitstream UUID to search for + * @return the matching requests (or empty iterator) + */ + public Iterator findByBitstreamId(Context context, UUID bitstreamId) throws SQLException; + } diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/dao/impl/RequestItemDAOImpl.java b/dspace-api/src/main/java/org/dspace/app/requestitem/dao/impl/RequestItemDAOImpl.java index 008174ded88..b1ef405f5e1 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/dao/impl/RequestItemDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/dao/impl/RequestItemDAOImpl.java @@ -9,6 +9,7 @@ import java.sql.SQLException; import java.util.Iterator; +import java.util.UUID; import javax.persistence.Query; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; @@ -42,10 +43,22 @@ public RequestItem findByToken(Context context, String token) throws SQLExceptio criteriaQuery.where(criteriaBuilder.equal(requestItemRoot.get(RequestItem_.token), token)); return uniqueResult(context, criteriaQuery, false, RequestItem.class); } + @Override public Iterator findByItem(Context context, Item item) throws SQLException { - Query query = createQuery(context, "FROM RequestItem WHERE item_id= :uuid"); - query.setParameter("uuid", item.getID()); + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, RequestItem.class); + Root requestItemRoot = criteriaQuery.from(RequestItem.class); + criteriaQuery.select(requestItemRoot); + criteriaQuery.where(criteriaBuilder.equal(requestItemRoot.get(RequestItem_.item), item)); + Query query = createQuery(context, criteriaQuery); + return iterate(query); + } + + @Override + public Iterator findByBitstreamId(Context context, UUID bitstreamId) throws SQLException { + Query query = createQuery(context, "FROM RequestItem WHERE bitstream.id = :bitstreamId"); + query.setParameter("bitstreamId", bitstreamId); return iterate(query); } } diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/service/RequestItemService.java b/dspace-api/src/main/java/org/dspace/app/requestitem/service/RequestItemService.java index efac3b18bc7..5c27ba9acb5 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/service/RequestItemService.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/service/RequestItemService.java @@ -10,6 +10,7 @@ import java.sql.SQLException; import java.util.Iterator; import java.util.List; +import java.util.UUID; import org.dspace.app.requestitem.RequestItem; import org.dspace.content.Bitstream; @@ -40,7 +41,7 @@ public interface RequestItemService { * @return the token of the request item * @throws SQLException if database error */ - public String createRequest(Context context, Bitstream bitstream, Item item, + String createRequest(Context context, Bitstream bitstream, Item item, boolean allFiles, String reqEmail, String reqName, String reqMessage) throws SQLException; @@ -49,35 +50,51 @@ public String createRequest(Context context, Bitstream bitstream, Item item, * * @param context current DSpace session. * @return all item requests. - * @throws java.sql.SQLException passed through. + * @throws SQLException passed through. */ - public List findAll(Context context) + List findAll(Context context) throws SQLException; /** - * Retrieve a request by its token. + * Retrieve a request by its approver token. * * @param context current DSpace session. - * @param token the token identifying the request. + * @param token the token identifying the request to be approved. * @return the matching request, or null if not found. */ - public RequestItem findByToken(Context context, String token); + RequestItem findByToken(Context context, String token); /** - * Retrieve a request based on the item. + * Retrieve all requests (as iterator) for a given item * @param context current DSpace session. * @param item the item to find requests for. - * @return the matching requests, or null if not found. + * @return the matching requests (or empty iterator) */ - public Iterator findByItem(Context context, Item item) throws SQLException; + Iterator findByItem(Context context, Item item) throws SQLException; + /** - * Save updates to the record. Only accept_request, and decision_date are set-able. + * Retrieve all requests (as iterator) for a given bitstream UUID + * A UUID parameter is used here rather than Bitstream object, to make it usable + * in situations even when a bitstream object no longer exists, but orphaned + * entries need to be found by their (previous) bitstream UUID. + * + * @param context current DSpace context + * @param bitstreamId the bitstream UUID to search for + * @return the matching requests (or empty iterator) + */ + Iterator findByBitstreamId(Context context, UUID bitstreamId) throws SQLException; + + /** + * Save updates to the record. Only accept_request, decision_date, access_period are settable. + * + * Note: the "is settable" rules mentioned here are enforced in RequestItemRest with annotations meaning that + * these JSON properties are considered READ-ONLY by the core DSpaceRestRepository methods * * @param context The relevant DSpace Context. * @param requestItem requested item */ - public void update(Context context, RequestItem requestItem); + void update(Context context, RequestItem requestItem); /** * Remove the record from the database. @@ -85,7 +102,7 @@ public List findAll(Context context) * @param context current DSpace context. * @param request record to be removed. */ - public void delete(Context context, RequestItem request); + void delete(Context context, RequestItem request); /** * Is there at least one valid READ resource policy for this object? @@ -94,6 +111,6 @@ public List findAll(Context context) * @return true if a READ policy applies. * @throws SQLException passed through. */ - public boolean isRestricted(Context context, DSpaceObject o) + boolean isRestricted(Context context, DSpaceObject o) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java index c19b87089e6..a9f509d1c3b 100644 --- a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java @@ -293,11 +293,9 @@ public void delete(Context context, Bitstream bitstream) throws SQLException, Au // Remove any RequestItem entities associated with this bitstream ensuring there are no requests referencing // a deleted bitstream - List requestItems = requestItemService.findAll(context); - for (RequestItem requestItem : requestItems) { - if (bitstream.equals(requestItem.getBitstream())) { - requestItemService.delete(context, requestItem); - } + Iterator requestItems = requestItemService.findByBitstreamId(context, bitstream.getID()); + while (requestItems.hasNext()) { + requestItemService.delete(context, requestItems.next()); } // Remove policies only after the bitstream has been updated (otherwise the current user has not WRITE rights) From 32f2d6adc648405c0580dcc9f6f25bcafc5d3547 Mon Sep 17 00:00:00 2001 From: bram-atmire Date: Wed, 25 Mar 2026 20:19:09 +0100 Subject: [PATCH 894/939] Add IT coverage for findByBitstreamId and bitstream deletion cleanup - Add testFindByBitstreamId: verifies the new findByBitstreamId method returns matching requests, returns empty for bitstreams without requests, and returns empty for nonexistent UUIDs. - Enhance testDeleteBitstreamRemovesRequestItem: also verify via findByBitstreamId both before and after deletion. (cherry picked from commit 11165705b9982498c1672f95968e79317c2fd8d2) --- .../requestitem/RequestItemServiceImpl.java | 1 - .../app/rest/RequestItemRepositoryIT.java | 45 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemServiceImpl.java index 009bbde7957..d6d0225655e 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemServiceImpl.java @@ -11,7 +11,6 @@ import java.util.Date; import java.util.Iterator; import java.util.List; -import java.util.TimeZone; import java.util.UUID; import org.apache.logging.log4j.LogManager; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java index 8495399169f..8b3e858500b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java @@ -684,11 +684,56 @@ public void testDeleteBitstreamRemovesRequestItem() throws Exception { // verify the body is empty .andExpect(jsonPath("$").doesNotExist()); + // Verify the request item exists via findByBitstreamId before deletion + Iterator bitstreamRequests = requestItemService.findByBitstreamId(context, bitstream.getID()); + assertTrue("Request item should exist before bitstream deletion", bitstreamRequests.hasNext()); + // Delete associated Bitstream ContentServiceFactory.getInstance().getBitstreamService().delete(context, bitstream); // Verify that all RequestItems related to this bitstream have been removed Iterator itemRequests = requestItemService.findByItem(context, item); assertFalse(itemRequests.hasNext()); + + // Also verify via findByBitstreamId + Iterator remaining = requestItemService.findByBitstreamId(context, bitstream.getID()); + assertFalse("Request items should be removed after bitstream deletion", remaining.hasNext()); + } + + /** + * Test that findByBitstreamId returns matching request items and does not return items for other bitstreams. + */ + @Test + public void testFindByBitstreamId() throws Exception { + context.turnOffAuthorisationSystem(); + + // Create a request item for the existing bitstream + RequestItemBuilder.createRequestItem(context, item, bitstream) + .build(); + + // Create a second bitstream with no request items + InputStream is2 = new ByteArrayInputStream("other content".getBytes()); + Bitstream bitstream2 = BitstreamBuilder + .createBitstream(context, item, is2) + .withName("Other Bitstream") + .build(); + + context.restoreAuthSystemState(); + + // findByBitstreamId should return the request for the first bitstream + Iterator results = requestItemService.findByBitstreamId(context, bitstream.getID()); + assertTrue("Should find request item for bitstream", results.hasNext()); + RequestItem found = results.next(); + assertEquals("Request item should reference correct bitstream", + bitstream.getID(), found.getBitstream().getID()); + assertFalse("Should only find one request item", results.hasNext()); + + // findByBitstreamId should return nothing for the second bitstream + Iterator noResults = requestItemService.findByBitstreamId(context, bitstream2.getID()); + assertFalse("Should find no request items for bitstream without requests", noResults.hasNext()); + + // findByBitstreamId should return nothing for a random UUID + Iterator randomResults = requestItemService.findByBitstreamId(context, UUID.randomUUID()); + assertFalse("Should find no request items for nonexistent bitstream", randomResults.hasNext()); } } From f3846c4f38862783ddd05269f0010e524ea87c9b Mon Sep 17 00:00:00 2001 From: abhinav Date: Tue, 20 May 2025 17:14:36 +0200 Subject: [PATCH 895/939] Improve caching for entities and relationship types (cherry picked from commit 11c06f21e694af5c7d7f630c2033fb2256f1a646) --- .../main/java/org/dspace/content/EntityType.java | 5 +++++ .../java/org/dspace/content/RelationshipType.java | 5 +++++ .../dspace/content/dao/impl/EntityTypeDAOImpl.java | 2 +- .../content/dao/impl/RelationshipTypeDAOImpl.java | 6 +++--- .../h2/V7.6_2025.05.20__entity_types_caching.sql | 9 +++++++++ .../V7.6_2025.05.20__entity_types_caching.sql | 9 +++++++++ dspace/config/hibernate-ehcache-config.xml | 14 ++++++++++++++ 7 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2025.05.20__entity_types_caching.sql create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2025.05.20__entity_types_caching.sql diff --git a/dspace-api/src/main/java/org/dspace/content/EntityType.java b/dspace-api/src/main/java/org/dspace/content/EntityType.java index 20ab758a0b7..a70825e5a23 100644 --- a/dspace-api/src/main/java/org/dspace/content/EntityType.java +++ b/dspace-api/src/main/java/org/dspace/content/EntityType.java @@ -8,6 +8,7 @@ package org.dspace.content; import java.util.Objects; +import javax.persistence.Cacheable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; @@ -19,6 +20,8 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.dspace.core.ReloadableEntity; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; /** * Class representing an EntityType @@ -26,6 +29,8 @@ * This also has a label that will be used to identify what kind of EntityType this object is */ @Entity +@Cacheable +@Cache(usage = CacheConcurrencyStrategy.READ_ONLY) @Table(name = "entity_type") public class EntityType implements ReloadableEntity { diff --git a/dspace-api/src/main/java/org/dspace/content/RelationshipType.java b/dspace-api/src/main/java/org/dspace/content/RelationshipType.java index 5e6941052b8..3e8227c88f4 100644 --- a/dspace-api/src/main/java/org/dspace/content/RelationshipType.java +++ b/dspace-api/src/main/java/org/dspace/content/RelationshipType.java @@ -7,6 +7,7 @@ */ package org.dspace.content; +import javax.persistence.Cacheable; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; @@ -21,6 +22,8 @@ import org.dspace.core.Context; import org.dspace.core.ReloadableEntity; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; /** * Class representing a RelationshipType @@ -31,6 +34,8 @@ * The cardinality properties describe how many of each relations this relationshipType can support */ @Entity +@Cacheable +@Cache(usage = CacheConcurrencyStrategy.READ_ONLY) @Table(name = "relationship_type") public class RelationshipType implements ReloadableEntity { diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/EntityTypeDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/EntityTypeDAOImpl.java index 489f4cd0667..957cbdbb157 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/EntityTypeDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/EntityTypeDAOImpl.java @@ -53,7 +53,7 @@ public List getEntityTypesByNames(Context context, List name orderList.add(criteriaBuilder.desc(entityTypeRoot.get(EntityType_.label))); criteriaQuery.select(entityTypeRoot).orderBy(orderList); criteriaQuery.where(entityTypeRoot.get(EntityType_.LABEL).in(names)); - return list(context, criteriaQuery, false, EntityType.class, limit, offset); + return list(context, criteriaQuery, true, EntityType.class, limit, offset); } @Override diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipTypeDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipTypeDAOImpl.java index 7fff2a1f57d..2d31707e6eb 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipTypeDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipTypeDAOImpl.java @@ -46,7 +46,7 @@ public RelationshipType findbyTypesAndTypeName(Context context, EntityType leftT criteriaBuilder.equal(relationshipTypeRoot.get(RelationshipType_.rightType), rightType), criteriaBuilder.equal(relationshipTypeRoot.get(RelationshipType_.leftwardType), leftwardType), criteriaBuilder.equal(relationshipTypeRoot.get(RelationshipType_.rightwardType), rightwardType))); - return uniqueResult(context, criteriaQuery, false, RelationshipType.class); + return uniqueResult(context, criteriaQuery, true, RelationshipType.class); } @Override @@ -96,7 +96,7 @@ public List findByEntityType(Context context, EntityType entit List orderList = new LinkedList<>(); orderList.add(criteriaBuilder.asc(relationshipTypeRoot.get(RelationshipType_.ID))); criteriaQuery.orderBy(orderList); - return list(context, criteriaQuery, false, RelationshipType.class, limit, offset); + return list(context, criteriaQuery, true, RelationshipType.class, limit, offset); } @Override @@ -122,7 +122,7 @@ public List findByEntityType(Context context, EntityType entit criteriaBuilder.equal(relationshipTypeRoot.get(RelationshipType_.rightType), entityType) ); } - return list(context, criteriaQuery, false, RelationshipType.class, limit, offset); + return list(context, criteriaQuery, true, RelationshipType.class, limit, offset); } @Override diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2025.05.20__entity_types_caching.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2025.05.20__entity_types_caching.sql new file mode 100644 index 00000000000..2a1510ff6f0 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2025.05.20__entity_types_caching.sql @@ -0,0 +1,9 @@ +-- +-- 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/ +-- + +CREATE INDEX entity_type_label_upper_idx ON entity_type (UPPER(label)); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2025.05.20__entity_types_caching.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2025.05.20__entity_types_caching.sql new file mode 100644 index 00000000000..2a1510ff6f0 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2025.05.20__entity_types_caching.sql @@ -0,0 +1,9 @@ +-- +-- 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/ +-- + +CREATE INDEX entity_type_label_upper_idx ON entity_type (UPPER(label)); diff --git a/dspace/config/hibernate-ehcache-config.xml b/dspace/config/hibernate-ehcache-config.xml index e2edf67b602..c88f0313560 100644 --- a/dspace/config/hibernate-ehcache-config.xml +++ b/dspace/config/hibernate-ehcache-config.xml @@ -145,4 +145,18 @@ + + + 500 + + + + + + 100 + + + From 895870e347e09050303e9486f09f8e8a1c27d72c Mon Sep 17 00:00:00 2001 From: abhinav Date: Wed, 21 May 2025 14:54:44 +0200 Subject: [PATCH 896/939] add expiry to ehcache-config (cherry picked from commit e6dec51c34915ddbeb83f1b09f87b41c0e777eea) --- dspace/config/hibernate-ehcache-config.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dspace/config/hibernate-ehcache-config.xml b/dspace/config/hibernate-ehcache-config.xml index c88f0313560..63c4a5ad143 100644 --- a/dspace/config/hibernate-ehcache-config.xml +++ b/dspace/config/hibernate-ehcache-config.xml @@ -147,6 +147,9 @@ + + + 500 @@ -154,6 +157,9 @@ + + + 100 From 28fd1e5084934e470673bd51730b3b2a0dee4187 Mon Sep 17 00:00:00 2001 From: abhinav Date: Thu, 22 May 2025 10:55:54 +0200 Subject: [PATCH 897/939] remove unsupported upper() from H2 migration (cherry picked from commit 11af1788712d3b9ec78ea2aee46bbe198efa8a65) --- .../sqlmigration/h2/V7.6_2025.05.20__entity_types_caching.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2025.05.20__entity_types_caching.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2025.05.20__entity_types_caching.sql index 2a1510ff6f0..c712cfa5171 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2025.05.20__entity_types_caching.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2025.05.20__entity_types_caching.sql @@ -6,4 +6,4 @@ -- http://www.dspace.org/license/ -- -CREATE INDEX entity_type_label_upper_idx ON entity_type (UPPER(label)); +CREATE INDEX entity_type_label_upper_idx ON entity_type (label); From 55f721b166988984a0518c5ba600910224408efd Mon Sep 17 00:00:00 2001 From: abhinav Date: Thu, 22 May 2025 13:13:21 +0200 Subject: [PATCH 898/939] fix cache concurrency strategy (cherry picked from commit 2091b2aa940176a4bfa5f144c72346981719bbe4) --- dspace-api/src/main/java/org/dspace/content/EntityType.java | 2 +- .../src/main/java/org/dspace/content/RelationshipType.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/EntityType.java b/dspace-api/src/main/java/org/dspace/content/EntityType.java index a70825e5a23..1c041e776e7 100644 --- a/dspace-api/src/main/java/org/dspace/content/EntityType.java +++ b/dspace-api/src/main/java/org/dspace/content/EntityType.java @@ -30,7 +30,7 @@ */ @Entity @Cacheable -@Cache(usage = CacheConcurrencyStrategy.READ_ONLY) +@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) @Table(name = "entity_type") public class EntityType implements ReloadableEntity { diff --git a/dspace-api/src/main/java/org/dspace/content/RelationshipType.java b/dspace-api/src/main/java/org/dspace/content/RelationshipType.java index 3e8227c88f4..5b89911e1f7 100644 --- a/dspace-api/src/main/java/org/dspace/content/RelationshipType.java +++ b/dspace-api/src/main/java/org/dspace/content/RelationshipType.java @@ -35,7 +35,7 @@ */ @Entity @Cacheable -@Cache(usage = CacheConcurrencyStrategy.READ_ONLY) +@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) @Table(name = "relationship_type") public class RelationshipType implements ReloadableEntity { From 1e0ae3bc9a85ab4a9cfd345de737c83f0ecbbda4 Mon Sep 17 00:00:00 2001 From: abhinav Date: Fri, 30 May 2025 12:20:25 +0200 Subject: [PATCH 899/939] add cache expiry time (cherry picked from commit 894ffbf68704f8b89279f01bafa55db55dee6cf9) --- dspace/config/hibernate-ehcache-config.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace/config/hibernate-ehcache-config.xml b/dspace/config/hibernate-ehcache-config.xml index 63c4a5ad143..80ba1717fe3 100644 --- a/dspace/config/hibernate-ehcache-config.xml +++ b/dspace/config/hibernate-ehcache-config.xml @@ -148,7 +148,7 @@ - + 1 500 @@ -158,7 +158,7 @@ - + 1 100 From 482fe4880e4867641a63a34bc40cec7dd015e770 Mon Sep 17 00:00:00 2001 From: abhinav Date: Mon, 2 Jun 2025 15:29:04 +0200 Subject: [PATCH 900/939] cache RelationshipDAO#findByItem as well (cherry picked from commit 8c0f5bfc566ffca00e4dc15c21de7eafe36d4e8c) --- .../java/org/dspace/content/dao/impl/RelationshipDAOImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java index e2f84bc1cb6..ac626cf9267 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java @@ -57,7 +57,7 @@ public List findByItem( ) ); - return list(context, criteriaQuery, false, Relationship.class, limit, offset); + return list(context, criteriaQuery, true, Relationship.class, limit, offset); } /** From 1a782f9dceeae76ccba4997a0a3afe7152e08672 Mon Sep 17 00:00:00 2001 From: abhinav Date: Wed, 25 Mar 2026 18:42:51 +0100 Subject: [PATCH 901/939] update date on migration files (cherry picked from commit e7d1e269748f322bd0672bbd2e651d5a80ec0bdd) --- ...ypes_caching.sql => V7.6_2026.03.25__entity_types_caching.sql} | 0 ...ypes_caching.sql => V7.6_2026.03.25__entity_types_caching.sql} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/{V7.6_2025.05.20__entity_types_caching.sql => V7.6_2026.03.25__entity_types_caching.sql} (100%) rename dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/{V7.6_2025.05.20__entity_types_caching.sql => V7.6_2026.03.25__entity_types_caching.sql} (100%) diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2025.05.20__entity_types_caching.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2026.03.25__entity_types_caching.sql similarity index 100% rename from dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2025.05.20__entity_types_caching.sql rename to dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2026.03.25__entity_types_caching.sql diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2025.05.20__entity_types_caching.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2026.03.25__entity_types_caching.sql similarity index 100% rename from dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2025.05.20__entity_types_caching.sql rename to dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2026.03.25__entity_types_caching.sql From 5846b13fcc08fdff467ba72c2b60cf3fb53779d0 Mon Sep 17 00:00:00 2001 From: abhinav Date: Wed, 20 May 2026 11:27:00 +0200 Subject: [PATCH 902/939] undo cache findByItem query in RelationshipDAOImpl (cherry picked from commit 9c57f390753a369dde30c6b1f6aaab5b7fa36335) --- .../java/org/dspace/content/dao/impl/RelationshipDAOImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java index ac626cf9267..e2f84bc1cb6 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java @@ -57,7 +57,7 @@ public List findByItem( ) ); - return list(context, criteriaQuery, true, Relationship.class, limit, offset); + return list(context, criteriaQuery, false, Relationship.class, limit, offset); } /** From 0ca2c2f56e040d8cffe42ae7fd3e2317e5813bc7 Mon Sep 17 00:00:00 2001 From: abhinav Date: Wed, 20 May 2026 11:34:09 +0200 Subject: [PATCH 903/939] add comments to cache configs (cherry picked from commit 50737b1b24bf2207059b0300329cf065ee21c05d) --- dspace/config/hibernate-ehcache-config.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dspace/config/hibernate-ehcache-config.xml b/dspace/config/hibernate-ehcache-config.xml index 80ba1717fe3..680211a9acd 100644 --- a/dspace/config/hibernate-ehcache-config.xml +++ b/dspace/config/hibernate-ehcache-config.xml @@ -145,6 +145,8 @@ + @@ -155,6 +157,7 @@ + From e59dd8452c5007f2e81aaf955c8f151ad424f28f Mon Sep 17 00:00:00 2001 From: abhinav Date: Wed, 20 May 2026 16:20:18 +0200 Subject: [PATCH 904/939] add comments to h2 migration (cherry picked from commit b49536bd8eb2b5e0209530a1ba0b78ad4e534f7c) --- .../sqlmigration/h2/V7.6_2026.03.25__entity_types_caching.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2026.03.25__entity_types_caching.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2026.03.25__entity_types_caching.sql index c712cfa5171..1b6db9f5284 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2026.03.25__entity_types_caching.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2026.03.25__entity_types_caching.sql @@ -6,4 +6,6 @@ -- http://www.dspace.org/license/ -- +-- H2 does not support upper and hence the migration differs slightly from the postgres version +-- In a test environment this will not make a meaningful impact CREATE INDEX entity_type_label_upper_idx ON entity_type (label); From 3185a1c8d435f0a5f834b466732a8d5b75a13f83 Mon Sep 17 00:00:00 2001 From: Yury Bondarenko Date: Tue, 19 May 2026 12:57:22 +0200 Subject: [PATCH 905/939] Fix missing OAI identifier interpolation on config change When dspace.cfg is updated while Tomcat is running, the oai.identifier.prefix fallback from dspace-oai's DSpaceConfigurationService gets cleared out. If the cached Identify request gets invalidated after this, it will be regenerated with ${oai.identifier.prefix} in place We re-initialize this fallback when re-creating the Identify content to account for this. This is the only place where setProperty is used outside of tests. (cherry picked from commit 263513bcbaffae00d1cbaf1f5237b0f05738a76d) --- .../api/config/ConfigurationService.java | 2 ++ .../impl/config/DSpaceConfigurationService.java | 16 +++++++++++----- .../impl/xoai/DSpaceRepositoryConfiguration.java | 1 + .../dspace/services/ConfigurationService.java | 2 ++ 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/dspace-oai/src/main/java/org/dspace/xoai/services/api/config/ConfigurationService.java b/dspace-oai/src/main/java/org/dspace/xoai/services/api/config/ConfigurationService.java index bc6083166ba..997e08adec8 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/services/api/config/ConfigurationService.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/services/api/config/ConfigurationService.java @@ -15,4 +15,6 @@ public interface ConfigurationService { boolean getBooleanProperty(String module, String key, boolean defaultValue); boolean getBooleanProperty(String key, boolean defaultValue); + + void ensureRequiredConfiguration(); } diff --git a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/config/DSpaceConfigurationService.java b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/config/DSpaceConfigurationService.java index 67d4f09f5c8..0fb0c51defd 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/config/DSpaceConfigurationService.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/config/DSpaceConfigurationService.java @@ -20,12 +20,18 @@ public class DSpaceConfigurationService implements ConfigurationService { * Initialize the OAI Configuration Service */ public DSpaceConfigurationService() { - // Check the DSpace ConfigurationService for required OAI-PMH settings. - // If they do not exist, set sane defaults as needed. + ensureRequiredConfiguration(); + } - // Per OAI Spec, "oai.identifier.prefix" should be the hostname / domain name of the site. - // This configuration is needed by the [dspace]/config/crosswalks/oai/description.xml template, so if - // unspecified we will dynamically set it to the hostname of the "dspace.ui.url" configuration. + /** + * Check the DSpace ConfigurationService for required OAI-PMH settings. + * If they do not exist, set sane defaults as needed. + *
    + * Per OAI Spec, "oai.identifier.prefix" should be the hostname / domain name of the site. + * This configuration is needed by the [dspace]/config/crosswalks/oai/description.xml template, so if + * unspecified we will dynamically set it to the hostname of the "dspace.ui.url" configuration. + */ + public void ensureRequiredConfiguration() { if (!configurationService.hasProperty("oai.identifier.prefix")) { configurationService.setProperty("oai.identifier.prefix", Utils.getHostName(configurationService.getProperty("dspace.ui.url"))); diff --git a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/xoai/DSpaceRepositoryConfiguration.java b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/xoai/DSpaceRepositoryConfiguration.java index 2a000f43ea0..79b04094740 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/xoai/DSpaceRepositoryConfiguration.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/xoai/DSpaceRepositoryConfiguration.java @@ -133,6 +133,7 @@ public String getRepositoryName() { @Override public List getDescription() { + configurationService.ensureRequiredConfiguration(); List result = new ArrayList(); String descriptionFile = configurationService.getProperty("oai.description.file"); if (descriptionFile == null) { diff --git a/dspace-services/src/main/java/org/dspace/services/ConfigurationService.java b/dspace-services/src/main/java/org/dspace/services/ConfigurationService.java index 526a518a091..7681553d416 100644 --- a/dspace-services/src/main/java/org/dspace/services/ConfigurationService.java +++ b/dspace-services/src/main/java/org/dspace/services/ConfigurationService.java @@ -251,6 +251,8 @@ public interface ConfigurationService { * Set a configuration property (setting) in the system. * Type is not important here since conversion happens automatically * when properties are requested. + *
    + * Note: use with care, the value will be reset when the configuration is reloaded! * * @param name the property name * @param value the property value (set this to null to clear out the property) From 421ff55dfe34fe91318c55e322bd6f4989614ccb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eike=20L=C3=B6hden?= <75250168+EikLoe@users.noreply.github.com> Date: Thu, 21 May 2026 17:28:39 +0200 Subject: [PATCH 906/939] Merge pull request #12350 from dspace-unimr/fix/ds-12349-handle-submission-with-non-public-items-and-item-filters Fix submission with non public items and AccessCondition filters (cherry picked from commit c70841f0ee92d6a655954fa53532bd47229b0207) --- .../logic/condition/ReadableByGroupCondition.java | 2 +- .../org/dspace/content/logic/LogicalFilterTest.java | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/content/logic/condition/ReadableByGroupCondition.java b/dspace-api/src/main/java/org/dspace/content/logic/condition/ReadableByGroupCondition.java index 20138beb47e..e7b0bb7e046 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/condition/ReadableByGroupCondition.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/ReadableByGroupCondition.java @@ -49,7 +49,7 @@ public boolean getResult(Context context, Item item) throws LogicalStatementExce List policies = authorizeService .getPoliciesActionFilter(context, item, Constants.getActionID(action)); for (ResourcePolicy policy : policies) { - if (policy.getGroup().getName().equals(group)) { + if (policy.getGroup() != null && policy.getGroup().getName().equals(group)) { return true; } } diff --git a/dspace-api/src/test/java/org/dspace/content/logic/LogicalFilterTest.java b/dspace-api/src/test/java/org/dspace/content/logic/LogicalFilterTest.java index 0e086462204..c84665f2985 100644 --- a/dspace-api/src/test/java/org/dspace/content/logic/LogicalFilterTest.java +++ b/dspace-api/src/test/java/org/dspace/content/logic/LogicalFilterTest.java @@ -57,8 +57,10 @@ import org.dspace.content.service.MetadataValueService; import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Constants; +import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.eperson.service.EPersonService; import org.dspace.eperson.service.GroupService; import org.junit.After; import org.junit.Before; @@ -81,6 +83,7 @@ public class LogicalFilterTest extends AbstractUnitTest { private MetadataValueService metadataValueService = ContentServiceFactory.getInstance().getMetadataValueService(); private AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); private GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + private EPersonService epersonService = EPersonServiceFactory.getInstance().getEPersonService(); // Logger private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(LogicalFilterTest.class); @@ -603,6 +606,10 @@ public void testReadableByGroupCondition() { groupService.setName(g, "Test Group"); groupService.update(context, g); authorizeService.addPolicy(context, itemOne, Constants.READ, g); + EPerson e = epersonService.create(context); + epersonService.update(context, e); + authorizeService.removeAllPolicies(context, itemThree); + authorizeService.addPolicy(context, itemThree, Constants.READ, e); context.restoreAuthSystemState(); } catch (AuthorizeException | SQLException e) { fail("Exception thrown adding group READ policy to item: " + itemOne + ": " + e.getMessage()); @@ -620,6 +627,9 @@ public void testReadableByGroupCondition() { // Test the filter on itemTwo - this item has no policies: expect false assertFalse("itemTwo unexpectedly matched the 'is readable by Test Group' test", filter.getResult(context, itemTwo)); + // Test the filter on itemThree - this item has only a person related policy: expect false + assertFalse("itemThree unexpectedly matched the 'is readable by Test Group' test", + filter.getResult(context, itemThree)); } catch (LogicalStatementException e) { log.error(e.getMessage()); fail("LogicalStatementException thrown testing the ReadableByGroup filter" + e.getMessage()); From c23e08e90ae2a9b23a98ca55643903f0a2822a0c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 15:50:07 +0000 Subject: [PATCH 907/939] build(deps): bump org.apache.commons:commons-configuration2 Bumps org.apache.commons:commons-configuration2 from 2.14.0 to 2.15.0. --- updated-dependencies: - dependency-name: org.apache.commons:commons-configuration2 dependency-version: 2.15.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] (cherry picked from commit 649fe8734e219d31eaa5704b4073509fa7406a2a) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0ee4bc7f0d5..eb1b3497aaa 100644 --- a/pom.xml +++ b/pom.xml @@ -1499,7 +1499,7 @@ org.apache.commons commons-configuration2 - 2.14.0 + 2.15.0 org.apache.commons From 7ac17f68432cff1e9463f05421fc8a29516000b8 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Sat, 16 May 2026 22:05:27 +0200 Subject: [PATCH 908/939] ORE aggregated resource URI validation --- .../crosswalk/OREIngestionCrosswalk.java | 84 +++++++++++++++++-- dspace/config/modules/oai.cfg | 7 ++ 2 files changed, 83 insertions(+), 8 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/OREIngestionCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/OREIngestionCrosswalk.java index f756aae2257..9e890a6046f 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/OREIngestionCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/OREIngestionCrosswalk.java @@ -11,6 +11,8 @@ import java.io.IOException; import java.io.InputStream; import java.net.ConnectException; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; import java.sql.SQLException; import java.text.NumberFormat; @@ -18,6 +20,8 @@ import java.util.Date; import java.util.HashSet; import java.util.List; +import java.util.Locale; +import java.util.Objects; import java.util.Set; import org.apache.logging.log4j.Logger; @@ -34,6 +38,8 @@ import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; import org.jdom2.Attribute; import org.jdom2.Document; import org.jdom2.Element; @@ -76,6 +82,7 @@ public class OREIngestionCrosswalk .getBitstreamFormatService(); protected BundleService bundleService = ContentServiceFactory.getInstance().getBundleService(); protected ItemService itemService = ContentServiceFactory.getInstance().getItemService(); + protected ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); @Override @@ -173,9 +180,13 @@ public void ingest(Context context, DSpaceObject dso, Element root, boolean crea try { // Make sure the url string escapes all the oddball characters String processedURL = encodeForURL(href); - // Generate a requeset for the aggregated resource - ARurl = new URL(processedURL); - in = ARurl.openStream(); + if (validResourceUri(entryId, processedURL)) { + // Generate a request for the aggregated resource + ARurl = new URL(processedURL); + in = ARurl.openStream(); + } else { + throw new FileNotFoundException("Failed to validate " + processedURL); + } } catch (FileNotFoundException fe) { log.error("The provided URI failed to return a resource: " + href); } catch (ConnectException fe) { @@ -219,17 +230,17 @@ public void ingest(Context context, DSpaceObject dso, Element root, boolean crea * @param sourceString source unescaped string */ private String encodeForURL(String sourceString) { - Character lowalpha[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', + Character[] lowalpha = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}; - Character upalpha[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', + Character[] upalpha = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}; - Character digit[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; - Character mark[] = {'-', '_', '.', '!', '~', '*', '\'', '(', ')'}; + Character[] digit = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; + Character[] mark = {'-', '_', '.', '!', '~', '*', '\'', '(', ')'}; // reserved - Character reserved[] = {';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '%', '#'}; + Character[] reserved = {';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '%', '#'}; Set URLcharsSet = new HashSet(); URLcharsSet.addAll(Arrays.asList(lowalpha)); @@ -251,4 +262,61 @@ private String encodeForURL(String sourceString) { return processedString.toString(); } + /** + * Validate a resource URI against the host and scheme of the remote OAI endpoint, or a configured + * list of allowed prefixes. + * This still implicitly "trusts" the remote OAI server, but will reject resource URIs with a totally + * different hostname to avoid downloading malicious resources from a compromised endpoint. + * Even if the URL prefix validation is disabled, schemes will still be enforced to http(s) so file:/// and + * other unwanted schemes cannot be used + * @param entryUrl the entryId of the parent ORE resource + * @param resourceUrl the resource URL of the aggregated ORE resource + * @return result of the validation + */ + private boolean validResourceUri(String entryUrl, String resourceUrl) { + try { + Set allowedSchemes = Set.of("http", "https"); + URI entryUri = new URI(entryUrl).normalize(); + URI resourceUri = new URI(resourceUrl).normalize(); + String scheme = resourceUri.getScheme(); + + if (scheme == null || + !allowedSchemes.contains(scheme.toLowerCase(Locale.ROOT))) { + log.warn("Illegal scheme requested for ORE resource: {}", resourceUri); + return false; + } + + if (configurationService.getBooleanProperty("oai.harvester.ore.file.validateUrlPrefix", false)) { + for (String allowedPrefix : configurationService + .getArrayProperty("oai.harvester.ore.file.allowedUrlPrefix")) { + URI allowedUri = new URI(allowedPrefix).normalize(); + // Return true on the first allowed prefix match + if (Objects.equals(resourceUri.getScheme(), allowedUri.getScheme()) + && Objects.equals(resourceUri.getHost().toLowerCase(Locale.ROOT), + allowedUri.getHost().toLowerCase(Locale.ROOT))) { + return true; + } + } + + // If no allowed prefixes were matched, we require scheme + host to match the remote OAI server + if (!Objects.equals(entryUri.getScheme(), resourceUri.getScheme())) { + log.warn("Illegal scheme requested for ORE resource: {}", resourceUri); + return false; + } + if (!Objects.equals( + entryUri.getHost().toLowerCase(Locale.ROOT), + resourceUri.getHost().toLowerCase(Locale.ROOT))) { + log.warn("Illegal host requested for ORE resource: {}", resourceUri); + return false; + } + } + + return true; + + } catch (URISyntaxException e) { + log.warn("Could not validate ORE resource URI: {}", resourceUrl); + return false; + } + } + } diff --git a/dspace/config/modules/oai.cfg b/dspace/config/modules/oai.cfg index 98b10f59dee..b1ec11447f3 100644 --- a/dspace/config/modules/oai.cfg +++ b/dspace/config/modules/oai.cfg @@ -132,3 +132,10 @@ oai.harvester.unknownSchema = fail # when attempting to find the handle of harvested items. If there is a match with # this config parameter, a new handle will be minted instead. Default value: 123456789. #oai.harvester.rejectedHandlePrefix = 123456789, myTestHandle + +# If ingesting files with ORE, only files with URLs that match the base URL of the remote +# OAI endpoint's domain name are accepted, or a list of other URL prefixes defined below +#oai.harvester.ore.file.validateUrlPrefix = true +# Prefixes that are allowed globally (for any endpoint) are below +#oai.harvester.ore.file.allowedUrlPrefix = dspace.myinstitution.edu +#oai.harvester.ore.file.allowedUrlPrefix = files.myinstitution.edu From 5b31db512f62bb530b71cb8fe85b1300f35e5601 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 5 May 2026 16:24:29 +0200 Subject: [PATCH 909/939] Velocity and template safety for Email and LDN messages * Safer Velocity configuration * New "message.templates.allowed-config" config * Remove "UnmodifiableConfiguration" in favour of a simple Map of whitelisted Config keys/values * Centralise Velocity config in core Utils * Small javadoc changes (cherry picked from commit b2d6141389f5652970b366325ed9deff21a86836) --- .../src/main/java/org/dspace/core/Email.java | 83 ++++++------------- .../src/main/java/org/dspace/core/Utils.java | 64 ++++++++++++++ dspace/config/dspace.cfg | 12 +++ 3 files changed, 100 insertions(+), 59 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/core/Email.java b/dspace-api/src/main/java/org/dspace/core/Email.java index c157d16d496..22bb3dc3963 100644 --- a/dspace-api/src/main/java/org/dspace/core/Email.java +++ b/dspace-api/src/main/java/org/dspace/core/Email.java @@ -22,7 +22,6 @@ import java.util.Date; import java.util.Enumeration; import java.util.List; -import java.util.Properties; import javax.activation.DataHandler; import javax.activation.DataSource; import javax.activation.FileDataSource; @@ -44,12 +43,10 @@ import org.apache.logging.log4j.Logger; import org.apache.velocity.Template; import org.apache.velocity.VelocityContext; -import org.apache.velocity.app.Velocity; import org.apache.velocity.app.VelocityEngine; import org.apache.velocity.exception.MethodInvocationException; import org.apache.velocity.exception.ParseErrorException; import org.apache.velocity.exception.ResourceNotFoundException; -import org.apache.velocity.runtime.resource.loader.StringResourceLoader; import org.apache.velocity.runtime.resource.util.StringResourceRepository; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; @@ -72,7 +69,7 @@ * Apache Velocity. They may contain VTL directives and property * placeholders. *

    - * {@link addArgument(string)} adds a property to the {@code params} array + * {@link #addArgument(Object)} adds a property to the {@code params} array * in the Velocity context, which can be used to replace placeholder tokens * in the message. These arguments are indexed by number in the order they were * added to the message. @@ -80,9 +77,9 @@ * The DSpace configuration properties are also available to templates as the * array {@code config}, indexed by name. Example: {@code ${config.get('dspace.name')}} *

    - * Recipients and attachments may be added as needed. See {@link addRecipient}, - * {@link addAttachment(File, String)}, and - * {@link addAttachment(InputStream, String, String)}. + * Recipients and attachments may be added as needed. See {@link #addRecipient}, + * {@link #addAttachment(File, String)}, and + * {@link #addAttachment(InputStream, String, String)}. *

    * Headers such as Subject may be supplied by the template, by defining them * using the VTL directive {@code #set()}. Only headers named in the DSpace @@ -125,8 +122,8 @@ * *

    * There are two ways to load a message body. One can create an instance of - * {@link Email} and call {@link setContent} on it, passing the body as a String. Or - * one can use the static factory method {@link getEmail} to load a file by its + * {@link Email} and call {@link #setContent} on it, passing the body as a String. Or + * one can use the static factory method {@link #getEmail} to load a file by its * complete filesystem path. In either case the text will be loaded into a * Velocity template. * @@ -172,18 +169,6 @@ public class Email { /** Velocity template settings. */ private static final String RESOURCE_REPOSITORY_NAME = "Email"; - private static final Properties VELOCITY_PROPERTIES = new Properties(); - static { - VELOCITY_PROPERTIES.put(Velocity.RESOURCE_LOADERS, "string"); - VELOCITY_PROPERTIES.put("resource.loader.string.description", - "Velocity StringResource loader"); - VELOCITY_PROPERTIES.put("resource.loader.string.class", - StringResourceLoader.class.getName()); - VELOCITY_PROPERTIES.put("resource.loader.string.repository.name", - RESOURCE_REPOSITORY_NAME); - VELOCITY_PROPERTIES.put("resource.loader.string.repository.static", - "false"); - } /** Velocity template for a message body */ private Template template; @@ -202,6 +187,13 @@ public Email() { charset = null; } + /** + * Get configuration service + */ + private static ConfigurationService getConfigurationService() { + return DSpaceServicesFactory.getInstance().getConfigurationService(); + } + /** * Add a recipient. * @@ -224,7 +216,7 @@ public void setContent(String name, String content) { arguments.clear(); VelocityEngine templateEngine = new VelocityEngine(); - templateEngine.init(VELOCITY_PROPERTIES); + templateEngine.init(Utils.getSecureVelocityProperties(RESOURCE_REPOSITORY_NAME)); StringResourceRepository repo = (StringResourceRepository) templateEngine.getApplicationAttribute(RESOURCE_REPOSITORY_NAME); @@ -254,7 +246,8 @@ public void setReplyTo(String email) { /** * Fill out the next argument in the template. * - * @param arg the value for the next argument + * @param arg the value for the next argument. If {@code null}, + * a zero-length string is substituted. */ public void addArgument(Object arg) { arguments.add(arg); @@ -332,7 +325,7 @@ public void reset() { * {@code mail.message.headers} then that name and its value will be added * to the message's headers. * - *

    "subject" is treated specially: if {@link setSubject()} has not been + *

    "subject" is treated specially: if {@link #setSubject} has not been * called, the value of any "subject" property will be used as if setSubject * had been called with that value. Thus a template may define its subject, * but the caller may override it. @@ -346,16 +339,13 @@ public void send() throws MessagingException, IOException { throw new MessagingException("Email has no body"); } - ConfigurationService config - = DSpaceServicesFactory.getInstance().getConfigurationService(); - // Get the mail configuration properties - String from = config.getProperty("mail.from.address"); - boolean disabled = config.getBooleanProperty("mail.server.disabled", false); + String from = getConfigurationService().getProperty("mail.from.address"); + boolean disabled = getConfigurationService().getBooleanProperty("mail.server.disabled", false); // If no character set specified, attempt to retrieve a default if (charset == null) { - charset = config.getProperty("mail.charset"); + charset = getConfigurationService().getProperty("mail.charset"); } // Get session @@ -370,11 +360,13 @@ public void send() throws MessagingException, IOException { new InternetAddress(recipient)); } // Get headers defined by the template. - String[] templateHeaders = config.getArrayProperty("mail.message.headers"); + String[] templateHeaders = getConfigurationService().getArrayProperty("mail.message.headers"); // Format the mail message body VelocityContext vctx = new VelocityContext(); - vctx.put("config", new UnmodifiableConfigurationService(config)); + // Pass a restricted (via configuration) list of resolved Configuration keys and values, for + // template lookup + vctx.put("config", Utils.getAllowedTemplateConfig()); vctx.put("params", Collections.unmodifiableList(arguments)); StringWriter writer = new StringWriter(); @@ -661,31 +653,4 @@ public OutputStream getOutputStream() throws IOException { throw new IOException("Cannot write to this read-only resource"); } } - - /** - * Wrap ConfigurationService to prevent templates from modifying - * the configuration. - */ - public static class UnmodifiableConfigurationService { - private final ConfigurationService configurationService; - - /** - * Swallow an instance of ConfigurationService. - * - * @param cs the real instance, to be wrapped. - */ - public UnmodifiableConfigurationService(ConfigurationService cs) { - configurationService = cs; - } - - /** - * Look up a key in the actual ConfigurationService. - * - * @param key to be looked up in the DSpace configuration. - * @return whatever value ConfigurationService associates with {@code key}. - */ - public String get(String key) { - return configurationService.getProperty(key); - } - } } diff --git a/dspace-api/src/main/java/org/dspace/core/Utils.java b/dspace-api/src/main/java/org/dspace/core/Utils.java index a1294c3317c..fcec140869c 100644 --- a/dspace-api/src/main/java/org/dspace/core/Utils.java +++ b/dspace-api/src/main/java/org/dspace/core/Utils.java @@ -29,16 +29,22 @@ import java.util.Collections; import java.util.Date; import java.util.GregorianCalendar; +import java.util.List; +import java.util.Map; +import java.util.Properties; import java.util.Random; import java.util.StringTokenizer; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; import com.coverity.security.Escape; import org.apache.commons.lang3.StringUtils; import org.apache.commons.text.StringSubstitutor; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.velocity.app.Velocity; +import org.apache.velocity.runtime.resource.loader.StringResourceLoader; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; @@ -101,6 +107,14 @@ public final class Utils { private static final Calendar outCal = GregorianCalendar.getInstance(); + // Allowed configuration properties to pass to Velocity templates (Email, LDN) + private static final String[] DEFAULT_ALLOWED_TEMPLATE_CONFIGS = { + "dspace.name", "dspace.shortname", "dspace.ui.url", + "mail.helpdesk", "mail.message.helpdesk.telephone", "mail.admin", "mail.admin.name"}; + + private static final ConfigurationService configurationService = + DSpaceServicesFactory.getInstance().getConfigurationService(); + /** * Private constructor */ @@ -507,4 +521,54 @@ public static String interpolateConfigsInString(String string) { return StringSubstitutor.replace(string, config.getProperties()); } + /** + * Get a list of allowed DSpace configuration property keys that will be exposed to Velocity templates + * (used in Email and LDN messages) as a simple Map of strings. + * @return Map of strings representing resolved configuration properties + */ + public static Map getAllowedTemplateConfig() { + // Pass a restricted (via configuration) list of resolved Configuration keys and values, for + // template lookup + List allowedConfigurationKeys = List.of(configurationService.getArrayProperty( + "message.templates.allowed-config", DEFAULT_ALLOWED_TEMPLATE_CONFIGS)); + return allowedConfigurationKeys.stream() + .map(key -> Map.entry(key, configurationService.getProperty(key))) + .filter(entry -> entry.getValue() != null) + .collect(Collectors.toMap( + Map.Entry::getKey, + Map.Entry::getValue + )); + } + + /** + * Create and return a set of default, secure Velocity configuration properties. + * @see {@link Email} + * + * @param resourceRepositoryName the templating context e.g. "LDN", "Email" + * @returns secure Velocity configuration for use with templating + */ + public static Properties getSecureVelocityProperties(String resourceRepositoryName) { + Properties secureVelocityProperties = new Properties(); + // Basic Velocity configuration + secureVelocityProperties.setProperty(Velocity.RESOURCE_LOADERS, "string"); + secureVelocityProperties.setProperty("resource.loader.string.description", + "Velocity StringResource loader"); + secureVelocityProperties.setProperty("resource.loader.string.class", + StringResourceLoader.class.getName()); + secureVelocityProperties.setProperty("resource.loader.string.repository.name", + resourceRepositoryName); + secureVelocityProperties.setProperty("resource.loader.string.repository.static", + "false"); + // Set secure default introspection and class restriction handling in Velocity + secureVelocityProperties.setProperty("introspector.uberspect.class", + "org.apache.velocity.util.introspection.SecureUberspector"); + secureVelocityProperties.setProperty("introspector.restrict.classes", + "java.lang.Class,java.lang.Runtime,java.lang.System"); + secureVelocityProperties.setProperty( "introspector.restrict.packages", + "java.lang.reflect,java.io,java.nio"); + secureVelocityProperties.setProperty("runtime.strict_mode.enable", "true"); + + return secureVelocityProperties; + } + } diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 7e45645983b..d0523f57708 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -162,6 +162,7 @@ mail.from.address = dspace-noreply@myu.edu # will use the above settings to create a Session. #mail.session.name = Session + # When feedback is submitted via the Feedback form, it is sent to this address # Currently limited to one recipient! # if this property is empty or commented out, feedback form is disabled @@ -228,6 +229,17 @@ mail.message.headers = charset # Helpdesk telephone. Not email, but should be with other contact info. Optional. #mail.message.helpdesk.telephone = +1 555 555 5555 +# Allowed configuration properties, to pass in a "config" map to email and LDN templates. +# This allows templates to easily access dynamic configuration properties, without +# exposing sensitive information to the templating engine +message.templates.allowed-config = dspace.name +message.templates.allowed-config = dspace.shortname +message.templates.allowed-config = dspace.ui.url +message.templates.allowed-config = mail.helpdesk +message.templates.allowed-config = mail.message.helpdesk.telephone +message.templates.allowed-config = mail.admin +message.templates.allowed-config = mail.admin.name + ##### Asset Storage (bitstreams / files) ###### # Moved to config/spring/api/bitstore.xml From 46a0dfb38197dfd9fa9970aa293e3a11a67c12b0 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Sat, 16 May 2026 15:55:24 +0200 Subject: [PATCH 910/939] Better null checking in allowed config props (cherry picked from commit 6b665313cb48131ada04ae0840ff531b08b31dad) --- dspace-api/src/main/java/org/dspace/core/Utils.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/core/Utils.java b/dspace-api/src/main/java/org/dspace/core/Utils.java index fcec140869c..355e3f01cc5 100644 --- a/dspace-api/src/main/java/org/dspace/core/Utils.java +++ b/dspace-api/src/main/java/org/dspace/core/Utils.java @@ -31,6 +31,7 @@ import java.util.GregorianCalendar; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Properties; import java.util.Random; import java.util.StringTokenizer; @@ -532,8 +533,11 @@ public static Map getAllowedTemplateConfig() { List allowedConfigurationKeys = List.of(configurationService.getArrayProperty( "message.templates.allowed-config", DEFAULT_ALLOWED_TEMPLATE_CONFIGS)); return allowedConfigurationKeys.stream() - .map(key -> Map.entry(key, configurationService.getProperty(key))) - .filter(entry -> entry.getValue() != null) + .map(key -> { + String value = configurationService.getProperty(key); + return value != null ? Map.entry(key, value) : null; + }) + .filter(Objects::nonNull) .collect(Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue From 4be430f4f0d404b88ad87454af8aeafefae9c042 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 27 May 2026 09:57:21 +0200 Subject: [PATCH 911/939] Access configurationService at runtime, not rely on class setup (cherry picked from commit 5803819ba65e7211a4f49318d0d3bbf2246e21c1) --- dspace-api/src/main/java/org/dspace/core/Utils.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/core/Utils.java b/dspace-api/src/main/java/org/dspace/core/Utils.java index 355e3f01cc5..90ce6634437 100644 --- a/dspace-api/src/main/java/org/dspace/core/Utils.java +++ b/dspace-api/src/main/java/org/dspace/core/Utils.java @@ -113,9 +113,6 @@ public final class Utils { "dspace.name", "dspace.shortname", "dspace.ui.url", "mail.helpdesk", "mail.message.helpdesk.telephone", "mail.admin", "mail.admin.name"}; - private static final ConfigurationService configurationService = - DSpaceServicesFactory.getInstance().getConfigurationService(); - /** * Private constructor */ @@ -530,6 +527,8 @@ public static String interpolateConfigsInString(String string) { public static Map getAllowedTemplateConfig() { // Pass a restricted (via configuration) list of resolved Configuration keys and values, for // template lookup + ConfigurationService configurationService = + DSpaceServicesFactory.getInstance().getConfigurationService(); List allowedConfigurationKeys = List.of(configurationService.getArrayProperty( "message.templates.allowed-config", DEFAULT_ALLOWED_TEMPLATE_CONFIGS)); return allowedConfigurationKeys.stream() From 655fc62874e9e4e5cf95ef2ff1e05b908484fc9f Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 27 May 2026 15:39:41 +0200 Subject: [PATCH 912/939] Remove strict mode Velocity engine configuration (allow nulls) --- dspace-api/src/main/java/org/dspace/core/Utils.java | 6 +++++- dspace/config/dspace.cfg | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/core/Utils.java b/dspace-api/src/main/java/org/dspace/core/Utils.java index 90ce6634437..8f8011acef5 100644 --- a/dspace-api/src/main/java/org/dspace/core/Utils.java +++ b/dspace-api/src/main/java/org/dspace/core/Utils.java @@ -569,7 +569,11 @@ public static Properties getSecureVelocityProperties(String resourceRepositoryNa "java.lang.Class,java.lang.Runtime,java.lang.System"); secureVelocityProperties.setProperty( "introspector.restrict.packages", "java.lang.reflect,java.io,java.nio"); - secureVelocityProperties.setProperty("runtime.strict_mode.enable", "true"); + // Set strict mode if configured (default: false, as we've always treated null values as blanks) + if (DSpaceServicesFactory.getInstance().getConfigurationService() + .getBooleanProperty("message.templates.strict_mode", false)) { + secureVelocityProperties.setProperty("runtime.strict_mode.enable", "true"); + } return secureVelocityProperties; } diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index d0523f57708..fa85cc669d6 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -240,6 +240,11 @@ message.templates.allowed-config = mail.message.helpdesk.telephone message.templates.allowed-config = mail.admin message.templates.allowed-config = mail.admin.name +# Whether to run Velocity in strict mode (null parameter values in templates for LDN or Email will result +# in an Exception instead of a blank string) +# Default: false (this can introduce unwanted side-effects if e.g. a submitter eperson is deleted for a workflow task) +#message.templates.strict_mode = false + ##### Asset Storage (bitstreams / files) ###### # Moved to config/spring/api/bitstore.xml From dc3e4553641bdd91bac344d0402ad796b80a75f9 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Sat, 16 May 2026 20:54:09 +0200 Subject: [PATCH 913/939] Filter requests for JSPs or traversal (cherry picked from commit cf9be8554d3597e2c80958cd62336b40b79ba19d) --- .../security/GlobalRequestSecurityFilter.java | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/security/GlobalRequestSecurityFilter.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/GlobalRequestSecurityFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/GlobalRequestSecurityFilter.java new file mode 100644 index 00000000000..53d6f706533 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/GlobalRequestSecurityFilter.java @@ -0,0 +1,134 @@ +/** + * 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.app.rest.security; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.net.URI; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.Locale; + +/** + * Global filter acting on all requests (not just /api/) to provide some additional hardening + * against common attacks or RCE, if a malicious payload was somehow written to a directory + * executable by the servlet container. + * The decoding and normalisation is designed to be tolerant of malformed URLs or broken clients, etc. + * so that this additional security filter does not introduce false positives or unintended side effects. + * + * @author Kim Shepherd + */ +@Component +@Order(Ordered.HIGHEST_PRECEDENCE) +public class GlobalRequestSecurityFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal( + HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain + ) throws ServletException, IOException { + String normalizedPath = normaliseUrl(request.getRequestURI()); + // Return 403 forbidden if JSP execution or URL traversal is attempted + if (isTraversalAttempt(normalizedPath) || isJspExecutionAttempt(normalizedPath)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN); + return; + } + filterChain.doFilter(request, response); + } + + /** + * Normalise the URI similarly to Tomcat, for testing how it will be interpreted + * @param rawUrl the unvalidated URL string + * @return a decoded, normalise URL + */ + private String normaliseUrl(String rawUrl) throws IOException { + if (rawUrl == null || rawUrl.isBlank()) { + throw new IOException("Empty URL"); + } + String url = rawUrl.split("\\?")[0]; + // Strip ;jspsession=... and so on + int semicolon = url.indexOf(';'); + if (semicolon >= 0) { + url = url.substring(0, semicolon); + } + url = decodeUrl(url); + if (url == null || url.isBlank()) { + throw new IOException("Decoded URL path is empty"); + } + url = normaliseUrlPath(url); + if (url == null || url.isBlank()) { + throw new IOException("Normalised URL path is empty"); + } + return url.toLowerCase(Locale.ROOT); + } + + /** + * Decode URL, falling back to original URL if it's malformed or undecodable + * @param url the encoded / unvalidated URL + * @return decoded URL or the original URL on error + */ + private String decodeUrl(String url) { + try { + return URLDecoder.decode(url, StandardCharsets.UTF_8); + } catch (IllegalArgumentException ex) { + // if we can't decode it, just return raw string + return url; + } + } + + /** + * Normalise the URL path and ensure it ends in a / + * @param url the URL path to normalise + * @return normalised path or the original parameter on error + */ + private String normaliseUrlPath(String url) { + try { + if (!url.startsWith("/")) { + url = "/" + url; + } + return new URI(url).normalize().getPath(); + } catch (Exception e) { + // if we can't use or normalise the path, just return the raw string + return url; + } + } + + /** + * Detect traversal after normalisation + * @param url the URL path to validate + * @return true if this looks like a traversal attempt + */ + private boolean isTraversalAttempt(String url) { + return url.contains("../") + || url.contains("/..") + || url.contains("%2e%2e") + || url.contains(".."); + } + + /** + * Block JSP execution attempts + * @param url the URL path to validate + */ + private boolean isJspExecutionAttempt(String url) { + return url.endsWith(".jsp") + || url.endsWith(".jspx") + || url.contains(".jsp/") + || url.contains(".jspx/") + || url.contains(".jsp\0") + || url.contains(".jspx\0"); + } +} \ No newline at end of file From 0b1deae3fe94f55fb3dcda8dd6d6ba436b417f38 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Sat, 16 May 2026 22:18:27 +0200 Subject: [PATCH 914/939] Add additional logging to GlobalRequestSecurityFilter (cherry picked from commit 295a046fba14502619b3e3d96a7f6abdc9a4a5fc) --- .../app/rest/security/GlobalRequestSecurityFilter.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/GlobalRequestSecurityFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/GlobalRequestSecurityFilter.java index 53d6f706533..13740a53c7a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/GlobalRequestSecurityFilter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/GlobalRequestSecurityFilter.java @@ -43,7 +43,13 @@ protected void doFilterInternal( ) throws ServletException, IOException { String normalizedPath = normaliseUrl(request.getRequestURI()); // Return 403 forbidden if JSP execution or URL traversal is attempted - if (isTraversalAttempt(normalizedPath) || isJspExecutionAttempt(normalizedPath)) { + if (isTraversalAttempt(normalizedPath)) { + logger.warn("Path traversal attempt detected. Skipping request: " + request.getRequestURI()); + response.sendError(HttpServletResponse.SC_FORBIDDEN); + return; + } + if (isJspExecutionAttempt(normalizedPath)) { + logger.warn("JSP execution attempt detected. Skipping request: " + request.getRequestURI()); response.sendError(HttpServletResponse.SC_FORBIDDEN); return; } From 2e400771353f2a6e50bfb0067e2c58ca602e04c9 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 27 May 2026 11:00:17 +0200 Subject: [PATCH 915/939] Fix import order (cherry picked from commit e2e6a796fd8d19de18a80b735e20a62d29c3c5cd) --- .../rest/security/GlobalRequestSecurityFilter.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/GlobalRequestSecurityFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/GlobalRequestSecurityFilter.java index 13740a53c7a..78e433a8fad 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/GlobalRequestSecurityFilter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/GlobalRequestSecurityFilter.java @@ -7,6 +7,12 @@ */ package org.dspace.app.rest.security; +import java.io.IOException; +import java.net.URI; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.Locale; + import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -16,12 +22,6 @@ import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; -import java.io.IOException; -import java.net.URI; -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; -import java.util.Locale; - /** * Global filter acting on all requests (not just /api/) to provide some additional hardening * against common attacks or RCE, if a malicious payload was somehow written to a directory @@ -137,4 +137,4 @@ private boolean isJspExecutionAttempt(String url) { || url.contains(".jsp\0") || url.contains(".jspx\0"); } -} \ No newline at end of file +} From 1a3dfd7c1a341ce9c04ee1aef0bea9f45c24dcbc Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 27 May 2026 13:38:55 +0200 Subject: [PATCH 916/939] Update sitemap traversal test expectations (cherry picked from commit 56ae2871eaba764900e4d9e23685a9472f485069) --- .../org/dspace/app/rest/SitemapRestControllerIT.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SitemapRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SitemapRestControllerIT.java index 04d22718e84..084b8272bd0 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SitemapRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SitemapRestControllerIT.java @@ -14,8 +14,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import javax.servlet.ServletException; - import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.authorize.service.ResourcePolicyService; import org.dspace.builder.CollectionBuilder; @@ -131,18 +129,20 @@ public void testSitemap_notValidSiteMapFile() throws Exception { .andExpect(status().isNotFound()); } - @Test(expected = ServletException.class) + @Test public void testSitemap_fileSystemTraversal_dspaceCfg() throws Exception { //** WHEN ** //We attempt to use endpoint for malicious file system traversal - getClient().perform(get("/" + SITEMAPS_ENDPOINT + "/%2e%2e/config/dspace.cfg")); + getClient().perform(get("/" + SITEMAPS_ENDPOINT + "/%2e%2e/config/dspace.cfg")) + .andExpect(status().isForbidden()); } - @Test(expected = ServletException.class) + @Test public void testSitemap_fileSystemTraversal_dspaceCfg2() throws Exception { //** WHEN ** //We attempt to use endpoint for malicious file system traversal - getClient().perform(get("/" + SITEMAPS_ENDPOINT + "/%2e%2e%2fconfig%2fdspace.cfg")); + getClient().perform(get("/" + SITEMAPS_ENDPOINT + "/%2e%2e%2fconfig%2fdspace.cfg")) + .andExpect(status().isForbidden()); } @Test From 8a2eee9d4adcbb4d858252877df8b020426e0802 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 27 May 2026 16:30:28 +0200 Subject: [PATCH 917/939] Backport GlobalRequestSecurityFilter for javax --- .../app/rest/security/GlobalRequestSecurityFilter.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/GlobalRequestSecurityFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/GlobalRequestSecurityFilter.java index 78e433a8fad..983da703878 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/GlobalRequestSecurityFilter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/GlobalRequestSecurityFilter.java @@ -13,10 +13,10 @@ import java.nio.charset.StandardCharsets; import java.util.Locale; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; From 22bec4459def712f529ff41283a8c7c5bcd1889c Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 27 May 2026 13:45:20 +0200 Subject: [PATCH 918/939] Add secure file access methods --- .../storage/secure/SecureFileAccess.java | 166 ++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 dspace-api/src/main/java/org/dspace/storage/secure/SecureFileAccess.java diff --git a/dspace-api/src/main/java/org/dspace/storage/secure/SecureFileAccess.java b/dspace-api/src/main/java/org/dspace/storage/secure/SecureFileAccess.java new file mode 100644 index 00000000000..45727d9ee5a --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/storage/secure/SecureFileAccess.java @@ -0,0 +1,166 @@ +/** + * 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.storage.secure; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +/** + * Decent I/O path validation - not perfect when symlinks are used and we are writing + * as 'toRealPath' check on the resolved path fails for new files + * + * @author Kim Shepherd + */ +public final class SecureFileAccess { + + private SecureFileAccess() {} + + /** + * Validate a given path against an allowed base path. Does not attempt to calculate "real path" + * before validation, as this breaks for new files which don't yet exist. This can make the resulting + * validation still vulnerable to symlink traversal in some cases + * @param file the unvalidated file, usually derived from user input or configuration + * This MUST be an absolute path, and the caller is expected to calculate it based on best + * context (e.g. configured base path, CWD, dspace.dir, and so on) + * @param allowedBasePaths list of allowed base paths for this use case as per system configuration + * @param purpose the name of the calling component / use case for logging and inspection + * @throws IOException on validation failure + */ + public static Path validatePathForWrite(String file, List allowedBasePaths, String purpose) + throws IOException { + Path filePath = Path.of(file); + if (!filePath.isAbsolute()) { + throw new IOException("Absolute path required for I/O (" + purpose + "): " + file); + } + for (String allowedBasePath : allowedBasePaths) { + Path basePath = Path.of(allowedBasePath) + .toRealPath() + .normalize(); + Path resolvedPath = basePath.resolve(file).normalize(); + if (resolvedPath.startsWith(basePath)) { + return resolvedPath; + } + } + + // If no valid path was resolved and returned by now + // we raise an exception and treat this as illegal access + throw new IOException("Illegal file path attempted for I/O (" + purpose + "): " + file); + } + + /** + * Validate a given path against an allowed base path. + * More secure than the 'write' variant because we can explicitly resolve links as well. + * + * @param file the unvalidated file, usually derived from user input or configuration + * This MUST be an absolute path, and the caller is expected to calculate it based on best + * context (e.g. configured base path, CWD, dspace.dir, and so on) + * @param allowedBasePaths the allowed base paths for this use case as per system configuration + * @param purpose the name of the calling component / use case for logging and inspection + * @throws IOException on validation failure + */ + public static Path validatePathForRead(String file, List allowedBasePaths, String purpose) + throws IOException { + Path filePath = Path.of(file); + if (!filePath.isAbsolute()) { + throw new IOException("Absolute path required for I/O (" + purpose + "): " + file); + } + for (String allowedBasePath : allowedBasePaths) { + Path basePath = Path.of(allowedBasePath) + .toRealPath() + .normalize(); + Path resolvedPath = basePath.resolve(file).toRealPath().normalize(); + if (resolvedPath.startsWith(basePath)) { + return resolvedPath; + } + } + // If no valid path was resolved and returned by now + // we raise an exception and treat this as illegal access + throw new IOException("Illegal file path attempted for I/O (" + purpose + "): " + file); + } + + /** + * Get a buffered reader after validating file path. + * @param unvalidatedFile the unvalidated file, usually derived from user input or configuration + * @param allowedBasePaths the allowed base paths for this use case as per system configuration + * @param purpose the name of the calling component / use case for logging and inspection + * @throws IOException on validation failure + */ + public static BufferedReader getBufferedReader(String unvalidatedFile, List allowedBasePaths, + String purpose, Charset charset) throws IOException { + if (charset == null) { + charset = StandardCharsets.UTF_8; + } + Path validatedFile = validatePathForRead(unvalidatedFile, allowedBasePaths, purpose); + return Files.newBufferedReader(validatedFile, charset); + } + + /** + * Get an input stream after validating file path. + * @param unvalidatedFile the unvalidated file, usually derived from user input or configuration + * @param allowedBasePaths the allowed base paths for this use case as per system configuration + * @param purpose the name of the calling component / use case for logging and inspection + * @throws IOException on validation failure + */ + public static InputStream getInputStream(String unvalidatedFile, List allowedBasePaths, String purpose) + throws IOException { + Path validatedFile = validatePathForRead(unvalidatedFile, allowedBasePaths, purpose); + return Files.newInputStream(validatedFile); + + } + + /** + * Get an output stream after validating file path. New files can't use toRealPath() for link calculation so + * there is a bit of a trade-off in allowing some symlink traversal to occur + * @param unvalidatedFile the unvalidated file, usually derived from user input or configuration + * @param allowedBasePaths the allowed base paths for this use case as per system configuration + * @param purpose the name of the calling component / use case for logging and inspection + * @throws IOException on validation failure + */ + public static OutputStream getOutputStream(String unvalidatedFile, List allowedBasePaths, String purpose) + throws IOException { + Path validatedFile = validatePathForWrite(unvalidatedFile, allowedBasePaths, purpose); + return Files.newOutputStream(validatedFile); + } + + /** + * Calculate an absolute path (if not already absolute) using current working dir as a root + * for relative file paths + * @param file the relative or absolute file given as input + * @return absolute path calculated from file and cwd + */ + public static String calculateAbsolutePathUsingCwd(String file) { + String filePath = file; + Path path = Path.of(filePath); + if (!path.isAbsolute()) { + filePath = Path.of("").toAbsolutePath().resolve(path).normalize().toString(); + } + return filePath; + } + + /** + * Calculate an absolute path (if not already absolute) using a given base dir as a root + * for relative file paths + * @param file the relative or absolute file given as input + * @return absolute path calculated from file and base dir + */ + public static String calculateAbsolutePathUsingBaseDir(String file, String baseDir) { + String filePath = file; + Path path = Path.of(filePath); + if (!path.isAbsolute()) { + filePath = Path.of(baseDir).toAbsolutePath().resolve(path).normalize().toString(); + } + return filePath; + } +} From 55905a2fc46b98194f92ec38e0fb8bafa7fee21a Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 27 May 2026 13:49:50 +0200 Subject: [PATCH 919/939] Backport Curation I/O using secure file access Removes some JDK >= 16 usage --- .../main/java/org/dspace/curate/Curation.java | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/curate/Curation.java b/dspace-api/src/main/java/org/dspace/curate/Curation.java index b894dcd85f0..43decd4add7 100644 --- a/dspace-api/src/main/java/org/dspace/curate/Curation.java +++ b/dspace-api/src/main/java/org/dspace/curate/Curation.java @@ -10,15 +10,18 @@ import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; -import java.io.FileReader; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintStream; import java.io.Writer; +import java.nio.charset.StandardCharsets; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.UUID; @@ -38,6 +41,7 @@ import org.dspace.handle.service.HandleService; import org.dspace.scripts.DSpaceRunnable; import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.storage.secure.SecureFileAccess; import org.dspace.utils.DSpace; /** @@ -112,8 +116,16 @@ private void handleCurationTask(Curator curator) throws IOException, SQLExceptio } else if (commandLine.hasOption('T')) { // load taskFile BufferedReader reader = null; + // in this case, Curation CLI expects to calculate the -T parameter from the user's current working dir + String taskFilePath = SecureFileAccess.calculateAbsolutePathUsingCwd(this.taskFile); try { - reader = new BufferedReader(new FileReader(this.taskFile)); + String dspaceDir = DSpaceServicesFactory.getInstance() + .getConfigurationService().getProperty("dspace.dir"); + List allowedTaskFileBasePath = new ArrayList<>( + Arrays.asList(DSpaceServicesFactory.getInstance().getConfigurationService() + .getArrayProperty("curate.taskfile.base", new String[]{dspaceDir}))); + reader = SecureFileAccess.getBufferedReader(taskFilePath, allowedTaskFileBasePath, + "curation-taskfile", StandardCharsets.UTF_8); while ((taskName = reader.readLine()) != null) { if (verbose) { super.handler.logInfo("Adding task: " + taskName); @@ -189,12 +201,25 @@ private void endScript(long timeRun) throws SQLException { private Curator initCurator() throws FileNotFoundException { Curator curator = new Curator(handler); OutputStream reporterStream; + String dspaceDir = DSpaceServicesFactory.getInstance() + .getConfigurationService().getProperty("dspace.dir"); + List allowedReporterBasePaths = new ArrayList<>(Arrays.asList(DSpaceServicesFactory.getInstance() + .getConfigurationService().getArrayProperty("curate.reporter.base", + new String[]{dspaceDir + File.separatorChar + "log"}))); if (null == this.reporter) { reporterStream = NullOutputStream.NULL_OUTPUT_STREAM; } else if ("-".equals(this.reporter)) { reporterStream = System.out; } else { - reporterStream = new PrintStream(this.reporter); + // Reporter param comes from CLI execution. Calculate abs path from user's current working dir + String reporterFilePath = SecureFileAccess.calculateAbsolutePathUsingCwd(this.reporter); + try { + reporterStream = new PrintStream( + SecureFileAccess.getOutputStream( + reporterFilePath, allowedReporterBasePaths, "curation-reporter")); + } catch (IOException e) { + throw new FileNotFoundException(e.getLocalizedMessage()); + } } Writer reportWriter = new OutputStreamWriter(reporterStream); curator.setReporter(reportWriter); From 45022245be2fabb5ba26d50b335f1aa1f905a660 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 27 May 2026 13:50:59 +0200 Subject: [PATCH 920/939] Curation config support for allowed base paths --- dspace/config/modules/curate.cfg | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/dspace/config/modules/curate.cfg b/dspace/config/modules/curate.cfg index 6e75738de54..dad40d61545 100644 --- a/dspace/config/modules/curate.cfg +++ b/dspace/config/modules/curate.cfg @@ -29,3 +29,15 @@ curate.taskqueue.dir = ${dspace.dir}/ctqueues # Maximum amount of redirects set to 0 for none and -1 for unlimited curate.checklinks.max-redirect = 0 + +# allowed base path(s) of curation task files +# it is recommended to restrict this path as much as possible +# so that the DSpace Processes framework may only load files as "tasks" +# from a trusted location. For multiple paths, repeat this configuration +# property for each trusted path +# Default: ${dspace.dir} +#curate.taskfile.base = ${dspace.dir} + +# allowed base path of reporter output. +# Default: ${dspace.dir}/log +#curate.reporter.base = ${dspace.dir}/log From 277af8233261ec0f61d71a4ce0908341c30e5e89 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 26 May 2026 14:06:14 +0200 Subject: [PATCH 921/939] Move curation -r reporter param to CLI only --- .../java/org/dspace/curate/CurationCliScriptConfiguration.java | 3 +++ .../src/main/java/org/dspace/curate/CurationClientOptions.java | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/curate/CurationCliScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/curate/CurationCliScriptConfiguration.java index eaa04f47782..07684f3643d 100644 --- a/dspace-api/src/main/java/org/dspace/curate/CurationCliScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/curate/CurationCliScriptConfiguration.java @@ -20,6 +20,9 @@ public Options getOptions() { options = super.getOptions(); options.addOption("e", "eperson", true, "email address of curating eperson"); options.getOption("e").setRequired(true); + options.addOption("r", "reporter", true, + "relative or absolute path to the desired report file. Use '-' to report to console. If absent, no " + + "reporting"); return options; } } diff --git a/dspace-api/src/main/java/org/dspace/curate/CurationClientOptions.java b/dspace-api/src/main/java/org/dspace/curate/CurationClientOptions.java index 8ec0f14697c..a8a3d358ce6 100644 --- a/dspace-api/src/main/java/org/dspace/curate/CurationClientOptions.java +++ b/dspace-api/src/main/java/org/dspace/curate/CurationClientOptions.java @@ -59,9 +59,6 @@ protected static Options constructOptions() { "Id (handle) of object to perform task on, or 'all' to perform on whole repository"); options.addOption("p", "parameter", true, "a task parameter 'NAME=VALUE'"); options.addOption("q", "queue", true, "name of task queue to process"); - options.addOption("r", "reporter", true, - "relative or absolute path to the desired report file. Use '-' to report to console. If absent, no " + - "reporting"); options.addOption("s", "scope", true, "transaction scope to impose: use 'object', 'curation', or 'open'. If absent, 'open' applies"); options.addOption("v", "verbose", false, "report activity to stdout"); From a7572212c135155fb8420a2bfc95869f1ba6959d Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 27 May 2026 16:57:21 +0200 Subject: [PATCH 922/939] Fix import order --- .../dspace/app/rest/security/GlobalRequestSecurityFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/GlobalRequestSecurityFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/GlobalRequestSecurityFilter.java index 983da703878..a9f0eea8d72 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/GlobalRequestSecurityFilter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/GlobalRequestSecurityFilter.java @@ -12,11 +12,11 @@ import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.util.Locale; - import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; + import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; From 37cd6eb791d4f61bb54fedf899aa96f99504e38d Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 27 May 2026 14:38:00 +0200 Subject: [PATCH 923/939] Ignore CurationScriptIT -T taskFile tests, to rewrite w/ CLI (cherry picked from commit 6437472b8277b9aa815dd71e14b499ba7515f87d) --- .../src/test/java/org/dspace/curate/CurationScriptIT.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java b/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java index 8745613d7af..347ed0935f0 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java @@ -49,6 +49,7 @@ import org.dspace.scripts.configuration.ScriptConfiguration; import org.dspace.scripts.factory.ScriptServiceFactory; import org.dspace.scripts.service.ScriptService; +import org.junit.Ignore; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -214,6 +215,7 @@ public void curateScript_InvalidScope() throws Exception { .andExpect(status().isBadRequest()); } + @Ignore @Test public void curateScript_InvalidTaskFile() throws Exception { String token = getAuthToken(admin.getEmail(), password); @@ -286,6 +288,7 @@ public void curateScript_validRequest_Task() throws Exception { } } + @Ignore @Test public void curateScript_validRequest_TaskFile() throws Exception { context.turnOffAuthorisationSystem(); From 27708ea6d70abe433f131cf3b875dfdc067d3c12 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 27 May 2026 06:43:22 +0200 Subject: [PATCH 924/939] Move taskfile -T option to CLI script config only (cherry picked from commit 00e4979a60fd69adbf4a7476926701ef59207ce7) --- .../org/dspace/curate/CurationCliScriptConfiguration.java | 1 + .../main/java/org/dspace/curate/CurationClientOptions.java | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/curate/CurationCliScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/curate/CurationCliScriptConfiguration.java index 07684f3643d..925bd4f2d23 100644 --- a/dspace-api/src/main/java/org/dspace/curate/CurationCliScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/curate/CurationCliScriptConfiguration.java @@ -23,6 +23,7 @@ public Options getOptions() { options.addOption("r", "reporter", true, "relative or absolute path to the desired report file. Use '-' to report to console. If absent, no " + "reporting"); + options.addOption("T", "taskfile", true, "file containing curation task names"); return options; } } diff --git a/dspace-api/src/main/java/org/dspace/curate/CurationClientOptions.java b/dspace-api/src/main/java/org/dspace/curate/CurationClientOptions.java index a8a3d358ce6..03ad2f34b23 100644 --- a/dspace-api/src/main/java/org/dspace/curate/CurationClientOptions.java +++ b/dspace-api/src/main/java/org/dspace/curate/CurationClientOptions.java @@ -31,7 +31,8 @@ public enum CurationClientOptions { /** * This method resolves the CommandLine parameters to figure out which action the curation script should perform * - * @param commandLine The relevant CommandLine for the curation script + * @param commandLine The relevant CommandLine for the curation script. Note that -T is passed only + * from CurationCliScriptConfig and is not accessible from UI processes * @return The curation option to be ran, parsed from the CommandLine */ protected static CurationClientOptions getClientOption(CommandLine commandLine) { @@ -54,7 +55,6 @@ protected static Options constructOptions() { Options options = new Options(); options.addOption("t", "task", true, "curation task name; options: " + getTaskOptions()); - options.addOption("T", "taskfile", true, "file containing curation task names"); options.addOption("i", "id", true, "Id (handle) of object to perform task on, or 'all' to perform on whole repository"); options.addOption("p", "parameter", true, "a task parameter 'NAME=VALUE'"); From f228458d1d2f3814171f64a3d00741b25b147110 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 27 May 2026 13:39:35 -0500 Subject: [PATCH 925/939] Update LICENSES_THIRD_PARTY for next release --- LICENSES_THIRD_PARTY | 289 ++++++++++++++++++++++--------------------- 1 file changed, 147 insertions(+), 142 deletions(-) diff --git a/LICENSES_THIRD_PARTY b/LICENSES_THIRD_PARTY index 174c8760cab..fdb56efc360 100644 --- a/LICENSES_THIRD_PARTY +++ b/LICENSES_THIRD_PARTY @@ -25,10 +25,10 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * com.drewnoakes:metadata-extractor (com.drewnoakes:metadata-extractor:2.19.0 - https://drewnoakes.com/code/exif/) * parso (com.epam:parso:2.0.14 - https://github.com/epam/parso) * Internet Time Utility (com.ethlo.time:itu:1.7.0 - https://github.com/ethlo/itu) - * ClassMate (com.fasterxml:classmate:1.7.1 - https://github.com/FasterXML/java-classmate) - * Jackson-annotations (com.fasterxml.jackson.core:jackson-annotations:2.20 - https://github.com/FasterXML/jackson) - * Jackson-core (com.fasterxml.jackson.core:jackson-core:2.20.1 - https://github.com/FasterXML/jackson-core) - * jackson-databind (com.fasterxml.jackson.core:jackson-databind:2.20.1 - https://github.com/FasterXML/jackson) + * ClassMate (com.fasterxml:classmate:1.7.3 - https://github.com/FasterXML/java-classmate) + * Jackson-annotations (com.fasterxml.jackson.core:jackson-annotations:2.21 - https://github.com/FasterXML/jackson) + * Jackson-core (com.fasterxml.jackson.core:jackson-core:2.21.2 - https://github.com/FasterXML/jackson-core) + * jackson-databind (com.fasterxml.jackson.core:jackson-databind:2.21.2 - https://github.com/FasterXML/jackson) * Jackson dataformat: Smile (com.fasterxml.jackson.dataformat:jackson-dataformat-smile:2.15.2 - https://github.com/FasterXML/jackson-dataformats-binary) * Jackson-dataformat-YAML (com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.14.0 - https://github.com/FasterXML/jackson-dataformats-text) * Jackson datatype: jdk8 (com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.13.5 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8) @@ -40,9 +40,9 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * zjsonpatch (com.flipkart.zjsonpatch:zjsonpatch:0.4.16 - https://github.com/flipkart-incubator/zjsonpatch/) * Caffeine cache (com.github.ben-manes.caffeine:caffeine:2.9.3 - https://github.com/ben-manes/caffeine) * JSON.simple (com.github.cliftonlabs:json-simple:3.0.2 - https://cliftonlabs.github.io/json-simple/) - * docker-java-api (com.github.docker-java:docker-java-api:3.7.0 - https://github.com/docker-java/docker-java) - * docker-java-transport (com.github.docker-java:docker-java-transport:3.7.0 - https://github.com/docker-java/docker-java) - * docker-java-transport-zerodep (com.github.docker-java:docker-java-transport-zerodep:3.7.0 - https://github.com/docker-java/docker-java) + * docker-java-api (com.github.docker-java:docker-java-api:3.7.1 - https://github.com/docker-java/docker-java) + * docker-java-transport (com.github.docker-java:docker-java-transport:3.7.1 - https://github.com/docker-java/docker-java) + * docker-java-transport-zerodep (com.github.docker-java:docker-java-transport-zerodep:3.7.1 - https://github.com/docker-java/docker-java) * btf (com.github.java-json-tools:btf:1.3 - https://github.com/java-json-tools/btf) * jackson-coreutils (com.github.java-json-tools:jackson-coreutils:2.0 - https://github.com/java-json-tools/jackson-coreutils) * jackson-coreutils-equivalence (com.github.java-json-tools:jackson-coreutils-equivalence:1.0 - https://github.com/java-json-tools/jackson-coreutils) @@ -56,16 +56,17 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Google Analytics API v3-rev145-1.23.0 (com.google.apis:google-api-services-analytics:v3-rev145-1.23.0 - http://nexus.sonatype.org/oss-repository-hosting.html/google-api-services-analytics) * FindBugs-jsr305 (com.google.code.findbugs:jsr305:3.0.2 - http://findbugs.sourceforge.net/) * Gson (com.google.code.gson:gson:2.11.0 - https://github.com/google/gson) - * error-prone annotations (com.google.errorprone:error_prone_annotations:2.21.1 - https://errorprone.info/error_prone_annotations) + * error-prone annotations (com.google.errorprone:error_prone_annotations:2.47.0 - https://errorprone.info/error_prone_annotations) * Guava InternalFutureFailureAccess and InternalFutures (com.google.guava:failureaccess:1.0.1 - https://github.com/google/guava/failureaccess) - * Guava: Google Core Libraries for Java (com.google.guava:guava:32.1.3-jre - https://github.com/google/guava) + * Guava InternalFutureFailureAccess and InternalFutures (com.google.guava:failureaccess:1.0.3 - https://github.com/google/guava/failureaccess) + * Guava: Google Core Libraries for Java (com.google.guava:guava:33.6.0-jre - https://github.com/google/guava) * Guava ListenableFuture only (com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava - https://github.com/google/guava/listenablefuture) * Google HTTP Client Library for Java (com.google.http-client:google-http-client:1.47.1 - https://github.com/googleapis/google-http-java-client/google-http-client) * Apache HTTP transport v2 for the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-apache-v2:1.42.0 - https://github.com/googleapis/google-http-java-client/google-http-client-apache-v2) * GSON extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-gson:1.47.1 - https://github.com/googleapis/google-http-java-client/google-http-client-gson) * Jackson 2 extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-jackson2:1.47.1 - https://github.com/googleapis/google-http-java-client/google-http-client-jackson2) * J2ObjC Annotations (com.google.j2objc:j2objc-annotations:1.3 - https://github.com/google/j2objc/) - * J2ObjC Annotations (com.google.j2objc:j2objc-annotations:2.8 - https://github.com/google/j2objc/) + * J2ObjC Annotations (com.google.j2objc:j2objc-annotations:3.1 - https://github.com/google/j2objc/) * Google OAuth Client Library for Java (com.google.oauth-client:google-oauth-client:1.39.0 - https://github.com/googleapis/google-oauth-java-client/google-oauth-client) * ConcurrentLinkedHashMap (com.googlecode.concurrentlinkedhashmap:concurrentlinkedhashmap-lru:1.4.2 - http://code.google.com/p/concurrentlinkedhashmap) * libphonenumber (com.googlecode.libphonenumber:libphonenumber:8.11.1 - https://github.com/google/libphonenumber/) @@ -93,13 +94,13 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * SparseBitSet (com.zaxxer:SparseBitSet:1.3 - https://github.com/brettwooldridge/SparseBitSet) * Apache Commons BeanUtils (commons-beanutils:commons-beanutils:1.11.0 - https://commons.apache.org/proper/commons-beanutils) * Apache Commons CLI (commons-cli:commons-cli:1.11.0 - https://commons.apache.org/proper/commons-cli/) - * Apache Commons Codec (commons-codec:commons-codec:1.20.0 - https://commons.apache.org/proper/commons-codec/) + * Apache Commons Codec (commons-codec:commons-codec:1.22.0 - https://commons.apache.org/proper/commons-codec/) * Apache Commons Collections (commons-collections:commons-collections:3.2.2 - http://commons.apache.org/collections/) * Commons Digester (commons-digester:commons-digester:2.1 - http://commons.apache.org/digester/) * Commons FileUpload (commons-fileupload:commons-fileupload:1.2.1 - http://commons.apache.org/fileupload/) - * Apache Commons IO (commons-io:commons-io:2.21.0 - https://commons.apache.org/proper/commons-io/) + * Apache Commons IO (commons-io:commons-io:2.22.0 - https://commons.apache.org/proper/commons-io/) * Commons Lang (commons-lang:commons-lang:2.6 - http://commons.apache.org/lang/) - * Apache Commons Logging (commons-logging:commons-logging:1.3.5 - https://commons.apache.org/proper/commons-logging/) + * Apache Commons Logging (commons-logging:commons-logging:1.3.6 - https://commons.apache.org/proper/commons-logging/) * Apache Commons Validator (commons-validator:commons-validator:1.10.1 - https://commons.apache.org/proper/commons-validator/) * GeoJson POJOs for Jackson (de.grundid.opendatalab:geojson-jackson:1.14 - https://github.com/opendatalab-de/geojson-jackson) * OpenAIRE Funders Model (eu.openaire:funders-model:2.0.0 - https://api.openaire.eu) @@ -108,34 +109,34 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Metrics Integration for Jetty 9.3 and higher (io.dropwizard.metrics:metrics-jetty9:4.1.5 - https://metrics.dropwizard.io/metrics-jetty9) * Metrics Integration with JMX (io.dropwizard.metrics:metrics-jmx:4.1.5 - https://metrics.dropwizard.io/metrics-jmx) * JVM Integration for Metrics (io.dropwizard.metrics:metrics-jvm:4.1.5 - https://metrics.dropwizard.io/metrics-jvm) - * io.grpc:grpc-api (io.grpc:grpc-api:1.77.0 - https://github.com/grpc/grpc-java) - * io.grpc:grpc-context (io.grpc:grpc-context:1.77.0 - https://github.com/grpc/grpc-java) + * io.grpc:grpc-api (io.grpc:grpc-api:1.80.0 - https://github.com/grpc/grpc-java) + * io.grpc:grpc-context (io.grpc:grpc-context:1.80.0 - https://github.com/grpc/grpc-java) * micrometer-core (io.micrometer:micrometer-core:1.9.17 - https://github.com/micrometer-metrics/micrometer) * Netty/Buffer (io.netty:netty-buffer:4.1.99.Final - https://netty.io/netty-buffer/) - * Netty/Buffer (io.netty:netty-buffer:4.2.7.Final - https://netty.io/netty-buffer/) + * Netty/Buffer (io.netty:netty-buffer:4.2.12.Final - https://netty.io/netty-buffer/) * Netty/Codec (io.netty:netty-codec:4.1.99.Final - https://netty.io/netty-codec/) - * Netty/Codec (io.netty:netty-codec:4.2.7.Final - https://netty.io/netty-codec/) - * Netty/Codec/Base (io.netty:netty-codec-base:4.2.7.Final - https://netty.io/netty-codec-base/) - * Netty/Codec/Compression (io.netty:netty-codec-compression:4.2.7.Final - https://netty.io/netty-codec-compression/) + * Netty/Codec (io.netty:netty-codec:4.2.12.Final - https://netty.io/netty-codec/) + * Netty/Codec/Base (io.netty:netty-codec-base:4.2.12.Final - https://netty.io/netty-codec-base/) + * Netty/Codec/Compression (io.netty:netty-codec-compression:4.2.12.Final - https://netty.io/netty-codec-compression/) * Netty/Codec/HTTP (io.netty:netty-codec-http:4.1.86.Final - https://netty.io/netty-codec-http/) * Netty/Codec/HTTP2 (io.netty:netty-codec-http2:4.1.86.Final - https://netty.io/netty-codec-http2/) - * Netty/Codec/Marshalling (io.netty:netty-codec-marshalling:4.2.7.Final - https://netty.io/netty-codec-marshalling/) - * Netty/Codec/Protobuf (io.netty:netty-codec-protobuf:4.2.7.Final - https://netty.io/netty-codec-protobuf/) + * Netty/Codec/Marshalling (io.netty:netty-codec-marshalling:4.2.12.Final - https://netty.io/netty-codec-marshalling/) + * Netty/Codec/Protobuf (io.netty:netty-codec-protobuf:4.2.12.Final - https://netty.io/netty-codec-protobuf/) * Netty/Codec/Socks (io.netty:netty-codec-socks:4.1.86.Final - https://netty.io/netty-codec-socks/) * Netty/Common (io.netty:netty-common:4.1.99.Final - https://netty.io/netty-common/) - * Netty/Common (io.netty:netty-common:4.2.7.Final - https://netty.io/netty-common/) + * Netty/Common (io.netty:netty-common:4.2.12.Final - https://netty.io/netty-common/) * Netty/Handler (io.netty:netty-handler:4.1.99.Final - https://netty.io/netty-handler/) - * Netty/Handler (io.netty:netty-handler:4.2.7.Final - https://netty.io/netty-handler/) + * Netty/Handler (io.netty:netty-handler:4.2.12.Final - https://netty.io/netty-handler/) * Netty/Handler/Proxy (io.netty:netty-handler-proxy:4.1.86.Final - https://netty.io/netty-handler-proxy/) * Netty/Resolver (io.netty:netty-resolver:4.1.99.Final - https://netty.io/netty-resolver/) * Netty/TomcatNative [BoringSSL - Static] (io.netty:netty-tcnative-boringssl-static:2.0.56.Final - https://github.com/netty/netty-tcnative/netty-tcnative-boringssl-static/) * Netty/TomcatNative [OpenSSL - Classes] (io.netty:netty-tcnative-classes:2.0.56.Final - https://github.com/netty/netty-tcnative/netty-tcnative-classes/) * Netty/Transport (io.netty:netty-transport:4.1.99.Final - https://netty.io/netty-transport/) - * Netty/Transport (io.netty:netty-transport:4.2.7.Final - https://netty.io/netty-transport/) + * Netty/Transport (io.netty:netty-transport:4.2.12.Final - https://netty.io/netty-transport/) * Netty/Transport/Classes/Epoll (io.netty:netty-transport-classes-epoll:4.1.99.Final - https://netty.io/netty-transport-classes-epoll/) * Netty/Transport/Native/Epoll (io.netty:netty-transport-native-epoll:4.1.99.Final - https://netty.io/netty-transport-native-epoll/) * Netty/Transport/Native/Unix/Common (io.netty:netty-transport-native-unix-common:4.1.99.Final - https://netty.io/netty-transport-native-unix-common/) - * Netty/Transport/Native/Unix/Common (io.netty:netty-transport-native-unix-common:4.2.7.Final - https://netty.io/netty-transport-native-unix-common/) + * Netty/Transport/Native/Unix/Common (io.netty:netty-transport-native-unix-common:4.2.12.Final - https://netty.io/netty-transport-native-unix-common/) * OpenCensus (io.opencensus:opencensus-api:0.31.1 - https://github.com/census-instrumentation/opencensus-java) * OpenCensus (io.opencensus:opencensus-contrib-http-util:0.31.1 - https://github.com/census-instrumentation/opencensus-java) * OpenTracing API (io.opentracing:opentracing-api:0.33.0 - https://github.com/opentracing/opentracing-java/opentracing-api) @@ -164,7 +165,7 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * JSR107 API and SPI (javax.cache:cache-api:1.1.1 - https://github.com/jsr107/jsr107spec) * javax.inject (javax.inject:javax.inject:1 - http://code.google.com/p/atinject/) * jdbm (jdbm:jdbm:1.0 - no url defined) - * Joda-Time (joda-time:joda-time:2.14.0 - https://www.joda.org/joda-time/) + * Joda-Time (joda-time:joda-time:2.14.1 - https://www.joda.org/joda-time/) * Byte Buddy (without dependencies) (net.bytebuddy:byte-buddy:1.11.13 - https://bytebuddy.net/byte-buddy) * Byte Buddy (without dependencies) (net.bytebuddy:byte-buddy:1.12.18 - https://bytebuddy.net/byte-buddy) * Byte Buddy agent (net.bytebuddy:byte-buddy-agent:1.11.13 - https://bytebuddy.net/byte-buddy-agent) @@ -176,25 +177,25 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * JSON Small and Fast Parser (net.minidev:json-smart:2.6.0 - https://urielch.github.io/) * Abdera Core (org.apache.abdera:abdera-core:1.1.3 - http://abdera.apache.org/abdera-core) * I18N Libraries (org.apache.abdera:abdera-i18n:1.1.3 - http://abdera.apache.org) - * Apache Ant Core (org.apache.ant:ant:1.10.15 - https://ant.apache.org/) - * Apache Ant Launcher (org.apache.ant:ant-launcher:1.10.15 - https://ant.apache.org/) - * Apache Commons BCEL (org.apache.bcel:bcel:6.11.0 - https://commons.apache.org/proper/commons-bcel) + * Apache Ant Core (org.apache.ant:ant:1.10.17 - https://ant.apache.org/) + * Apache Ant Launcher (org.apache.ant:ant-launcher:1.10.17 - https://ant.apache.org/) + * Apache Commons BCEL (org.apache.bcel:bcel:6.12.0 - https://commons.apache.org/proper/commons-bcel) * Calcite Core (org.apache.calcite:calcite-core:1.35.0 - https://calcite.apache.org) * Calcite Linq4j (org.apache.calcite:calcite-linq4j:1.35.0 - https://calcite.apache.org) * Apache Calcite Avatica (org.apache.calcite.avatica:avatica-core:1.23.0 - https://calcite.apache.org/avatica) * Apache Calcite Avatica Metrics (org.apache.calcite.avatica:avatica-metrics:1.23.0 - https://calcite.apache.org/avatica) * Apache Commons Collections (org.apache.commons:commons-collections4:4.5.0 - https://commons.apache.org/proper/commons-collections/) * Apache Commons Compress (org.apache.commons:commons-compress:1.28.0 - https://commons.apache.org/proper/commons-compress/) - * Apache Commons Configuration (org.apache.commons:commons-configuration2:2.13.0 - https://commons.apache.org/proper/commons-configuration/) + * Apache Commons Configuration (org.apache.commons:commons-configuration2:2.15.0 - https://commons.apache.org/proper/commons-configuration/) * Apache Commons CSV (org.apache.commons:commons-csv:1.14.1 - https://commons.apache.org/proper/commons-csv/) - * Apache Commons DBCP (org.apache.commons:commons-dbcp2:2.13.0 - https://commons.apache.org/proper/commons-dbcp/) + * Apache Commons DBCP (org.apache.commons:commons-dbcp2:2.14.0 - https://commons.apache.org/proper/commons-dbcp/) * Apache Commons Digester (org.apache.commons:commons-digester3:3.2 - http://commons.apache.org/digester/) * Apache Commons Exec (org.apache.commons:commons-exec:1.3 - http://commons.apache.org/proper/commons-exec/) - * Apache Commons Exec (org.apache.commons:commons-exec:1.5.0 - https://commons.apache.org/proper/commons-exec/) + * Apache Commons Exec (org.apache.commons:commons-exec:1.6.0 - https://commons.apache.org/proper/commons-exec/) * Apache Commons Lang (org.apache.commons:commons-lang3:3.20.0 - https://commons.apache.org/proper/commons-lang/) * Apache Commons Math (org.apache.commons:commons-math3:3.6.1 - http://commons.apache.org/proper/commons-math/) - * Apache Commons Pool (org.apache.commons:commons-pool2:2.12.1 - https://commons.apache.org/proper/commons-pool/) - * Apache Commons Text (org.apache.commons:commons-text:1.14.0 - https://commons.apache.org/proper/commons-text) + * Apache Commons Pool (org.apache.commons:commons-pool2:2.13.1 - https://commons.apache.org/proper/commons-pool/) + * Apache Commons Text (org.apache.commons:commons-text:1.15.0 - https://commons.apache.org/proper/commons-text) * Curator Client (org.apache.curator:curator-client:2.13.0 - http://curator.apache.org/curator-client) * Curator Framework (org.apache.curator:curator-framework:2.13.0 - http://curator.apache.org/curator-framework) * Curator Recipes (org.apache.curator:curator-recipes:2.13.0 - http://curator.apache.org/curator-recipes) @@ -210,7 +211,7 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Apache HttpClient (org.apache.httpcomponents.client5:httpclient5:5.1.3 - https://hc.apache.org/httpcomponents-client-5.0.x/5.1.3/httpclient5/) * Apache HttpComponents Core HTTP/1.1 (org.apache.httpcomponents.core5:httpcore5:5.1.3 - https://hc.apache.org/httpcomponents-core-5.1.x/5.1.3/httpcore5/) * Apache HttpComponents Core HTTP/2 (org.apache.httpcomponents.core5:httpcore5-h2:5.1.3 - https://hc.apache.org/httpcomponents-core-5.1.x/5.1.3/httpcore5-h2/) - * Apache James :: Mime4j :: Core (org.apache.james:apache-mime4j-core:0.8.13 - http://james.apache.org/mime4j/apache-mime4j-core) + * Apache James :: Mime4j :: Core (org.apache.james:apache-mime4j-core:0.8.14 - http://james.apache.org/mime4j/apache-mime4j-core) * Apache James :: Mime4j :: DOM (org.apache.james:apache-mime4j-dom:0.8.13 - http://james.apache.org/mime4j/apache-mime4j-dom) * Apache Jena - Libraries POM (org.apache.jena:apache-jena-libs:2.13.0 - http://jena.apache.org/apache-jena-libs/) * Apache Jena - ARQ (SPARQL 1.1 Query Engine) (org.apache.jena:jena-arq:2.13.0 - http://jena.apache.org/jena-arq/) @@ -221,9 +222,9 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Kerby-kerb Util (org.apache.kerby:kerb-util:1.0.1 - http://directory.apache.org/kerby/kerby-kerb/kerb-util) * Kerby ASN1 Project (org.apache.kerby:kerby-asn1:1.0.1 - http://directory.apache.org/kerby/kerby-common/kerby-asn1) * Kerby PKIX Project (org.apache.kerby:kerby-pkix:1.0.1 - http://directory.apache.org/kerby/kerby-pkix) - * Apache Log4j 1.x Compatibility API (org.apache.logging.log4j:log4j-1.2-api:2.25.2 - https://logging.apache.org/log4j/2.x/) - * Apache Log4j API (org.apache.logging.log4j:log4j-api:2.25.2 - https://logging.apache.org/log4j/2.x/) - * Apache Log4j Core (org.apache.logging.log4j:log4j-core:2.25.2 - https://logging.apache.org/log4j/2.x/) + * Apache Log4j 1.x Compatibility API (org.apache.logging.log4j:log4j-1.2-api:2.25.4 - https://logging.apache.org/log4j/2.x/) + * Apache Log4j API (org.apache.logging.log4j:log4j-api:2.25.4 - https://logging.apache.org/log4j/2.x/) + * Apache Log4j Core (org.apache.logging.log4j:log4j-core:2.25.4 - https://logging.apache.org/log4j/2.x/) * Apache Log4j JUL Adapter (org.apache.logging.log4j:log4j-jul:2.17.2 - https://logging.apache.org/log4j/2.x/log4j-jul/) * Apache Log4j Layout for JSON template (org.apache.logging.log4j:log4j-layout-template-json:2.17.2 - https://logging.apache.org/log4j/2.x/log4j-layout-template-json/) * Apache Log4j SLF4J Binding (org.apache.logging.log4j:log4j-slf4j-impl:2.17.2 - https://logging.apache.org/log4j/2.x/log4j-slf4j-impl/) @@ -251,46 +252,46 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Lucene Spatial Extras (org.apache.lucene:lucene-spatial-extras:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-spatial-extras) * Lucene Spatial 3D (org.apache.lucene:lucene-spatial3d:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-spatial3d) * Lucene Suggest (org.apache.lucene:lucene-suggest:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-suggest) - * Apache FontBox (org.apache.pdfbox:fontbox:3.0.5 - http://pdfbox.apache.org/) + * Apache FontBox (org.apache.pdfbox:fontbox:3.0.7 - http://pdfbox.apache.org/) * PDFBox JBIG2 ImageIO plugin (org.apache.pdfbox:jbig2-imageio:3.0.4 - https://www.apache.org/jbig2-imageio/) * Apache JempBox (org.apache.pdfbox:jempbox:1.8.17 - http://www.apache.org/pdfbox-parent/jempbox/) - * Apache PDFBox (org.apache.pdfbox:pdfbox:3.0.5 - https://www.apache.org/pdfbox-parent/pdfbox/) - * Apache PDFBox io (org.apache.pdfbox:pdfbox-io:3.0.5 - https://www.apache.org/pdfbox-parent/pdfbox-io/) - * Apache PDFBox tools (org.apache.pdfbox:pdfbox-tools:3.0.5 - https://www.apache.org/pdfbox-parent/pdfbox-tools/) - * Apache XmpBox (org.apache.pdfbox:xmpbox:3.0.5 - https://www.apache.org/pdfbox-parent/xmpbox/) - * Apache POI - Common (org.apache.poi:poi:5.4.1 - https://poi.apache.org/) - * Apache POI - API based on OPC and OOXML schemas (org.apache.poi:poi-ooxml:5.4.1 - https://poi.apache.org/) - * Apache POI (org.apache.poi:poi-ooxml-lite:5.4.1 - https://poi.apache.org/) - * Apache POI (org.apache.poi:poi-scratchpad:5.4.1 - https://poi.apache.org/) + * Apache PDFBox (org.apache.pdfbox:pdfbox:3.0.7 - https://www.apache.org/pdfbox-parent/pdfbox/) + * Apache PDFBox io (org.apache.pdfbox:pdfbox-io:3.0.7 - https://www.apache.org/pdfbox-parent/pdfbox-io/) + * Apache PDFBox tools (org.apache.pdfbox:pdfbox-tools:3.0.7 - https://www.apache.org/pdfbox-parent/pdfbox-tools/) + * Apache XmpBox (org.apache.pdfbox:xmpbox:3.0.7 - https://www.apache.org/pdfbox-parent/xmpbox/) + * Apache POI - Common (org.apache.poi:poi:5.5.1 - https://poi.apache.org/) + * Apache POI - API based on OPC and OOXML schemas (org.apache.poi:poi-ooxml:5.5.1 - https://poi.apache.org/) + * Apache POI - OOXML schemas (full) (org.apache.poi:poi-ooxml-full:5.5.1 - https://poi.apache.org/) + * Apache POI (org.apache.poi:poi-scratchpad:5.5.1 - https://poi.apache.org/) * Apache Solr Core (org.apache.solr:solr-core:8.11.4 - https://lucene.apache.org/solr-parent/solr-core) * Apache Solr Solrj (org.apache.solr:solr-solrj:8.11.4 - https://lucene.apache.org/solr-parent/solr-solrj) * Apache Standard Taglib Implementation (org.apache.taglibs:taglibs-standard-impl:1.2.5 - http://tomcat.apache.org/taglibs/standard-1.2.5/taglibs-standard-impl) * Apache Standard Taglib Specification API (org.apache.taglibs:taglibs-standard-spec:1.2.5 - http://tomcat.apache.org/taglibs/standard-1.2.5/taglibs-standard-spec) * Apache Thrift (org.apache.thrift:libthrift:0.9.2 - http://thrift.apache.org) - * Apache Tika core (org.apache.tika:tika-core:3.2.3 - https://tika.apache.org/) - * Apache Tika Apple parser module (org.apache.tika:tika-parser-apple-module:3.2.3 - https://tika.apache.org/tika-parser-apple-module/) - * Apache Tika audiovideo parser module (org.apache.tika:tika-parser-audiovideo-module:3.2.3 - https://tika.apache.org/tika-parser-audiovideo-module/) - * Apache Tika cad parser module (org.apache.tika:tika-parser-cad-module:3.2.3 - https://tika.apache.org/tika-parser-cad-module/) - * Apache Tika code parser module (org.apache.tika:tika-parser-code-module:3.2.3 - https://tika.apache.org/tika-parser-code-module/) - * Apache Tika crypto parser module (org.apache.tika:tika-parser-crypto-module:3.2.3 - https://tika.apache.org/tika-parser-crypto-module/) - * Apache Tika digest commons (org.apache.tika:tika-parser-digest-commons:3.2.3 - https://tika.apache.org/tika-parser-digest-commons/) - * Apache Tika font parser module (org.apache.tika:tika-parser-font-module:3.2.3 - https://tika.apache.org/tika-parser-font-module/) - * Apache Tika html parser module (org.apache.tika:tika-parser-html-module:3.2.3 - https://tika.apache.org/tika-parser-html-module/) - * Apache Tika image parser module (org.apache.tika:tika-parser-image-module:3.2.3 - https://tika.apache.org/tika-parser-image-module/) - * Apache Tika mail commons (org.apache.tika:tika-parser-mail-commons:3.2.3 - https://tika.apache.org/tika-parser-mail-commons/) - * Apache Tika mail parser module (org.apache.tika:tika-parser-mail-module:3.2.3 - https://tika.apache.org/tika-parser-mail-module/) - * Apache Tika Microsoft parser module (org.apache.tika:tika-parser-microsoft-module:3.2.3 - https://tika.apache.org/tika-parser-microsoft-module/) - * Apache Tika miscellaneous office format parser module (org.apache.tika:tika-parser-miscoffice-module:3.2.3 - https://tika.apache.org/tika-parser-miscoffice-module/) - * Apache Tika news parser module (org.apache.tika:tika-parser-news-module:3.2.3 - https://tika.apache.org/tika-parser-news-module/) - * Apache Tika OCR parser module (org.apache.tika:tika-parser-ocr-module:3.2.3 - https://tika.apache.org/tika-parser-ocr-module/) - * Apache Tika PDF parser module (org.apache.tika:tika-parser-pdf-module:3.2.3 - https://tika.apache.org/tika-parser-pdf-module/) - * Apache Tika package parser module (org.apache.tika:tika-parser-pkg-module:3.2.3 - https://tika.apache.org/tika-parser-pkg-module/) - * Apache Tika text parser module (org.apache.tika:tika-parser-text-module:3.2.3 - https://tika.apache.org/tika-parser-text-module/) - * Apache Tika WARC parser module (org.apache.tika:tika-parser-webarchive-module:3.2.3 - https://tika.apache.org/tika-parser-webarchive-module/) - * Apache Tika XML parser module (org.apache.tika:tika-parser-xml-module:3.2.3 - https://tika.apache.org/tika-parser-xml-module/) - * Apache Tika XMP commons (org.apache.tika:tika-parser-xmp-commons:3.2.3 - https://tika.apache.org/tika-parser-xmp-commons/) - * Apache Tika ZIP commons (org.apache.tika:tika-parser-zip-commons:3.2.3 - https://tika.apache.org/tika-parser-zip-commons/) - * Apache Tika standard parser package (org.apache.tika:tika-parsers-standard-package:3.2.3 - https://tika.apache.org/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/) + * Apache Tika core (org.apache.tika:tika-core:3.3.0 - https://tika.apache.org/) + * Apache Tika Apple parser module (org.apache.tika:tika-parser-apple-module:3.3.0 - https://tika.apache.org/tika-parser-apple-module/) + * Apache Tika audiovideo parser module (org.apache.tika:tika-parser-audiovideo-module:3.3.0 - https://tika.apache.org/tika-parser-audiovideo-module/) + * Apache Tika cad parser module (org.apache.tika:tika-parser-cad-module:3.3.0 - https://tika.apache.org/tika-parser-cad-module/) + * Apache Tika code parser module (org.apache.tika:tika-parser-code-module:3.3.0 - https://tika.apache.org/tika-parser-code-module/) + * Apache Tika crypto parser module (org.apache.tika:tika-parser-crypto-module:3.3.0 - https://tika.apache.org/tika-parser-crypto-module/) + * Apache Tika digest commons (org.apache.tika:tika-parser-digest-commons:3.3.0 - https://tika.apache.org/tika-parser-digest-commons/) + * Apache Tika font parser module (org.apache.tika:tika-parser-font-module:3.3.0 - https://tika.apache.org/tika-parser-font-module/) + * Apache Tika html parser module (org.apache.tika:tika-parser-html-module:3.3.0 - https://tika.apache.org/tika-parser-html-module/) + * Apache Tika image parser module (org.apache.tika:tika-parser-image-module:3.3.0 - https://tika.apache.org/tika-parser-image-module/) + * Apache Tika mail commons (org.apache.tika:tika-parser-mail-commons:3.3.0 - https://tika.apache.org/tika-parser-mail-commons/) + * Apache Tika mail parser module (org.apache.tika:tika-parser-mail-module:3.3.0 - https://tika.apache.org/tika-parser-mail-module/) + * Apache Tika Microsoft parser module (org.apache.tika:tika-parser-microsoft-module:3.3.0 - https://tika.apache.org/tika-parser-microsoft-module/) + * Apache Tika miscellaneous office format parser module (org.apache.tika:tika-parser-miscoffice-module:3.3.0 - https://tika.apache.org/tika-parser-miscoffice-module/) + * Apache Tika news parser module (org.apache.tika:tika-parser-news-module:3.3.0 - https://tika.apache.org/tika-parser-news-module/) + * Apache Tika OCR parser module (org.apache.tika:tika-parser-ocr-module:3.3.0 - https://tika.apache.org/tika-parser-ocr-module/) + * Apache Tika PDF parser module (org.apache.tika:tika-parser-pdf-module:3.3.0 - https://tika.apache.org/tika-parser-pdf-module/) + * Apache Tika package parser module (org.apache.tika:tika-parser-pkg-module:3.3.0 - https://tika.apache.org/tika-parser-pkg-module/) + * Apache Tika text parser module (org.apache.tika:tika-parser-text-module:3.3.0 - https://tika.apache.org/tika-parser-text-module/) + * Apache Tika WARC parser module (org.apache.tika:tika-parser-webarchive-module:3.3.0 - https://tika.apache.org/tika-parser-webarchive-module/) + * Apache Tika XML parser module (org.apache.tika:tika-parser-xml-module:3.3.0 - https://tika.apache.org/tika-parser-xml-module/) + * Apache Tika XMP commons (org.apache.tika:tika-parser-xmp-commons:3.3.0 - https://tika.apache.org/tika-parser-xmp-commons/) + * Apache Tika ZIP commons (org.apache.tika:tika-parser-zip-commons:3.3.0 - https://tika.apache.org/tika-parser-zip-commons/) + * Apache Tika standard parser package (org.apache.tika:tika-parsers-standard-package:3.3.0 - https://tika.apache.org/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/) * tomcat-embed-core (org.apache.tomcat.embed:tomcat-embed-core:9.0.83 - https://tomcat.apache.org/) * tomcat-embed-el (org.apache.tomcat.embed:tomcat-embed-el:9.0.83 - https://tomcat.apache.org/) * tomcat-embed-websocket (org.apache.tomcat.embed:tomcat-embed-websocket:9.0.83 - https://tomcat.apache.org/) @@ -345,21 +346,22 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * flyway-core (org.flywaydb:flyway-core:8.5.13 - https://flywaydb.org/flyway-core) * Ogg and Vorbis for Java, Core (org.gagravarr:vorbis-java-core:0.8 - https://github.com/Gagravarr/VorbisJava) * Apache Tika plugin for Ogg, Vorbis and FLAC (org.gagravarr:vorbis-java-tika:0.8 - https://github.com/Gagravarr/VorbisJava) - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.48 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.48 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.48 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * Hibernate Validator Engine (org.hibernate.validator:hibernate-validator:6.2.5.Final - http://hibernate.org/validator/hibernate-validator) * Hibernate Validator Portable Extension (org.hibernate.validator:hibernate-validator-cdi:6.2.5.Final - http://hibernate.org/validator/hibernate-validator-cdi) * org.immutables.value-annotations (org.immutables:value-annotations:2.9.2 - http://immutables.org/value-annotations) * Javassist (org.javassist:javassist:3.30.2-GA - https://www.javassist.org/) * Java Annotation Indexer (org.jboss:jandex:2.4.2.Final - http://www.jboss.org/jandex) - * JBoss Logging 3 (org.jboss.logging:jboss-logging:3.6.1.Final - http://www.jboss.org) + * JBoss Logging 3 (org.jboss.logging:jboss-logging:3.4.3.Final - http://www.jboss.org) * JDOM (org.jdom:jdom2:2.0.6.1 - http://www.jdom.org) * JetBrains Java Annotations (org.jetbrains:annotations:17.0.0 - https://github.com/JetBrains/java-annotations) * Kotlin Stdlib (org.jetbrains.kotlin:kotlin-stdlib:1.8.21 - https://kotlinlang.org/) * Kotlin Stdlib Common (org.jetbrains.kotlin:kotlin-stdlib-common:1.8.21 - https://kotlinlang.org/) * Kotlin Stdlib Jdk7 (org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.21 - https://kotlinlang.org/) * Kotlin Stdlib Jdk8 (org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.21 - https://kotlinlang.org/) + * JSpecify annotations (org.jspecify:jspecify:1.0.0 - http://jspecify.org/) * jtwig-core (org.jtwig:jtwig-core:5.87.0.RELEASE - http://jtwig.org) * jtwig-reflection (org.jtwig:jtwig-reflection:5.87.0.RELEASE - http://jtwig.org) * jtwig-spring (org.jtwig:jtwig-spring:5.87.0.RELEASE - http://jtwig.org) @@ -377,7 +379,7 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Jetty Servlet Tester (org.mortbay.jetty:jetty-servlet-tester:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/jetty-servlet-tester) * Jetty Utilities (org.mortbay.jetty:jetty-util:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/jetty-util) * Servlet Specification API (org.mortbay.jetty:servlet-api:2.5-20081211 - http://jetty.mortbay.org/servlet-api) - * jwarc (org.netpreserve:jwarc:0.32.0 - https://github.com/iipc/jwarc) + * jwarc (org.netpreserve:jwarc:0.35.0 - https://github.com/iipc/jwarc) * Objenesis (org.objenesis:objenesis:3.2 - http://objenesis.org/objenesis) * parboiled-core (org.parboiled:parboiled-core:1.1.7 - http://parboiled.org) * parboiled-java (org.parboiled:parboiled-java:1.1.7 - http://parboiled.org) @@ -436,39 +438,43 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * org.xmlunit:xmlunit-core (org.xmlunit:xmlunit-core:2.9.1 - https://www.xmlunit.org/) * org.xmlunit:xmlunit-placeholders (org.xmlunit:xmlunit-placeholders:2.9.1 - https://www.xmlunit.org/xmlunit-placeholders/) * SnakeYAML (org.yaml:snakeyaml:1.30 - https://bitbucket.org/snakeyaml/snakeyaml) - * AWS Java SDK :: Annotations (software.amazon.awssdk:annotations:2.38.8 - https://aws.amazon.com/sdkforjava/core/annotations) - * AWS Java SDK :: Arns (software.amazon.awssdk:arns:2.38.8 - https://aws.amazon.com/sdkforjava) - * AWS Java SDK :: Auth (software.amazon.awssdk:auth:2.38.8 - https://aws.amazon.com/sdkforjava) - * AWS Java SDK :: AWS Core (software.amazon.awssdk:aws-core:2.38.8 - https://aws.amazon.com/sdkforjava) - * AWS Java SDK :: Core :: Protocols :: AWS Query Protocol (software.amazon.awssdk:aws-query-protocol:2.38.8 - https://aws.amazon.com/sdkforjava) - * AWS Java SDK :: Core :: Protocols :: AWS Xml Protocol (software.amazon.awssdk:aws-xml-protocol:2.38.8 - https://aws.amazon.com/sdkforjava) - * AWS Java SDK :: Checksums (software.amazon.awssdk:checksums:2.38.8 - https://aws.amazon.com/sdkforjava) - * AWS Java SDK :: Checksums SPI (software.amazon.awssdk:checksums-spi:2.38.8 - https://aws.amazon.com/sdkforjava) - * AWS Java SDK :: AWS CRT Core (software.amazon.awssdk:crt-core:2.38.8 - https://aws.amazon.com/sdkforjava) - * AWS Java SDK :: Endpoints SPI (software.amazon.awssdk:endpoints-spi:2.38.8 - https://aws.amazon.com/sdkforjava/core/endpoints-spi) - * AWS Java SDK :: HTTP Auth (software.amazon.awssdk:http-auth:2.38.8 - https://aws.amazon.com/sdkforjava) - * AWS Java SDK :: HTTP Auth AWS (software.amazon.awssdk:http-auth-aws:2.38.8 - https://aws.amazon.com/sdkforjava) - * AWS Java SDK :: HTTP Auth Event Stream (software.amazon.awssdk:http-auth-aws-eventstream:2.38.8 - https://aws.amazon.com/sdkforjava) - * AWS Java SDK :: HTTP Auth SPI (software.amazon.awssdk:http-auth-spi:2.38.8 - https://aws.amazon.com/sdkforjava) - * AWS Java SDK :: HTTP Client Interface (software.amazon.awssdk:http-client-spi:2.38.8 - https://aws.amazon.com/sdkforjava/http-client-spi) - * AWS Java SDK :: Identity SPI (software.amazon.awssdk:identity-spi:2.38.8 - https://aws.amazon.com/sdkforjava) - * AWS Java SDK :: Core :: Protocols :: Json Utils (software.amazon.awssdk:json-utils:2.38.8 - https://aws.amazon.com/sdkforjava) - * AWS Java SDK :: Metrics SPI (software.amazon.awssdk:metrics-spi:2.38.8 - https://aws.amazon.com/sdkforjava/core/metrics-spi) - * AWS Java SDK :: Profiles (software.amazon.awssdk:profiles:2.38.8 - https://aws.amazon.com/sdkforjava) - * AWS Java SDK :: Core :: Protocols :: Protocol Core (software.amazon.awssdk:protocol-core:2.38.8 - https://aws.amazon.com/sdkforjava) - * AWS Java SDK :: Regions (software.amazon.awssdk:regions:2.38.8 - https://aws.amazon.com/sdkforjava/core/regions) - * AWS Java SDK :: Retries (software.amazon.awssdk:retries:2.38.8 - https://aws.amazon.com/sdkforjava/core/retries) - * AWS Java SDK :: Retries API (software.amazon.awssdk:retries-spi:2.38.8 - https://aws.amazon.com/sdkforjava/core/retries-spi) - * AWS Java SDK :: Services :: Amazon S3 (software.amazon.awssdk:s3:2.38.8 - https://aws.amazon.com/sdkforjava) - * AWS Java SDK :: SDK Core (software.amazon.awssdk:sdk-core:2.38.8 - https://aws.amazon.com/sdkforjava) - * AWS Java SDK :: Third Party :: Jackson-core (software.amazon.awssdk:third-party-jackson-core:2.38.8 - https://aws.amazon.com/sdkforjava) - * AWS Java SDK :: Utilities (software.amazon.awssdk:utils:2.38.8 - https://aws.amazon.com/sdkforjava/utils) - * AWS Java SDK :: Utils Lite (software.amazon.awssdk:utils-lite:2.38.8 - https://aws.amazon.com/sdkforjava) - * software.amazon.awssdk.crt:aws-crt (software.amazon.awssdk.crt:aws-crt:0.39.4 - https://github.com/awslabs/aws-crt-java) + * AWS Java SDK :: Annotations (software.amazon.awssdk:annotations:2.42.40 - https://aws.amazon.com/sdkforjava/core/annotations) + * AWS Java SDK :: Arns (software.amazon.awssdk:arns:2.42.40 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: Auth (software.amazon.awssdk:auth:2.42.40 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: AWS Core (software.amazon.awssdk:aws-core:2.42.40 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: Core :: Protocols :: AWS Query Protocol (software.amazon.awssdk:aws-query-protocol:2.42.40 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: Core :: Protocols :: AWS Xml Protocol (software.amazon.awssdk:aws-xml-protocol:2.42.40 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: Checksums (software.amazon.awssdk:checksums:2.42.40 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: Checksums SPI (software.amazon.awssdk:checksums-spi:2.42.40 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: AWS CRT Core (software.amazon.awssdk:crt-core:2.42.40 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: Endpoints SPI (software.amazon.awssdk:endpoints-spi:2.42.40 - https://aws.amazon.com/sdkforjava/core/endpoints-spi) + * AWS Java SDK :: HTTP Auth (software.amazon.awssdk:http-auth:2.42.40 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: HTTP Auth AWS (software.amazon.awssdk:http-auth-aws:2.42.40 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: HTTP Auth Event Stream (software.amazon.awssdk:http-auth-aws-eventstream:2.42.40 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: HTTP Auth SPI (software.amazon.awssdk:http-auth-spi:2.42.40 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: HTTP Client Interface (software.amazon.awssdk:http-client-spi:2.42.40 - https://aws.amazon.com/sdkforjava/http-client-spi) + * AWS Java SDK :: Identity SPI (software.amazon.awssdk:identity-spi:2.42.40 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: Core :: Protocols :: Json Utils (software.amazon.awssdk:json-utils:2.42.40 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: Metrics SPI (software.amazon.awssdk:metrics-spi:2.42.40 - https://aws.amazon.com/sdkforjava/core/metrics-spi) + * AWS Java SDK :: Profiles (software.amazon.awssdk:profiles:2.42.40 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: Core :: Protocols :: Protocol Core (software.amazon.awssdk:protocol-core:2.42.40 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: Regions (software.amazon.awssdk:regions:2.42.40 - https://aws.amazon.com/sdkforjava/core/regions) + * AWS Java SDK :: Retries (software.amazon.awssdk:retries:2.42.40 - https://aws.amazon.com/sdkforjava/core/retries) + * AWS Java SDK :: Retries API (software.amazon.awssdk:retries-spi:2.42.40 - https://aws.amazon.com/sdkforjava/core/retries-spi) + * AWS Java SDK :: Services :: Amazon S3 (software.amazon.awssdk:s3:2.42.40 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: SDK Core (software.amazon.awssdk:sdk-core:2.42.40 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: Third Party :: Jackson-core (software.amazon.awssdk:third-party-jackson-core:2.42.40 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK :: Utilities (software.amazon.awssdk:utils:2.42.40 - https://aws.amazon.com/sdkforjava/utils) + * AWS Java SDK :: Utils Lite (software.amazon.awssdk:utils-lite:2.42.40 - https://aws.amazon.com/sdkforjava) + * software.amazon.awssdk.crt:aws-crt (software.amazon.awssdk.crt:aws-crt:0.45.1 - https://github.com/awslabs/aws-crt-java) * AWS Event Stream (software.amazon.eventstream:eventstream:1.0.1 - https://github.com/awslabs/aws-eventstream-java) * Xerces2-j (xerces:xercesImpl:2.12.2 - https://xerces.apache.org/xerces2-j/) * XML Commons External Components XML APIs (xml-apis:xml-apis:1.4.01 - http://xml.apache.org/commons/components/external/) + BSD 2-Clause License: + + * zstd-jni (com.github.luben:zstd-jni:1.5.7-4 - https://github.com/luben/zstd-jni) + BSD License: * AntLR Parser Generator (antlr:antlr:2.7.7 - http://www.antlr.org/) @@ -480,15 +486,15 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Protocol Buffers [Core] (com.google.protobuf:protobuf-java:3.15.0 - https://developers.google.com/protocol-buffers/protobuf-java/) * JZlib (com.jcraft:jzlib:1.1.3 - http://www.jcraft.com/jzlib/) * jmustache (com.samskivert:jmustache:1.15 - http://github.com/samskivert/jmustache) - * dnsjava (dnsjava:dnsjava:3.6.3 - https://github.com/dnsjava/dnsjava) - * jaxen (jaxen:jaxen:2.0.0 - http://www.cafeconleche.org/jaxen/jaxen) + * dnsjava (dnsjava:dnsjava:3.6.4 - https://github.com/dnsjava/dnsjava) + * jaxen (jaxen:jaxen:2.0.1 - https://jaxen-xpath.github.io/jaxen/jaxen/) * ANTLR 4 Runtime (org.antlr:antlr4-runtime:4.5.1-1 - http://www.antlr.org/antlr4-runtime) * commons-compiler (org.codehaus.janino:commons-compiler:3.1.8 - http://janino-compiler.github.io/commons-compiler/) * janino (org.codehaus.janino:janino:3.1.8 - http://janino-compiler.github.io/janino/) * Stax2 API (org.codehaus.woodstox:stax2-api:4.2.1 - http://github.com/FasterXML/stax2-api) * Hamcrest Date (org.exparity:hamcrest-date:2.0.8 - https://github.com/exparity/hamcrest-date) - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.48 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.48 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * Hamcrest (org.hamcrest:hamcrest:2.2 - http://hamcrest.org/JavaHamcrest/) * Hamcrest Core (org.hamcrest:hamcrest-core:2.2 - http://hamcrest.org/JavaHamcrest/) * HdrHistogram (org.hdrhistogram:HdrHistogram:2.1.12 - http://hdrhistogram.github.io/HdrHistogram/) @@ -498,10 +504,10 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * asm-commons (org.ow2.asm:asm-commons:9.3 - http://asm.ow2.io/) * ASM Tree (org.ow2.asm:asm-tree:5.0.3 - http://asm.objectweb.org/asm-tree/) * ASM Util (org.ow2.asm:asm-util:5.0.3 - http://asm.objectweb.org/asm-util/) - * PostgreSQL JDBC Driver (org.postgresql:postgresql:42.7.8 - https://jdbc.postgresql.org) + * PostgreSQL JDBC Driver (org.postgresql:postgresql:42.7.11 - https://jdbc.postgresql.org) * Reflections (org.reflections:reflections:0.9.12 - http://github.com/ronmamo/reflections) * JMatIO (org.tallison:jmatio:1.5 - https://github.com/tballison/jmatio) - * XZ for Java (org.tukaani:xz:1.10 - https://tukaani.org/xz/java.html) + * XZ for Java (org.tukaani:xz:1.12 - https://tukaani.org/xz/java.html) * XMLUnit for Java (xmlunit:xmlunit:1.3 - http://xmlunit.sourceforge.net/) Common Development and Distribution License (CDDL): @@ -519,15 +525,15 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Java Servlet API (javax.servlet:javax.servlet-api:3.1.0 - http://servlet-spec.java.net) * javax.transaction API (javax.transaction:javax.transaction-api:1.3 - http://jta-spec.java.net) * jaxb-api (javax.xml.bind:jaxb-api:2.3.1 - https://github.com/javaee/jaxb-spec/jaxb-api) - * JHighlight (org.codelibs:jhighlight:1.1.0 - https://github.com/codelibs/jhighlight) + * JHighlight (org.codelibs:jhighlight:1.1.1 - https://github.com/codelibs/jhighlight) * HK2 API module (org.glassfish.hk2:hk2-api:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-api) * ServiceLocator Default Implementation (org.glassfish.hk2:hk2-locator:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-locator) * HK2 Implementation Utilities (org.glassfish.hk2:hk2-utils:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-utils) * OSGi resource locator (org.glassfish.hk2:osgi-resource-locator:1.0.3 - https://projects.eclipse.org/projects/ee4j/osgi-resource-locator) * aopalliance version 1.0 repackaged as a module (org.glassfish.hk2.external:aopalliance-repackaged:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/external/aopalliance-repackaged) * javax.inject:1 as OSGi bundle (org.glassfish.hk2.external:jakarta.inject:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/external/jakarta.inject) - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.48 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.48 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * Java Transaction API (org.jboss.spec.javax.transaction:jboss-transaction-api_1.2_spec:1.1.1.Final - http://www.jboss.org/jboss-transaction-api_1.2_spec) Cordra (Version 2) License Agreement: @@ -548,8 +554,8 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * javax.persistence-api (javax.persistence:javax.persistence-api:2.2 - https://github.com/javaee/jpa-spec) * JAXB Runtime (org.glassfish.jaxb:jaxb-runtime:2.3.9 - https://eclipse-ee4j.github.io/jaxb-ri/) * TXW2 Runtime (org.glassfish.jaxb:txw2:2.3.9 - https://eclipse-ee4j.github.io/jaxb-ri/) - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.48 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.48 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * Java Persistence API, Version 2.1 (org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.2.Final - http://hibernate.org) * org.locationtech.jts:jts-core (org.locationtech.jts:jts-core:1.19.0 - https://www.locationtech.org/projects/technology.jts/jts-modules/jts-core) * org.locationtech.jts.io:jts-io-common (org.locationtech.jts.io:jts-io-common:1.19.0 - https://www.locationtech.org/projects/technology.jts/jts-modules/jts-io/jts-io-common) @@ -604,9 +610,9 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * OSGi resource locator (org.glassfish.hk2:osgi-resource-locator:1.0.3 - https://projects.eclipse.org/projects/ee4j/osgi-resource-locator) * aopalliance version 1.0 repackaged as a module (org.glassfish.hk2.external:aopalliance-repackaged:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/external/aopalliance-repackaged) * javax.inject:1 as OSGi bundle (org.glassfish.hk2.external:jakarta.inject:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/external/jakarta.inject) - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.48 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.48 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.48 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * Java Persistence API, Version 2.1 (org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.2.Final - http://hibernate.org) * org.locationtech.jts:jts-core (org.locationtech.jts:jts-core:1.19.0 - https://www.locationtech.org/projects/technology.jts/jts-modules/jts-core) * org.locationtech.jts.io:jts-io-common (org.locationtech.jts.io:jts-io-common:1.19.0 - https://www.locationtech.org/projects/technology.jts/jts-modules/jts-io/jts-io-common) @@ -633,14 +639,14 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * msg-simple (com.github.java-json-tools:msg-simple:1.2 - https://github.com/java-json-tools/msg-simple) * uri-template (com.github.java-json-tools:uri-template:0.10 - https://github.com/java-json-tools/uri-template) * FindBugs-Annotations (com.google.code.findbugs:annotations:3.0.1u2 - http://findbugs.sourceforge.net/) - * JHighlight (org.codelibs:jhighlight:1.1.0 - https://github.com/codelibs/jhighlight) + * JHighlight (org.codelibs:jhighlight:1.1.1 - https://github.com/codelibs/jhighlight) * Hibernate ORM - hibernate-core (org.hibernate:hibernate-core:5.6.15.Final - https://hibernate.org/orm) * Hibernate ORM - hibernate-jcache (org.hibernate:hibernate-jcache:5.6.15.Final - https://hibernate.org/orm) * Hibernate ORM - hibernate-jpamodelgen (org.hibernate:hibernate-jpamodelgen:5.6.15.Final - https://hibernate.org/orm) * Hibernate Commons Annotations (org.hibernate.common:hibernate-commons-annotations:5.1.2.Final - http://hibernate.org) * im4java (org.im4java:im4java:1.4.0 - http://sourceforge.net/projects/im4java/) * Javassist (org.javassist:javassist:3.30.2-GA - https://www.javassist.org/) - * XOM (xom:xom:1.3.9 - https://xom.nu) + * XOM (xom:xom:1.4.0 - https://xom.nu) Go License: @@ -661,28 +667,27 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines MIT License: * Java SemVer (com.github.zafarkhaja:java-semver:0.9.0 - https://github.com/zafarkhaja/jsemver) - * dd-plist (com.googlecode.plist:dd-plist:1.28 - http://www.github.com/3breadt/dd-plist) + * dd-plist (com.googlecode.plist:dd-plist:1.29 - http://www.github.com/3breadt/dd-plist) * DigitalCollections: IIIF API Library (de.digitalcollections.iiif:iiif-apis:0.3.11 - https://github.com/dbmdz/iiif-apis) * ClassGraph (io.github.classgraph:classgraph:4.8.154 - https://github.com/classgraph/classgraph) * JOpt Simple (net.sf.jopt-simple:jopt-simple:5.0.4 - http://jopt-simple.github.io/jopt-simple) - * Bouncy Castle JavaMail Jakarta S/MIME APIs (org.bouncycastle:bcjmail-jdk18on:1.81 - https://www.bouncycastle.org/download/bouncy-castle-java/) - * Bouncy Castle PKIX, CMS, EAC, TSP, PKCS, OCSP, CMP, and CRMF APIs (org.bouncycastle:bcpkix-jdk18on:1.82 - https://www.bouncycastle.org/download/bouncy-castle-java/) - * Bouncy Castle Provider (org.bouncycastle:bcprov-jdk18on:1.82 - https://www.bouncycastle.org/download/bouncy-castle-java/) - * Bouncy Castle ASN.1 Extension and Utility APIs (org.bouncycastle:bcutil-jdk18on:1.82 - https://www.bouncycastle.org/download/bouncy-castle-java/) + * Bouncy Castle JavaMail Jakarta S/MIME APIs (org.bouncycastle:bcjmail-jdk18on:1.83 - https://www.bouncycastle.org/download/bouncy-castle-java/) + * Bouncy Castle PKIX, CMS, EAC, TSP, PKCS, OCSP, CMP, and CRMF APIs (org.bouncycastle:bcpkix-jdk18on:1.81 - https://www.bouncycastle.org/download/bouncy-castle-java/) + * Bouncy Castle Provider (org.bouncycastle:bcprov-jdk18on:1.81 - https://www.bouncycastle.org/download/bouncy-castle-java/) + * Bouncy Castle ASN.1 Extension and Utility APIs (org.bouncycastle:bcutil-jdk18on:1.81 - https://www.bouncycastle.org/download/bouncy-castle-java/) * org.brotli:dec (org.brotli:dec:0.1.2 - http://brotli.org/dec) * Checker Qual (org.checkerframework:checker-qual:3.23.0 - https://checkerframework.org) - * Checker Qual (org.checkerframework:checker-qual:3.49.5 - https://checkerframework.org/) - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) - * jsoup Java HTML Parser (org.jsoup:jsoup:1.21.2 - https://jsoup.org/) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.48 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.48 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jsoup Java HTML Parser (org.jsoup:jsoup:1.22.1 - https://jsoup.org/) * mockito-core (org.mockito:mockito-core:3.12.4 - https://github.com/mockito/mockito) * mockito-inline (org.mockito:mockito-inline:3.12.4 - https://github.com/mockito/mockito) * ORCID - Model (org.orcid:orcid-model:3.0.2 - http://github.com/ORCID/orcid-model) * Duct Tape (org.rnorth.duct-tape:duct-tape:1.0.8 - https://github.com/rnorth/duct-tape) * JUL to SLF4J bridge (org.slf4j:jul-to-slf4j:1.7.36 - http://www.slf4j.org) * SLF4J API Module (org.slf4j:slf4j-api:1.7.36 - http://www.slf4j.org) - * Testcontainers Core (org.testcontainers:testcontainers:2.0.2 - https://java.testcontainers.org) - * Testcontainers :: Localstack (org.testcontainers:testcontainers-localstack:2.0.2 - https://java.testcontainers.org) + * Testcontainers Core (org.testcontainers:testcontainers:2.0.5 - https://java.testcontainers.org) + * Testcontainers :: Localstack (org.testcontainers:testcontainers-localstack:2.0.5 - https://java.testcontainers.org) * HAL Browser (org.webjars:hal-browser:ad9b865 - http://webjars.org) * toastr (org.webjars.bowergithub.codeseven:toastr:2.1.4 - http://webjars.org) * backbone (org.webjars.bowergithub.jashkenas:backbone:1.4.1 - https://www.webjars.org) @@ -690,7 +695,7 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * jquery (org.webjars.bowergithub.jquery:jquery-dist:3.7.1 - https://www.webjars.org) * urijs (org.webjars.bowergithub.medialize:uri.js:1.19.11 - https://www.webjars.org) * bootstrap (org.webjars.bowergithub.twbs:bootstrap:4.6.2 - https://www.webjars.org) - * core-js (org.webjars.npm:core-js:3.46.0 - https://www.webjars.org) + * core-js (org.webjars.npm:core-js:3.49.0 - https://www.webjars.org) * @json-editor/json-editor (org.webjars.npm:json-editor__json-editor:2.15.2 - https://www.webjars.org) MIT-0: @@ -707,9 +712,9 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines Public Domain: - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.48 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.48 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.48 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * HdrHistogram (org.hdrhistogram:HdrHistogram:2.1.12 - http://hdrhistogram.github.io/HdrHistogram/) * JSON in Java (org.json:json:20231013 - https://github.com/douglascrockford/JSON-java) * LatencyUtils (org.latencyutils:LatencyUtils:2.0.3 - http://latencyutils.github.io/LatencyUtils/) @@ -717,11 +722,11 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines The Apache Software License, version 2.0: - * picocli (info.picocli:picocli:4.7.6 - https://picocli.info) + * picocli (info.picocli:picocli:4.7.7 - https://picocli.info) UnRar License: - * Java Unrar (com.github.junrar:junrar:7.5.5 - https://github.com/junrar/junrar) + * Java Unrar (com.github.junrar:junrar:7.5.8 - https://github.com/junrar/junrar) Unicode/ICU License: @@ -729,10 +734,10 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines W3C license: - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.48 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.48 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) jQuery license: - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.47 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.48 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.48 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) From 7d35ae52c9f80811c21fb4aea228e7ad6e73e711 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 27 May 2026 15:15:20 -0500 Subject: [PATCH 926/939] [maven-release-plugin] prepare release dspace-7.6.7 --- dspace-api/pom.xml | 2 +- dspace-iiif/pom.xml | 2 +- dspace-oai/pom.xml | 2 +- dspace-rdf/pom.xml | 2 +- dspace-server-webapp/pom.xml | 2 +- dspace-services/pom.xml | 2 +- dspace-sword/pom.xml | 2 +- dspace-swordv2/pom.xml | 2 +- dspace/modules/additions/pom.xml | 2 +- dspace/modules/pom.xml | 2 +- dspace/modules/server/pom.xml | 2 +- dspace/pom.xml | 2 +- pom.xml | 28 ++++++++++++++-------------- 13 files changed, 26 insertions(+), 26 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 4f4874d5d25..a64a19fa391 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6.7-SNAPSHOT + 7.6.7 .. diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index 38dc845d0b1..c3a4e248485 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.7-SNAPSHOT + 7.6.7 .. diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index 5ea46fc1db1..64612f9d5f4 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - 7.6.7-SNAPSHOT + 7.6.7 .. diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index 8c7a6fa4225..480b101177b 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.7-SNAPSHOT + 7.6.7 .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 0ac7b38b078..61dc210b0ee 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.7-SNAPSHOT + 7.6.7 .. diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index fa5643c48eb..7f9015b0489 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.7-SNAPSHOT + 7.6.7 diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index 3363a34e04e..aec6677ffe4 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.7-SNAPSHOT + 7.6.7 .. diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index d97c39bba33..d80386698f2 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - 7.6.7-SNAPSHOT + 7.6.7 .. diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index a316d600cd9..adcf996f984 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - 7.6.7-SNAPSHOT + 7.6.7 .. diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index 72649577de7..036dd7c2f25 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - 7.6.7-SNAPSHOT + 7.6.7 ../../pom.xml diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index ea548ba7f35..ea334423ac7 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -13,7 +13,7 @@ just adding new jar in the classloader modules org.dspace - 7.6.7-SNAPSHOT + 7.6.7 .. diff --git a/dspace/pom.xml b/dspace/pom.xml index da6820074a5..ddea9bac0ba 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - 7.6.7-SNAPSHOT + 7.6.7 ../pom.xml diff --git a/pom.xml b/pom.xml index eb1b3497aaa..c8065bcbaf4 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - 7.6.7-SNAPSHOT + 7.6.7 DSpace Parent Project DSpace open source software is a turnkey institutional repository application. @@ -1032,69 +1032,69 @@ org.dspace dspace-api - 7.6.7-SNAPSHOT + 7.6.7 org.dspace dspace-api test-jar - 7.6.7-SNAPSHOT + 7.6.7 test org.dspace.modules additions - 7.6.7-SNAPSHOT + 7.6.7 org.dspace dspace-sword - 7.6.7-SNAPSHOT + 7.6.7 org.dspace dspace-swordv2 - 7.6.7-SNAPSHOT + 7.6.7 org.dspace dspace-oai - 7.6.7-SNAPSHOT + 7.6.7 org.dspace dspace-services - 7.6.7-SNAPSHOT + 7.6.7 org.dspace dspace-server-webapp test-jar - 7.6.7-SNAPSHOT + 7.6.7 test org.dspace dspace-rdf - 7.6.7-SNAPSHOT + 7.6.7 org.dspace dspace-iiif - 7.6.7-SNAPSHOT + 7.6.7 org.dspace dspace-server-webapp - 7.6.7-SNAPSHOT + 7.6.7 jar classes org.dspace dspace-server-webapp - 7.6.7-SNAPSHOT + 7.6.7 war @@ -1950,7 +1950,7 @@ scm:git:git@github.com:DSpace/DSpace.git scm:git:git@github.com:DSpace/DSpace.git https://github.com/DSpace/DSpace - dspace-7_x + dspace-7.6.7 From 5d0ff98c68fb4dd5d5bf55a3995169a8c788562f Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 27 May 2026 15:15:24 -0500 Subject: [PATCH 927/939] [maven-release-plugin] prepare for next development iteration --- dspace-api/pom.xml | 2 +- dspace-iiif/pom.xml | 2 +- dspace-oai/pom.xml | 2 +- dspace-rdf/pom.xml | 2 +- dspace-server-webapp/pom.xml | 2 +- dspace-services/pom.xml | 2 +- dspace-sword/pom.xml | 2 +- dspace-swordv2/pom.xml | 2 +- dspace/modules/additions/pom.xml | 2 +- dspace/modules/pom.xml | 2 +- dspace/modules/server/pom.xml | 2 +- dspace/pom.xml | 2 +- pom.xml | 28 ++++++++++++++-------------- 13 files changed, 26 insertions(+), 26 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index a64a19fa391..5c1d6f725d1 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6.7 + 7.6.8-SNAPSHOT .. diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index c3a4e248485..6f18f0bef49 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.7 + 7.6.8-SNAPSHOT .. diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index 64612f9d5f4..e2f9e613db2 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - 7.6.7 + 7.6.8-SNAPSHOT .. diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index 480b101177b..46d342d8af3 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.7 + 7.6.8-SNAPSHOT .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 61dc210b0ee..78fdc0276a8 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.7 + 7.6.8-SNAPSHOT .. diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index 7f9015b0489..2b339810654 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.7 + 7.6.8-SNAPSHOT diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index aec6677ffe4..4e040ee12c8 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.7 + 7.6.8-SNAPSHOT .. diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index d80386698f2..f77204e7bda 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - 7.6.7 + 7.6.8-SNAPSHOT .. diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index adcf996f984..23e1f49316b 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - 7.6.7 + 7.6.8-SNAPSHOT .. diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index 036dd7c2f25..6605e64e360 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - 7.6.7 + 7.6.8-SNAPSHOT ../../pom.xml diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index ea334423ac7..8042f7508b1 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -13,7 +13,7 @@ just adding new jar in the classloader modules org.dspace - 7.6.7 + 7.6.8-SNAPSHOT .. diff --git a/dspace/pom.xml b/dspace/pom.xml index ddea9bac0ba..e224a7ecaa5 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - 7.6.7 + 7.6.8-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index c8065bcbaf4..5fe3a04d2c9 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - 7.6.7 + 7.6.8-SNAPSHOT DSpace Parent Project DSpace open source software is a turnkey institutional repository application. @@ -1032,69 +1032,69 @@ org.dspace dspace-api - 7.6.7 + 7.6.8-SNAPSHOT org.dspace dspace-api test-jar - 7.6.7 + 7.6.8-SNAPSHOT test org.dspace.modules additions - 7.6.7 + 7.6.8-SNAPSHOT org.dspace dspace-sword - 7.6.7 + 7.6.8-SNAPSHOT org.dspace dspace-swordv2 - 7.6.7 + 7.6.8-SNAPSHOT org.dspace dspace-oai - 7.6.7 + 7.6.8-SNAPSHOT org.dspace dspace-services - 7.6.7 + 7.6.8-SNAPSHOT org.dspace dspace-server-webapp test-jar - 7.6.7 + 7.6.8-SNAPSHOT test org.dspace dspace-rdf - 7.6.7 + 7.6.8-SNAPSHOT org.dspace dspace-iiif - 7.6.7 + 7.6.8-SNAPSHOT org.dspace dspace-server-webapp - 7.6.7 + 7.6.8-SNAPSHOT jar classes org.dspace dspace-server-webapp - 7.6.7 + 7.6.8-SNAPSHOT war @@ -1950,7 +1950,7 @@ scm:git:git@github.com:DSpace/DSpace.git scm:git:git@github.com:DSpace/DSpace.git https://github.com/DSpace/DSpace - dspace-7.6.7 + dspace-7_x From c5d4927ac2c77392099394387aead0aa611ae0e9 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 27 May 2026 15:29:28 -0500 Subject: [PATCH 928/939] Undo partial/flawed 7.6.7 release --- dspace-api/pom.xml | 2 +- dspace-iiif/pom.xml | 2 +- dspace-oai/pom.xml | 2 +- dspace-rdf/pom.xml | 2 +- dspace-server-webapp/pom.xml | 2 +- dspace-services/pom.xml | 2 +- dspace-sword/pom.xml | 2 +- dspace-swordv2/pom.xml | 2 +- dspace/modules/additions/pom.xml | 2 +- dspace/modules/pom.xml | 2 +- dspace/modules/server/pom.xml | 2 +- dspace/pom.xml | 2 +- pom.xml | 26 +++++++++++++------------- 13 files changed, 25 insertions(+), 25 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 5c1d6f725d1..4f4874d5d25 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6.8-SNAPSHOT + 7.6.7-SNAPSHOT .. diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index 6f18f0bef49..38dc845d0b1 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.8-SNAPSHOT + 7.6.7-SNAPSHOT .. diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index e2f9e613db2..5ea46fc1db1 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - 7.6.8-SNAPSHOT + 7.6.7-SNAPSHOT .. diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index 46d342d8af3..8c7a6fa4225 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.8-SNAPSHOT + 7.6.7-SNAPSHOT .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 78fdc0276a8..0ac7b38b078 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.8-SNAPSHOT + 7.6.7-SNAPSHOT .. diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index 2b339810654..fa5643c48eb 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.8-SNAPSHOT + 7.6.7-SNAPSHOT diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index 4e040ee12c8..3363a34e04e 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.8-SNAPSHOT + 7.6.7-SNAPSHOT .. diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index f77204e7bda..d97c39bba33 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - 7.6.8-SNAPSHOT + 7.6.7-SNAPSHOT .. diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index 23e1f49316b..a316d600cd9 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - 7.6.8-SNAPSHOT + 7.6.7-SNAPSHOT .. diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index 6605e64e360..72649577de7 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - 7.6.8-SNAPSHOT + 7.6.7-SNAPSHOT ../../pom.xml diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index 8042f7508b1..ea548ba7f35 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -13,7 +13,7 @@ just adding new jar in the classloader modules org.dspace - 7.6.8-SNAPSHOT + 7.6.7-SNAPSHOT .. diff --git a/dspace/pom.xml b/dspace/pom.xml index e224a7ecaa5..da6820074a5 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - 7.6.8-SNAPSHOT + 7.6.7-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index 5fe3a04d2c9..eb1b3497aaa 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - 7.6.8-SNAPSHOT + 7.6.7-SNAPSHOT DSpace Parent Project DSpace open source software is a turnkey institutional repository application. @@ -1032,69 +1032,69 @@ org.dspace dspace-api - 7.6.8-SNAPSHOT + 7.6.7-SNAPSHOT org.dspace dspace-api test-jar - 7.6.8-SNAPSHOT + 7.6.7-SNAPSHOT test org.dspace.modules additions - 7.6.8-SNAPSHOT + 7.6.7-SNAPSHOT org.dspace dspace-sword - 7.6.8-SNAPSHOT + 7.6.7-SNAPSHOT org.dspace dspace-swordv2 - 7.6.8-SNAPSHOT + 7.6.7-SNAPSHOT org.dspace dspace-oai - 7.6.8-SNAPSHOT + 7.6.7-SNAPSHOT org.dspace dspace-services - 7.6.8-SNAPSHOT + 7.6.7-SNAPSHOT org.dspace dspace-server-webapp test-jar - 7.6.8-SNAPSHOT + 7.6.7-SNAPSHOT test org.dspace dspace-rdf - 7.6.8-SNAPSHOT + 7.6.7-SNAPSHOT org.dspace dspace-iiif - 7.6.8-SNAPSHOT + 7.6.7-SNAPSHOT org.dspace dspace-server-webapp - 7.6.8-SNAPSHOT + 7.6.7-SNAPSHOT jar classes org.dspace dspace-server-webapp - 7.6.8-SNAPSHOT + 7.6.7-SNAPSHOT war From 7b754e4326ca8152be33ce89544db93166dfa035 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 27 May 2026 15:52:06 -0500 Subject: [PATCH 929/939] [maven-release-plugin] prepare release dspace-7.6.7 --- dspace-api/pom.xml | 2 +- dspace-iiif/pom.xml | 2 +- dspace-oai/pom.xml | 2 +- dspace-rdf/pom.xml | 2 +- dspace-rest/pom.xml | 4 ++-- dspace-server-webapp/pom.xml | 2 +- dspace-services/pom.xml | 2 +- dspace-sword/pom.xml | 2 +- dspace-swordv2/pom.xml | 2 +- dspace/modules/additions/pom.xml | 2 +- dspace/modules/pom.xml | 2 +- dspace/modules/rest/pom.xml | 2 +- dspace/modules/server/pom.xml | 2 +- dspace/pom.xml | 2 +- pom.xml | 32 ++++++++++++++++---------------- 15 files changed, 31 insertions(+), 31 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 4f4874d5d25..a64a19fa391 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6.7-SNAPSHOT + 7.6.7 .. diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index 38dc845d0b1..c3a4e248485 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.7-SNAPSHOT + 7.6.7 .. diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index 5ea46fc1db1..64612f9d5f4 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - 7.6.7-SNAPSHOT + 7.6.7 .. diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index 8c7a6fa4225..480b101177b 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.7-SNAPSHOT + 7.6.7 .. diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index f37cf04ecf2..f501de21f52 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -3,7 +3,7 @@ org.dspace dspace-rest war - 7.6.7-SNAPSHOT + 7.6.7 DSpace (Deprecated) REST Webapp DSpace RESTful Web Services API. NOTE: this REST API is DEPRECATED. Please consider using the REST API in the dspace-server-webapp instead! @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6.7-SNAPSHOT + 7.6.7 .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 0ac7b38b078..61dc210b0ee 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.7-SNAPSHOT + 7.6.7 .. diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index fa5643c48eb..7f9015b0489 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.7-SNAPSHOT + 7.6.7 diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index 3363a34e04e..aec6677ffe4 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.7-SNAPSHOT + 7.6.7 .. diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index d97c39bba33..d80386698f2 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - 7.6.7-SNAPSHOT + 7.6.7 .. diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index a316d600cd9..adcf996f984 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - 7.6.7-SNAPSHOT + 7.6.7 .. diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index 72649577de7..036dd7c2f25 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - 7.6.7-SNAPSHOT + 7.6.7 ../../pom.xml diff --git a/dspace/modules/rest/pom.xml b/dspace/modules/rest/pom.xml index 39d1973c31e..470626505c4 100644 --- a/dspace/modules/rest/pom.xml +++ b/dspace/modules/rest/pom.xml @@ -13,7 +13,7 @@ org.dspace modules - 7.6.7-SNAPSHOT + 7.6.7 .. diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index ea548ba7f35..ea334423ac7 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -13,7 +13,7 @@ just adding new jar in the classloader modules org.dspace - 7.6.7-SNAPSHOT + 7.6.7 .. diff --git a/dspace/pom.xml b/dspace/pom.xml index da6820074a5..ddea9bac0ba 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - 7.6.7-SNAPSHOT + 7.6.7 ../pom.xml diff --git a/pom.xml b/pom.xml index eb1b3497aaa..e54bd9ce387 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - 7.6.7-SNAPSHOT + 7.6.7 DSpace Parent Project DSpace open source software is a turnkey institutional repository application. @@ -876,14 +876,14 @@ org.dspace dspace-rest - 7.6.7-SNAPSHOT + 7.6.7 jar classes org.dspace dspace-rest - 7.6.7-SNAPSHOT + 7.6.7 war @@ -1032,69 +1032,69 @@ org.dspace dspace-api - 7.6.7-SNAPSHOT + 7.6.7 org.dspace dspace-api test-jar - 7.6.7-SNAPSHOT + 7.6.7 test org.dspace.modules additions - 7.6.7-SNAPSHOT + 7.6.7 org.dspace dspace-sword - 7.6.7-SNAPSHOT + 7.6.7 org.dspace dspace-swordv2 - 7.6.7-SNAPSHOT + 7.6.7 org.dspace dspace-oai - 7.6.7-SNAPSHOT + 7.6.7 org.dspace dspace-services - 7.6.7-SNAPSHOT + 7.6.7 org.dspace dspace-server-webapp test-jar - 7.6.7-SNAPSHOT + 7.6.7 test org.dspace dspace-rdf - 7.6.7-SNAPSHOT + 7.6.7 org.dspace dspace-iiif - 7.6.7-SNAPSHOT + 7.6.7 org.dspace dspace-server-webapp - 7.6.7-SNAPSHOT + 7.6.7 jar classes org.dspace dspace-server-webapp - 7.6.7-SNAPSHOT + 7.6.7 war @@ -1950,7 +1950,7 @@ scm:git:git@github.com:DSpace/DSpace.git scm:git:git@github.com:DSpace/DSpace.git https://github.com/DSpace/DSpace - dspace-7_x + dspace-7.6.7 From ce1c00d00ad51a230367c3c52673cdc10b9ff152 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 27 May 2026 15:55:36 -0500 Subject: [PATCH 930/939] [maven-release-plugin] rollback the release of dspace-7.6.7 --- dspace-api/pom.xml | 2 +- dspace-iiif/pom.xml | 2 +- dspace-oai/pom.xml | 2 +- dspace-rdf/pom.xml | 2 +- dspace-server-webapp/pom.xml | 2 +- dspace-services/pom.xml | 2 +- dspace-sword/pom.xml | 2 +- dspace-swordv2/pom.xml | 2 +- dspace/modules/additions/pom.xml | 2 +- dspace/modules/pom.xml | 2 +- dspace/modules/server/pom.xml | 2 +- dspace/pom.xml | 2 +- pom.xml | 32 ++++++++++++++++---------------- 13 files changed, 28 insertions(+), 28 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index a64a19fa391..4f4874d5d25 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6.7 + 7.6.7-SNAPSHOT .. diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index c3a4e248485..38dc845d0b1 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.7 + 7.6.7-SNAPSHOT .. diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index 64612f9d5f4..5ea46fc1db1 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - 7.6.7 + 7.6.7-SNAPSHOT .. diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index 480b101177b..8c7a6fa4225 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.7 + 7.6.7-SNAPSHOT .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 61dc210b0ee..0ac7b38b078 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.7 + 7.6.7-SNAPSHOT .. diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index 7f9015b0489..fa5643c48eb 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.7 + 7.6.7-SNAPSHOT diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index aec6677ffe4..3363a34e04e 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.7 + 7.6.7-SNAPSHOT .. diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index d80386698f2..d97c39bba33 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - 7.6.7 + 7.6.7-SNAPSHOT .. diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index adcf996f984..a316d600cd9 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - 7.6.7 + 7.6.7-SNAPSHOT .. diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index 036dd7c2f25..72649577de7 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - 7.6.7 + 7.6.7-SNAPSHOT ../../pom.xml diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index ea334423ac7..ea548ba7f35 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -13,7 +13,7 @@ just adding new jar in the classloader modules org.dspace - 7.6.7 + 7.6.7-SNAPSHOT .. diff --git a/dspace/pom.xml b/dspace/pom.xml index ddea9bac0ba..da6820074a5 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - 7.6.7 + 7.6.7-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index e54bd9ce387..eb1b3497aaa 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - 7.6.7 + 7.6.7-SNAPSHOT DSpace Parent Project DSpace open source software is a turnkey institutional repository application. @@ -876,14 +876,14 @@ org.dspace dspace-rest - 7.6.7 + 7.6.7-SNAPSHOT jar classes org.dspace dspace-rest - 7.6.7 + 7.6.7-SNAPSHOT war @@ -1032,69 +1032,69 @@ org.dspace dspace-api - 7.6.7 + 7.6.7-SNAPSHOT org.dspace dspace-api test-jar - 7.6.7 + 7.6.7-SNAPSHOT test org.dspace.modules additions - 7.6.7 + 7.6.7-SNAPSHOT org.dspace dspace-sword - 7.6.7 + 7.6.7-SNAPSHOT org.dspace dspace-swordv2 - 7.6.7 + 7.6.7-SNAPSHOT org.dspace dspace-oai - 7.6.7 + 7.6.7-SNAPSHOT org.dspace dspace-services - 7.6.7 + 7.6.7-SNAPSHOT org.dspace dspace-server-webapp test-jar - 7.6.7 + 7.6.7-SNAPSHOT test org.dspace dspace-rdf - 7.6.7 + 7.6.7-SNAPSHOT org.dspace dspace-iiif - 7.6.7 + 7.6.7-SNAPSHOT org.dspace dspace-server-webapp - 7.6.7 + 7.6.7-SNAPSHOT jar classes org.dspace dspace-server-webapp - 7.6.7 + 7.6.7-SNAPSHOT war @@ -1950,7 +1950,7 @@ scm:git:git@github.com:DSpace/DSpace.git scm:git:git@github.com:DSpace/DSpace.git https://github.com/DSpace/DSpace - dspace-7.6.7 + dspace-7_x From 79eb174f1257d05c9d830ca3a95ec92898377c02 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 27 May 2026 16:03:53 -0500 Subject: [PATCH 931/939] Undo improper versioning in old REST API. --- dspace-rest/pom.xml | 4 ++-- dspace/modules/rest/pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index f501de21f52..f37cf04ecf2 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -3,7 +3,7 @@ org.dspace dspace-rest war - 7.6.7 + 7.6.7-SNAPSHOT DSpace (Deprecated) REST Webapp DSpace RESTful Web Services API. NOTE: this REST API is DEPRECATED. Please consider using the REST API in the dspace-server-webapp instead! @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6.7 + 7.6.7-SNAPSHOT .. diff --git a/dspace/modules/rest/pom.xml b/dspace/modules/rest/pom.xml index 470626505c4..39d1973c31e 100644 --- a/dspace/modules/rest/pom.xml +++ b/dspace/modules/rest/pom.xml @@ -13,7 +13,7 @@ org.dspace modules - 7.6.7 + 7.6.7-SNAPSHOT .. From ef3524af202c1755925f94c2470351f920be351f Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 27 May 2026 16:14:05 -0500 Subject: [PATCH 932/939] [maven-release-plugin] prepare release dspace-7.6.7 --- dspace-api/pom.xml | 2 +- dspace-iiif/pom.xml | 2 +- dspace-oai/pom.xml | 2 +- dspace-rdf/pom.xml | 2 +- dspace-rest/pom.xml | 4 ++-- dspace-server-webapp/pom.xml | 2 +- dspace-services/pom.xml | 2 +- dspace-sword/pom.xml | 2 +- dspace-swordv2/pom.xml | 2 +- dspace/modules/additions/pom.xml | 2 +- dspace/modules/pom.xml | 2 +- dspace/modules/rest/pom.xml | 2 +- dspace/modules/server/pom.xml | 2 +- dspace/pom.xml | 2 +- pom.xml | 32 ++++++++++++++++---------------- 15 files changed, 31 insertions(+), 31 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 4f4874d5d25..a64a19fa391 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6.7-SNAPSHOT + 7.6.7 .. diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index 38dc845d0b1..c3a4e248485 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.7-SNAPSHOT + 7.6.7 .. diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index 5ea46fc1db1..64612f9d5f4 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - 7.6.7-SNAPSHOT + 7.6.7 .. diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index 8c7a6fa4225..480b101177b 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.7-SNAPSHOT + 7.6.7 .. diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index f37cf04ecf2..f501de21f52 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -3,7 +3,7 @@ org.dspace dspace-rest war - 7.6.7-SNAPSHOT + 7.6.7 DSpace (Deprecated) REST Webapp DSpace RESTful Web Services API. NOTE: this REST API is DEPRECATED. Please consider using the REST API in the dspace-server-webapp instead! @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.6.7-SNAPSHOT + 7.6.7 .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 0ac7b38b078..61dc210b0ee 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.7-SNAPSHOT + 7.6.7 .. diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index fa5643c48eb..7f9015b0489 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.6.7-SNAPSHOT + 7.6.7 diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index 3363a34e04e..aec6677ffe4 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.6.7-SNAPSHOT + 7.6.7 .. diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index d97c39bba33..d80386698f2 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - 7.6.7-SNAPSHOT + 7.6.7 .. diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index a316d600cd9..adcf996f984 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - 7.6.7-SNAPSHOT + 7.6.7 .. diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index 72649577de7..036dd7c2f25 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - 7.6.7-SNAPSHOT + 7.6.7 ../../pom.xml diff --git a/dspace/modules/rest/pom.xml b/dspace/modules/rest/pom.xml index 39d1973c31e..470626505c4 100644 --- a/dspace/modules/rest/pom.xml +++ b/dspace/modules/rest/pom.xml @@ -13,7 +13,7 @@ org.dspace modules - 7.6.7-SNAPSHOT + 7.6.7 .. diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index ea548ba7f35..ea334423ac7 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -13,7 +13,7 @@ just adding new jar in the classloader modules org.dspace - 7.6.7-SNAPSHOT + 7.6.7 .. diff --git a/dspace/pom.xml b/dspace/pom.xml index da6820074a5..ddea9bac0ba 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - 7.6.7-SNAPSHOT + 7.6.7 ../pom.xml diff --git a/pom.xml b/pom.xml index eb1b3497aaa..e54bd9ce387 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - 7.6.7-SNAPSHOT + 7.6.7 DSpace Parent Project DSpace open source software is a turnkey institutional repository application. @@ -876,14 +876,14 @@ org.dspace dspace-rest - 7.6.7-SNAPSHOT + 7.6.7 jar classes org.dspace dspace-rest - 7.6.7-SNAPSHOT + 7.6.7 war @@ -1032,69 +1032,69 @@ org.dspace dspace-api - 7.6.7-SNAPSHOT + 7.6.7 org.dspace dspace-api test-jar - 7.6.7-SNAPSHOT + 7.6.7 test org.dspace.modules additions - 7.6.7-SNAPSHOT + 7.6.7 org.dspace dspace-sword - 7.6.7-SNAPSHOT + 7.6.7 org.dspace dspace-swordv2 - 7.6.7-SNAPSHOT + 7.6.7 org.dspace dspace-oai - 7.6.7-SNAPSHOT + 7.6.7 org.dspace dspace-services - 7.6.7-SNAPSHOT + 7.6.7 org.dspace dspace-server-webapp test-jar - 7.6.7-SNAPSHOT + 7.6.7 test org.dspace dspace-rdf - 7.6.7-SNAPSHOT + 7.6.7 org.dspace dspace-iiif - 7.6.7-SNAPSHOT + 7.6.7 org.dspace dspace-server-webapp - 7.6.7-SNAPSHOT + 7.6.7 jar classes org.dspace dspace-server-webapp - 7.6.7-SNAPSHOT + 7.6.7 war @@ -1950,7 +1950,7 @@ scm:git:git@github.com:DSpace/DSpace.git scm:git:git@github.com:DSpace/DSpace.git https://github.com/DSpace/DSpace - dspace-7_x + dspace-7.6.7 From 51f4b9e01283355e945f5ab25b2202a4a62b6005 Mon Sep 17 00:00:00 2001 From: milanmajchrak Date: Thu, 30 Jul 2026 11:57:17 +0200 Subject: [PATCH 933/939] Add CLARIN content-disposition allowlist; use reload-safe helper in Shib ITs - clarin-dspace.cfg: webui.content_disposition_inline allowlist (vanilla flipped blocklist->allowlist; text/plain, audio, tiff, csv would force-download). video/ogg is listed alongside audio/ogg because bitstream-formats.xml maps the .ogg extension to video/ogg, so audio/ogg alone would never match. - AuthenticationRestControllerIT: 2 new vanilla tests now use setAuthenticationMethodSequence() instead of unreliable setProperty() - test local.cfg: pin the vanilla allowlist for the test environment, otherwise the new CLARIN override reaches the ITs and breaks the upstream BitstreamRestControllerIT#checkContentDispositionOfFormats assertion on text/plain --- .../src/test/data/dspaceFolder/config/local.cfg | 6 ++++++ .../app/rest/AuthenticationRestControllerIT.java | 9 ++++++--- dspace/config/clarin-dspace.cfg | 11 +++++++++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/test/data/dspaceFolder/config/local.cfg b/dspace-api/src/test/data/dspaceFolder/config/local.cfg index b714d516d59..1fd5e527c84 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/local.cfg +++ b/dspace-api/src/test/data/dspaceFolder/config/local.cfg @@ -335,3 +335,9 @@ s3.download.direct.enabled = false user.registration = true config.admin.updateable.files = + +# Keep the *vanilla* inline-preview allowlist in the test environment. The CLARIN production +# override in clarin-dspace.cfg adds text/plain, text/csv, tiff and more audio formats, which +# would break the upstream BitstreamRestControllerIT#checkContentDispositionOfFormats test that +# asserts the vanilla default (those formats download). local.cfg wins over clarin-dspace.cfg. +webui.content_disposition_inline = application/pdf, image/gif, image/jpeg, image/png, audio/mpeg, video/mpeg, video/mp4 diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java index d458a3a3b42..006f6e8d392 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java @@ -113,6 +113,10 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio "org.dspace.authenticate.ShibAuthentication", "org.dspace.authenticate.PasswordAuthentication" }; + public static final String[] PASS_AND_SHIB = { + "org.dspace.authenticate.PasswordAuthentication", + "org.dspace.authenticate.ShibAuthentication" + }; public static final String[] SHIB_AND_IP = { "org.dspace.authenticate.IPAuthentication", "org.dspace.authenticate.ShibAuthentication" @@ -1860,7 +1864,7 @@ public void testShibbolethStaffMappedToStaffAndMembers() throws Exception { .withName("Member") .build(); - configurationService.setProperty("plugin.sequence.org.dspace.authenticate.AuthenticationMethod", SHIB_ONLY); + setAuthenticationMethodSequence(SHIB_ONLY); configurationService.setProperty("authentication-shibboleth.role.staff", "Staff, Member"); configurationService.setProperty("authentication-shibboleth.default-roles", "staff"); configurationService.setProperty("authentication-shibboleth.netid-header", "mail"); @@ -1911,8 +1915,7 @@ public void testPasswordLoginNotMappedToStaffAndMembers() throws Exception { .build(); - configurationService.setProperty("plugin.sequence.org.dspace.authenticate.AuthenticationMethod", - "org.dspace.authenticate.PasswordAuthentication, org.dspace.authenticate.ShibAuthentication"); + setAuthenticationMethodSequence(PASS_AND_SHIB); configurationService.setProperty("authentication-shibboleth.role.staff", "Staff, Member"); configurationService.setProperty("authentication-shibboleth.default-roles", "staff"); configurationService.setProperty("authentication-shibboleth.netid-header", "mail"); diff --git a/dspace/config/clarin-dspace.cfg b/dspace/config/clarin-dspace.cfg index a481beb856a..a1e6ad61f54 100644 --- a/dspace/config/clarin-dspace.cfg +++ b/dspace/config/clarin-dspace.cfg @@ -344,3 +344,14 @@ user.registration = false # This option allows submitter to skip file upload step in the submission process webui.submit.upload.required = false + +# CLARIN/LINDAT allowlist for inline preview. DSpace 7.6.6 flipped this setting from a blocklist +# to an allowlist, so any MIME type not listed here is force-downloaded. LINDAT serves language +# resources, so plain-text corpora, audio, TIFF scans and CSV tables must stay previewable inline. +# HTML/XML/TEI/SVG/JS are deliberately NOT listed: inline delivery would re-open the XSS hole that +# the allowlist flip closed. TEI/XML use the sanitized CLARIN preview path instead. +# Matching is on the MIME type recorded in config/registries/bitstream-formats.xml, so .wav resolves +# to audio/x-wav and .ogg to video/ogg - both are listed. .flac has no registry entry at all, so it +# stays an Unknown format and is always downloaded; audio/flac here only takes effect once the format +# is registered (backlog). +webui.content_disposition_inline = application/pdf, image/gif, image/jpeg, image/png, image/tiff, audio/mpeg, audio/wav, audio/x-wav, audio/ogg, audio/flac, video/mpeg, video/mp4, video/ogg, video/webm, text/plain, text/csv From e4978e2b95da50d50bd7d01fed0bce05a89667c3 Mon Sep 17 00:00:00 2001 From: milanmajchrak Date: Thu, 30 Jul 2026 13:23:31 +0200 Subject: [PATCH 934/939] Correct the content-disposition comment: 8 MB threshold still applies Live rehearsal showed the previous wording ("must stay previewable inline") is untrue for real file sizes: BitstreamRestController also enforces webui.content_disposition_threshold, code default 8 MB, so most real audio/video and large TIFF scans are sent as attachment despite being allowlisted. Comment only - no value change. Raising the threshold widens the inline-render surface and is left as a deliberate deployment decision. Co-Authored-By: Claude Opus 5 (1M context) --- dspace/config/clarin-dspace.cfg | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/dspace/config/clarin-dspace.cfg b/dspace/config/clarin-dspace.cfg index a1e6ad61f54..862b108855e 100644 --- a/dspace/config/clarin-dspace.cfg +++ b/dspace/config/clarin-dspace.cfg @@ -347,11 +347,18 @@ webui.submit.upload.required = false # CLARIN/LINDAT allowlist for inline preview. DSpace 7.6.6 flipped this setting from a blocklist # to an allowlist, so any MIME type not listed here is force-downloaded. LINDAT serves language -# resources, so plain-text corpora, audio, TIFF scans and CSV tables must stay previewable inline. +# resources, so plain-text corpora, audio, TIFF scans and CSV tables are listed here to keep them +# previewable instead of force-downloaded. # HTML/XML/TEI/SVG/JS are deliberately NOT listed: inline delivery would re-open the XSS hole that # the allowlist flip closed. TEI/XML use the sanitized CLARIN preview path instead. # Matching is on the MIME type recorded in config/registries/bitstream-formats.xml, so .wav resolves # to audio/x-wav and .ogg to video/ogg - both are listed. .flac has no registry entry at all, so it # stays an Unknown format and is always downloaded; audio/flac here only takes effect once the format # is registered (backlog). +# IMPORTANT - this allowlist is only half of the decision: BitstreamRestController also enforces +# webui.content_disposition_threshold, whose code default is 8 MB (dspace.cfg keeps it commented, +# same as vanilla). Files LARGER than that are sent as attachment even when their MIME type is +# listed here, which in practice covers most real audio/video and large TIFF scans. Raising the +# threshold widens the inline-render surface, so it is a deliberate deployment decision and is +# intentionally NOT changed here. webui.content_disposition_inline = application/pdf, image/gif, image/jpeg, image/png, image/tiff, audio/mpeg, audio/wav, audio/x-wav, audio/ogg, audio/flac, video/mpeg, video/mp4, video/ogg, video/webm, text/plain, text/csv From c6a1101410f6861788236c021708352518e1875a Mon Sep 17 00:00:00 2001 From: milanmajchrak Date: Thu, 30 Jul 2026 14:19:50 +0200 Subject: [PATCH 935/939] Fix OAI double-escaping and complete the content-disposition allowlist OAI (regression introduced by taking 7.6.7, breaks CLARIN's VLO/OLAC harvesting): vanilla 7.6.7 added ItemUtils.sanitize() and applies it to every metadata value before the XOAI serializer escapes it again, so any value containing & < > " ' reaches harvesters double-escaped. Measured live on cmdi and olac: REST: u767xssprobe end cmdi: u767xssprobe &lt;script&gt;alert(1)&lt;/script&gt; end sanitize() now removes characters XML 1.0 forbids instead of escaping, which is what its own javadoc says it does. Upstream defect - should be reported upstream. content-disposition: the previous allowlist restored inline for only 14 of the 96 registered formats; 7.6.5 allowed 90 (every image/* was inline unconditionally). Added the omitted formats LINDAT actually serves - WebVTT subtitles, AIFF/AU/MPEG audio, JPEG2000/WebP/AVIF/BMP images, Quicktime. Also added the parameterised "text/plain; charset=utf-8": the allowlist is matched by exact string equality, so the registry's extension-less "License" format could never match a bare "text/plain" token and license bitstreams always downloaded. Still deliberately absent: HTML/XML/TEI/SVG/JS. Threshold left at the 8 MB default. Verified: dspace-oai compiles, checkstyle clean, sanitize() behaviour unit-checked (metachars pass through, illegal C0/noncharacters removed, tab/LF/CR and CJK kept). Test config pin keeps the vanilla value, so upstream BitstreamRestControllerIT is unaffected. Co-Authored-By: Claude Opus 5 (1M context) --- .../java/org/dspace/xoai/util/ItemUtils.java | 21 +++++++++++++++---- dspace/config/clarin-dspace.cfg | 11 +++++++++- 2 files changed, 27 insertions(+), 5 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 789b4b1b609..6e6e7319337 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 @@ -15,11 +15,11 @@ import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.regex.Pattern; import com.lyncode.xoai.dataprovider.xml.xoai.Element; import com.lyncode.xoai.dataprovider.xml.xoai.Metadata; import com.lyncode.xoai.util.Base64Utils; -import org.apache.commons.text.StringEscapeUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.app.util.factory.UtilServiceFactory; @@ -243,8 +243,21 @@ private static Element createBundlesElement(Context context, Item item, AtomicBo } /** - * Sanitizes a string to remove characters that are invalid - * in XML 1.0 using the Apache Commons Text library. + * Matches the characters that XML 1.0 forbids outright: C0 controls other than tab, LF and CR, + * plus the two non-characters U+FFFE and U+FFFF. See https://www.w3.org/TR/xml/#charsets + */ + private static final Pattern INVALID_XML10_CHARS = + Pattern.compile("[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\uFFFE\\uFFFF]"); + + /** + * Sanitizes a string to remove characters that are invalid in XML 1.0. + *

    + * NOTE: this deliberately REMOVES illegal characters rather than escaping the string. The value + * returned here is handed to the XOAI serializer, which performs XML escaping itself, so escaping + * here as well would double-escape every value containing &, <, >, " or ' — a harvester + * would then read the literal text "&lt;" instead of a "<" character. That silently corrupts + * every OAI format built on the xoai document, including the cmdi and olac formats CLARIN/LINDAT + * is aggregated through. * @param value The string to sanitize. * @return A sanitized string, or null if the input was null. */ @@ -252,7 +265,7 @@ private static String sanitize(String value) { if (value == null) { return null; } - return StringEscapeUtils.escapeXml10(value); + return INVALID_XML10_CHARS.matcher(value).replaceAll(""); } private static Element createLicenseElement(Context context, Item item) diff --git a/dspace/config/clarin-dspace.cfg b/dspace/config/clarin-dspace.cfg index 862b108855e..9411ab2e3af 100644 --- a/dspace/config/clarin-dspace.cfg +++ b/dspace/config/clarin-dspace.cfg @@ -361,4 +361,13 @@ webui.submit.upload.required = false # listed here, which in practice covers most real audio/video and large TIFF scans. Raising the # threshold widens the inline-render surface, so it is a deliberate deployment decision and is # intentionally NOT changed here. -webui.content_disposition_inline = application/pdf, image/gif, image/jpeg, image/png, image/tiff, audio/mpeg, audio/wav, audio/x-wav, audio/ogg, audio/flac, video/mpeg, video/mp4, video/ogg, video/webm, text/plain, text/csv +# The list below was derived from config/registries/bitstream-formats.xml, not hand-picked: under +# 7.6.5 every image/* was served inline unconditionally and 90 of 96 registered formats could be +# inline, so anything omitted here is a silent inline->attachment regression for LINDAT content. +# "text/plain; charset=utf-8" is listed verbatim because the allowlist is matched with exact string +# equality (unlike the banned list, which uses substring matching): the registry's "License" format +# carries that parameterised MIME type and has no file extensions, so a bare "text/plain" token can +# never match it and license bitstreams would always download. Registry entries whose MIME type +# carries parameters must therefore be listed in full - upstream should compare the MIME type with +# parameters stripped instead (backlog: upstream PR). +webui.content_disposition_inline = application/pdf, image/gif, image/jpeg, image/png, image/tiff, image/jp2, image/webp, image/avif, image/x-ms-bmp, audio/mpeg, audio/wav, audio/x-wav, audio/ogg, audio/flac, audio/x-aiff, audio/basic, audio/x-mpeg, video/mpeg, video/mp4, video/ogg, video/webm, video/quicktime, text/plain, text/plain; charset=utf-8, text/csv, text/vtt From b28457142a9a77fb5ca7c0ee0d79ea868d5183d3 Mon Sep 17 00:00:00 2001 From: milanmajchrak Date: Thu, 30 Jul 2026 15:26:35 +0200 Subject: [PATCH 936/939] Also strip unpaired surrogates in the OAI sanitizer The previous commit replaced escapeXml10 with a regex to stop the double escaping, but escapeXml10 was an AggregateTranslator that ALSO ran UnicodeUnpairedSurrogateRemover, and the regex reproduced only the control-char part. Consequence: a lone surrogate in metadata (truncated 4-byte character, mojibake corpus) makes the StAX writer throw "Broken surrogate pair" inside XOAI.index(), and because that is caught per item the record is silently dropped from the OAI index. Measured before this fix: 2048 code units broke indexing, against 0 for vanilla 7.6.7. Verified over the whole BMP: 0 XML-1.0-illegal code units leak, 0 legal ones are removed, valid surrogate pairs survive, unpaired ones do not, and XML metacharacters still pass through unescaped. Known and intentional difference from escapeXml10: C0/DEL controls U+007F-U+009F are left as-is instead of being turned into &#NNN; entities. They are legal XML 1.0 characters (only XML 1.1 requires escaping them), so well-formedness is unaffected. Co-Authored-By: Claude Opus 5 (1M context) --- .../java/org/dspace/xoai/util/ItemUtils.java | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 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 6e6e7319337..80cb4d4816c 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 @@ -243,21 +243,35 @@ private static Element createBundlesElement(Context context, Item item, AtomicBo } /** - * Matches the characters that XML 1.0 forbids outright: C0 controls other than tab, LF and CR, - * plus the two non-characters U+FFFE and U+FFFF. See https://www.w3.org/TR/xml/#charsets + * Matches everything XML 1.0 forbids outright, in three alternations: + *

      + *
    1. C0 controls other than tab, LF and CR, plus the non-characters U+FFFE and U+FFFF;
    2. + *
    3. a high surrogate not followed by a low surrogate;
    4. + *
    5. a low surrogate not preceded by a high surrogate.
    6. + *
    + * See https://www.w3.org/TR/xml/#charsets. Unpaired surrogates matter in practice: a truncated + * 4-byte character in ingested metadata makes the StAX writer throw "Broken surrogate pair", and + * because XOAI.index() catches that per item the record is silently dropped from the OAI index. */ - private static final Pattern INVALID_XML10_CHARS = - Pattern.compile("[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\uFFFE\\uFFFF]"); + private static final Pattern INVALID_XML10_CHARS = Pattern.compile( + "[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\uFFFE\\uFFFF]" + + "|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])" + + "|(? * NOTE: this deliberately REMOVES illegal characters rather than escaping the string. The value - * returned here is handed to the XOAI serializer, which performs XML escaping itself, so escaping - * here as well would double-escape every value containing &, <, >, " or ' — a harvester - * would then read the literal text "&lt;" instead of a "<" character. That silently corrupts - * every OAI format built on the xoai document, including the cmdi and olac formats CLARIN/LINDAT - * is aggregated through. + * returned here is handed to the XOAI serializer, which performs XML escaping itself (every text + * event goes through {@code XMLStreamWriter.writeCharacters}), so escaping here as well would + * double-escape every value containing &, <, >, " or ' — a harvester would then read the + * literal text "&lt;" instead of a "<" character. That silently corrupts every OAI format + * built on the xoai document, including the cmdi and olac formats CLARIN/LINDAT is aggregated + * through. + *

    + * The removal set must stay equivalent to what {@code StringEscapeUtils.escapeXml10} removed — + * notably including unpaired surrogates — otherwise items carrying them fail to serialize and + * drop out of the OAI index entirely. * @param value The string to sanitize. * @return A sanitized string, or null if the input was null. */ From 1bfc45b88839dc5e80a95214ba716a0e6a152e09 Mon Sep 17 00:00:00 2001 From: milanmajchrak Date: Mon, 3 Aug 2026 13:08:35 +0200 Subject: [PATCH 937/939] Port the CLARIN S3 stack to AWS SDK v2 (decision D1) 7.6.7 rewrote S3BitStoreService from com.amazonaws (v1) to software.amazon.awssdk (v2 CRT client). The original plan rejected that and kept v1; the user reversed it: "if 7.6.7 is on AWS2, go to AWS2". v1 is past AWS end-of-support anyway. The fork owns three S3 classes vanilla does not know, so this was not a file takeover: - S3BitStoreService = vanilla 7.6.7 + exactly three CLARIN items (47 added lines): getFile(Bitstream), `protected` client so the subclasses can reuse it, and a configurable pathStyleAccessEnabled (vanilla hardcodes forcePathStyle(true) on an endpoint override; keeping the property preserves the fork's v1 behaviour and keeps bitstore.xml valid). getFile is byte-identical to the fork's own v9 implementation (5e930d1f2a) so the branches do not diverge. - SyncS3BitStoreService - hand-ported, including the explicit multipart path. - S3DirectDownloadServiceImpl - presigning moved to S3Presigner, built from the same credentials/region/endpoint as the bitstore client. - S3BitStoreServiceIT - taken from 7.6.7 wholesale; the fork's copy was byte-identical to 7.6.5, so no CLARIN test was lost. LocalStack replaces s3mock, which only ever spoke v1. - ClarinS3BitStoreServiceIT - new. The CLARIN-only paths had no test at all. - bitstore.xml / assetstore.cfg - the merge had dropped vanilla's four new tuning keys (targetThroughputGbps, minPartSizeBytes, maxConcurrency, s3ChecksumAlgorithm). Two defects the new tests caught, neither visible by reading the code: 1. v1 returned part ETags unquoted, v2 returns them quoted. The multipart checksum comparison would have failed on every part, and since that path logs and swallows, completeMultipartUpload would never run - a silent upload failure. 2. SyncS3BitStoreService.put() never closed the FileOutputStream on the local assetstore copy. Pre-existing, not a port regression, but it leaks a handle per stored bitstream and made remove() silently fail to delete the local file. Evidence (local, this head): - mvn test-compile -DskipTests: 13/13 modules SUCCESS - S3BitStoreServiceIT Tests run: 15, Failures: 0, Errors: 0 - ClarinS3BitStoreServiceIT Tests run: 6, Failures: 0, Errors: 0 - S3DirectDownloadServiceTest Tests run: 8, Failures: 0, Errors: 0 - checkstyle 0 violations; `git grep com.amazonaws` finds nothing s3Store is lazy-init and nothing else instantiates it, so springWiringBindsStoreProperties loads the bean from bitstore.xml on purpose - it is the only check that the blank maxConcurrency binds to a null Integer and that s3ChecksumAlgorithm binds to the enum. Co-Authored-By: Claude Opus 5 (1M context) --- dspace-api/pom.xml | 41 +- .../storage/bitstore/S3BitStoreService.java | 511 +++++++----------- .../bitstore/S3DirectDownloadServiceImpl.java | 110 +++- .../bitstore/SyncS3BitStoreService.java | 140 +++-- .../bitstore/ClarinS3BitStoreServiceIT.java | 263 +++++++++ .../storage/bitstore/S3BitStoreServiceIT.java | 133 ++--- .../bitstore/S3DirectDownloadServiceTest.java | 114 ++-- dspace/config/modules/assetstore.cfg | 18 +- dspace/config/spring/api/bitstore.xml | 16 + 9 files changed, 829 insertions(+), 517 deletions(-) create mode 100644 dspace-api/src/test/java/org/dspace/storage/bitstore/ClarinS3BitStoreServiceIT.java diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 508b5b6fd25..fbe0c946bbd 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -742,9 +742,25 @@ - com.amazonaws - aws-java-sdk-s3 - 1.12.792 + software.amazon.awssdk + s3 + 2.42.40 + + + software.amazon.awssdk + netty-nio-client + + + software.amazon.awssdk + apache-client + + + + + + software.amazon.awssdk.crt + aws-crt + 0.45.1 @@ -860,21 +876,12 @@ + - io.findify - s3mock_2.13 - 0.2.6 - test - - - com.amazonawsl - aws-java-sdk-s3 - - - com.amazonaws - aws-java-sdk-s3 - - + org.testcontainers + testcontainers-localstack + 2.0.5 + test diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java index ca4fe0b8c93..e5ab66e70c0 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java @@ -10,10 +10,10 @@ import static java.lang.String.valueOf; import java.io.File; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.net.URI; import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -21,34 +21,20 @@ import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.function.Supplier; -import javax.validation.constraints.NotNull; - -import com.amazonaws.AmazonClientException; -import com.amazonaws.auth.AWSCredentials; -import com.amazonaws.auth.AWSStaticCredentialsProvider; -import com.amazonaws.auth.BasicAWSCredentials; -import com.amazonaws.client.builder.AwsClientBuilder; -import com.amazonaws.regions.Region; -import com.amazonaws.regions.Regions; -import com.amazonaws.services.s3.AmazonS3; -import com.amazonaws.services.s3.AmazonS3ClientBuilder; -import com.amazonaws.services.s3.model.AmazonS3Exception; -import com.amazonaws.services.s3.model.GetObjectRequest; -import com.amazonaws.services.s3.model.ObjectMetadata; -import com.amazonaws.services.s3.transfer.Download; -import com.amazonaws.services.s3.transfer.TransferManager; -import com.amazonaws.services.s3.transfer.TransferManagerBuilder; -import com.amazonaws.services.s3.transfer.Upload; + import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; +import org.apache.commons.io.IOUtils; import org.apache.commons.io.output.NullOutputStream; import org.apache.commons.lang3.StringUtils; -import org.apache.http.HttpStatus; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.content.Bitstream; @@ -59,6 +45,19 @@ import org.dspace.storage.bitstore.service.BitstreamStorageService; import org.dspace.util.FunctionalUtils; import org.springframework.beans.factory.annotation.Autowired; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.awscore.exception.AwsServiceException; +import software.amazon.awssdk.core.async.AsyncRequestBody; +import software.amazon.awssdk.core.async.AsyncResponseTransformer; +import software.amazon.awssdk.http.HttpStatusCode; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3AsyncClient; +import software.amazon.awssdk.services.s3.S3CrtAsyncClientBuilder; +import software.amazon.awssdk.services.s3.model.ChecksumAlgorithm; +import software.amazon.awssdk.services.s3.model.HeadObjectResponse; +import software.amazon.awssdk.services.s3.model.NoSuchBucketException; /** * Asset store using Amazon's Simple Storage Service (S3). @@ -67,7 +66,7 @@ * * @author Richard Rodgers, Peter Dietz * @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com) - * + * @author Mark Patton */ public class S3BitStoreService extends BaseBitStoreService { @@ -84,33 +83,28 @@ public class S3BitStoreService extends BaseBitStoreService { */ static final String CSA = "MD5"; - // These settings control the way an identifier is hashed into - // directory and file names - // - // With digitsPerLevel 2 and directoryLevels 3, an identifier - // like 12345678901234567890 turns into the relative name - // /12/34/56/12345678901234567890. - // - // You should not change these settings if you have data in the - // asset store, as the BitstreamStorageManager will be unable - // to find your existing data. - protected static final int digitsPerLevel = 2; - protected static final int directoryLevels = 3; - private boolean enabled = false; + /** + * Override AWS endpoint if not null + */ + private String endpoint = null; + + /** + * CLARIN: use path-style addressing against the overridden endpoint. Vanilla hardcodes `true` + * whenever an endpoint is set; the fork keeps it configurable through + * `assetstore.s3.pathStyleAccessEnabled`, which is what the fork's v1 client did. + */ + private boolean pathStyleAccessEnabled = false; + private String awsAccessKey; private String awsSecretKey; private String awsRegionName; private boolean useRelativePath; - - private String endpoint; - private boolean pathStyleAccessEnabled; - - /** - * The maximum size of individual chunk to download from S3 when a file is accessed. Default 5Mb - */ - private long bufferSize = 5 * 1024 * 1024; + private double targetThroughputGbps = 10.0; + private long minPartSizeBytes = 8 * 1024 * 1024L; + private ChecksumAlgorithm s3ChecksumAlgorithm = ChecksumAlgorithm.CRC32; + private Integer maxConcurrency = null; /** * container for all the assets @@ -124,14 +118,11 @@ public class S3BitStoreService extends BaseBitStoreService { /** * S3 service + * + * CLARIN: `protected` rather than vanilla's `private`, so that {@link SyncS3BitStoreService} and + * {@link S3DirectDownloadServiceImpl} reuse this client instead of opening a second one. */ - protected AmazonS3 s3Service = null; - - /** - * S3 transfer manager - * this is reused between put calls to use less resources for multiple uploads - */ - protected TransferManager tm = null; + protected S3AsyncClient s3AsyncClient = null; private static final ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); @@ -141,45 +132,55 @@ public class S3BitStoreService extends BaseBitStoreService { * * @param regions wanted regions in client * @param awsCredentials credentials of the client + * @param endpoint custom AWS endpoint + * @param targetThroughput target throughput in Gbps + * @param minPartSize minimum part size in bytes + * @param maxConcurrency maximum number of concurrent requests + * @param pathStyleAccessEnabled use path-style addressing when the endpoint is overridden * @return builder with the specified parameters */ - protected static Supplier amazonClientBuilderBy( - @NotNull Regions regions, - @NotNull AWSCredentials awsCredentials + protected static Supplier amazonClientBuilderBy( + Region region, + AwsCredentialsProvider credentialsProvider, + String endpoint, + double targetThroughput, + long minPartSize, + Integer maxConcurrency, + boolean pathStyleAccessEnabled ) { - return () -> AmazonS3ClientBuilder.standard() - .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) - .withRegion(regions) - .build(); - } + return () -> { + S3CrtAsyncClientBuilder crtBuilder = S3AsyncClient.crtBuilder(); - /** - * Utility method for generate AmazonS3 builder with specific endpoint - * - * @param endpointConfiguration configuration of endpoint - * @param awsCredentials credentials of the client - * @param pathStyleAccessEnabled enable path style access to S3 service - * @return builder with the specified parameters - */ - protected static Supplier amazonClientBuilderBy( - @NotNull AwsClientBuilder.EndpointConfiguration endpointConfiguration, - @NotNull AWSCredentials awsCredentials, - @NotNull boolean pathStyleAccessEnabled - ) { - return () -> AmazonS3ClientBuilder.standard() - .withPathStyleAccessEnabled( pathStyleAccessEnabled) - .withEndpointConfiguration(endpointConfiguration) - .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)).build(); + if (credentialsProvider != null) { + crtBuilder.credentialsProvider(credentialsProvider); + } + + if (region != null) { + crtBuilder.region(region); + } + + if (maxConcurrency != null) { + crtBuilder.maxConcurrency(maxConcurrency); + } + + if (StringUtils.isNotBlank(endpoint)) { + crtBuilder.endpointOverride(URI.create(endpoint)); + crtBuilder.forcePathStyle(pathStyleAccessEnabled); + } + + return crtBuilder.targetThroughputInGbps(targetThroughput).minimumPartSizeInBytes(minPartSize).build(); + }; } + public S3BitStoreService() {} /** * This constructor is used for test purpose. * - * @param s3Service AmazonS3 service + * @param s3AsyncClient AmazonS3 service */ - protected S3BitStoreService(AmazonS3 s3Service) { - this.s3Service = s3Service; + protected S3BitStoreService(S3AsyncClient s3AsyncClient) { + this.s3AsyncClient = s3AsyncClient; } @Override @@ -196,47 +197,39 @@ public boolean isEnabled() { */ @Override public void init() throws IOException { - if (this.isInitialized() || !this.isEnabled()) { return; } try { - if (StringUtils.isNotBlank(getEndpoint())) { - log.info("Creating s3service from different endpoint than amazon: " + getEndpoint()); - BasicAWSCredentials credentials = new BasicAWSCredentials(getAwsAccessKey(), getAwsSecretKey()); - AwsClientBuilder.EndpointConfiguration ec = - new AwsClientBuilder.EndpointConfiguration(getEndpoint(), ""); - s3Service = FunctionalUtils.getDefaultOrBuild( - this.s3Service, - amazonClientBuilderBy(ec, credentials, getPathStyleAccessEnabled()) - ); - } else if (StringUtils.isNotBlank(getAwsAccessKey()) && StringUtils.isNotBlank(getAwsSecretKey())) { + if (StringUtils.isNotBlank(getAwsAccessKey()) && StringUtils.isNotBlank(getAwsSecretKey())) { log.warn("Use local defined S3 credentials"); // region - Regions regions = Regions.DEFAULT_REGION; + Region region = Region.US_EAST_1; if (StringUtils.isNotBlank(awsRegionName)) { try { - regions = Regions.fromName(awsRegionName); + region = Region.of(awsRegionName); } catch (IllegalArgumentException e) { log.warn("Invalid aws_region: " + awsRegionName); } } + // init client - s3Service = FunctionalUtils.getDefaultOrBuild( - this.s3Service, + s3AsyncClient = FunctionalUtils.getDefaultOrBuild( + this.s3AsyncClient, amazonClientBuilderBy( - regions, - new BasicAWSCredentials(getAwsAccessKey(), getAwsSecretKey()) - ) + region, + StaticCredentialsProvider.create(AwsBasicCredentials.create(getAwsAccessKey(), + getAwsSecretKey())), endpoint, targetThroughputGbps, + minPartSizeBytes, maxConcurrency, pathStyleAccessEnabled) ); - log.warn("S3 Region set to: " + regions.getName()); + log.warn("S3 Region set to: " + region.id()); } else { log.info("Using a IAM role or aws environment credentials"); - s3Service = FunctionalUtils.getDefaultOrBuild( - this.s3Service, - AmazonS3ClientBuilder::defaultClient - ); + s3AsyncClient = FunctionalUtils.getDefaultOrBuild( + this.s3AsyncClient, + amazonClientBuilderBy(null, null , endpoint, targetThroughputGbps, + minPartSizeBytes, maxConcurrency, pathStyleAccessEnabled)); } // bucket name @@ -247,13 +240,10 @@ public void init() throws IOException { log.warn("S3 BucketName is not configured, setting default: " + bucketName); } - try { - if (!s3Service.doesBucketExistV2(bucketName)) { - s3Service.createBucket(bucketName); - log.info("Creating new S3 Bucket: " + bucketName); - } - } catch (AmazonClientException e) { - throw new IOException(e); + + if (!doesBucketExist(bucketName)) { + s3AsyncClient.createBucket(r -> r.bucket(bucketName)).join(); + log.info("Creating new S3 Bucket: " + bucketName); } this.initialized = true; log.info("AWS S3 Assetstore ready to go! bucket:" + bucketName); @@ -261,13 +251,23 @@ public void init() throws IOException { this.initialized = false; log.error("Can't initialize this store!", e); } + } - log.info("AWS S3 Assetstore ready to go! bucket:" + bucketName); + /** + * @param bucketName + * @return whether or not the specified bucket exists + */ + public boolean doesBucketExist(String bucketName ) { + try { + s3AsyncClient.headBucket(r -> r.bucket(bucketName)).join(); + return true; + } catch (CompletionException ce) { + if (!(ce.getCause() instanceof NoSuchBucketException)) { + log.error("headBucket(" + bucketName + ")", ce.getCause()); + } - tm = FunctionalUtils.getDefaultOrBuild(tm, () -> TransferManagerBuilder.standard() - .withAlwaysCalculateMultipartMd5(true) - .withS3Client(s3Service) - .build()); + return false; + } } /** @@ -295,30 +295,33 @@ public InputStream get(Bitstream bitstream) throws IOException { if (isRegisteredBitstream(key)) { key = key.substring(REGISTERED_FLAG.length()); } - return new S3LazyInputStream(key, bufferSize, bitstream.getSizeBytes()); + + final String objectKey = key; + + try { + return s3AsyncClient.getObject(r -> r.bucket(bucketName).key(objectKey), + AsyncResponseTransformer.toBlockingInputStream()).join(); + } catch (CompletionException e) { + throw new IOException(e.getCause()); + } } + /** + * CLARIN: download the bitstream from S3 into a local temp File (used by the file-preview feature). + * Reuses get(Bitstream) (AWS SDK v2 async client) and streams it to a temp file. The dtq-dev 7.x + * version used AWS SDK v1 (tm.download), which does not apply to the SDK-v2 client. + * + * Kept identical to the fork's v9 implementation (5e930d1f2a) so the two branches do not diverge. + */ @Override public File getFile(Bitstream bitstream) throws IOException { - String key = getFullKey(bitstream.getInternalId()); - // Strip -R from bitstream key if it's registered - if (isRegisteredBitstream(key)) { - key = key.substring(REGISTERED_FLAG.length()); - } - try { - File tempFile = File.createTempFile("s3-disk-copy-" + UUID.randomUUID(), "temp"); - tempFile.deleteOnExit(); - - GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, key); - - Download download = tm.download(getObjectRequest, tempFile); - download.waitForCompletion(); - - return tempFile; - } catch (AmazonClientException | InterruptedException e) { - log.error("getFile(" + key + ")", e); - throw new IOException(e); + File tempFile = File.createTempFile("s3-disk-copy-" + UUID.randomUUID(), ".temp"); + tempFile.deleteOnExit(); + try (InputStream in = this.get(bitstream); + FileOutputStream out = new FileOutputStream(tempFile)) { + IOUtils.copy(in, out); } + return tempFile; } /** @@ -335,44 +338,40 @@ public File getFile(Bitstream bitstream) throws IOException { @Override public void put(Bitstream bitstream, InputStream in) throws IOException { String key = getFullKey(bitstream.getInternalId()); - //Copy istream to temp file, and send the file, with some metadata - File scratchFile = File.createTempFile(bitstream.getInternalId(), "s3bs"); - try ( - FileOutputStream fos = new FileOutputStream(scratchFile); - // Read through a digest input stream that will work out the MD5 - DigestInputStream dis = new DigestInputStream(in, MessageDigest.getInstance(CSA)); - ) { - Utils.bufferedCopy(dis, fos); - in.close(); + ExecutorService executor = Executors.newSingleThreadExecutor(); + + try (DigestInputStream dis = new DigestInputStream(in, MessageDigest.getInstance(CSA))) { + AsyncRequestBody body = AsyncRequestBody.fromInputStream(dis, null, executor); - Upload upload = tm.upload(bucketName, key, scratchFile); + s3AsyncClient.putObject(b -> b.bucket(bucketName).key(key).checksumAlgorithm(s3ChecksumAlgorithm), + body).join(); - upload.waitForUploadResult(); + bitstream.setSizeBytes(s3AsyncClient.headObject(r -> r.bucket(bucketName).key(key)) + .join().contentLength()); - bitstream.setSizeBytes(scratchFile.length()); // we cannot use the S3 ETAG here as it could be not a MD5 in case of multipart upload (large files) or if // the bucket is encrypted bitstream.setChecksum(Utils.toHex(dis.getMessageDigest().digest())); bitstream.setChecksumAlgorithm(CSA); - - } catch (AmazonClientException | IOException | InterruptedException e) { + } catch (CompletionException e) { + log.error("put(" + bitstream.getInternalId() + ", is)", e.getCause()); + throw new IOException(e.getCause()); + } catch (IOException e) { log.error("put(" + bitstream.getInternalId() + ", is)", e); throw new IOException(e); } catch (NoSuchAlgorithmException nsae) { // Should never happen log.warn("Caught NoSuchAlgorithmException", nsae); } finally { - if (!scratchFile.delete()) { - scratchFile.deleteOnExit(); - } + executor.shutdown(); + in.close(); } } /** * Obtain technical metadata about an asset in the asset store. * - * Checksum used is (ETag) hex encoded 128-bit MD5 digest of an object's content as calculated by Amazon S3 - * (Does not use getContentMD5, as that is 128-bit MD5 digest calculated on caller's side) + * The MD5 checksum is calculated locally because it is not supported by AWS. * * @param bitstream The asset to describe * @param attrs A List of desired metadata fields @@ -383,7 +382,6 @@ public void put(Bitstream bitstream, InputStream in) throws IOException { */ @Override public Map about(Bitstream bitstream, List attrs) throws IOException { - String key = getFullKey(bitstream.getInternalId()); // If this is a registered bitstream, strip the -R prefix before retrieving if (isRegisteredBitstream(key)) { @@ -393,20 +391,18 @@ public Map about(Bitstream bitstream, List attrs) throws Map metadata = new HashMap<>(); try { + final String objectKey = key; + HeadObjectResponse response = s3AsyncClient.headObject(r -> r.bucket(bucketName).key(objectKey)).join(); - ObjectMetadata objectMetadata = s3Service.getObjectMetadata(bucketName, key); - if (objectMetadata != null) { - putValueIfExistsKey(attrs, metadata, "size_bytes", objectMetadata.getContentLength()); - putValueIfExistsKey(attrs, metadata, "modified", valueOf(objectMetadata.getLastModified().getTime())); - } - + putValueIfExistsKey(attrs, metadata, "size_bytes", response.contentLength()); + putValueIfExistsKey(attrs, metadata, "modified", valueOf(response.lastModified().toEpochMilli())); putValueIfExistsKey(attrs, metadata, "checksum_algorithm", CSA); if (attrs.contains("checksum")) { try (InputStream in = get(bitstream); DigestInputStream dis = new DigestInputStream(in, MessageDigest.getInstance(CSA)) ) { - Utils.copy(dis, NullOutputStream.NULL_OUTPUT_STREAM); + Utils.copy(dis, NullOutputStream.INSTANCE); byte[] md5Digest = dis.getMessageDigest().digest(); metadata.put("checksum", Utils.toHex(md5Digest)); } catch (NoSuchAlgorithmException nsae) { @@ -416,15 +412,16 @@ public Map about(Bitstream bitstream, List attrs) throws } return metadata; - } catch (AmazonS3Exception e) { - if (e.getStatusCode() == HttpStatus.SC_NOT_FOUND) { - return metadata; + } catch (CompletionException e) { + if (e.getCause() instanceof AwsServiceException) { + if (((AwsServiceException)e.getCause()).statusCode() == HttpStatusCode.NOT_FOUND) { + return metadata; + } } - } catch (AmazonClientException e) { + log.error("about(" + key + ", attrs)", e); throw new IOException(e); } - return metadata; } /** @@ -437,10 +434,10 @@ public Map about(Bitstream bitstream, List attrs) throws public void remove(Bitstream bitstream) throws IOException { String key = getFullKey(bitstream.getInternalId()); try { - s3Service.deleteObject(bucketName, key); - } catch (AmazonClientException e) { - log.error("remove(" + key + ")", e); - throw new IOException(e); + s3AsyncClient.deleteObject(r -> r.bucket(bucketName).key(key)).join(); + } catch (CompletionException e) { + log.error("remove(" + key + ")", e.getCause()); + throw new IOException(e.getCause()); } } @@ -551,6 +548,38 @@ public void setUseRelativePath(boolean useRelativePath) { this.useRelativePath = useRelativePath; } + public double getTargetThroughputGbps() { + return targetThroughputGbps; + } + + public void setTargetThroughputGbps(double targetThroughputGbps) { + this.targetThroughputGbps = targetThroughputGbps; + } + + public long getMinPartSizeBytes() { + return minPartSizeBytes; + } + + public void setMinPartSizeBytes(long minPartSizeBytes) { + this.minPartSizeBytes = minPartSizeBytes; + } + + public ChecksumAlgorithm getS3ChecksumAlgorithm() { + return s3ChecksumAlgorithm; + } + + public void setS3ChecksumAlgorithm(ChecksumAlgorithm s3ChecksumAlgorithm) { + this.s3ChecksumAlgorithm = s3ChecksumAlgorithm; + } + + public Integer getMaxConcurrency() { + return maxConcurrency; + } + + public void setMaxConcurrency(Integer maxConcurrency) { + this.maxConcurrency = maxConcurrency; + } + public String getEndpoint() { return endpoint; } @@ -607,73 +636,18 @@ public static void main(String[] args) throws Exception { S3BitStoreService store = new S3BitStoreService(); - AWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey); + StaticCredentialsProvider credentialsProvider = StaticCredentialsProvider.create( + AwsBasicCredentials.create(accessKey, secretKey)); - store.s3Service = AmazonS3ClientBuilder.standard() - .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) - .build(); - - //Todo configurable region - Region usEast1 = Region.getRegion(Regions.US_EAST_1); - store.s3Service.setRegion(usEast1); + // Todo configurable region + store.s3AsyncClient = S3AsyncClient.builder().credentialsProvider(credentialsProvider). + region(Region.US_EAST_1).build(); // get hostname of DSpace UI to use to name bucket String hostname = Utils.getHostName(configurationService.getProperty("dspace.ui.url")); //Bucketname should be lowercase store.bucketName = DEFAULT_BUCKET_PREFIX + hostname + ".s3test"; - store.s3Service.createBucket(store.bucketName); - /* Broken in DSpace 6 TODO Refactor - // time everything, todo, swtich to caliper - long start = System.currentTimeMillis(); - // Case 1: store a file - String id = store.generateId(); - System.out.print("put() file " + assetFile + " under ID " + id + ": "); - FileInputStream fis = new FileInputStream(assetFile); - //TODO create bitstream for assetfile... - Map attrs = store.put(fis, id); - long now = System.currentTimeMillis(); - System.out.println((now - start) + " msecs"); - start = now; - // examine the metadata returned - Iterator iter = attrs.keySet().iterator(); - System.out.println("Metadata after put():"); - while (iter.hasNext()) - { - String key = (String)iter.next(); - System.out.println( key + ": " + (String)attrs.get(key) ); - } - // Case 2: get metadata and compare - System.out.print("about() file with ID " + id + ": "); - Map attrs2 = store.about(id, attrs); - now = System.currentTimeMillis(); - System.out.println((now - start) + " msecs"); - start = now; - iter = attrs2.keySet().iterator(); - System.out.println("Metadata after about():"); - while (iter.hasNext()) - { - String key = (String)iter.next(); - System.out.println( key + ": " + (String)attrs.get(key) ); - } - // Case 3: retrieve asset and compare bits - System.out.print("get() file with ID " + id + ": "); - java.io.FileOutputStream fos = new java.io.FileOutputStream(assetFile+".echo"); - InputStream in = store.get(id); - Utils.bufferedCopy(in, fos); - fos.close(); - in.close(); - now = System.currentTimeMillis(); - System.out.println((now - start) + " msecs"); - start = now; - // Case 4: remove asset - System.out.print("remove() file with ID: " + id + ": "); - store.remove(id); - now = System.currentTimeMillis(); - System.out.println((now - start) + " msecs"); - System.out.flush(); - // should get nothing back now - will throw exception - store.get(id); -*/ + store.s3AsyncClient.createBucket(r -> r.bucket(store.bucketName)).join(); } /** @@ -684,85 +658,4 @@ public static void main(String[] args) throws Exception { public boolean isRegisteredBitstream(String internalId) { return internalId.startsWith(REGISTERED_FLAG); } - - public void setBufferSize(long bufferSize) { - this.bufferSize = bufferSize; - } - - /** - * This inner class represent an InputStream that uses temporary files to - * represent chunk of the object downloaded from S3. When the input stream is - * read the class look first to the current chunk and download a new one once if - * the current one as been fully read. The class is responsible to close a chunk - * as soon as a new one is retrieved, the last chunk is closed when the input - * stream itself is closed or the last byte is read (the first of the two) - */ - public class S3LazyInputStream extends InputStream { - private InputStream currentChunkStream; - private String objectKey; - private long endOfChunk = -1; - private long chunkMaxSize; - private long currPos = 0; - private long fileSize; - - public S3LazyInputStream(String objectKey, long chunkMaxSize, long fileSize) throws IOException { - this.objectKey = objectKey; - this.chunkMaxSize = chunkMaxSize; - this.endOfChunk = 0; - this.fileSize = fileSize; - downloadChunk(); - } - - @Override - public int read() throws IOException { - // is the current chunk completely read and other are available? - if (currPos == endOfChunk && currPos < fileSize) { - currentChunkStream.close(); - downloadChunk(); - } - - int byteRead = currPos < endOfChunk ? currentChunkStream.read() : -1; - // do we get any data or are we at the end of the file? - if (byteRead != -1) { - currPos++; - } else { - currentChunkStream.close(); - } - return byteRead; - } - - /** - * This method download the next chunk from S3 - * - * @throws IOException - * @throws FileNotFoundException - */ - private void downloadChunk() throws IOException, FileNotFoundException { - // Create a DownloadFileRequest with the desired byte range - long startByte = currPos; // Start byte (inclusive) - long endByte = Long.min(startByte + chunkMaxSize - 1, fileSize - 1); // End byte (inclusive) - GetObjectRequest getRequest = new GetObjectRequest(bucketName, objectKey) - .withRange(startByte, endByte); - - File currentChunkFile = File.createTempFile("s3-disk-copy-" + UUID.randomUUID(), "temp"); - currentChunkFile.deleteOnExit(); - try { - Download download = tm.download(getRequest, currentChunkFile); - download.waitForCompletion(); - currentChunkStream = new DeleteOnCloseFileInputStream(currentChunkFile); - endOfChunk = endOfChunk + download.getProgress().getBytesTransferred(); - } catch (AmazonClientException | InterruptedException e) { - currentChunkFile.delete(); - throw new IOException(e); - } - } - - @Override - public void close() throws IOException { - if (currentChunkStream != null) { - currentChunkStream.close(); - } - } - - } } diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3DirectDownloadServiceImpl.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3DirectDownloadServiceImpl.java index 455f34d8f01..fba2d2807bb 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3DirectDownloadServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3DirectDownloadServiceImpl.java @@ -8,23 +8,30 @@ package org.dspace.storage.bitstore; import java.io.IOException; +import java.net.URI; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; -import java.time.Instant; -import java.util.Date; +import java.time.Duration; -import com.amazonaws.HttpMethod; -import com.amazonaws.services.s3.AmazonS3; -import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest; +import org.apache.commons.lang3.StringUtils; import org.dspace.services.ConfigurationService; import org.dspace.storage.bitstore.service.S3DirectDownloadService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3AsyncClient; +import software.amazon.awssdk.services.s3.S3Configuration; +import software.amazon.awssdk.services.s3.model.GetObjectRequest; +import software.amazon.awssdk.services.s3.presigner.S3Presigner; +import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest; /** * Implementation of the S3DirectDownloadService interface for generating presigned URLs for S3 downloads. - * This implementation uses the AmazonS3 client provided by the S3BitStoreService. + * This implementation reuses the S3 client provided by the S3BitStoreService and derives the presigner + * from the same configuration. * * @author Milan Majchrak (dspace at dataquest.sk) */ @@ -38,16 +45,21 @@ public class S3DirectDownloadServiceImpl implements S3DirectDownloadService { @Autowired private S3BitStoreService s3BitStoreService; - private AmazonS3 s3Client; + private S3AsyncClient s3Client; + + /** + * Unlike the v1 SDK, presigning in v2 is done by a separate object rather than by the client itself. + */ + private S3Presigner s3Presigner; private void init() { - // Use the S3BitStoreService to get the AmazonS3 client - do not create a new one - this.s3Client = s3BitStoreService.s3Service; + // Use the S3BitStoreService to get the S3 client - do not create a new one + this.s3Client = s3BitStoreService.s3AsyncClient; if (this.s3Client == null) { try { s3BitStoreService.init(); - this.s3Client = s3BitStoreService.s3Service; + this.s3Client = s3BitStoreService.s3AsyncClient; } catch (IOException e) { throw new RuntimeException("Failed to initialize S3 client from S3BitStoreService", e); } @@ -57,6 +69,61 @@ private void init() { "on S3BitStoreService."); } } + + if (this.s3Presigner == null) { + this.s3Presigner = buildPresigner(); + } + } + + /** + * Build a presigner from the same credentials, region and endpoint the bitstore client uses, so that + * the signed URL points at the same S3 provider the assets actually live on. + */ + private S3Presigner buildPresigner() { + S3Presigner.Builder builder = S3Presigner.builder(); + + String accessKey = s3BitStoreService.getAwsAccessKey(); + String secretKey = s3BitStoreService.getAwsSecretKey(); + if (StringUtils.isNotBlank(accessKey) && StringUtils.isNotBlank(secretKey)) { + builder.credentialsProvider( + StaticCredentialsProvider.create(AwsBasicCredentials.create(accessKey, secretKey))); + } + // otherwise fall back to the default provider chain (IAM role / environment), as S3BitStoreService does + + // Mirror S3BitStoreService: an unset or unparseable region falls back to us-east-1 + Region region = Region.US_EAST_1; + String regionName = s3BitStoreService.getAwsRegionName(); + if (StringUtils.isNotBlank(regionName)) { + try { + region = Region.of(regionName); + } catch (IllegalArgumentException e) { + log.warn("Invalid aws_region: {}", regionName); + } + } + builder.region(region); + + String endpoint = s3BitStoreService.getEndpoint(); + if (StringUtils.isNotBlank(endpoint)) { + builder.endpointOverride(URI.create(endpoint)); + builder.serviceConfiguration(S3Configuration.builder() + .pathStyleAccessEnabled(s3BitStoreService.getPathStyleAccessEnabled()) + .build()); + } + + return builder.build(); + } + + /** + * Whether the object is really there. A URL we cannot verify is never signed. + */ + private boolean doesObjectExist(String bucket, String key) { + try { + s3Client.headObject(r -> r.bucket(bucket).key(key)).join(); + return true; + } catch (Exception e) { + log.debug("headObject(bucket={}, key={}) failed", bucket, key, e); + return false; + } } public String generatePresignedUrl(String bucket, String key, int expirationSeconds, String desiredFilename) { @@ -64,20 +131,15 @@ public String generatePresignedUrl(String bucket, String key, int expirationSeco log.error("Cannot generate presigned URL – desired filename is null"); throw new IllegalArgumentException("Desired filename cannot be null"); } - if (s3Client == null) { + if (s3Client == null || s3Presigner == null) { init(); } // Verify object exists before generating URL - if (!s3Client.doesObjectExist(bucket, key)) { + if (!doesObjectExist(bucket, key)) { log.error("Cannot generate presigned URL – object does not exist: bucket={}, key={}", bucket, key); throw new IllegalArgumentException("Requested S3 object does not exist"); } - Date expiration = Date.from(Instant.now().plusSeconds(expirationSeconds)); - // Create request - GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucket, key) - .withMethod(HttpMethod.GET) - .withExpiration(expiration); // Add custom response header for filename - to download the file with the desired name // Remove CRLF and quotes to prevent header injection String safeName = desiredFilename.replaceAll("[\\r\\n\"]", "_"); @@ -87,9 +149,19 @@ public String generatePresignedUrl(String bucket, String key, int expirationSeco "attachment; filename=\"%s\"; filename*=UTF-8''%s", safeName, encoded); - request.addRequestParameter("response-content-disposition", contentDisposition); try { - return s3Client.generatePresignedUrl(request).toString(); + GetObjectRequest getObjectRequest = GetObjectRequest.builder() + .bucket(bucket) + .key(key) + .responseContentDisposition(contentDisposition) + .build(); + + GetObjectPresignRequest presignRequest = GetObjectPresignRequest.builder() + .signatureDuration(Duration.ofSeconds(expirationSeconds)) + .getObjectRequest(getObjectRequest) + .build(); + + return s3Presigner.presignGetObject(presignRequest).url().toString(); } catch (Exception e) { log.error("Failed to generate presigned URL for bucket: {}, key: {}", bucket, key, e); throw new RuntimeException("Failed to generate presigned URL", e); diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/SyncS3BitStoreService.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/SyncS3BitStoreService.java index ff1e2f86740..ae81a15b3a8 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/SyncS3BitStoreService.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/SyncS3BitStoreService.java @@ -18,15 +18,12 @@ import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; -import com.amazonaws.AmazonClientException; -import com.amazonaws.services.s3.model.CompleteMultipartUploadRequest; -import com.amazonaws.services.s3.model.InitiateMultipartUploadRequest; -import com.amazonaws.services.s3.model.PartETag; -import com.amazonaws.services.s3.model.UploadPartRequest; -import com.amazonaws.services.s3.model.UploadPartResult; -import com.amazonaws.services.s3.transfer.Upload; import org.apache.commons.io.IOUtils; +import org.apache.commons.io.input.BoundedInputStream; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -34,6 +31,11 @@ import org.dspace.core.Utils; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; +import software.amazon.awssdk.core.async.AsyncRequestBody; +import software.amazon.awssdk.core.exception.SdkException; +import software.amazon.awssdk.services.s3.model.CompletedMultipartUpload; +import software.amazon.awssdk.services.s3.model.CompletedPart; +import software.amazon.awssdk.services.s3.model.UploadPartResponse; /** * Override of the S3BitStoreService to store all the data also in the local assetstore. @@ -124,13 +126,18 @@ public void put(Bitstream bitstream, InputStream in) throws IOException { // Create a new file in the assetstore if it does not exist createFileIfNotExist(localFile); - // Copy content from scratch file to local assetstore file - FileInputStream fisScratchFile = new FileInputStream(scratchFile); - FileOutputStream fosLocalFile = new FileOutputStream(localFile); - Utils.bufferedCopy(fisScratchFile, fosLocalFile); - fisScratchFile.close(); + // Copy content from scratch file to local assetstore file. Both streams have to be closed - + // leaking the output handle keeps the assetstore file locked and a later remove() silently + // fails to delete it. + try (FileInputStream fisScratchFile = new FileInputStream(scratchFile); + FileOutputStream fosLocalFile = new FileOutputStream(localFile)) { + Utils.bufferedCopy(fisScratchFile, fosLocalFile); + } } - } catch (AmazonClientException | IOException | InterruptedException e) { + } catch (CompletionException e) { + log.error("put(" + bitstream.getInternalId() + ", is)", e.getCause()); + throw new IOException(e.getCause()); + } catch (SdkException | IOException e) { log.error("put(" + bitstream.getInternalId() + ", is)", e); throw new IOException(e); } catch (NoSuchAlgorithmException nsae) { @@ -145,17 +152,11 @@ public void put(Bitstream bitstream, InputStream in) throws IOException { @Override public void remove(Bitstream bitstream) throws IOException { - String key = getFullKey(bitstream.getInternalId()); - try { - // Remove file from S3 - s3Service.deleteObject(getBucketName(), key); - if (syncEnabled) { - // Remove file from local assetstore - dsBitStoreService.remove(bitstream); - } - } catch (AmazonClientException e) { - log.error("remove(" + key + ")", e); - throw new IOException(e); + // Remove file from S3 - the parent already logs and wraps the failure in an IOException + super.remove(bitstream); + if (syncEnabled) { + // Remove file from local assetstore + dsBitStoreService.remove(bitstream); } } @@ -182,16 +183,14 @@ private void createFileIfNotExist(File localFile) throws IOException { } /** - * Upload a file fluently. The file is uploaded in a single request. + * Upload a file fluently. The CRT client splits it into parts on its own if it is large enough. * * @param key the bitstream's internalId * @param scratchFile the file to upload - * @throws InterruptedException if the S3 upload is interrupted */ - private void uploadFluently(String key, File scratchFile) throws InterruptedException { - Upload upload = tm.upload(getBucketName(), key, scratchFile); - - upload.waitForUploadResult(); + private void uploadFluently(String key, File scratchFile) { + s3AsyncClient.putObject(r -> r.bucket(getBucketName()).key(key), + AsyncRequestBody.fromFile(scratchFile)).join(); } /** @@ -212,45 +211,47 @@ private void uploadByParts(String key, File scratchFile) throws IOException { } // Initiate multipart upload - InitiateMultipartUploadRequest initiateRequest = new InitiateMultipartUploadRequest(getBucketName(), key); - String uploadId = this.s3Service.initiateMultipartUpload(initiateRequest).getUploadId(); + String uploadId = s3AsyncClient.createMultipartUpload(r -> r.bucket(getBucketName()).key(key)) + .join().uploadId(); // Create a list to hold the ETags for individual parts - List partETags = new ArrayList<>(); + List completedParts = new ArrayList<>(); + ExecutorService executor = Executors.newSingleThreadExecutor(); try { // Upload parts - File file = new File(scratchFile.getPath()); - long fileLength = file.length(); + long fileLength = scratchFile.length(); long remainingBytes = fileLength; int partNumber = 1; while (remainingBytes > 0) { long bytesToUpload = Math.min(UPLOAD_FILE_PART_SIZE, remainingBytes); + long offset = fileLength - remainingBytes; // Calculate the checksum for the part - String partChecksum = calculatePartChecksum(file, fileLength - remainingBytes, bytesToUpload, digest); - - UploadPartRequest uploadRequest = new UploadPartRequest() - .withBucketName(this.getBucketName()) - .withKey(key) - .withUploadId(uploadId) - .withPartNumber(partNumber) - .withFile(file) - .withFileOffset(fileLength - remainingBytes) - .withPartSize(bytesToUpload); - - // Upload the part - UploadPartResult uploadPartResponse = this.s3Service.uploadPart(uploadRequest); + String partChecksum = calculatePartChecksum(scratchFile, offset, bytesToUpload, digest); + + final int currentPartNumber = partNumber; + UploadPartResponse uploadPartResponse; + try (InputStream partStream = openPart(scratchFile, offset, bytesToUpload)) { + // Upload the part + uploadPartResponse = s3AsyncClient.uploadPart( + r -> r.bucket(getBucketName()).key(key).uploadId(uploadId).partNumber(currentPartNumber), + AsyncRequestBody.fromInputStream(partStream, bytesToUpload, executor)).join(); + } // Collect the ETag for the part - partETags.add(uploadPartResponse.getPartETag()); - - // Compare checksums - local with ETag - if (!StringUtils.equals(uploadPartResponse.getETag(), partChecksum)) { + completedParts.add(CompletedPart.builder() + .partNumber(currentPartNumber) + .eTag(uploadPartResponse.eTag()) + .build()); + + // Compare checksums - local with ETag. Unlike the v1 SDK, v2 hands the ETag back quoted. + String eTag = StringUtils.strip(uploadPartResponse.eTag(), "\""); + if (!StringUtils.equals(eTag, partChecksum)) { String errorMessage = "Checksums do not match error: The locally computed checksum does " + "not match with the ETag from the UploadPartResult. Local checksum: " + partChecksum + - ", ETag: " + uploadPartResponse.getETag() + ", partNumber: " + partNumber; + ", ETag: " + eTag + ", partNumber: " + currentPartNumber; log.error(errorMessage); throw new IOException(errorMessage); } @@ -260,11 +261,32 @@ private void uploadByParts(String key, File scratchFile) throws IOException { } // Complete the multipart upload - CompleteMultipartUploadRequest completeRequest = new CompleteMultipartUploadRequest(this.getBucketName(), - key, uploadId, partETags); - this.s3Service.completeMultipartUpload(completeRequest); - } catch (AmazonClientException e) { + s3AsyncClient.completeMultipartUpload(r -> r.bucket(getBucketName()).key(key).uploadId(uploadId) + .multipartUpload(CompletedMultipartUpload.builder().parts(completedParts).build())).join(); + } catch (SdkException | CompletionException e) { log.error("Cannot upload the file by parts because: ", e); + } finally { + executor.shutdown(); + } + } + + /** + * Open a stream over exactly one part of the file, without loading it into memory. + * + * @param file the uploading file + * @param offset the offset in the file + * @param length the length of the part + * @return a stream bounded to the requested part + * @throws IOException if an I/O error occurs + */ + private static InputStream openPart(File file, long offset, long length) throws IOException { + FileInputStream fis = new FileInputStream(file); + try { + fis.getChannel().position(offset); + return BoundedInputStream.builder().setInputStream(fis).setMaxCount(length).get(); + } catch (IOException | RuntimeException e) { + fis.close(); + throw e; } } @@ -282,8 +304,8 @@ public static String calculatePartChecksum(File file, long offset, long length, throws IOException { try (FileInputStream fis = new FileInputStream(file); DigestInputStream dis = new DigestInputStream(fis, digest)) { - // Skip to the specified offset - fis.skip(offset); + // Skip to the specified offset. `position` is exact, unlike `skip`, which may stop short. + fis.getChannel().position(offset); // Read the specified length IOUtils.copyLarge(dis, OutputStream.nullOutputStream(), 0, length); diff --git a/dspace-api/src/test/java/org/dspace/storage/bitstore/ClarinS3BitStoreServiceIT.java b/dspace-api/src/test/java/org/dspace/storage/bitstore/ClarinS3BitStoreServiceIT.java new file mode 100644 index 00000000000..9a3e30445c0 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/storage/bitstore/ClarinS3BitStoreServiceIT.java @@ -0,0 +1,263 @@ +/** + * 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.storage.bitstore; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.apache.commons.io.IOUtils.toInputStream; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.sql.SQLException; + +import org.apache.commons.io.FileUtils; +import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.authorize.AuthorizeException; +import org.dspace.builder.BitstreamBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Bitstream; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.test.util.ReflectionTestUtils; +import org.testcontainers.localstack.LocalStackContainer; +import org.testcontainers.utility.DockerImageName; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3AsyncClient; +import software.amazon.awssdk.services.s3.model.ChecksumAlgorithm; + +/** + * Covers the CLARIN-only parts of the S3 asset store, which upstream's {@link S3BitStoreServiceIT} + * does not touch: {@link S3BitStoreService#getFile(Bitstream)} and {@link SyncS3BitStoreService}. + * + * @author Milan Majchrak (dspace at dataquest.sk) + */ +public class ClarinS3BitStoreServiceIT extends AbstractIntegrationTestWithDatabase { + + // Pinned to the same image upstream's S3BitStoreServiceIT uses. + private static DockerImageName localstackName = DockerImageName.parse("localstack/localstack:4.14.0"); + + @SuppressWarnings("resource") + private static LocalStackContainer localstackContainer = new LocalStackContainer(localstackName).withServices("s3"); + + private static S3AsyncClient s3AsyncClient; + + private static final String BUCKET_NAME = "clarin-testbucket"; + + private Collection collection; + + private File assetstoreDir; + + private DSBitStoreService dsBitStoreService; + + private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + + @BeforeClass + public static void setupS3() { + localstackContainer.start(); + + s3AsyncClient = S3AsyncClient.crtBuilder() + .endpointOverride(localstackContainer.getEndpoint()) + .credentialsProvider(StaticCredentialsProvider.create( + AwsBasicCredentials.create(localstackContainer.getAccessKey(), + localstackContainer.getSecretKey()) + )) + .region(Region.of(localstackContainer.getRegion())) + .build(); + } + + @AfterClass + public static void cleanupS3() { + localstackContainer.close(); + s3AsyncClient.close(); + } + + @Before + public void setup() throws Exception { + configurationService.setProperty("assetstore.s3.enabled", "true"); + // clarin-dspace.cfg turns both of these on; the tests opt in explicitly instead + configurationService.setProperty("sync.storage.service.enabled", "false"); + configurationService.setProperty("s3.upload.by.parts.enabled", "false"); + + assetstoreDir = Files.createTempDirectory("clarin-assetstore").toFile(); + dsBitStoreService = new DSBitStoreService(); + dsBitStoreService.setBaseDir(assetstoreDir); + dsBitStoreService.init(); + + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context).build(); + collection = CollectionBuilder.createCollection(context, parentCommunity).build(); + context.restoreAuthSystemState(); + } + + /** + * {@link S3BitStoreService#getFile(Bitstream)} is declared by the fork's BitStoreService and has no + * upstream counterpart, so nothing else exercises it. + */ + @Test + public void getFileReturnsStoredContent() throws Exception { + String content = "CLARIN getFile content"; + S3BitStoreService store = initStore(new S3BitStoreService(s3AsyncClient)); + + Bitstream bitstream = createBitstream(content); + store.put(bitstream, toInputStream(content, UTF_8)); + + File file = store.getFile(bitstream); + + assertTrue(file.exists()); + assertThat(FileUtils.readFileToString(file, UTF_8), is(content)); + assertThat(file.length(), is((long) content.length())); + } + + /** + * Default path: a single putObject, the CRT client splits large files on its own. + */ + @Test + public void syncStoreUploadsFluently() throws Exception { + String content = "CLARIN fluent upload"; + SyncS3BitStoreService store = initSyncStore(false, false); + + Bitstream bitstream = createBitstream(content); + store.put(bitstream, toInputStream(content, UTF_8)); + + assertThat(readS3(store, bitstream), is(content)); + assertThat(bitstream.getSizeBytes(), is((long) content.length())); + // the local assetstore must stay untouched while syncEnabled is false + assertFalse(dsBitStoreService.getFile(bitstream).exists()); + } + + /** + * `s3.upload.by.parts.enabled` path: explicit createMultipartUpload / uploadPart / + * completeMultipartUpload, with the per-part ETag compared against a locally computed MD5. + * That comparison is what regressed in the v1 -> v2 port, because v2 returns the ETag quoted. + */ + @Test + public void syncStoreUploadsByParts() throws Exception { + String content = "CLARIN multipart upload"; + SyncS3BitStoreService store = initSyncStore(false, true); + + Bitstream bitstream = createBitstream(content); + store.put(bitstream, toInputStream(content, UTF_8)); + + assertThat(readS3(store, bitstream), is(content)); + assertThat(bitstream.getSizeBytes(), is((long) content.length())); + } + + /** + * The reason SyncS3BitStoreService exists: every asset is also written to the local assetstore. + */ + @Test + public void syncStoreWritesLocalCopy() throws Exception { + String content = "CLARIN synced to local assetstore"; + SyncS3BitStoreService store = initSyncStore(true, false); + + Bitstream bitstream = createBitstream(content); + store.put(bitstream, toInputStream(content, UTF_8)); + + assertThat(readS3(store, bitstream), is(content)); + + File localFile = dsBitStoreService.getFile(bitstream); + assertTrue("asset was not mirrored into the local assetstore", localFile.exists()); + assertThat(FileUtils.readFileToString(localFile, UTF_8), is(content)); + } + + /** + * remove() has to clear both stores when syncing is on. + */ + @Test + public void syncStoreRemovesFromBothStores() throws Exception { + String content = "CLARIN removed from both"; + SyncS3BitStoreService store = initSyncStore(true, false); + + Bitstream bitstream = createBitstream(content); + store.put(bitstream, toInputStream(content, UTF_8)); + assertTrue(dsBitStoreService.getFile(bitstream).exists()); + + store.remove(bitstream); + + assertFalse(dsBitStoreService.getFile(bitstream).exists()); + assertFalse(objectExists(store, bitstream)); + } + + /** + * bitstore.xml is the only place the store is configured in production and `s3Store` is lazy-init, + * so nothing otherwise forces Spring to bind its properties. Two of them are easy to get wrong: + * `maxConcurrency` is a blank config value bound to an Integer, and `s3ChecksumAlgorithm` is a + * String bound to an SDK enum. + */ + @Test + public void springWiringBindsStoreProperties() { + SyncS3BitStoreService store = DSpaceServicesFactory.getInstance().getServiceManager() + .getServiceByName("s3Store", SyncS3BitStoreService.class); + + assertNotNull("s3Store bean could not be created from bitstore.xml", store); + assertThat(store.getS3ChecksumAlgorithm(), is(ChecksumAlgorithm.CRC32)); + assertThat(store.getTargetThroughputGbps(), is(10.0)); + assertThat(store.getMinPartSizeBytes(), is(8 * 1024 * 1024L)); + assertNull("blank assetstore.s3.maxConcurrency has to bind to null", store.getMaxConcurrency()); + assertFalse(store.getPathStyleAccessEnabled()); + } + + private T initStore(T store) throws IOException { + ReflectionTestUtils.setField(store, "s3AsyncClient", s3AsyncClient); + store.setEnabled(true); + store.setBucketName(BUCKET_NAME); + store.init(); + return store; + } + + private SyncS3BitStoreService initSyncStore(boolean syncEnabled, boolean uploadByParts) throws IOException { + SyncS3BitStoreService store = new SyncS3BitStoreService(syncEnabled, uploadByParts); + ReflectionTestUtils.setField(store, "configurationService", configurationService); + ReflectionTestUtils.setField(store, "dsBitStoreService", dsBitStoreService); + return initStore(store); + } + + private String readS3(S3BitStoreService store, Bitstream bitstream) throws IOException { + return FileUtils.readFileToString(store.getFile(bitstream), UTF_8); + } + + private boolean objectExists(S3BitStoreService store, Bitstream bitstream) { + try { + String key = store.getFullKey(bitstream.getInternalId()); + s3AsyncClient.headObject(r -> r.bucket(BUCKET_NAME).key(key)).join(); + return true; + } catch (Exception e) { + return false; + } + } + + private Bitstream createBitstream(String content) { + context.turnOffAuthorisationSystem(); + try { + Item item = ItemBuilder.createItem(context, collection).build(); + return BitstreamBuilder + .createBitstream(context, item, toInputStream(content, UTF_8)) + .build(); + } catch (SQLException | AuthorizeException | IOException e) { + throw new RuntimeException(e); + } finally { + context.restoreAuthSystemState(); + } + } +} diff --git a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java index 63d88e95011..4478684abdb 100644 --- a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java +++ b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java @@ -7,13 +7,12 @@ */ package org.dspace.storage.bitstore; -import static com.amazonaws.regions.Regions.DEFAULT_REGION; import static java.nio.charset.StandardCharsets.UTF_8; import static org.dspace.storage.bitstore.S3BitStoreService.CSA; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; @@ -24,8 +23,10 @@ import static org.junit.Assert.assertTrue; import java.io.File; +import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; +import java.io.PrintWriter; import java.nio.file.Paths; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -34,15 +35,6 @@ import java.util.List; import java.util.Map; -import com.amazonaws.auth.AWSStaticCredentialsProvider; -import com.amazonaws.auth.AnonymousAWSCredentials; -import com.amazonaws.client.builder.AwsClientBuilder.EndpointConfiguration; -import com.amazonaws.services.s3.AmazonS3; -import com.amazonaws.services.s3.AmazonS3ClientBuilder; -import com.amazonaws.services.s3.model.AmazonS3Exception; -import com.amazonaws.services.s3.model.Bucket; -import com.amazonaws.services.s3.model.ObjectMetadata; -import io.findify.s3mock.S3Mock; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.BooleanUtils; import org.dspace.AbstractIntegrationTestWithDatabase; @@ -60,44 +52,71 @@ import org.dspace.services.factory.DSpaceServicesFactory; import org.hamcrest.Matcher; import org.hamcrest.Matchers; -import org.junit.After; +import org.junit.AfterClass; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; - - +import org.testcontainers.localstack.LocalStackContainer; +import org.testcontainers.utility.DockerImageName; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.awscore.exception.AwsServiceException; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3AsyncClient; +import software.amazon.awssdk.services.s3.model.Bucket; +import software.amazon.awssdk.services.s3.model.ChecksumAlgorithm; +import software.amazon.awssdk.services.s3.model.HeadObjectResponse; /** * @author Luca Giamminonni (luca.giamminonni at 4science.com) */ public class S3BitStoreServiceIT extends AbstractIntegrationTestWithDatabase { + // Pin to version 4.1.4 of Docker image. Newer versions (starting with 2026-03-0) require an Auth Token. + // See https://blog.localstack.cloud/localstack-for-aws-release-2026-03-0 + private static DockerImageName localstackName = DockerImageName.parse("localstack/localstack:4.14.0"); - private static final String DEFAULT_BUCKET_NAME = "dspace-asset-localhost"; + @SuppressWarnings("resource") + private static LocalStackContainer localstackContainer = new LocalStackContainer(localstackName).withServices("s3"); - private S3BitStoreService s3BitStoreService; + private static S3AsyncClient s3AsyncClient; - private AmazonS3 amazonS3Client; + private static final String DEFAULT_BUCKET_NAME = "dspace-asset-localhost"; - private S3Mock s3Mock; + private S3BitStoreService s3BitStoreService; private Collection collection; private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + @BeforeClass + public static void setupS3() { + localstackContainer.start(); + + s3AsyncClient = S3AsyncClient.crtBuilder() + .endpointOverride(localstackContainer.getEndpoint()) + .credentialsProvider(StaticCredentialsProvider.create( + AwsBasicCredentials.create(localstackContainer.getAccessKey(), + localstackContainer.getSecretKey()) + )) + .region(Region.of(localstackContainer.getRegion())) + .build(); + } + + @AfterClass + public static void cleanupS3() { + localstackContainer.close(); + s3AsyncClient.close(); + } @Before public void setup() throws Exception { - configurationService.setProperty("assetstore.s3.enabled", "true"); - s3Mock = new S3Mock.Builder().withPort(8001).withInMemoryBackend().build(); - s3Mock.start(); - - amazonS3Client = createAmazonS3Client(); - - s3BitStoreService = new S3BitStoreService(amazonS3Client); + s3BitStoreService = new S3BitStoreService(s3AsyncClient); s3BitStoreService.setEnabled(BooleanUtils.toBoolean( configurationService.getProperty("assetstore.s3.enabled"))); - s3BitStoreService.setBufferSize(22); + s3BitStoreService.setS3ChecksumAlgorithm(ChecksumAlgorithm.SHA256); + context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) @@ -109,22 +128,17 @@ public void setup() throws Exception { context.restoreAuthSystemState(); } - @After - public void cleanUp() { - s3Mock.shutdown(); - } - @Test public void testBitstreamPutAndGetWithAlreadyPresentBucket() throws IOException { String bucketName = "testbucket"; - amazonS3Client.createBucket(bucketName); + s3AsyncClient.createBucket(r -> r.bucket(bucketName)).join(); s3BitStoreService.setBucketName(bucketName); s3BitStoreService.init(); - assertThat(amazonS3Client.listBuckets(), contains(bucketNamed(bucketName))); + assertThat(s3AsyncClient.listBuckets().join().buckets(), hasItem(bucketNamed(bucketName))); context.turnOffAuthorisationSystem(); String content = "Test bitstream content"; @@ -146,7 +160,7 @@ public void testBitstreamPutAndGetWithAlreadyPresentBucket() throws IOException private void checkGetPut(String bucketName, String content, Bitstream bitstream) throws IOException { s3BitStoreService.put(bitstream, toInputStream(content)); - String expectedChecksum = Utils.toHex(generateChecksum(content)); + String expectedChecksum = Utils.toHex(generateChecksum("MD5", content)); assertThat(bitstream.getSizeBytes(), is((long) content.length())); assertThat(bitstream.getChecksum(), is(expectedChecksum)); @@ -154,20 +168,16 @@ private void checkGetPut(String bucketName, String content, Bitstream bitstream) InputStream inputStream = s3BitStoreService.get(bitstream); assertThat(IOUtils.toString(inputStream, UTF_8), is(content)); - - String key = s3BitStoreService.getFullKey(bitstream.getInternalId()); - ObjectMetadata objectMetadata = amazonS3Client.getObjectMetadata(bucketName, key); - assertThat(objectMetadata.getContentMD5(), is(expectedChecksum)); } @Test - public void testBitstreamPutAndGetWithoutSpecifingBucket() throws IOException { + public void testBitstreamPutAndGetWithoutSpecifyingBucket() throws IOException { s3BitStoreService.init(); assertThat(s3BitStoreService.getBucketName(), is(DEFAULT_BUCKET_NAME)); - assertThat(amazonS3Client.listBuckets(), contains(bucketNamed(DEFAULT_BUCKET_NAME))); + assertThat(s3AsyncClient.listBuckets().join().buckets(), hasItem(bucketNamed(DEFAULT_BUCKET_NAME))); context.turnOffAuthorisationSystem(); String content = "Test bitstream content"; @@ -176,7 +186,7 @@ public void testBitstreamPutAndGetWithoutSpecifingBucket() throws IOException { s3BitStoreService.put(bitstream, toInputStream(content)); - String expectedChecksum = Utils.toHex(generateChecksum(content)); + String expectedChecksum = Utils.toHex(generateChecksum("MD5", content)); assertThat(bitstream.getSizeBytes(), is((long) content.length())); assertThat(bitstream.getChecksum(), is(expectedChecksum)); @@ -184,11 +194,6 @@ public void testBitstreamPutAndGetWithoutSpecifingBucket() throws IOException { InputStream inputStream = s3BitStoreService.get(bitstream); assertThat(IOUtils.toString(inputStream, UTF_8), is(content)); - - String key = s3BitStoreService.getFullKey(bitstream.getInternalId()); - ObjectMetadata objectMetadata = amazonS3Client.getObjectMetadata(DEFAULT_BUCKET_NAME, key); - assertThat(objectMetadata.getContentMD5(), is(expectedChecksum)); - } @Test @@ -210,9 +215,9 @@ public void testBitstreamPutAndGetWithSubFolder() throws IOException { String key = s3BitStoreService.getFullKey(bitstream.getInternalId()); assertThat(key, startsWith("test/DSpace7/")); - ObjectMetadata objectMetadata = amazonS3Client.getObjectMetadata(DEFAULT_BUCKET_NAME, key); - assertThat(objectMetadata, notNullValue()); - + HeadObjectResponse response = s3AsyncClient.headObject(r -> + r.bucket(DEFAULT_BUCKET_NAME).key(key)).join(); + assertThat(response, notNullValue()); } @Test @@ -232,8 +237,8 @@ public void testBitstreamDeletion() throws IOException { s3BitStoreService.remove(bitstream); IOException exception = assertThrows(IOException.class, () -> s3BitStoreService.get(bitstream)); - assertThat(exception.getCause(), instanceOf(AmazonS3Exception.class)); - assertThat(((AmazonS3Exception) exception.getCause()).getStatusCode(), is(404)); + assertThat(exception.getCause(), instanceOf(AwsServiceException.class)); + assertThat(((AwsServiceException) exception.getCause()).statusCode(), is(404)); } @@ -253,6 +258,14 @@ public void testAbout() throws IOException { assertThat(about.size(), is(0)); about = s3BitStoreService.about(bitstream, List.of("size_bytes")); + + { + PrintWriter out = new PrintWriter(new FileWriter("/tmp/about.txt")); + out.println("moo"); + out.println(about); + out.close(); + } + assertThat(about, hasEntry("size_bytes", 22L)); assertThat(about.size(), is(1)); @@ -261,7 +274,7 @@ public void testAbout() throws IOException { assertThat(about, hasEntry(is("modified"), notNullValue())); assertThat(about.size(), is(2)); - String expectedChecksum = Utils.toHex(generateChecksum(content)); + String expectedChecksum = Utils.toHex(generateChecksum("MD5", content)); about = s3BitStoreService.about(bitstream, List.of("size_bytes", "modified", "checksum")); assertThat(about, hasEntry("size_bytes", 22L)); @@ -275,7 +288,6 @@ public void testAbout() throws IOException { assertThat(about, hasEntry("checksum", expectedChecksum)); assertThat(about, hasEntry("checksum_algorithm", CSA)); assertThat(about.size(), is(4)); - } @Test @@ -406,16 +418,16 @@ public void givenBitStreamIdentifierWithSlashesWhenSanitizedThenSlashesMustBeRem public void testDoNotInitializeConfigured() throws Exception { String assetstores3enabledOldValue = configurationService.getProperty("assetstore.s3.enabled"); configurationService.setProperty("assetstore.s3.enabled", "false"); - s3BitStoreService = new S3BitStoreService(amazonS3Client); + s3BitStoreService = new S3BitStoreService(s3AsyncClient); s3BitStoreService.init(); assertFalse(s3BitStoreService.isInitialized()); assertFalse(s3BitStoreService.isEnabled()); configurationService.setProperty("assetstore.s3.enabled", assetstores3enabledOldValue); } - private byte[] generateChecksum(String content) { + private byte[] generateChecksum(String algorithm, String content) { try { - MessageDigest m = MessageDigest.getInstance("MD5"); + MessageDigest m = MessageDigest.getInstance(algorithm); m.update(content.getBytes()); return m.digest(); } catch (NoSuchAlgorithmException e) { @@ -423,13 +435,6 @@ private byte[] generateChecksum(String content) { } } - private AmazonS3 createAmazonS3Client() { - return AmazonS3ClientBuilder.standard() - .withCredentials(new AWSStaticCredentialsProvider(new AnonymousAWSCredentials())) - .withEndpointConfiguration(new EndpointConfiguration("http://127.0.0.1:8001", DEFAULT_REGION.getName())) - .build(); - } - private Item createItem() { return ItemBuilder.createItem(context, collection) .withTitle("Test item") @@ -447,7 +452,7 @@ private Bitstream createBitstream(String content) { } private Matcher bucketNamed(String name) { - return LambdaMatcher.matches(bucket -> bucket.getName().equals(name)); + return LambdaMatcher.matches(bucket -> bucket.name().equals(name)); } private InputStream toInputStream(String content) { diff --git a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3DirectDownloadServiceTest.java b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3DirectDownloadServiceTest.java index 906cdb04a1b..1f01864495d 100644 --- a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3DirectDownloadServiceTest.java +++ b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3DirectDownloadServiceTest.java @@ -11,17 +11,17 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.io.UnsupportedEncodingException; import java.net.URL; -import java.util.Date; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; -import com.amazonaws.services.s3.AmazonS3; -import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest; import org.dspace.AbstractUnitTest; import org.dspace.services.ConfigurationService; import org.dspace.storage.bitstore.service.S3DirectDownloadService; @@ -31,6 +31,13 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.springframework.test.util.ReflectionTestUtils; +import software.amazon.awssdk.services.s3.S3AsyncClient; +import software.amazon.awssdk.services.s3.model.HeadObjectRequest; +import software.amazon.awssdk.services.s3.model.HeadObjectResponse; +import software.amazon.awssdk.services.s3.model.NoSuchKeyException; +import software.amazon.awssdk.services.s3.presigner.S3Presigner; +import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest; +import software.amazon.awssdk.services.s3.presigner.model.PresignedGetObjectRequest; /** * Test class for S3DirectDownloadService. @@ -46,82 +53,94 @@ public class S3DirectDownloadServiceTest extends AbstractUnitTest { @Mock private ConfigurationService configService; @Mock - private AmazonS3 amazonS3; + private S3AsyncClient s3AsyncClient; + @Mock + private S3Presigner s3Presigner; @Before + @SuppressWarnings("unchecked") public void setUp() throws Exception { MockitoAnnotations.openMocks(this); - when(amazonS3.doesObjectExist(anyString(), anyString())).thenReturn(true); + + // Resolve the consumer for real, so a null bucket/key behaves as "object not found" like the v1 + // `doesObjectExist(bucket, key)` call this replaced. + when(s3AsyncClient.headObject(any(Consumer.class))).thenAnswer(invocation -> { + Consumer consumer = invocation.getArgument(0); + HeadObjectRequest.Builder builder = HeadObjectRequest.builder(); + consumer.accept(builder); + HeadObjectRequest request = builder.build(); + if (request.bucket() == null || request.key() == null) { + return CompletableFuture.failedFuture(NoSuchKeyException.builder().build()); + } + return CompletableFuture.completedFuture(HeadObjectResponse.builder().build()); + }); s3DirectDownloadService = new S3DirectDownloadServiceImpl(); ReflectionTestUtils.setField(s3DirectDownloadService, "s3BitStoreService", s3BitstoreService); ReflectionTestUtils.setField(s3DirectDownloadService, "configurationService", configService); - // Reflectively set the mock’s private/public field - ReflectionTestUtils.setField(s3BitstoreService, "s3Service", amazonS3); + // Reflectively set the mock's protected field + ReflectionTestUtils.setField(s3BitstoreService, "s3AsyncClient", s3AsyncClient); + // Pre-seed the presigner so init() does not try to build a real one from empty credentials + ReflectionTestUtils.setField(s3DirectDownloadService, "s3Presigner", s3Presigner); ReflectionTestUtils.invokeMethod(s3DirectDownloadService, "init"); } + /** Stub the presigner so that presignGetObject returns a request pointing at the given URL. */ + private void presignReturns(String url) throws Exception { + PresignedGetObjectRequest presigned = mock(PresignedGetObjectRequest.class); + when(presigned.url()).thenReturn(new URL(url)); + when(s3Presigner.presignGetObject(any(GetObjectPresignRequest.class))).thenReturn(presigned); + } + @Test public void generatePresignedUrl() throws Exception { // Mock the presigned URL generation - URL fakeUrl = new URL("https://example.com/foo"); - when(amazonS3.generatePresignedUrl(any(GeneratePresignedUrlRequest.class))) - .thenReturn(fakeUrl); + presignReturns("https://example.com/foo"); - // Rum the method to generate the presigned URL + // Run the method to generate the presigned URL String url = s3DirectDownloadService.generatePresignedUrl("bucket", "key", 120, "myfile.txt"); // Compare the generated URL with the mocked one assertEquals("https://example.com/foo", url); // Verify that the presigned URL was generated with the correct parameters - GeneratePresignedUrlRequest req = captureRequest(); - assertEquals("bucket", req.getBucketName()); - assertEquals("key", req.getKey()); - assertTrue(req.getRequestParameters() - .get("response-content-disposition") + GetObjectPresignRequest req = captureRequest(); + assertEquals("bucket", req.getObjectRequest().bucket()); + assertEquals("key", req.getObjectRequest().key()); + assertTrue(req.getObjectRequest().responseContentDisposition() .contains("attachment; filename=\"myfile.txt\"")); - assertTrue(req.getExpiration().after(new Date())); + assertEquals(Duration.ofSeconds(120), req.signatureDuration()); } - // Zero expiration → URL still generated with expiration == now (or slightly after) + // Zero expiration → URL still generated, with a zero-length signature window @Test public void zeroExpiration() throws Exception { - URL fake = new URL("https://zero"); - when(amazonS3.generatePresignedUrl(any(GeneratePresignedUrlRequest.class))).thenReturn(fake); + presignReturns("https://zero"); s3DirectDownloadService.generatePresignedUrl("b", "k", 0, "f"); - GeneratePresignedUrlRequest req = captureRequest(); - // Expiration should be >= now - Date expiration = req.getExpiration(); - assertTrue("Expiration should not be in the past by more than 1 second", - expiration.getTime() >= new Date().getTime() - 1000); + assertEquals(Duration.ZERO, captureRequest().signatureDuration()); } - // Negative expiration → expiration in the past + // Negative expiration → signature window already elapsed @Test public void negativeExpiration() throws Exception { - URL fake = new URL("https://neg"); - when(amazonS3.generatePresignedUrl(any(GeneratePresignedUrlRequest.class))).thenReturn(fake); + presignReturns("https://neg"); s3DirectDownloadService.generatePresignedUrl("b", "k", -30, "f"); - GeneratePresignedUrlRequest req = captureRequest(); - // Expiration < now + a small slack (1s) - assertTrue(req.getExpiration().before(new Date(System.currentTimeMillis() + 1000))); + assertTrue(captureRequest().signatureDuration().isNegative()); } // DesiredFilename with control chars / path traversal @Test public void weirdFilename() throws Exception { - URL fake = new URL("https://weird"); - when(amazonS3.generatePresignedUrl(any(GeneratePresignedUrlRequest.class))).thenReturn(fake); + presignReturns("https://weird"); String weird = "../secret\nname\t.txt"; s3DirectDownloadService.generatePresignedUrl("b", "k", 60, weird); - GeneratePresignedUrlRequest req = captureRequest(); + GetObjectPresignRequest req = captureRequest(); - String cd = req.getRequestParameters().get("response-content-disposition"); + String cd = req.getObjectRequest().responseContentDisposition(); // Should start with attachment and include both filename and filename* assertTrue(cd.startsWith("attachment; filename=\"")); @@ -139,36 +158,37 @@ public void weirdFilename() throws Exception { assertTrue(cd.contains("UTF-8")); } - // Underlying AmazonS3 throws → IllegalArgumentException + // Null filename → IllegalArgumentException @Test(expected = IllegalArgumentException.class) public void nullFilename() throws Exception { s3DirectDownloadService.generatePresignedUrl("b", "k", 60, null); } - // Underlying AmazonS3 throws → bubbles up + // Underlying presigner throws → bubbles up @Test(expected = RuntimeException.class) - public void amazonThrows() throws UnsupportedEncodingException { - when(amazonS3.generatePresignedUrl(any())).thenThrow(new RuntimeException("boom")); + public void presignerThrows() throws UnsupportedEncodingException { + when(s3Presigner.presignGetObject(any(GetObjectPresignRequest.class))) + .thenThrow(new RuntimeException("boom")); s3DirectDownloadService.generatePresignedUrl("b", "k", 1, "f"); } - // Bucket key == null → should IllegalArgumentException + // Bucket == null → should IllegalArgumentException @Test(expected = IllegalArgumentException.class) public void nullBucket() throws UnsupportedEncodingException { s3DirectDownloadService.generatePresignedUrl(null, "k", 60, "f"); } - // Bucket key == null → should NPE + // Key == null → should IllegalArgumentException @Test(expected = IllegalArgumentException.class) public void nullKey() throws UnsupportedEncodingException { s3DirectDownloadService.generatePresignedUrl("b", null, 60, "f"); } // helper to pull out the single captured request - private GeneratePresignedUrlRequest captureRequest() { - ArgumentCaptor cap = - ArgumentCaptor.forClass(GeneratePresignedUrlRequest.class); - verify(amazonS3, atLeastOnce()).generatePresignedUrl(cap.capture()); + private GetObjectPresignRequest captureRequest() { + ArgumentCaptor cap = + ArgumentCaptor.forClass(GetObjectPresignRequest.class); + verify(s3Presigner, atLeastOnce()).presignGetObject(cap.capture()); return cap.getValue(); } -} \ No newline at end of file +} diff --git a/dspace/config/modules/assetstore.cfg b/dspace/config/modules/assetstore.cfg index b74731a8774..d61e76a0e7a 100644 --- a/dspace/config/modules/assetstore.cfg +++ b/dspace/config/modules/assetstore.cfg @@ -51,8 +51,8 @@ assetstore.s3.subfolder = # please don't use root credentials in production but rely on the aws credentials default # discovery mechanism to configure them (ENV VAR, EC2 Iam role, etc.) # The preferred approach for security reason is to use the IAM user credentials, but isn't always possible. -# More information about credentials here: https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html -# More information about IAM usage here: https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/java-dg-roles.html +# More information about credentials here: https://docs.aws.amazon.com/sdk-for-java/v2/developer-guide/credentials.html +# More information about IAM usage here: https://docs.aws.amazon.com/sdk-for-java/v2/developer-guide/credentials-chain.html assetstore.s3.awsAccessKey = assetstore.s3.awsSecretKey = @@ -64,3 +64,17 @@ assetstore.s3.awsRegionName = assetstore.s3.pathStyleAccessEnabled = false # Leave empty to use default (Amazon AWS) endpoint assetstore.s3.endpoint = + +# The target throughput for transfer requests in Gbps. Higher value means more connections will be established with S3. +assetstore.s3.targetThroughputGbps = 10.0 + +# Sets the minimum part size for transfer parts. Decreasing the minimum part size causes multipart transfer to be split +# into a larger number of smaller parts. +assetstore.s3.minPartSizeBytes = 8388608 + +# Specifies the maximum number of S3 connections that should be established during a transfer. +# If not provided, it will be based on targetThroughputGbps +assetstore.s3.maxConcurrency = + +# The algorithm the S3 client will use to create a checksum when doing putObject. +assetstore.s3.s3ChecksumAlgorithm = CRC32 diff --git a/dspace/config/spring/api/bitstore.xml b/dspace/config/spring/api/bitstore.xml index c208e27e71f..2a8dc2c8d22 100644 --- a/dspace/config/spring/api/bitstore.xml +++ b/dspace/config/spring/api/bitstore.xml @@ -38,6 +38,22 @@ + + + + + + + + + + + + From 75848b2639e945ed6c8f690c658c675aef6fbb4a Mon Sep 17 00:00:00 2001 From: milanmajchrak Date: Mon, 3 Aug 2026 17:26:45 +0200 Subject: [PATCH 938/939] Fix the S3 defects a 10-reviewer audit found in the SDK v2 port **Silent data loss on multipart upload.** `uploadPart` was handed a `BoundedInputStream` over a `FileInputStream`, which cannot be reset, so the SDK refused to retry a part after any transient error ("Request cannot be retried, because the request stream could not be reset"). The v1 SDK passed the file plus an offset and retried freely. The failure then landed in a catch that logged and swallowed, `completeMultipartUpload` never ran, and `put()` carried on setting size and checksum - DSpace recorded a bitstream that S3 does not hold, with no error anywhere. Now uses the public `AsyncRequestBody.fromFile(FileRequestBodyConfiguration)` with `position`/`numBytesToRead`, which is file-backed and therefore retryable; this also removes the hand-rolled `openPart` helper and its executor. A failed upload now aborts the multipart upload (parts were previously leaked and billed) and throws instead of returning normally. **The multipart loop was never executed more than once.** 50 MB part size against a 23-byte fixture meant one iteration with offset 0, so the offset arithmetic and last-part handling this port rewrote had no coverage at all. `uploadPartSizeBytes` is now settable and `syncStoreUploadsInMultipleParts` uploads 5 MB + 4 KB across a real part boundary, verifying the object byte for byte. **A tripwire in `amazonClientBuilderBy` left all 21 S3 tests green.** Every test injects a ready-made client, so `FunctionalUtils.getDefaultOrBuild` never called the supplier and `endpointOverride`, `forcePathStyle`, `maxConcurrency` and the part sizes never ran - the fork's headline delta, configurable `pathStyleAccessEnabled`, was verified only at its getter. `clientBuilderAppliesEndpointAndPathStyle` builds a client through that method and uses it against LocalStack. Verified it detects breakage: removing `endpointOverride` turns it red, restoring it turns it green. **Content-Disposition had two implementations and the newer one was wrong.** It escaped `"` but not `\`, so a bitstream named `evil\` terminated the quoted string and swallowed `filename*`; and it used `URLEncoder` directly, so a space arrived as `+`, which RFC 8187 reads literally - re-introducing the bug vanilla had just fixed. Extracted the correct implementation to `org.dspace.util.ContentDispositionUtils`, now used by both call sites. **Enabling direct downloads silently disabled inline preview.** The redirect hardcoded `attachment`, discarding the disposition the allowlist and the 8 MB threshold had just computed. `generatePresignedUrl` takes the caller's disposition; `BitstreamRestController` passes the exact header the non-redirect path would have sent. **Presigner region could diverge from the client's.** It hardcoded `us-east-1` while the client leaves the region to the default provider chain when no credentials are configured, so presigned URLs would be signed for the wrong region on an IAM role outside us-east-1. It now mirrors `init()` branch for branch. **A 403 was reported as "bucket absent".** `doesBucketExist` treated everything except `NoSuchBucketException` as absent, so `init()` would try to create a bucket that already exists; a least-privilege policy denies that and the assetstore comes up dead. Only 404 and `NoSuchBucketException` now answer false. `doesObjectExist` likewise stops reporting a 403 or a timeout as "object does not exist" with the cause hidden at DEBUG. **`s3ChecksumAlgorithm` was dead configuration.** `bitstore.xml` wires `SyncS3BitStoreService`, which overrides `put()`, so the parent's `putObject` - the only consumer - never ran. `uploadFluently` now applies it. Also `${assetstore.s3.endpoint:}` is self-defaulting, matching vanilla. Tests: S3BitStoreServiceIT 15, ClarinS3BitStoreServiceIT 8, S3DirectDownloadServiceTest 11, ContentDispositionUtilsTest 9 - all 0 failures, checkstyle 0 violations. Maven does not recompile edited sources reliably here, so every run used `-Dmaven.compiler.useIncrementalCompilation=false`. Co-Authored-By: Claude Opus 5 (1M context) --- .../storage/bitstore/S3BitStoreService.java | 14 ++- .../bitstore/S3DirectDownloadServiceImpl.java | 66 +++++++++---- .../bitstore/SyncS3BitStoreService.java | 84 ++++++++++------- .../service/S3DirectDownloadService.java | 16 ++++ .../dspace/util/ContentDispositionUtils.java | 57 ++++++++++++ .../bitstore/ClarinS3BitStoreServiceIT.java | 65 ++++++++++++- .../bitstore/S3DirectDownloadServiceTest.java | 46 ++++++++- .../util/ContentDispositionUtilsTest.java | 93 +++++++++++++++++++ .../rest/BitstreamByHandleRestController.java | 16 +--- .../app/rest/BitstreamRestController.java | 7 +- dspace/config/spring/api/bitstore.xml | 2 +- 11 files changed, 395 insertions(+), 71 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/util/ContentDispositionUtils.java create mode 100644 dspace-api/src/test/java/org/dspace/util/ContentDispositionUtilsTest.java diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java index e5ab66e70c0..d70cad3bbe9 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3BitStoreService.java @@ -58,6 +58,7 @@ import software.amazon.awssdk.services.s3.model.ChecksumAlgorithm; import software.amazon.awssdk.services.s3.model.HeadObjectResponse; import software.amazon.awssdk.services.s3.model.NoSuchBucketException; +import software.amazon.awssdk.services.s3.model.S3Exception; /** * Asset store using Amazon's Simple Storage Service (S3). @@ -262,11 +263,18 @@ public boolean doesBucketExist(String bucketName ) { s3AsyncClient.headBucket(r -> r.bucket(bucketName)).join(); return true; } catch (CompletionException ce) { - if (!(ce.getCause() instanceof NoSuchBucketException)) { - log.error("headBucket(" + bucketName + ")", ce.getCause()); + Throwable cause = ce.getCause(); + if (cause instanceof NoSuchBucketException + || (cause instanceof S3Exception + && ((S3Exception) cause).statusCode() == HttpStatusCode.NOT_FOUND)) { + return false; } - return false; + // CLARIN: only a genuinely absent bucket may answer "false". Reporting a 403 as "absent" makes + // init() try to create a bucket that already exists, which a least-privilege policy denies - + // and the assetstore then comes up dead. The v1 SDK's doesBucketExistV2 drew the same line. + log.error("headBucket(" + bucketName + ") failed for a reason other than an absent bucket", cause); + throw ce; } } diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3DirectDownloadServiceImpl.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3DirectDownloadServiceImpl.java index fba2d2807bb..fce4332ea9c 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/S3DirectDownloadServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/S3DirectDownloadServiceImpl.java @@ -9,22 +9,25 @@ import java.io.IOException; import java.net.URI; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; import java.time.Duration; +import java.util.concurrent.CompletionException; import org.apache.commons.lang3.StringUtils; import org.dspace.services.ConfigurationService; import org.dspace.storage.bitstore.service.S3DirectDownloadService; +import org.dspace.util.ContentDispositionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.http.HttpStatusCode; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3.S3AsyncClient; import software.amazon.awssdk.services.s3.S3Configuration; import software.amazon.awssdk.services.s3.model.GetObjectRequest; +import software.amazon.awssdk.services.s3.model.NoSuchKeyException; +import software.amazon.awssdk.services.s3.model.S3Exception; import software.amazon.awssdk.services.s3.presigner.S3Presigner; import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest; @@ -90,17 +93,28 @@ private S3Presigner buildPresigner() { } // otherwise fall back to the default provider chain (IAM role / environment), as S3BitStoreService does - // Mirror S3BitStoreService: an unset or unparseable region falls back to us-east-1 - Region region = Region.US_EAST_1; + // Mirror S3BitStoreService exactly. With explicit credentials it falls back to us-east-1; without + // them it leaves the region unset so the default provider chain resolves it. Hardcoding us-east-1 + // in the no-credentials case would sign against the wrong region on an IAM role outside us-east-1, + // and the signature would be rejected. String regionName = s3BitStoreService.getAwsRegionName(); - if (StringUtils.isNotBlank(regionName)) { + if (StringUtils.isNotBlank(accessKey) && StringUtils.isNotBlank(secretKey)) { + Region region = Region.US_EAST_1; + if (StringUtils.isNotBlank(regionName)) { + try { + region = Region.of(regionName); + } catch (IllegalArgumentException e) { + log.warn("Invalid aws_region: {}", regionName); + } + } + builder.region(region); + } else if (StringUtils.isNotBlank(regionName)) { try { - region = Region.of(regionName); + builder.region(Region.of(regionName)); } catch (IllegalArgumentException e) { log.warn("Invalid aws_region: {}", regionName); } } - builder.region(region); String endpoint = s3BitStoreService.getEndpoint(); if (StringUtils.isNotBlank(endpoint)) { @@ -115,18 +129,40 @@ private S3Presigner buildPresigner() { /** * Whether the object is really there. A URL we cannot verify is never signed. + * + * The v1 SDK's `doesObjectExist` rethrew anything that was not a 404. There is no v2 equivalent, so + * this stays fail-closed - but a 403, an expired credential or a timeout is a misconfiguration the + * operator has to see, not a missing object, so it is logged at ERROR rather than DEBUG. */ private boolean doesObjectExist(String bucket, String key) { try { s3Client.headObject(r -> r.bucket(bucket).key(key)).join(); return true; + } catch (CompletionException e) { + Throwable cause = e.getCause(); + if (cause instanceof NoSuchKeyException + || (cause instanceof S3Exception + && ((S3Exception) cause).statusCode() == HttpStatusCode.NOT_FOUND)) { + log.debug("headObject(bucket={}, key={}): object not found", bucket, key); + } else { + log.error("headObject(bucket={}, key={}) failed for a reason other than a missing object; " + + "refusing to sign a URL", bucket, key, cause); + } + return false; } catch (Exception e) { - log.debug("headObject(bucket={}, key={}) failed", bucket, key, e); + log.error("headObject(bucket={}, key={}) failed; refusing to sign a URL", bucket, key, e); return false; } } + @Override public String generatePresignedUrl(String bucket, String key, int expirationSeconds, String desiredFilename) { + return generatePresignedUrl(bucket, key, expirationSeconds, desiredFilename, null); + } + + @Override + public String generatePresignedUrl(String bucket, String key, int expirationSeconds, String desiredFilename, + String contentDispositionOverride) { if (desiredFilename == null) { log.error("Cannot generate presigned URL – desired filename is null"); throw new IllegalArgumentException("Desired filename cannot be null"); @@ -140,14 +176,12 @@ public String generatePresignedUrl(String bucket, String key, int expirationSeco throw new IllegalArgumentException("Requested S3 object does not exist"); } - // Add custom response header for filename - to download the file with the desired name - // Remove CRLF and quotes to prevent header injection - String safeName = desiredFilename.replaceAll("[\\r\\n\"]", "_"); - // RFC-5987: percent-encode UTF-8, e.g. filename*=UTF-8''%E2%82%ACrates.txt - String encoded = URLEncoder.encode(desiredFilename, StandardCharsets.UTF_8); - String contentDisposition = String.format( - "attachment; filename=\"%s\"; filename*=UTF-8''%s", - safeName, encoded); + // Add custom response header for filename - to download the file with the desired name. + // The caller passes the disposition it would have served itself, so that redirecting to S3 does not + // silently turn an inline preview into a download; falling back to `attachment` is the safe default. + String contentDisposition = StringUtils.isNotBlank(contentDispositionOverride) + ? contentDispositionOverride + : ContentDispositionUtils.build(ContentDispositionUtils.ATTACHMENT, desiredFilename); try { GetObjectRequest getObjectRequest = GetObjectRequest.builder() diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/SyncS3BitStoreService.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/SyncS3BitStoreService.java index ae81a15b3a8..c9dfbc48328 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/SyncS3BitStoreService.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/SyncS3BitStoreService.java @@ -19,11 +19,8 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import org.apache.commons.io.IOUtils; -import org.apache.commons.io.input.BoundedInputStream; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -31,8 +28,10 @@ import org.dspace.core.Utils; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; +import software.amazon.awssdk.core.FileRequestBodyConfiguration; import software.amazon.awssdk.core.async.AsyncRequestBody; import software.amazon.awssdk.core.exception.SdkException; +import software.amazon.awssdk.services.s3.model.ChecksumAlgorithm; import software.amazon.awssdk.services.s3.model.CompletedMultipartUpload; import software.amazon.awssdk.services.s3.model.CompletedPart; import software.amazon.awssdk.services.s3.model.UploadPartResponse; @@ -52,8 +51,12 @@ public class SyncS3BitStoreService extends S3BitStoreService { /** * The uploading file is divided into parts and each part is uploaded separately. The size of the part is 50 MB. + * + * Settable so the multipart path can actually be exercised by a test: with the 50 MB default and a small + * fixture the loop only ever runs once, which leaves the offset arithmetic and the last-part handling + * unverified. S3 requires every part except the last to be at least 5 MB. */ - private static final long UPLOAD_FILE_PART_SIZE = 50 * 1024 * 1024; // 50 MB + private long uploadPartSizeBytes = 50 * 1024 * 1024; // 50 MB /** * Upload large file by parts - check the checksum of every part @@ -189,8 +192,15 @@ private void createFileIfNotExist(File localFile) throws IOException { * @param scratchFile the file to upload */ private void uploadFluently(String key, File scratchFile) { - s3AsyncClient.putObject(r -> r.bucket(getBucketName()).key(key), - AsyncRequestBody.fromFile(scratchFile)).join(); + // `assetstore.s3.s3ChecksumAlgorithm` would otherwise be dead configuration for the fork: + // bitstore.xml wires this class, which overrides put(), so the parent's putObject never runs. + ChecksumAlgorithm algorithm = getS3ChecksumAlgorithm(); + s3AsyncClient.putObject(r -> { + r.bucket(getBucketName()).key(key); + if (algorithm != null) { + r.checksumAlgorithm(algorithm); + } + }, AsyncRequestBody.fromFile(scratchFile)).join(); } /** @@ -217,7 +227,6 @@ private void uploadByParts(String key, File scratchFile) throws IOException { // Create a list to hold the ETags for individual parts List completedParts = new ArrayList<>(); - ExecutorService executor = Executors.newSingleThreadExecutor(); try { // Upload parts long fileLength = scratchFile.length(); @@ -225,20 +234,24 @@ private void uploadByParts(String key, File scratchFile) throws IOException { int partNumber = 1; while (remainingBytes > 0) { - long bytesToUpload = Math.min(UPLOAD_FILE_PART_SIZE, remainingBytes); + long bytesToUpload = Math.min(uploadPartSizeBytes, remainingBytes); long offset = fileLength - remainingBytes; // Calculate the checksum for the part String partChecksum = calculatePartChecksum(scratchFile, offset, bytesToUpload, digest); final int currentPartNumber = partNumber; - UploadPartResponse uploadPartResponse; - try (InputStream partStream = openPart(scratchFile, offset, bytesToUpload)) { - // Upload the part - uploadPartResponse = s3AsyncClient.uploadPart( - r -> r.bucket(getBucketName()).key(key).uploadId(uploadId).partNumber(currentPartNumber), - AsyncRequestBody.fromInputStream(partStream, bytesToUpload, executor)).join(); - } + // A file-backed body, not a stream: the SDK re-reads it from the start when it retries a + // part. A non-resettable stream makes any transient S3 error permanent + // ("Request cannot be retried, because the request stream could not be reset"), which is + // what the v1 SDK avoided by taking the file plus an offset. + UploadPartResponse uploadPartResponse = s3AsyncClient.uploadPart( + r -> r.bucket(getBucketName()).key(key).uploadId(uploadId).partNumber(currentPartNumber), + AsyncRequestBody.fromFile(FileRequestBodyConfiguration.builder() + .path(scratchFile.toPath()) + .position(offset) + .numBytesToRead(bytesToUpload) + .build())).join(); // Collect the ETag for the part completedParts.add(CompletedPart.builder() @@ -263,33 +276,42 @@ private void uploadByParts(String key, File scratchFile) throws IOException { // Complete the multipart upload s3AsyncClient.completeMultipartUpload(r -> r.bucket(getBucketName()).key(key).uploadId(uploadId) .multipartUpload(CompletedMultipartUpload.builder().parts(completedParts).build())).join(); + } catch (IOException e) { + abortQuietly(key, uploadId); + throw e; } catch (SdkException | CompletionException e) { - log.error("Cannot upload the file by parts because: ", e); - } finally { - executor.shutdown(); + // This used to be logged and swallowed, which let put() carry on and record a bitstream that + // S3 does not actually hold - silent data loss. Abort so the parts are not billed forever. + abortQuietly(key, uploadId); + throw new IOException("Multipart upload of " + key + " failed", e); } } /** - * Open a stream over exactly one part of the file, without loading it into memory. + * Abort a multipart upload, reporting but not rethrowing - the caller is already failing and the + * original cause is the one worth propagating. * - * @param file the uploading file - * @param offset the offset in the file - * @param length the length of the part - * @return a stream bounded to the requested part - * @throws IOException if an I/O error occurs + * @param key the bitstream's internalId + * @param uploadId the multipart upload to abort */ - private static InputStream openPart(File file, long offset, long length) throws IOException { - FileInputStream fis = new FileInputStream(file); + private void abortQuietly(String key, String uploadId) { try { - fis.getChannel().position(offset); - return BoundedInputStream.builder().setInputStream(fis).setMaxCount(length).get(); - } catch (IOException | RuntimeException e) { - fis.close(); - throw e; + s3AsyncClient.abortMultipartUpload( + r -> r.bucket(getBucketName()).key(key).uploadId(uploadId)).join(); + } catch (SdkException | CompletionException e) { + log.error("Could not abort multipart upload " + uploadId + " for " + key + + "; its parts will remain until a lifecycle rule removes them", e); } } + public long getUploadPartSizeBytes() { + return uploadPartSizeBytes; + } + + public void setUploadPartSizeBytes(long uploadPartSizeBytes) { + this.uploadPartSizeBytes = uploadPartSizeBytes; + } + /** * Calculate the checksum of the specified part of the file (Multipart upload) * diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/service/S3DirectDownloadService.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/service/S3DirectDownloadService.java index bf7572d21a2..b989a8fdc69 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/service/S3DirectDownloadService.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/service/S3DirectDownloadService.java @@ -26,4 +26,20 @@ public interface S3DirectDownloadService { */ String generatePresignedUrl(String bucket, String key, int expirationSeconds, String bitstreamName) throws UnsupportedEncodingException; + + /** + * Generate a presigned URL, serving it with a caller-supplied Content-Disposition. + * + * Without this the redirect always forced `attachment`, so turning on direct downloads silently + * disabled inline preview for every format in `webui.content_disposition_inline`. + * + * @param bucket The S3 bucket name + * @param key The bitstream path in the S3 bucket + * @param expirationSeconds The number of seconds until the URL expires + * @param bitstreamName The name of the bitstream, used when no override is given + * @param contentDispositionOverride The exact Content-Disposition to serve, or null for `attachment` + * @return A string containing the presigned URL for direct download access + */ + String generatePresignedUrl(String bucket, String key, int expirationSeconds, String bitstreamName, + String contentDispositionOverride) throws UnsupportedEncodingException; } diff --git a/dspace-api/src/main/java/org/dspace/util/ContentDispositionUtils.java b/dspace-api/src/main/java/org/dspace/util/ContentDispositionUtils.java new file mode 100644 index 00000000000..91487175b4d --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/util/ContentDispositionUtils.java @@ -0,0 +1,57 @@ +/** + * 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.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +/** + * Builds a `Content-Disposition` header value from a bitstream name. + * + * The fork had two independent implementations of this, one of them wrong: it escaped `"` but not `\`, + * so a bitstream named `evil\` terminated the quoted string early and swallowed the `filename*` + * parameter; and it used {@link URLEncoder} directly, which encodes a space as `+`. RFC 8187 treats + * `+` as a literal character, so `my report.pdf` arrived as `my+report.pdf`. + * + * @author Milan Majchrak (dspace at dataquest.sk) + */ +public final class ContentDispositionUtils { + + public static final String ATTACHMENT = "attachment"; + public static final String INLINE = "inline"; + + private ContentDispositionUtils() { + } + + /** + * Build a `Content-Disposition` value carrying both the RFC 6266 ASCII fallback and the RFC 8187 + * percent-encoded UTF-8 name. + * + * @param disposition `attachment` or `inline` + * @param name the bitstream name; must not be null + * @return the header value + */ + public static String build(String disposition, String name) { + if (name == null) { + throw new IllegalArgumentException("Bitstream name cannot be null"); + } + + // RFC 8187 percent-encoding for filename*. URLEncoder is form-encoding, so `+` has to be + // converted back to `%20` - a literal `+` in a filename is already encoded as `%2B` by then. + String encoded = URLEncoder.encode(name, StandardCharsets.UTF_8) + .replace("+", "%20"); + + // ASCII fallback for clients that ignore filename*. Non-ASCII becomes `_`; backslash and quote + // are escaped, in that order, so the quoted-string cannot be terminated early. + String asciiFallback = name.replaceAll("[^\\x20-\\x7E]", "_") + .replace("\\", "\\\\") + .replace("\"", "\\\""); + + return String.format("%s; filename=\"%s\"; filename*=UTF-8''%s", disposition, asciiFallback, encoded); + } +} diff --git a/dspace-api/src/test/java/org/dspace/storage/bitstore/ClarinS3BitStoreServiceIT.java b/dspace-api/src/test/java/org/dspace/storage/bitstore/ClarinS3BitStoreServiceIT.java index 9a3e30445c0..da93ba05ef3 100644 --- a/dspace-api/src/test/java/org/dspace/storage/bitstore/ClarinS3BitStoreServiceIT.java +++ b/dspace-api/src/test/java/org/dspace/storage/bitstore/ClarinS3BitStoreServiceIT.java @@ -11,13 +11,16 @@ import static org.apache.commons.io.IOUtils.toInputStream; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.nio.file.Files; import java.sql.SQLException; @@ -163,6 +166,58 @@ public void syncStoreUploadsByParts() throws Exception { assertThat(bitstream.getSizeBytes(), is((long) content.length())); } + /** + * The same path across a real part boundary. With the 50 MB production part size and a short fixture + * the loop only ever ran once, so the offset arithmetic, the ranged request body and the ordering of + * CompletedParts were never executed - a review found the loop untested even though it had just been + * rewritten. S3 requires every part but the last to be at least 5 MB, so the part size is lowered + * rather than the fixture made enormous. + */ + @Test + public void syncStoreUploadsInMultipleParts() throws Exception { + int partSize = 5 * 1024 * 1024; + byte[] content = new byte[partSize + 4096]; + for (int i = 0; i < content.length; i++) { + content[i] = (byte) (i % 251); + } + + SyncS3BitStoreService store = initSyncStore(false, true); + store.setUploadPartSizeBytes(partSize); + + Bitstream bitstream = createBitstream(content); + store.put(bitstream, new ByteArrayInputStream(content)); + + assertThat(bitstream.getSizeBytes(), is((long) content.length)); + // read the object back byte for byte - a wrong offset would corrupt the second part silently + assertArrayEquals(content, FileUtils.readFileToByteArray(store.getFile(bitstream))); + } + + /** + * The endpoint override and path-style flag are the fork's headline S3 delta, and every other test + * injects a ready-made client - so `amazonClientBuilderBy` never ran and a tripwire planted in it + * left all 21 S3 tests green. This builds a client through that method and uses it for real. + */ + @Test + public void clientBuilderAppliesEndpointAndPathStyle() { + S3AsyncClient client = S3BitStoreService.amazonClientBuilderBy( + Region.of(localstackContainer.getRegion()), + StaticCredentialsProvider.create(AwsBasicCredentials.create( + localstackContainer.getAccessKey(), localstackContainer.getSecretKey())), + localstackContainer.getEndpoint().toString(), + 10.0, + 8 * 1024 * 1024L, + null, + true).get(); + + try { + // Only reachable if endpointOverride was applied - the default endpoint is real AWS. + client.createBucket(r -> r.bucket("clarin-builder-probe")).join(); + assertTrue(new S3BitStoreService(client).doesBucketExist("clarin-builder-probe")); + } finally { + client.close(); + } + } + /** * The reason SyncS3BitStoreService exists: every asset is also written to the local assetstore. */ @@ -248,11 +303,19 @@ private boolean objectExists(S3BitStoreService store, Bitstream bitstream) { } private Bitstream createBitstream(String content) { + return createBitstream(toInputStream(content, UTF_8)); + } + + private Bitstream createBitstream(byte[] content) { + return createBitstream(new ByteArrayInputStream(content)); + } + + private Bitstream createBitstream(InputStream content) { context.turnOffAuthorisationSystem(); try { Item item = ItemBuilder.createItem(context, collection).build(); return BitstreamBuilder - .createBitstream(context, item, toInputStream(content, UTF_8)) + .createBitstream(context, item, content) .build(); } catch (SQLException | AuthorizeException | IOException e) { throw new RuntimeException(e); diff --git a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3DirectDownloadServiceTest.java b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3DirectDownloadServiceTest.java index 1f01864495d..568c140cc21 100644 --- a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3DirectDownloadServiceTest.java +++ b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3DirectDownloadServiceTest.java @@ -147,17 +147,53 @@ public void weirdFilename() throws Exception { assertTrue(cd.contains("filename=")); assertTrue(cd.contains("filename*=")); - // Make sure the filename is sanitized, sanitized are only `\\r\\n\"` + // The ASCII fallback now drops every control character, not just CR/LF - the old sanitiser let a + // TAB through and, worse, did not escape a backslash, so a name ending in `\` terminated the + // quoted string early and swallowed the filename* parameter. String fallbackName = cd.split("filename=\"")[1].split("\"")[0]; - assertTrue(fallbackName.contains("../")); - assertTrue(fallbackName.contains("\t")); - assertFalse(fallbackName.contains("\n")); - assertFalse(fallbackName.contains("\"")); + assertTrue(fallbackName, fallbackName.contains("../")); + assertFalse(fallbackName, fallbackName.contains("\t")); + assertFalse(fallbackName, fallbackName.contains("\n")); + assertFalse(fallbackName, fallbackName.contains("\r")); // It's valid and desirable to include UTF-8 assertTrue(cd.contains("UTF-8")); } + // A trailing backslash used to escape the closing quote and swallow filename* + @Test + public void backslashInFilename() throws Exception { + presignReturns("https://backslash"); + + s3DirectDownloadService.generatePresignedUrl("b", "k", 60, "evil\\"); + String cd = captureRequest().getObjectRequest().responseContentDisposition(); + + assertTrue(cd, cd.contains("filename=\"evil\\\\\"")); + assertTrue(cd, cd.contains("filename*=UTF-8''evil%5C")); + } + + // The caller's disposition has to win, otherwise enabling direct downloads kills inline preview + @Test + public void callerSuppliedDispositionIsUsedVerbatim() throws Exception { + presignReturns("https://inline"); + + String supplied = "inline; filename=\"paper.pdf\"; filename*=UTF-8''paper.pdf"; + s3DirectDownloadService.generatePresignedUrl("b", "k", 60, "paper.pdf", supplied); + + assertEquals(supplied, captureRequest().getObjectRequest().responseContentDisposition()); + } + + // ... and a blank override still falls back to attachment + @Test + public void blankDispositionFallsBackToAttachment() throws Exception { + presignReturns("https://fallback"); + + s3DirectDownloadService.generatePresignedUrl("b", "k", 60, "paper.pdf", null); + + assertTrue(captureRequest().getObjectRequest().responseContentDisposition() + .startsWith("attachment; ")); + } + // Null filename → IllegalArgumentException @Test(expected = IllegalArgumentException.class) public void nullFilename() throws Exception { diff --git a/dspace-api/src/test/java/org/dspace/util/ContentDispositionUtilsTest.java b/dspace-api/src/test/java/org/dspace/util/ContentDispositionUtilsTest.java new file mode 100644 index 00000000000..4fc53a58a5f --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/util/ContentDispositionUtilsTest.java @@ -0,0 +1,93 @@ +/** + * 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +/** + * The two defects this helper was extracted to fix are the first two tests: a backslash used to escape + * the closing quote of the ASCII fallback, and a space used to arrive as `+`. + * + * @author Milan Majchrak (dspace at dataquest.sk) + */ +public class ContentDispositionUtilsTest { + + @Test + public void backslashMustNotTerminateTheQuotedString() { + String header = ContentDispositionUtils.build(ContentDispositionUtils.ATTACHMENT, "evil\\"); + + // the fallback has to carry an escaped backslash, not a bare one that eats the closing quote + assertTrue(header, header.contains("filename=\"evil\\\\\"")); + // and filename* must still be present and parseable + assertTrue(header, header.contains("filename*=UTF-8''evil%5C")); + } + + @Test + public void spaceMustBePercentEncodedNotPlus() { + String header = ContentDispositionUtils.build(ContentDispositionUtils.ATTACHMENT, "my report.pdf"); + + assertTrue(header, header.contains("filename*=UTF-8''my%20report.pdf")); + assertFalse(header, header.contains("+")); + } + + @Test + public void literalPlusSurvivesAsPercent2B() { + String header = ContentDispositionUtils.build(ContentDispositionUtils.ATTACHMENT, "a+b.txt"); + + assertTrue(header, header.contains("filename*=UTF-8''a%2Bb.txt")); + } + + @Test + public void quoteIsEscapedInTheFallback() { + String header = ContentDispositionUtils.build(ContentDispositionUtils.ATTACHMENT, "say \"hi\".txt"); + + assertTrue(header, header.contains("filename=\"say \\\"hi\\\".txt\"")); + } + + @Test + public void crlfCannotInjectAHeader() { + String header = ContentDispositionUtils.build(ContentDispositionUtils.ATTACHMENT, "a\r\nX-Evil: 1.txt"); + + assertFalse(header, header.contains("\r")); + assertFalse(header, header.contains("\n")); + } + + @Test + public void nonAsciiFallsBackToUnderscoresButKeepsUtf8Name() { + String header = ContentDispositionUtils.build(ContentDispositionUtils.ATTACHMENT, "žluťoučký.txt"); + + // only the four non-ASCII letters are replaced, one underscore each + assertTrue(header, header.contains("filename=\"_lu_ou_k_.txt\"")); + assertTrue(header, header.contains("filename*=UTF-8''%C5%BElu%C5%A5ou%C4%8Dk%C3%BD.txt")); + } + + @Test + public void dispositionIsHonoured() { + assertTrue(ContentDispositionUtils.build(ContentDispositionUtils.INLINE, "a.pdf") + .startsWith("inline; ")); + assertTrue(ContentDispositionUtils.build(ContentDispositionUtils.ATTACHMENT, "a.pdf") + .startsWith("attachment; ")); + } + + @Test + public void plainNameRoundTrips() { + assertEquals("attachment; filename=\"corpus.zip\"; filename*=UTF-8''corpus.zip", + ContentDispositionUtils.build(ContentDispositionUtils.ATTACHMENT, "corpus.zip")); + } + + @Test + public void nullNameIsRejected() { + assertThrows(IllegalArgumentException.class, + () -> ContentDispositionUtils.build(ContentDispositionUtils.ATTACHMENT, null)); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamByHandleRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamByHandleRestController.java index 36cdffc9b20..82988a41324 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamByHandleRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamByHandleRestController.java @@ -12,8 +12,6 @@ import java.io.IOException; import java.io.InputStream; import java.net.URI; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; import java.sql.SQLException; import java.util.List; import java.util.Objects; @@ -41,6 +39,7 @@ import org.dspace.services.EventService; import org.dspace.storage.bitstore.S3BitStoreService; import org.dspace.storage.bitstore.service.S3DirectDownloadService; +import org.dspace.util.ContentDispositionUtils; import org.dspace.usage.UsageEvent; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; @@ -288,16 +287,9 @@ private void redirectToS3DownloadUrl(String bitName, String bitInternalId, * @return the Content-Disposition header value */ private String buildContentDisposition(String name) { - // RFC 5987 percent-encoding for filename* - String encoded = URLEncoder.encode(name, StandardCharsets.UTF_8) - .replace("+", "%20"); - // ASCII fallback: replace non-ASCII chars with underscore, escape quotes. - // Modern clients use filename* (RFC 5987 / RFC 6266) with real UTF-8 name. - String asciiFallback = name.replaceAll("[^\\x20-\\x7E]", "_") - .replace("\\", "\\\\") - .replace("\"", "\\\""); - return String.format("attachment; filename=\"%s\"; filename*=UTF-8''%s", - asciiFallback, encoded); + // Delegates to the shared helper; this used to be one of two divergent copies of the same logic, + // and the other one - in the S3 presigned-URL path - was the incorrect one. + return ContentDispositionUtils.build(ContentDispositionUtils.ATTACHMENT, name); } /** diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java index 99ea76fd5d6..ec5ddf1d70d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java @@ -250,8 +250,11 @@ private ResponseEntity redirectToS3DownloadUrl(HttpHeaders httpHeaders, String b // Generate a presigned URL for the bitstream with a configurable expiration time int expirationTime = configurationService.getIntProperty("s3.download.direct.expiration", 3600); log.debug("Generating presigned URL with expiration time of {} seconds", expirationTime); - String presignedUrl = - s3DirectDownloadService.generatePresignedUrl(bucket, bitstreamPath, expirationTime, bitName); + // Serve the same Content-Disposition the non-redirect path would have sent, so that enabling + // direct downloads does not silently turn every inline preview into a forced download. + String contentDisposition = httpHeaders.getFirst(HttpHeaders.CONTENT_DISPOSITION); + String presignedUrl = s3DirectDownloadService.generatePresignedUrl( + bucket, bitstreamPath, expirationTime, bitName, contentDisposition); if (StringUtils.isBlank(presignedUrl)) { throw new InternalServerErrorException("Failed to generate presigned URL for bitstream: " diff --git a/dspace/config/spring/api/bitstore.xml b/dspace/config/spring/api/bitstore.xml index 2a8dc2c8d22..9cd0dad6d2f 100644 --- a/dspace/config/spring/api/bitstore.xml +++ b/dspace/config/spring/api/bitstore.xml @@ -36,7 +36,7 @@ - + From 4b5390f9d47edaa00257c6ccd8b056a3f751ac29 Mon Sep 17 00:00:00 2001 From: milanmajchrak Date: Tue, 4 Aug 2026 07:59:52 +0200 Subject: [PATCH 939/939] Add the OAI sanitize IT the change never had `ItemUtils.sanitize()` decides what VLO and OLAC receive, was changed twice in this PR, and had no test at all - a reviewer called it the highest-risk file in the upgrade for exactly that reason. `OAIPMHBundleExposureIT` already drove `ItemUtils.retrieveMetadata()`, so the harness existed; this uses the same serialisation path as `DSpaceXOAIItemCacheService.put()` and re-parses the result, because writing alone proves nothing - `U+FFFE` serialises happily and only fails on the harvester's parser. Four cases: metacharacters round-trip exactly once (the double-escaping 7.6.6 introduced); control characters and unpaired surrogates are removed rather than throwing inside `XOAI.index()`, which catches per item and would silently drop the record; supplementary characters and `U+1FFFE` survive while `U+FFFE` does not; and the output always re-parses. Verified non-vacuous: making `sanitize()` return its input unchanged turns three of the four red, with `WritingXmlException` and `SAXParseException` - the exact failure modes the fix exists to prevent. Two things the test itself established, both recorded in comments so nobody re-derives them: `U+0000` never reaches the sanitiser because PostgreSQL cannot store a NUL in a text column, and DSpace replaces C0 controls with a space before persisting, so the spacing around a removed control character is the persistence layer's doing and not something this test can assert on. Also fixes the import order `ContentDispositionUtils` was inserted at in `BitstreamByHandleRestController` (checkstyle). Tests: OAIPMHSanitizeIT 4, OAIPMHBundleExposureIT 3, 0 failures, checkstyle 0 violations. Co-Authored-By: Claude Opus 5 (1M context) --- .../rest/BitstreamByHandleRestController.java | 2 +- .../org/dspace/app/oai/OAIPMHSanitizeIT.java | 204 ++++++++++++++++++ 2 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/oai/OAIPMHSanitizeIT.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamByHandleRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamByHandleRestController.java index 82988a41324..92ae2ec75cb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamByHandleRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamByHandleRestController.java @@ -39,8 +39,8 @@ import org.dspace.services.EventService; import org.dspace.storage.bitstore.S3BitStoreService; import org.dspace.storage.bitstore.service.S3DirectDownloadService; -import org.dspace.util.ContentDispositionUtils; import org.dspace.usage.UsageEvent; +import org.dspace.util.ContentDispositionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/oai/OAIPMHSanitizeIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/oai/OAIPMHSanitizeIT.java new file mode 100644 index 00000000000..32864425c46 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/oai/OAIPMHSanitizeIT.java @@ -0,0 +1,204 @@ +/** + * 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.app.oai; + +import static com.lyncode.xoai.dataprovider.core.Granularity.Second; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.nio.charset.StandardCharsets; +import javax.xml.parsers.DocumentBuilderFactory; + +import com.lyncode.xoai.dataprovider.xml.XmlOutputContext; +import com.lyncode.xoai.dataprovider.xml.xoai.Metadata; +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.xoai.util.ItemUtils; +import org.junit.Before; +import org.junit.Test; +import org.xml.sax.InputSource; + +/** + * `ItemUtils.sanitize()` decides what reaches VLO and OLAC, and had no test at all. + * + * Two failure modes matter. Escaping the value before the XOAI writer escapes it again produces + * double-escaped output - a title containing `` reaches a harvester as `&lt;b&gt;`, which is + * what 7.6.6's `escapeXml10` did. Leaving XML-1.0-illegal characters in place makes the StAX writer + * throw inside `XOAI.index()`, which catches per item, so the record is silently dropped from the OAI + * index instead of failing loudly. + * + * The assertions run over the real serialisation path used by `DSpaceXOAIItemCacheService.put()`, and + * re-parse the result rather than only writing it - `U+FFFE` writes without complaint and only blows up + * on the harvester's parser. + * + * @author Milan Majchrak (dspace at dataquest.sk) + */ +public class OAIPMHSanitizeIT extends AbstractControllerIntegrationTest { + + private Collection collection; + + @Before + public void setupStructure() { + context.turnOffAuthorisationSystem(); + Community community = CommunityBuilder.createCommunity(context) + .withName("Sanitize Test Community") + .build(); + collection = CollectionBuilder.createCollection(context, community) + .withName("Sanitize Test Collection") + .build(); + context.restoreAuthSystemState(); + } + + /** + * The regression 7.6.6 introduced: metacharacters must survive one round trip, not two escapes. + */ + @Test + public void metacharactersRoundTripExactlyOnce() throws Exception { + String title = "Corpus of Czech & \"spoken\" 'texts' > 2000"; + + String xml = serialize(buildItem(title)); + + // written form is escaped once - if it were escaped twice this would contain `&lt;` + assertThat(xml, not(org.hamcrest.Matchers.containsString("&lt;"))); + assertThat(xml, not(org.hamcrest.Matchers.containsString("&amp;"))); + // and parsing it back yields the original characters + assertThat(parsedTitle(xml), is(title)); + } + + /** + * Control characters and unpaired surrogates are what actually drop records. + */ + @Test + public void illegalCharactersAreRemovedInsteadOfDroppingTheRecord() throws Exception { + // built from explicit code points so the source file carries no raw control bytes. + // U+0000 is deliberately absent: PostgreSQL cannot store a NUL in a text column, so it never + // reaches the sanitiser and asserting on it would be testing the persistence layer instead. + char backspace = (char) 0x08; + char verticalTab = (char) 0x0B; + char loneHigh = '\uD800'; + char loneLow = '\uDC00'; + char[] illegal = {backspace, verticalTab, loneHigh, loneLow}; + + String title = "corpus with " + backspace + " controls " + verticalTab + + " and " + loneHigh + " a lone high and " + loneLow + " a lone low surrogate"; + + String parsed = parsedTitle(serialize(buildItem(title))); + + assertThat("the record must still carry a title", StringUtils.isNotBlank(parsed), is(true)); + for (char c : illegal) { + assertThat("illegal code unit " + (int) c + " leaked into the OAI output", + parsed.indexOf(c), is(-1)); + } + + // No visible text may be lost. Whitespace is normalised out of the comparison on purpose: + // DSpace replaces C0 controls with a space on the way into the database, so by the time the + // value reaches sanitize() the spacing is already not ours to predict - asserting on it would + // be testing the persistence layer. What is ours is that nothing else disappears. + String expected = title; + for (char c : illegal) { + expected = expected.replace(String.valueOf(c), ""); + } + assertThat(parsed.replaceAll("\\s+", " ").trim(), + is(expected.replaceAll("\\s+", " ").trim())); + } + + /** + * Supplementary characters are legal and must not be mistaken for unpaired surrogates - CLARIN + * metadata carries emoji and non-BMP scripts. `U+1FFFE` is legal XML 1.0, only discouraged, and its + * BMP sibling `U+FFFE` is not - removing the wrong one of the two would be invisible until a + * harvester re-parses the page. + */ + @Test + public void supplementaryCharactersSurviveButNoncharactersInTheBmpDoNot() throws Exception { + // written as surrogate pairs so the source file stays plain ASCII + String emoji = "😀"; // U+1F600 grinning face + String linearB = "𐀀"; // U+10000, the lowest supplementary code point + String legalNoncharacter = "🿾"; // U+1FFFE - legal XML 1.0, merely discouraged + char illegalNoncharacter = (char) 0xFFFE; // U+FFFE - illegal, breaks a re-parse + + String title = "emoji " + emoji + " rare " + linearB + " legal " + legalNoncharacter + + " illegal " + illegalNoncharacter + " end"; + + String parsed = parsedTitle(serialize(buildItem(title))); + + assertTrue("emoji must survive", parsed.contains(emoji)); + assertTrue("valid surrogate pair must survive", parsed.contains(linearB)); + assertTrue("U+1FFFE is legal XML 1.0 and must survive", parsed.contains(legalNoncharacter)); + assertThat("U+FFFE is illegal and would break the harvester's parser", + parsed.indexOf(illegalNoncharacter), is(-1)); + } + + /** + * Whatever we emit has to be re-parseable; writing alone proves nothing. + */ + @Test + public void outputIsAlwaysWellFormed() throws Exception { + String title = "]]> " + (char) 0x0C + " " + (char) 0xFFFF + " \" '"; + + String xml = serialize(buildItem(title)); + + DocumentBuilderFactory.newInstance().newDocumentBuilder() + .parse(new InputSource(new java.io.StringReader(xml))); + } + + private Item buildItem(String title) { + context.turnOffAuthorisationSystem(); + try { + return ItemBuilder.createItem(context, collection).withTitle(title).build(); + } finally { + context.restoreAuthSystemState(); + } + } + + /** + * Exactly what DSpaceXOAIItemCacheService.put() does. + */ + private String serialize(Item item) throws Exception { + Metadata metadata = ItemUtils.retrieveMetadata(context, item); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + XmlOutputContext outputContext = XmlOutputContext.emptyContext(output, Second); + metadata.write(outputContext); + outputContext.getWriter().flush(); + outputContext.getWriter().close(); + return output.toString(StandardCharsets.UTF_8); + } + + /** + * Pull dc.title back out of the serialised document by parsing it, so the assertions are about what + * a harvester sees rather than about our own in-memory objects. + */ + private String parsedTitle(String xml) throws Exception { + org.w3c.dom.Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder() + .parse(new InputSource(new java.io.StringReader(xml))); + // … + org.w3c.dom.NodeList elements = doc.getElementsByTagName("element"); + for (int i = 0; i < elements.getLength(); i++) { + org.w3c.dom.Element element = (org.w3c.dom.Element) elements.item(i); + if (!"title".equals(element.getAttribute("name"))) { + continue; + } + org.w3c.dom.NodeList fields = element.getElementsByTagName("field"); + for (int j = 0; j < fields.getLength(); j++) { + org.w3c.dom.Element field = (org.w3c.dom.Element) fields.item(j); + if ("value".equals(field.getAttribute("name"))) { + return field.getTextContent(); + } + } + } + throw new AssertionError("no dc.title in the serialised XOAI document:\n" + xml); + } +}