From 31d36e7abf89f188fffba004520e4cbe2c4890a0 Mon Sep 17 00:00:00 2001 From: Miika Nurminen Date: Tue, 27 Aug 2024 08:46:15 +0300 Subject: [PATCH 001/180] 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 22b18724b9..242b231654 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 class BitstreamRestController { 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 class BitstreamRestController { 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 4e5545fabc..2f47dacc7c 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.Set; 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.factory.EPersonServiceFactory; 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 @@ import org.springframework.core.io.AbstractResource; */ 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 class BitstreamResource extends AbstractResource { */ 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 class BitstreamResource extends AbstractResource { @Override public InputStream getInputStream() throws IOException { - try (Context context = initializeContext()) { + fetchDocument(); - Bitstream bitstream = bitstreamService.find(context, uuid); - InputStream out; - - if (shouldGenerateCoverPage) { - out = new ByteArrayInputStream(getCoverpageByteArray(context, bitstream)); - } else { - out = bitstreamService.retrieve(context, bitstream); - } - - this.file = null; - return out; - } catch (SQLException | AuthorizeException e) { - throw new IOException(e); - } + return this.documentInputStream; } @Override @@ -111,17 +103,63 @@ public class BitstreamResource extends AbstractResource { } @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 @@ public class BitstreamResource extends AbstractResource { 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 855fedbaa2..45f8565fba 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.dspace.core.Constants.WRITE; 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.IOException; 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.junit.Test; 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 class BitstreamRestControllerIT extends AbstractControllerIntegrationTest //** 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 class BitstreamRestControllerIT extends AbstractControllerIntegrationTest // 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 @@ public class BitstreamRestControllerIT extends AbstractControllerIntegrationTest 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 745e9c468a083b916f89682f414126e0f044bc7c Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Thu, 16 Jan 2025 12:01:13 +0100 Subject: [PATCH 002/180] 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 78ad6b7b79..7be5cbc0d8 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 java.util.List; 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 class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident 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 @@ public class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident * 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 @@ public class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident 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 0c58cc1de9..9e59952744 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. --> - - - + + + - - - - + + + + - + @@ -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 009/180] Remove unused byte-buddy --- pom.xml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pom.xml b/pom.xml index 9695ae6830..28be939a99 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 010/180] 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 7710ec7eed..6c93761180 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 011/180] 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 7710ec7eed..da8f18e3b9 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 012/180] 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 e90773d1de..29b9865a5a 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 013/180] 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 7710ec7eed..505395fafe 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 014/180] 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 7710ec7eed..ce859ee7e7 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 015/180] Dependency convergence fix --- pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pom.xml b/pom.xml index ce859ee7e7..313a6bbfe9 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 016/180] [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 0000000000..a8f667eb97 --- /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 f61a81140d..a182e7d890 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 @@ public class CrossRefImportMetadataSourceServiceIT extends AbstractLiveImportInt "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 @@ public class CrossRefImportMetadataSourceServiceIT extends AbstractLiveImportInt 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 0000000000..3bd4468148 --- /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 4786c44a78..6fe9723d79 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 017/180] 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 73acab877f..6cac529978 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 018/180] 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 84bba38473..0de103004d 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 019/180] 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 84bba38473..a6d2a60846 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 020/180] 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 f3fd8613fb..5210580388 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 021/180] 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 29b9865a5a..322e1820d3 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 022/180] 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 34efe96ae7..29b695b5e7 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.SolrQuery; 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 @@ public class SolrServiceImpl implements SearchService, IndexingService { */ 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 023/180] 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 507503ffaa..1a142802d4 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 class DOIOrganiser { + " 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 class DOIOrganiser { 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 class DOIOrganiser { + 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 @@ public class DOIOrganiser { 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 024/180] 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 92a973dff8..b816e22253 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 @@ public class DiscoverQueryBuilder implements InitializingBean { 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 025/180] 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 8dc8239ca5..c77c3bf10e 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 @@ public class DCInputsReader { } // sanity check number of fields if (fields.size() < 1) { - throw new DCInputsReaderException("Form " + formName + "row " + rowIdx + " has no fields"); + throw new DCInputsReaderException("Form " + formName + ", row " + rowIdx + " has no fields"); } } From bdfe2bfe2e8263c2cfe150c5d99048c870daad7f Mon Sep 17 00:00:00 2001 From: jameswsullivan <81947235+jameswsullivan@users.noreply.github.com> Date: Fri, 21 Mar 2025 19:01:09 +0000 Subject: [PATCH 026/180] 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 2d26795778..16c16b0115 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 @@ public class XOAI { 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 027/180] [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 7aece5acf3..6142dd0dba 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 class SolrServiceFileInfoPlugin implements SolrServiceIndexPlugin { 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 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 028/180] 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 886aa3fb36..6fdf081573 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 029/180] 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 | 91 +++++++++---------- .../impl/OrcidV3AuthorDataProvider.java | 81 +++++++++-------- .../model/factory/OrcidFactoryUtils.java | 55 +++++++++++ .../org/dspace/authority/orcid/MockOrcid.java | 17 +++- 4 files changed, 155 insertions(+), 89 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 6753a5d113..494daa9773 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 class Orcidv3SolrAuthorityImpl implements SolrAuthorityInterface { 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"); + // Initialize access token at spring instantiation. If it fails, the access token will be null rather + // than causing a fatal Spring startup error + initializeAccessToken(); + } - 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); - } + 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 class Orcidv3SolrAuthorityImpl implements SolrAuthorityInterface { */ @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 class Orcidv3SolrAuthorityImpl implements SolrAuthorityInterface { */ @Override public AuthorityValue queryAuthorityID(String id) { - init(); + initializeAccessToken(); Person person = getBio(id); AuthorityValue valueFromPerson = Orcidv3AuthorityValue.create(person); return valueFromPerson; @@ -151,11 +137,14 @@ public class Orcidv3SolrAuthorityImpl implements SolrAuthorityInterface { 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 class Orcidv3SolrAuthorityImpl implements SolrAuthorityInterface { * @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 125da8f7c6..c7e41171a5 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.OrcidRestConnector; 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 class OrcidV3AuthorDataProvider extends AbstractExternalDataProvider { * @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"); + // Initialize access token at spring instantiation. If it fails, the access token will be null rather + // than causing a fatal Spring startup error + initializeAccessToken(); + } - 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, 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 class OrcidV3AuthorDataProvider extends AbstractExternalDataProvider { 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 @@ public class OrcidV3AuthorDataProvider extends AbstractExternalDataProvider { @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 class OrcidV3AuthorDataProvider extends AbstractExternalDataProvider { } 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 class OrcidV3AuthorDataProvider extends AbstractExternalDataProvider { @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 4b8c1178ef..38aa611ff3 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 @@ public final class OrcidFactoryUtils { 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 562aa86a58..82dc3fa5cc 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 @@ import org.mockito.stubbing.Answer; */ 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 class MockOrcid extends Orcidv3SolrAuthorityImpl { 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 030/180] 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 4a73b215cd..3e38055b67 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 82dc3fa5cc..511df79f1e 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 @@ import org.mockito.stubbing.Answer; */ 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 class MockOrcid extends Orcidv3SolrAuthorityImpl { 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 30890d7ef8..7a3bc738eb 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.app.rest.test.AbstractControllerIntegrationTest; 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.DCInputAuthority; 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 031/180] 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 3b74f250b5..aec03603ce 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 032/180] 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 c6d335d8dd..0000000000 --- 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 124ed38d33..0000000000 --- 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 00bd9c17a5..0000000000 --- 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 1ffbfca022..0000000000 --- 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 11474782c1..0000000000 --- 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 842e9d483d..0000000000 --- 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 dfd8b6c60f..0000000000 --- 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 e703f79ccc..0000000000 --- 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 3124f02b45..0000000000 --- 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 84815b327e..0000000000 --- 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 a44f21ce2b..0000000000 --- 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 6d43cb68ef..0000000000 --- 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 0df7fd5310..0000000000 --- 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 1355b9d4ab..0000000000 --- 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 42fcc082ff..0000000000 --- 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 81de59ae68..0000000000 --- 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 03489f0d2e..0000000000 --- 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 bc3031c333..0000000000 --- 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 fa97549fd9..0000000000 --- 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 963e9637dd..0000000000 --- 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 ff80fa6e52..0000000000 --- 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 033/180] 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 322e1820d3..dc5ce96df3 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 034/180] 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 322e1820d3..50e5f44bc7 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 035/180] 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 886aa3fb36..ec07adf6bf 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 036/180] 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 886aa3fb36..6debf4a73f 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 037/180] 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 886aa3fb36..3e8092aa5c 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 038/180] 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 886aa3fb36..6bc32b1206 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 039/180] 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 ad78fe2db4..15b9bd9506 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 java.util.Arrays; 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 class ActuatorConfiguration { 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 0000000000..d936fce635 --- /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 040/180] 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 0b4fe8288c..ab0d14de6e 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 041/180] 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 d936fce635..a071e12088 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 @@ public class SEOHealthIndicator extends AbstractHealthIndicator { 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 @@ public class SEOHealthIndicator extends AbstractHealthIndicator { } } - 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 @@ public class SEOHealthIndicator extends AbstractHealthIndicator { 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 042/180] 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 a071e12088..740c6ab649 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 @@ public class SEOHealthIndicator extends AbstractHealthIndicator { .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 @@ public class SEOHealthIndicator extends AbstractHealthIndicator { "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 043/180] 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 740c6ab649..5b57f2d537 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 @@ public class SEOHealthIndicator extends AbstractHealthIndicator { 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 044/180] 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 d48979c1c0..b5d4fdfb66 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 045/180] 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 5fe8ca54d9..1b051ae32f 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 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i 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 50e60087ad..1e18723ca9 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 class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes .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 class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes 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 046/180] [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 2e4d94f443..0f5d2ba319 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 class SubscribeServiceImpl implements SubscribeService { @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 945dd481d0..128b2e552b 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 class SubscribeServiceIT extends AbstractIntegrationTestWithDatabase { 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 class SubscribeServiceIT extends AbstractIntegrationTestWithDatabase { 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 047/180] Refactor browse entries facet query to use JSON facet query (cherry picked from commit 8e88547932c9c0260c50307d7c90657182de45af) --- .../java/org/dspace/browse/SolrBrowseDAO.java | 69 +++++++------------ .../org/dspace/discovery/SolrServiceImpl.java | 56 ++++++++------- 2 files changed, 57 insertions(+), 68 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 1917dec423..14dab3e561 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.authorize.service.AuthorizeService; 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.SearchService; 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 @@ public class SolrBrowseDAO implements BrowseDAO { 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); - - 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); + 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 { - dff = new DiscoverFacetField(facetField, - DiscoveryConfigurationParameters.TYPE_TEXT, limit, - DiscoveryConfigurationParameters.SORT.VALUE, offset); + entriesFacet.put("sort", "index desc"); } - query.addFacetField(dff); - query.setFacetMinCount(1); + if (StringUtils.isNotBlank(startsWith)) { + // Add the prefix to the json facet query + entriesFacet.put("prefix", startsWith); + } + jsonFacet.set(facetField, entriesFacet); query.setMaxResults(0); query.addProperty("json.facet", jsonFacet.toString()); } else { @@ -282,26 +276,15 @@ public class SolrBrowseDAO implements BrowseDAO { 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 29b695b5e7..e4889cb8cb 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.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.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 @@ public class SolrServiceImpl implements SearchService, IndexingService { } //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 @@ public class SolrServiceImpl implements SearchService, IndexingService { } /** - * 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 048/180] 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 d1791ab872..6b8113e5af 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 class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe ))); } + @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 049/180] 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 e55734e513..00c804ad2d 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 class EPersonRestAuthenticationProvider implements AuthenticationProvider } 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 @@ public class EPersonRestAuthenticationProvider implements AuthenticationProvider * 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 c84840e770..e73a3e8d3c 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 @@ package org.dspace.app.rest.security; 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 @@ import org.springframework.security.core.AuthenticationException; 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 class OidcLoginFilter extends StatelessLoginFilter { 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 c28729ff83..425ba505b4 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 class JWTTokenRestAuthenticationServiceImpl implements RestAuthentication 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 050/180] 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 b3f4a00d37..1f7331f5f0 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 class CustomLogoutHandler implements LogoutHandler { 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 051/180] 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 b5d4fdfb66..aff93df4a2 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 052/180] 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 ea0aad1820..b659fef931 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 053/180] 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 03f41e535c..08900f8fff 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 interface OpenSearchService { 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 054/180] 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 bff741b5ca..2075ef7a38 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 @@ public class OpenSearchServiceImpl implements OpenSearchService { 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 055/180] 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 91057ef1ec..94b731cd34 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 056/180] 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 fef6269e3e..d4e0036449 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.TransformerFactory; 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.configuration.DiscoverySortFieldConfiguration; 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 @@ import org.w3c.dom.Document; 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 class OpenSearchController { @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 @@ public class OpenSearchController { 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 057/180] 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 c7e41171a5..dfbd07a83a 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.AbstractExternalDataProvider; 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 @@ public class OrcidV3AuthorDataProvider extends AbstractExternalDataProvider { 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 @@ public class OrcidV3AuthorDataProvider extends AbstractExternalDataProvider { 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 @@ public class OrcidV3AuthorDataProvider extends AbstractExternalDataProvider { 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 class OrcidV3AuthorDataProvider extends AbstractExternalDataProvider { 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 0000000000..34b3a6838d --- /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 0000000000..64e4b292b9 --- /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 0000000000..c91b020724 --- /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 0000000000..b24ed9d354 --- /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 0000000000..98ec721be9 --- /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 0000000000..64e4b292b9 --- /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 0000000000..c91b020724 --- /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 0000000000..b24ed9d354 --- /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 0000000000..98ec721be9 --- /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 058/180] fix: Checkstyle violations (cherry picked from commit 9a831e53933771228c7e141db3d0217651d5b32c) --- .../impl/OrcidV3AuthorDataProviderTest.java | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 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 34b3a6838d..a68c0519ba 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 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 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 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.*; +import org.dspace.AbstractDSpaceTest; +import org.dspace.external.OrcidRestConnector; +import org.dspace.external.model.ExternalDataObject; +import org.junit.Before; +import org.junit.Test; + /** * Unit tests for {@link OrcidV3AuthorDataProvider}. @@ -60,10 +62,11 @@ public class OrcidV3AuthorDataProviderTest extends AbstractDSpaceTest { 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 059/180] 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 64e4b292b9..0000000000 --- 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 c91b020724..0000000000 --- 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 b24ed9d354..0000000000 --- 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 98ec721be9..0000000000 --- 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 060/180] 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 aff93df4a2..39dddad33f 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 061/180] 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 aff93df4a2..ac8dde9b55 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 062/180] 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 aff93df4a2..d847bcc281 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 063/180] 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 aff93df4a2..8b079f29ad 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 064/180] 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 f4d1f02710..bbe8e4461f 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 final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService .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 6cb93e2993..9d603941de 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 @@ package org.dspace.discovery.configuration; 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 class DiscoveryConfigurationService { } /** - * @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 065/180] 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 0a072a9819..8d3964a3e3 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 @@ public class 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 @@ public class InitializeEntities { 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 @@ public class InitializeEntities { 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 @@ public class InitializeEntities { 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 @@ public class InitializeEntities { } } - 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 066/180] Improve help and docs for RegistryLoader And a few other small improvements (cherry picked from commit f1b4e6ef174559f901354176f934b05e3d698d58) --- .../org/dspace/administer/RegistryLoader.java | 136 ++++++++++++------ 1 file changed, 96 insertions(+), 40 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 bbf320a0d5..d503bfc00b 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.XPathConstants; 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 @@ import org.xml.sax.SAXException; *

* 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,50 +74,99 @@ public class 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(); + } } - - // Commit changes and close Context - context.complete(); - - System.exit(0); - } catch (ArrayIndexOutOfBoundsException ae) { - System.err.println(usage); - + } catch (ParseException e) { + System.err.println("Error parsing command-line arguments: " + e.getMessage()); + printHelp(options); System.exit(1); - } 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(); - } } } + /** + * Create the command-line options + * @return the command-line options + */ + private static Options createCommandLineOptions() { + Options options = new Options(); + + 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"); + + return options; + } + + /** + * 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); + } + /** * Load Bitstream Format metadata * @@ -221,7 +277,7 @@ public class RegistryLoader { * contains: *

* - * <foo><mimetype>application/pdf</mimetype></foo> + * application/pdf * * passing this the foo node and mimetype will * return application/pdf. @@ -262,10 +318,10 @@ public class RegistryLoader { * 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 @@ public class RegistryLoader { 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 067/180] 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 625692a866..b894dcd85f 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 java.util.UUID; 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.eperson.service.EPersonService; 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 @@ import org.dspace.utils.DSpace; 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 @@ public class Curation extends DSpaceRunnable { 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 068/180] 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 a6cfa625bb..13201b8fcd 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 @@ public class PubmedImportMetadataSourceServiceImpl extends AbstractImportMetadat 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 069/180] 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 13201b8fcd..000ef19eae 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 @@ public class PubmedImportMetadataSourceServiceImpl extends AbstractImportMetadat 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 070/180] [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 fc64b66e8a..38f829be34 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 class ItemConverter 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 714ad0b419..48e70a8d5f 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 class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { .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 071/180] [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 38f829be34..0c5a36d5fb 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 class ItemConverter 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 072/180] [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 0c5a36d5fb..abcd707118 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 class ItemConverter 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 073/180] [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 abcd707118..a1e9442f74 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 class ItemConverter /** * 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 074/180] 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 8fbe4ef2cf..ca3f48da61 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.Iterator; 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 class ADSImportMetadataSourceServiceImpl extends AbstractImportMetadataSo @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 96689e62ba..4369b0d48b 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.Arrays; 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 class ArXivImportMetadataSourceServiceImpl extends AbstractImportMetadata @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 587ad5b258..82a4b2d779 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.List; 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 class CiniiImportMetadataSourceServiceImpl extends AbstractImportMetadata @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 419f6ca8a0..88aeec8d94 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.List; 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 class CrossRefImportMetadataSourceServiceImpl extends AbstractImportMetad @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 6c65d96b37..34405cc3ee 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.HashMap; 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 class DataCiteImportMetadataSourceServiceImpl @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 92d7d9fbd3..7cd297eb28 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.List; 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 class PubmedEuropeMetadataSourceServiceImpl extends AbstractImportMetadat @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 4f83ffe978..87b501470b 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.Objects; 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 class ScieloImportMetadataSourceServiceImpl extends AbstractImportMetadat @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 e61ca05286..a3f74694be 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.Objects; 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 class ScopusImportMetadataSourceServiceImpl extends AbstractImportMetadat @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 a4f90fa5ba..7eb3743d20 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.List; 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 class VuFindImportMetadataSourceServiceImpl extends AbstractImportMetadat @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 f550b65995..c33a2f8901 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.Map; 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 class WOSImportMetadataSourceServiceImpl extends AbstractImportMetadataSo @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 4878cdecab..4150ca580e 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.ArrayList; 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 class ADSImportMetadataSourceServiceIT extends AbstractLiveImportIntegrat } } - @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 a182e7d890..27b5e6933b 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.ArrayList; 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 class CrossRefImportMetadataSourceServiceIT extends AbstractLiveImportInt } } - @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 c1481f0573..a8cd59a1af 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.nio.charset.Charset; 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 class DataCiteImportMetadataSourceServiceIT extends AbstractLiveImportInt } } - @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 aafc75a065..af23ff4d14 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.ArrayList; 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 class ScieloImportMetadataSourceServiceIT extends AbstractLiveImportInteg } } - @Test(expected = MethodNotFoundException.class) + @Test(expected = UnsupportedOperationException.class) public void scieloImportMetadataFindMatchingRecordsTest() throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) @@ -111,7 +110,7 @@ public class ScieloImportMetadataSourceServiceIT extends AbstractLiveImportInteg 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 c3063ca234..89b67bbb4a 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.ArrayList; 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 class VuFindImportMetadataSourceServiceIT extends AbstractLiveImportInteg } } - @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 075/180] 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 3de7099ad6..bb246c20ad 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 a58392cd80..e2683a5844 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 076/180] 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 027ad116a7..e4bbe335d6 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 class MetadataExportSearch extends DSpaceRunnable 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 8bc34d3f5e..7e313b7951 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.List; 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 class MetadataDSpaceCsvExportServiceImpl implements MetadataDSpaceCsvExpo } } - 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 class MetadataDSpaceCsvExportServiceImpl implements MetadataDSpaceCsvExpo } @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 d3fc2e8236..052754a163 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 interface MetadataDSpaceCsvExportService { * @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 interface MetadataDSpaceCsvExportService { * @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 c2d4f56ca6..5a34126464 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 java.util.List; 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 @@ import org.junit.Test; */ 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 class MetadataDSpaceCsvExportServiceImplIT 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 class MetadataDSpaceCsvExportServiceImplIT .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 94b731cd34..32598bd8ec 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 077/180] 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 32598bd8ec..023bc45ff7 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 078/180] 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 7e313b7951..3dd3965893 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.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; @@ -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 @@ public class MetadataDSpaceCsvExportServiceImpl implements MetadataDSpaceCsvExpo 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 023bc45ff7..2e5c06c707 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 537eedbd08..86f0b801db 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 079/180] 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 e4bbe335d6..04c3a75ded 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 class MetadataExportSearch extends DSpaceRunnable 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 3dd3965893..33e87a43fc 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.List; 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 class MetadataDSpaceCsvExportServiceImpl implements MetadataDSpaceCsvExpo 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 class MetadataDSpaceCsvExportServiceImpl implements MetadataDSpaceCsvExpo } 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 class MetadataDSpaceCsvExportServiceImpl implements MetadataDSpaceCsvExpo // 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 class MetadataDSpaceCsvExportServiceImpl implements MetadataDSpaceCsvExpo 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 @@ public class MetadataDSpaceCsvExportServiceImpl implements MetadataDSpaceCsvExpo 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 052754a163..a951e8cf77 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 interface MetadataDSpaceCsvExportService { 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 2e5c06c707..94b731cd34 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 6174af53a0..e326e007f8 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 86f0b801db..b123b5b654 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 080/180] 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 04c3a75ded..e4bbe335d6 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 class MetadataExportSearch extends DSpaceRunnable 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 33e87a43fc..757c5d0cd5 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 class MetadataDSpaceCsvExportServiceImpl implements MetadataDSpaceCsvExpo 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 class MetadataDSpaceCsvExportServiceImpl implements MetadataDSpaceCsvExpo 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 @@ public class MetadataDSpaceCsvExportServiceImpl implements MetadataDSpaceCsvExpo 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 081/180] [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 c33a2f8901..9bffa2a84a 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 class WOSImportMetadataSourceServiceImpl extends AbstractImportMetadataSo */ private class SearchNBByQueryCallable implements Callable { - private String query; + private final String query; private SearchNBByQueryCallable(String queryString) { this.query = queryString; @@ -155,7 +155,8 @@ public class WOSImportMetadataSourceServiceImpl extends AbstractImportMetadataSo 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 class WOSImportMetadataSourceServiceImpl extends AbstractImportMetadataSo */ 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 class WOSImportMetadataSourceServiceImpl extends AbstractImportMetadataSo 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 class WOSImportMetadataSourceServiceImpl extends AbstractImportMetadataSo */ 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 class WOSImportMetadataSourceServiceImpl extends AbstractImportMetadataSo 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 @@ public class WOSImportMetadataSourceServiceImpl extends AbstractImportMetadataSo } 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 082/180] [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 7bef232f04..30f68efaf3 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 @@ public class BulkAccessControl extends DSpaceRunnable 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 083/180] 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 106b176ee5..0dd1bf0761 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 class BulkAccessControlIT extends AbstractIntegrationTestWithDatabase { 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 class BulkAccessControlIT extends AbstractIntegrationTestWithDatabase { 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 084/180] [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 9e61b9ac2a..860334847c 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 @@ public class SHERPAv2JournalISSNDataProvider extends AbstractExternalDataProvide 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 0414f3f8e4..da42880218 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 085/180] [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 6b9666c830..134561f65e 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 class SHERPADataProviderTest extends AbstractDSpaceTest { 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 class SHERPADataProviderTest extends AbstractDSpaceTest { 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 086/180] [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 a4276c83ed..f0ce6a979a 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 @@ public class SHERPAv2JournalDataProvider extends AbstractExternalDataProvider { 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 087/180] [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 134561f65e..210eedc9fb 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 class SHERPADataProviderTest extends AbstractDSpaceTest { 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 class SHERPADataProviderTest extends AbstractDSpaceTest { 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 class SHERPADataProviderTest extends AbstractDSpaceTest { 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 class SHERPADataProviderTest extends AbstractDSpaceTest { 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 class SHERPADataProviderTest extends AbstractDSpaceTest { 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 class SHERPADataProviderTest extends AbstractDSpaceTest { 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 class SHERPADataProviderTest extends AbstractDSpaceTest { // 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 088/180] 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 bb246c20ad..df1549a98b 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 089/180] 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 e2683a5844..107318fccb 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 090/180] 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 bb246c20ad..5e3e67d704 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 091/180] 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 e83bf706ed..b7a6063165 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 @@ public class TikaTextExtractionFilter @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 class TikaTextExtractionFilter @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 092/180] [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 0000000000..999f13c1b3 --- /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 0000000000..1df7fa4a29 --- /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 ead725e842..73ec0cc589 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.config.RequestConfig; 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 8dcd78c882..064a9fdc3e 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.HttpResponse; 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 class WebAppServiceImpl implements WebAppService { 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 ddab01e8cb..35856d3756 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.HttpClient; 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 class OidcClientImpl implements OidcClient { 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 5891fa017c..da7588be6c 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.HttpResponse; 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 class MetadataWebService extends AbstractCurationTask implements Namespac 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 49c0c36a59..0d682d9406 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.commons.io.IOUtils; 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 @@ public class MicrosoftTranslator extends AbstractTranslator { 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 0ab66aea5c..e10de57e47 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.NameValuePair; 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 class CaptchaServiceImpl implements CaptchaService { 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 b0aa4aba13..06a4b83614 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.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.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 class OpenAIRERestConnector { 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 class OpenAIRERestConnector { 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 d45be7e6b5..3ccbd257e2 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.commons.lang3.StringUtils; 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 class OrcidRestConnector { 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 cdecadba52..524ff04fac 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.HttpGet; 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 1532abe634..2285bb11c7 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.entity.UrlEncodedFormEntity; 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 @@ public class OrcidClientImpl implements OrcidClient { 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 @@ public class OrcidClientImpl implements OrcidClient { private T executeAndParseJson(HttpUriRequest httpUriRequest, Class clazz) { - HttpClient client = HttpClientBuilder.create().build(); + HttpClient client = httpClientFactory.build(); return executeAndReturns(() -> { @@ -302,7 +305,7 @@ public class OrcidClientImpl implements OrcidClient { */ private T executeAndUnmarshall(HttpUriRequest httpUriRequest, boolean handleNotFoundAsNull, Class clazz) { - HttpClient client = HttpClientBuilder.create().build(); + HttpClient client = httpClientFactory.build(); return executeAndReturns(() -> { @@ -322,7 +325,7 @@ public class OrcidClientImpl implements OrcidClient { } 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 c5f7c46b58..27f2ad3e41 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.HttpResponse; 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 @@ public class HttpConnectionPoolService { * @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 5f976bbfd9..da9c7eb3bf 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.commons.lang3.time.DateFormatUtils; 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.MapSolrParams; 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 class SolrLoggerServiceImpl implements SolrLoggerService, InitializingBea + "." + 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 class SolrLoggerServiceImpl implements SolrLoggerService, InitializingBea 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 b7a9562fb5..d8f4aa6b7e 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.HttpResponse; 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 @@ public class OpenUrlServiceImpl implements OpenUrlService { } 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 0000000000..b518f19ff4 --- /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 7a5642f464..be0e5977ed 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 093/180] [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 999f13c1b3..59c8172f72 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 class DSpaceHttpClientFactory { 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 0ed0abe218..c43d8e797b 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.File; 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.ZipEntry; 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 @@ public abstract class AbstractMETSIngester extends AbstractPackageIngester { // 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 a302159ea9..fd6d69c98f 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 @@ package org.dspace.ctask.general; 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 @@ public class BasicLinkChecker extends AbstractCurationTask { */ 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 7c6336ed3c..87bd777064 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 static org.dspace.iiif.canvasdimension.Util.checkDimensions; 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 1a8a7a7861..2cb8236842 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 java.util.Optional; 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 class LiveImportClientImpl implements LiveImportClient { } } - configureProxy(method, defaultRequestConfig); if (log.isDebugEnabled()) { log.debug("Performing GET request to \"" + uri + "\"..."); } @@ -95,21 +87,17 @@ public class LiveImportClientImpl implements LiveImportClient { @Override public String executeHttpPostRequest(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 class LiveImportClientImpl implements LiveImportClient { 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 524ff04fac..e9fe49e9b4 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 @@ package org.dspace.license; 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 @@ public class CCLicenseConnectorServiceImpl implements CCLicenseConnectorService, @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 da9c7eb3bf..6c3ccdc082 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 class SolrLoggerServiceImpl implements SolrLoggerService, InitializingBea + "." + 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 class SolrLoggerServiceImpl implements SolrLoggerService, InitializingBea 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 94b731cd34..4621ca51dc 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 094/180] [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 2cb8236842..84475b62c0 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 class LiveImportClientImpl implements LiveImportClient { .orElse(DSpaceHttpClientFactory.getInstance().buildWithRequestConfig(config))) { String uri = buildUrl(URL, params.get(URI_PARAMETERS)); method = new HttpGet(uri); - method.setConfig(config); 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 095/180] [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 73ec0cc589..87042c502d 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 javax.annotation.PostConstruct; 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 @@ import org.springframework.cache.annotation.Cacheable; */ 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 class SHERPAService { 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 class SHERPAService { 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 064a9fdc3e..fa23903ba4 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.ArrayList; 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 class WebAppServiceImpl implements WebAppService { 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 35856d3756..8dd6099511 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.commons.io.IOUtils; 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 class OidcClientImpl implements OidcClient { } 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 c43d8e797b..835b9f0e9b 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 @@ public abstract class AbstractMETSIngester extends AbstractPackageIngester { 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 fd6d69c98f..189f777338 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 @@ public class BasicLinkChecker extends AbstractCurationTask { * @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 3ccbd257e2..f2c6246f4d 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.nio.charset.StandardCharsets; 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 class OrcidRestConnector { } public InputStream get(String path, String accessToken) { - HttpResponse getResponse = null; + CloseableHttpResponse getResponse = null; InputStream result = null; path = trimSlashes(path); @@ -49,10 +49,8 @@ public class OrcidRestConnector { 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 87bd777064..ccb2c170d9 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 class IIIFApiQueryServiceImpl implements IIIFApiQueryService { 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 e9fe49e9b4..1d777a2e13 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 @@ public class CCLicenseConnectorServiceImpl implements CCLicenseConnectorService, 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 2285bb11c7..954336da25 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.HttpEntity; 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 @@ public class OrcidClientImpl implements OrcidClient { } 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 @@ public class OrcidClientImpl implements OrcidClient { } 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 @@ public class OrcidClientImpl implements OrcidClient { * @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 096/180] [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 27f2ad3e41..69fea9c72c 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 @@ public class HttpConnectionPoolService { * @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 097/180] [DURACOM-109] Continued fixing http connection leaks --- .../packager/AbstractMETSIngester.java | 8 +- .../ctask/general/BasicLinkChecker.java | 24 +++-- .../ctask/general/MetadataWebService.java | 87 +++++++++---------- .../ctask/general/MicrosoftTranslator.java | 26 +++--- .../export/service/OpenUrlServiceImpl.java | 16 ++-- .../service/OpenUrlServiceImplTest.java | 10 +-- 6 files changed, 83 insertions(+), 88 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 835b9f0e9b..77236be9d5 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 @@ public abstract class AbstractMETSIngester extends AbstractPackageIngester { // 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 189f777338..a6621fcb2b 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 @@ public class BasicLinkChecker extends AbstractCurationTask { 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 da7588be6c..7f61cad412 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.XPathExpressionException; 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 class MetadataWebService extends AbstractCurationTask implements Namespac } 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(); - } - // 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"); + 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"); + } + 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 0d682d9406..af9c2de0c8 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.net.URLEncoder; 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 @@ public class MicrosoftTranslator extends AbstractTranslator { 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 d8f4aa6b7e..64014fcbc0 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.Date; 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 class OpenUrlServiceImpl implements OpenUrlService { * @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 d214050e6b..718ef701e1 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.sql.SQLException; 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 class OpenUrlServiceImplTest { * @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 098/180] [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 a6621fcb2b..0203318427 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 @@ public class BasicLinkChecker extends AbstractCurationTask { 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 7f61cad412..fc62d7a4b2 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 @@ public class MetadataWebService extends AbstractCurationTask implements Namespac // 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 099/180] [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 bb246c20ad..247d2da63c 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 100/180] [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 dfbd07a83a..a9e10f9294 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 class OrcidV3AuthorDataProvider extends AbstractExternalDataProvider { } 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 class OrcidV3AuthorDataProvider extends AbstractExternalDataProvider { } } } - 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 101/180] [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 77e27b8768..46bd9ca80d 100644 --- a/checkstyle-suppressions.xml +++ b/checkstyle-suppressions.xml @@ -7,4 +7,5 @@ + diff --git a/checkstyle.xml b/checkstyle.xml index a33fc48319..36d2b15bd8 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 102/180] [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 f2c6246f4d..aa16af7a52 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 class OrcidRestConnector { } 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 103/180] [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 2b4cee0449..0ffac4b49d 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 a9af7c66f5..bf02e6a23e 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 b91916ca31..89003549f7 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 @@ package org.dspace.app.rest.submit.step; 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 class UploadStep extends AbstractProcessingStep 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 fe8e67bbf5..de98f1b0ce 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 @@ package org.dspace.app.rest; 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 class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT } + @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 104/180] [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 de98f1b0ce..8a19678f51 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 class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT 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 class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT // 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 105/180] [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 1a1a4576dc..a4ef63afa6 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 106/180] [DURACOM-109] Fixed conflicts --- .../org/dspace/app/sherpa/SHERPAService.java | 126 +++++++++--------- .../dspace/eperson/CaptchaServiceImpl.java | 19 ++- .../external/OpenAIRERestConnector.java | 110 +++++++-------- .../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 87042c502d..cb1125f61c 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 class SHERPAService { 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); - } - - 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(); - } + 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 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 class SHERPAService { 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); - } - - 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(); - } + 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 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 class SHERPAService { 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 e10de57e47..3e967d59b6 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 java.util.regex.Pattern; 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 class CaptchaServiceImpl implements CaptchaService { 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 06a4b83614..8b5fb1e523 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.HttpStatus; 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 class OpenAIRERestConnector { 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); + 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; + 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 class OpenAIRERestConnector { httpGet.addHeader("Authorization", "Bearer " + accessToken); } - HttpClient httpClient = DSpaceHttpClientFactory.getInstance().build(); - getResponse = httpClient.execute(httpGet); + try (CloseableHttpClient httpClient = DSpaceHttpClientFactory.getInstance().build()) { + getResponse = httpClient.execute(httpGet); - StatusLine status = getResponse.getStatusLine(); + 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"); + // 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()); + 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 b5ee1806cd..6fa748cfd4 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.CloseableHttpResponse; 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 5bb37add2d..86c7b8322e 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.ContentType; 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 @@ public class DataCiteConnector 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 525ad46b25..8da40470d6 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.client.protocol.HttpClientContext; 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 38aa611ff3..ce68ab47c2 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.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.dspace.app.client.DSpaceHttpClientFactory; import org.json.JSONObject; /** @@ -97,7 +97,7 @@ public final class OrcidFactoryUtils { 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 107/180] [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 6898bc13fb..44bbbeee19 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 4159661b1c..90248321e5 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.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.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 107318fccb..132b3bb4e1 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 108/180] [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 44bbbeee19..5f8943bcdb 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 109/180] 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 bd8301617b..7899bf861b 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 @@ public class SimpleZipContentIngester extends AbstractSwordContentIngester { 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 110/180] 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 5f8943bcdb..05cb803cdb 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 111/180] 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 5210580388..15ee58e6e1 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 121/180] 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 05cb803cdb..58ac06c44f 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 122/180] 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 132b3bb4e1..a6c30c0e57 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 123/180] 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 132b3bb4e1..86fec0602c 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 124/180] 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 132b3bb4e1..59b126b109 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 125/180] 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 58ac06c44f..f89aee3074 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 126/180] Alphabetize importers by service name --- .../config/spring/api/external-services.xml | 308 +++++++++--------- 1 file changed, 161 insertions(+), 147 deletions(-) diff --git a/dspace/config/spring/api/external-services.xml b/dspace/config/spring/api/external-services.xml index 6d7d50c39f..4792a4fd85 100644 --- a/dspace/config/spring/api/external-services.xml +++ b/dspace/config/spring/api/external-services.xml @@ -7,6 +7,165 @@ + + + + + + + + Publication + none + + + + + + + + + + + + Publication + none + + + + + + + + + + + + Publication + none + + + + + + + + + + + + Publication + none + + + + + + + + + + + + Publication + none + + + + + + + + + + + + Publication + none + + + + + + + + + + + Publication + + + + + + + + + + + + + + Person + + + + + + + + + + + + + + + + Publication + none + + + + + + + + + + + Publication + none + + + + + + + + + + + + Publication + none + + + + + + + + + + + + Publication + none + + + + + @@ -52,118 +211,8 @@ - - - - - - - - - - Person - - - - - - - - - - - - - - - Publication - none - - - - - - - - - - - Publication - none - - - - - - - - - - Publication - - - - - - - - - - - Publication - none - - - - - - - - - - - Publication - none - - - - - - - - - - - Publication - none - - - - - - - - - - - Publication - none - - - - - - - - - - - Publication - none - - - + @@ -176,30 +225,7 @@ - - - - - - - Publication - none - - - - - - - - - - - Publication - none - - - - + @@ -211,16 +237,4 @@ - - - - - - - - Publication - none - - - 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 127/180] 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 fc0df5c2de..af2c99bb77 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 128/180] 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 fc0df5c2de..ad009b631e 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 129/180] 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 f89aee3074..6d5ff4306b 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 130/180] 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 84475b62c0..987f2fde34 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 131/180] 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 6d5ff4306b..1f591e2ce2 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 132/180] 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 6d5ff4306b..95fd4c103d 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 133/180] 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 af2c99bb77..8f9e210029 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 134/180] 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 756f65ff69..7b3ab4a315 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 d0ff123d74..fc436f6689 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 af2c99bb77..cd7606e06c 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 135/180] 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 0000000000..727d6b92ae --- /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 3b39d25121..5cac853c07 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 @@ public class PubmedImportMetadataSourceServiceIT extends AbstractLiveImportInteg //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 @@ public class PubmedImportMetadataSourceServiceIT extends AbstractLiveImportInteg 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 @@ public class PubmedImportMetadataSourceServiceIT extends AbstractLiveImportInteg 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 666fb1e7d5..6af5272469 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 adec4456ea..f488327789 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 136/180] [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 90962d12aa..41b0b0f6b3 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 class GenerateSitemaps { 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 class GenerateSitemaps { 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 class GenerateSitemaps { 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 aac42ce1ac..0ce6b1a9ef 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 class SolrDatabaseResyncCli extends DSpaceRunnable Date: Wed, 11 Jun 2025 07:26:49 -0500 Subject: [PATCH 137/180] [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 b5b066d9c3..7df892cd56 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.Constants; 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 class EntityTypeServiceImpl implements EntityTypeService { 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 138/180] 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 a99d83764a..6bd4ed798d 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 @@ package org.dspace.authorize; 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 class AuthorizeServiceImpl implements AuthorizeService { */ @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 class AuthorizeServiceImpl implements AuthorizeService { */ @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 class AuthorizeServiceImpl implements AuthorizeService { */ @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 class AuthorizeServiceImpl implements AuthorizeService { @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 class AuthorizeServiceImpl implements AuthorizeService { 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 class AuthorizeServiceImpl implements AuthorizeService { 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 class AuthorizeServiceImpl implements AuthorizeService { } 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 class AuthorizeServiceImpl implements AuthorizeService { 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 139/180] 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 6bd4ed798d..7198e28d03 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 class AuthorizeServiceImpl implements AuthorizeService { 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 class AuthorizeServiceImpl implements AuthorizeService { 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 140/180] dspace-api: set search fields in Solr query only if we are interested in the actual search results --- .../org/dspace/discovery/SolrServiceImpl.java | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 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 8cf1ec8588..06edf4408c 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 @@ public class SolrServiceImpl implements SearchService, IndexingService { 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); + 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); } - // 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 141/180] 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 4621ca51dc..3047c2ce4d 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 142/180] 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 9e8a029ae6..0000000000 --- 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 143/180] [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 5d2a05ab64..08ce836f3d 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. 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 java.util.concurrent.atomic.AtomicReference; 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.app.rest.test.AbstractControllerIntegrationTest; 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 class ResourcePolicyRestRepositoryIT extends AbstractControllerIntegratio .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 class ResourcePolicyRestRepositoryIT extends AbstractControllerIntegratio .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 144/180] [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 a79a9fe4ea..c0341f15e3 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.exception.UnprocessableEntityException; 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.domain.Pageable; 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 class ResourcePolicyRestRepository extends DSpaceRestRepository Date: Fri, 24 Jan 2025 02:06:16 +0100 Subject: [PATCH 145/180] [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 86998a2196..08a8a1463c 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.collections4.CollectionUtils; 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 class ResourcePolicyServiceImpl implements ResourcePolicyService { } 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 ea9ed57eca..90df724050 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 final class Utils { 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 421d25f940..69188b6cca 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 @@ package org.dspace.app.rest.security; 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 class ResourcePolicyAdminPermissionEvalutatorPlugin extends RestObjectPer 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 class ResourcePolicyAdminPermissionEvalutatorPlugin extends RestObjectPer 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 bf7ce3b53f..5728fb8667 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 class ResourcePolicyRestPermissionEvaluatorPlugin extends RestObjectPermi 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 146/180] [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 08ce836f3d..2e5998b999 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 class ResourcePolicyRestRepositoryIT extends AbstractControllerIntegratio 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 class ResourcePolicyRestRepositoryIT extends AbstractControllerIntegratio 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 class ResourcePolicyRestRepositoryIT extends AbstractControllerIntegratio 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 147/180] [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 90df724050..59d37f71cf 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 final class Utils { 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 69188b6cca..280946e647 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.io.Serializable; 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 class ResourcePolicyAdminPermissionEvalutatorPlugin extends RestObjectPer 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 148/180] [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 280946e647..e544665e43 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.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; From 1732285d59fc143b1a9b8f112ef5292f861016fc Mon Sep 17 00:00:00 2001 From: "max.nuding" Date: Mon, 23 Jun 2025 10:12:56 +0200 Subject: [PATCH 149/180] 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 59d37f71cf..df1e0218a1 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.MessageDigest; 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 150/180] 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 df1e0218a1..b13eb3ba36 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.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; From 61c6e59a060c63189974949651861a64279e9ff0 Mon Sep 17 00:00:00 2001 From: "max.nuding" Date: Mon, 23 Jun 2025 11:44:22 +0200 Subject: [PATCH 151/180] 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 2e5998b999..61e86310c3 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 java.util.concurrent.atomic.AtomicReference; 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 152/180] 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 fd6340fc63..58ebd7866f 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 class VersionedHandleIdentifierProviderIT extends AbstractIdentifierProvi 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 class VersionedHandleIdentifierProviderIT extends AbstractIdentifierProvi @Test public void testCollectionHandleMetadata() { - registerProvider(VersionedHandleIdentifierProvider.class); - + context.turnOffAuthorisationSystem(); Community testCommunity = CommunityBuilder.createCommunity(context) .withName("Test community") .build(); @@ -85,6 +90,7 @@ public class VersionedHandleIdentifierProviderIT extends AbstractIdentifierProvi 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 class VersionedHandleIdentifierProviderIT extends AbstractIdentifierProvi @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 b4f2bc9b20..fde52f42de 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.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; @@ -27,10 +26,11 @@ import org.dspace.content.MetadataValue; 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 class VersionedHandleIdentifierProviderWithCanonicalHandlesIT extends Abs .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 class VersionedHandleIdentifierProviderWithCanonicalHandlesIT extends Abs @Test public void testCollectionHandleMetadata() { - registerProvider(VersionedHandleIdentifierProviderWithCanonicalHandles.class); - + context.turnOffAuthorisationSystem(); Community testCommunity = CommunityBuilder.createCommunity(context) .withName("Test community") .build(); @@ -115,6 +114,7 @@ public class VersionedHandleIdentifierProviderWithCanonicalHandlesIT extends Abs 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 class VersionedHandleIdentifierProviderWithCanonicalHandlesIT extends Abs @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 153/180] 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 61e86310c3..279b3b378f 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. 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 154/180] 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 e819160df5..b6450fe077 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 155/180] 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 f56a97776f..a8a6e5b98a 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('-'); + Date result = MultiFormatDateParser.parse(value); - if (endYearIdx >= 0 && endYearIdx < 4) { - padding = 4 - endYearIdx; - } else if (value.length() < 4) { - padding = 4 - value.length(); - } - - 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 156/180] 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 e819160df5..b13dd09b3f 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 157/180] 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 e819160df5..0baab9fdf1 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 158/180] 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 b13eb3ba36..a1294c3317 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.MessageDigest; 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 final class Utils { 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 159/180] 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 aec03603ce..528f5779ca 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 160/180] 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 b7a6063165..5728f4f42f 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 java.nio.charset.StandardCharsets; 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 @@ import org.xml.sax.SAXException; 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 class TikaTextExtractionFilter } // 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 class TikaTextExtractionFilter 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 class TikaTextExtractionFilter } }); + 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 4621ca51dc..c0600dbceb 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 161/180] 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 1cf1568458..a3de8d3c9e 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 @@ public class DSpaceObjectMetadataReplaceOperation extend 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 840792f31b..95ebb480bc 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 class PatchMetadataIT extends AbstractEntityIntegrationTest { 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 162/180] 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 95ebb480bc..368128fd6e 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 class PatchMetadataIT extends AbstractEntityIntegrationTest { @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 class PatchMetadataIT extends AbstractEntityIntegrationTest { 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 163/180] 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 954cc844f2..5bbd5c013c 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.JsonNode; 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 @@ public final class DSpaceObjectMetadataPatchUtils { * @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 a3de8d3c9e..1cf1568458 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 @@ public class DSpaceObjectMetadataReplaceOperation extend 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 164/180] 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 368128fd6e..95ebb480bc 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 class PatchMetadataIT extends AbstractEntityIntegrationTest { @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 class PatchMetadataIT extends AbstractEntityIntegrationTest { 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 165/180] 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 6524cf95c0..928d60d1a7 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 166/180] 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 928d60d1a7..85fb3ce6cc 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 167/180] 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 6b0ef3e9b9..c758966f44 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 168/180] 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 27a6534213..c74e56bce8 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 @@ package org.dspace.administer; 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.XPathConstants; 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 @@ public class 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 d503bfc00b..8bb72e1852 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.sql.SQLException; 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.Options; 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 @@ public class RegistryLoader { */ 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 @@ public class RegistryLoader { 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 8bbcfe0ff7..f2577a37b1 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.HashMap; 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.Option; 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 @@ public class StructBuilder { */ 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 1e219ee631..3af383b04c 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.ZipEntry; 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.apache.logging.log4j.Logger; 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 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea */ 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 26de45caf7..7dda65a0a7 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.ArrayList; 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 javax.xml.transform.TransformerFactory; 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 class ItemArchive { 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 class ItemArchive { 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 class ItemArchive { 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 89a416bfa8..ab8807c2ca 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 java.util.TreeMap; 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 class ScriptLauncher { 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 184f00a53e..d3b447374a 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 javax.xml.parsers.ParserConfigurationException; 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 class SFXFileReaderServiceImpl implements SFXFileReaderService { 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 c77c3bf10e..d2f3ac0dc5 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 @@ public class DCInputsReader { 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 8d3964a3e3..4e64c18fce 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.File; 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 @@ public class InitializeEntities { 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 70c5092602..1914ded86b 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 @@ public class SubmissionConfigReader { 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 class SubmissionConfigReader { } 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 c39d0d26fd..389d53fe6d 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 @@ package org.dspace.app.util; 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 class XMLUtils { } 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 b8a4a8aef3..5ceacc933e 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.ArrayList; 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 class METSDisseminationCrosswalk 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 1e63be5ba1..205b3ef5b3 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 java.util.Properties; 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 class MODSDisseminationCrosswalk extends SelfNamedPlugin 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 2fdbaaad00..51e6357d93 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 java.util.Properties; 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 2c763036ce..8d5bf49902 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.io.IOException; 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 class RoleCrosswalk 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 63ef5f7336..b07b2b2228 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 javax.xml.transform.TransformerException; 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 class XSLTIngestionCrosswalk "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 3399bdf0f0..a1ed3c1243 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 java.util.List; 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 @@ public class METSManifest { 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 class METSManifest { 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 2ce3f50a3c..d71012ff83 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 class RoleIngester implements PackageIngester { 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 class RoleIngester implements PackageIngester { 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 fc62d7a4b2..1b618ad31f 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.http.impl.client.CloseableHttpClient; 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 class MetadataWebService extends AbstractCurationTask implements Namespac 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 class MetadataWebService extends AbstractCurationTask implements Namespac } } // 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 756b8654f2..af4e31e7fa 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 86c7b8322e..c8e316eaf6 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.BasicCredentialsProvider; 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 @@ public class DataCiteConnector } // 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 4369b0d48b..a8d01fc3fd 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.MediaType; 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 class ArXivImportMetadataSourceServiceImpl extends AbstractImportMetadata 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 @@ public class ArXivImportMetadataSourceServiceImpl extends AbstractImportMetadata 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 82a4b2d779..41c80ab7fe 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.HttpException; 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 @@ public class CiniiImportMetadataSourceServiceImpl extends AbstractImportMetadata 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 @@ public class CiniiImportMetadataSourceServiceImpl extends AbstractImportMetadata 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 @@ public class CiniiImportMetadataSourceServiceImpl extends AbstractImportMetadata 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 @@ public class CiniiImportMetadataSourceServiceImpl extends AbstractImportMetadata 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 1b6da9d37b..99f1ee37a5 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.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; @@ -21,6 +20,7 @@ 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.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 @@ public class CrossRefAbstractProcessor implements JsonPathMetadataProcessor { } 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 fbae302bca..7edd3f9d01 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.http.client.utils.URIBuilder; 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 @@ public class EpoImportMetadataSourceServiceImpl extends AbstractImportMetadataSo 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 @@ public class EpoImportMetadataSourceServiceImpl extends AbstractImportMetadataSo 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 @@ public class EpoImportMetadataSourceServiceImpl extends AbstractImportMetadataSo 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 000ef19eae..7718e59e48 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 java.util.concurrent.Callable; 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 @@ public class PubmedImportMetadataSourceServiceImpl extends AbstractImportMetadat 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 class PubmedImportMetadataSourceServiceImpl extends AbstractImportMetadat 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 7cd297eb28..24f40339dd 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.ClientProtocolException; 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 class PubmedEuropeMetadataSourceServiceImpl extends AbstractImportMetadat 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 class PubmedEuropeMetadataSourceServiceImpl extends AbstractImportMetadat 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 class PubmedEuropeMetadataSourceServiceImpl extends AbstractImportMetadat 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 a3f74694be..22e3534ca8 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 java.util.regex.Pattern; 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 class ScopusImportMetadataSourceServiceImpl extends AbstractImportMetadat 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 @@ public class ScopusImportMetadataSourceServiceImpl extends AbstractImportMetadat 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 9bffa2a84a..2ac63d5051 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.collections.CollectionUtils; 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 class WOSImportMetadataSourceServiceImpl extends AbstractImportMetadataSo 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 @@ public class WOSImportMetadataSourceServiceImpl extends AbstractImportMetadataSo 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 class WOSImportMetadataSourceServiceImpl extends AbstractImportMetadataSo 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 1d777a2e13..3c9088bda5 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.impl.client.CloseableHttpClient; 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 954336da25..682e90828e 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.entity.StringEntity; 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 @@ public class OrcidClientImpl implements OrcidClient { @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 7f2bdc6ef7..bd19a1254f 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.io.IOException; 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.XPathConstants; 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 class ControlledVocabulary { 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 169/180] 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 7edd3f9d01..552f607827 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 @@ public class EpoImportMetadataSourceServiceImpl extends AbstractImportMetadataSo 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 7718e59e48..c870161bf9 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 @@ public class PubmedImportMetadataSourceServiceImpl extends AbstractImportMetadat 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 class PubmedImportMetadataSourceServiceImpl extends AbstractImportMetadat 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 170/180] 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 d2f3ac0dc5..293ceaac47 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 @@ public class DCInputsReader { 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 1914ded86b..27138d1ba1 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 @@ public class SubmissionConfigReader { 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 389d53fe6d..6b419a0485 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.apache.commons.lang3.StringUtils; 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 class XMLUtils { } /** - * 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 class XMLUtils { 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 171/180] 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 e12c429121..304ee4e343 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 85fb3ce6cc..8a5c266b84 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 172/180] 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 3af383b04c..4c53dc6ee5 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.InputStream; 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 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea 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 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea } } 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 45a9f8b530d69a29de5c6e071ce76ebcb2837812 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Mon, 14 Jul 2025 12:48:55 +0200 Subject: [PATCH 173/180] 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 4c53dc6ee5..6536af7139 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 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea 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 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea } } 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 174/180] 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 293ceaac47..7c0ad4830f 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.LinkedList; 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 27138d1ba1..899702757a 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.LinkedList; 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 175/180] 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 d71012ff83..5c4cf21444 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.DocumentBuilderFactory; 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 af4e31e7fa..0e156f7ab5 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.XMLInputFactory; 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 176/180] 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 6536af7139..8f8e7438ac 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 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea } } + /** + * 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 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea sDescription = sDescription.replaceFirst("description:", ""); } + validateFilePath(path, sFilePath); registerBitstream(c, i, iAssetstore, sFilePath, sBundle, sDescription); logInfo("\tRegistering Bitstream: " + sFilePath + "\tAssetstore: " + iAssetstore @@ -1423,6 +1452,7 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea 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 177/180] 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 6fef7365e4..3f92ccaac5 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.FileInputStream; 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 @@ public class DSBitStoreService extends BaseBitStoreService { 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 178/180] 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 3f92ccaac5..6589f99bc7 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 @@ public class DSBitStoreService extends BaseBitStoreService { 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 179/180] 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 6589f99bc7..7743b93ca4 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 @@ public class DSBitStoreService extends BaseBitStoreService { 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 180/180] [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 7b3ab4a315..b6028cdb42 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 24c3d5f164..080962f677 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 b6c419702d..9060c6a02f 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 ee06135cdc..951793b5f7 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 e7f2c20ec8..663ed04c4d 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 15ee58e6e1..ce71d1cad2 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 6d783349f1..3d55556936 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 fc436f6689..5e502e2b3f 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 755dc30d69..97cf8e235f 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 dd38555808..7687c54b92 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 bc902913e9..798fb1045d 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 c424668c89..bd2b9a2128 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 318c01edd2..cbc2aee61e 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 af6881303b..d5ad247b3e 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 8a5c266b84..a87d28e8f6 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