diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/WebApplication.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/WebApplication.java index d6d57e9e5fbe..299fab3ad2f4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/WebApplication.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/WebApplication.java @@ -70,11 +70,17 @@ public void generateSitemap() throws IOException, SQLException { @Scheduled(cron = "${ldn.queue.extractor.cron:-}") public void ldnExtractFromQueue() throws IOException, SQLException { + if (!configuration.getLdnEnabled()) { + return; + } LDNQueueExtractor.extractMessageFromQueue(); } @Scheduled(cron = "${ldn.queue.timeout.checker.cron:-}") public void ldnQueueTimeoutCheck() throws IOException, SQLException { + if (!configuration.getLdnEnabled()) { + return; + } LDNQueueTimeoutChecker.checkQueueMessageTimeout(); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataUpload.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataUpload.java index d1cbdeb4b413..a28a5f3ad397 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataUpload.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataUpload.java @@ -9,6 +9,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.UUID; import com.fasterxml.jackson.annotation.JsonUnwrapped; @@ -19,6 +20,11 @@ */ public class DataUpload implements SectionData { + /* + * primary bitstream uuid + */ + private UUID primary; + @JsonUnwrapped private List files; @@ -32,4 +38,13 @@ public List getFiles() { public void setFiles(List files) { this.files = files; } + + public UUID getPrimary() { + return primary; + } + + public void setPrimary(UUID primary) { + this.primary = primary; + } + } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/DataProcessingStep.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/DataProcessingStep.java index fc2a8ea0ad53..f8aeacbf08a4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/DataProcessingStep.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/DataProcessingStep.java @@ -39,6 +39,7 @@ public interface DataProcessingStep extends RestProcessingStep { public static final String ACCESS_CONDITION_STEP_OPERATION_ENTRY = "discoverable"; public static final String ACCESS_CONDITION_POLICY_STEP_OPERATION_ENTRY = "accessConditions"; public static final String SHOW_IDENTIFIERS_ENTRY = "identifiers"; + public static final String PRIMARY_FLAG_ENTRY = "primary"; public static final String UPLOAD_STEP_METADATA_PATH = "metadata"; public static final String COARNOTIFY_STEP_PATH = "coarnotify"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamMetadataValueAddPatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamMetadataValueAddPatchOperation.java index 38ec37e7d7a0..f9ef16fa58c3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamMetadataValueAddPatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamMetadataValueAddPatchOperation.java @@ -53,7 +53,7 @@ void add(Context context, HttpServletRequest currentRequest, InProgressSubmissio bitstreamMetadataValuePathUtils.validate(absolutePath); Item item = source.getItem(); List bundle = itemService.getBundles(item, Constants.CONTENT_BUNDLE_NAME); - ; + for (Bundle bb : bundle) { int idx = 0; for (Bitstream b : bb.getBitstreams()) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamAddPatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamAddPatchOperation.java new file mode 100644 index 000000000000..5653678a502a --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamAddPatchOperation.java @@ -0,0 +1,87 @@ +/** + * 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.submit.factory.impl; + +import static org.dspace.core.Constants.CONTENT_BUNDLE_NAME; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.content.InProgressSubmission; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Submission "add" operation to set primary bitstream. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ +public class PrimaryBitstreamAddPatchOperation extends AddPatchOperation { + + @Autowired + private ItemService itemService; + + @Override + void add(Context context, HttpServletRequest currentRequest, InProgressSubmission source, String path, Object value) + throws Exception { + Item item = source.getItem(); + UUID primaryUUID = parseValue(value); + List bundles = itemService.getBundles(item, CONTENT_BUNDLE_NAME); + Optional currentPrimaryBundle = bundles.stream() + .filter(bundle -> Objects.nonNull(bundle.getPrimaryBitstream())) + .findFirst(); + + Optional primaryBitstreamToAdd = null; + for (Bundle bundle : bundles) { + primaryBitstreamToAdd = bundle.getBitstreams().stream() + .filter(b -> b.getID().equals(primaryUUID)) + .findFirst(); + if (primaryBitstreamToAdd.isPresent()) { + if (currentPrimaryBundle.isPresent()) { + currentPrimaryBundle.get().setPrimaryBitstreamID(null); + } + bundle.setPrimaryBitstreamID(primaryBitstreamToAdd.get()); + break; + } + } + + if (primaryBitstreamToAdd.isEmpty()) { + throw new UnprocessableEntityException("The provided uuid: " + primaryUUID + + " of bitstream to set as primary doesn't match any bitstream!"); + } + } + + private UUID parseValue(Object value) { + UUID primaryBitstreamUUID; + try { + primaryBitstreamUUID = UUID.fromString((String) value); + } catch (Exception e) { + throw new UnprocessableEntityException("The provided value is invalid!", e); + } + return primaryBitstreamUUID; + } + + @Override + protected Class getArrayClassForEvaluation() { + return null; + } + + @Override + protected Class getClassForEvaluation() { + return null; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamRemovePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamRemovePatchOperation.java new file mode 100644 index 000000000000..57b688898adc --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamRemovePatchOperation.java @@ -0,0 +1,50 @@ +/** + * 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.submit.factory.impl; + +import static org.dspace.core.Constants.CONTENT_BUNDLE_NAME; + +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.content.Bundle; +import org.dspace.content.InProgressSubmission; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Submission "remove" operation to remove primary bitstream. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ +public class PrimaryBitstreamRemovePatchOperation extends RemovePatchOperation { + + @Autowired + private ItemService itemService; + + @Override + void remove(Context context, HttpServletRequest request, InProgressSubmission source, String path, Object value) + throws Exception { + Item item = source.getItem(); + List bundles = itemService.getBundles(item, CONTENT_BUNDLE_NAME); + bundles.forEach(b -> b.setPrimaryBitstreamID(null)); + } + + @Override + protected Class getArrayClassForEvaluation() { + return null; + } + + @Override + protected Class getClassForEvaluation() { + return null; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamReplacePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamReplacePatchOperation.java new file mode 100644 index 000000000000..abd59101f448 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/PrimaryBitstreamReplacePatchOperation.java @@ -0,0 +1,88 @@ +/** + * 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.submit.factory.impl; + +import static org.dspace.core.Constants.CONTENT_BUNDLE_NAME; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.content.InProgressSubmission; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Submission "replace" operation to replace primary bitstream. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ +public class PrimaryBitstreamReplacePatchOperation extends ReplacePatchOperation { + + private final String EX_MESSAGE = "It is impossible to replace primary bitstrem if it wasn't set!"; + + @Autowired + private ItemService itemService; + + @Override + void replace(Context context, HttpServletRequest request, InProgressSubmission source, String path, Object value) + throws Exception { + Item item = source.getItem(); + UUID primaryUUID = parseValue(value); + List bundles = itemService.getBundles(item, CONTENT_BUNDLE_NAME); + Bundle currentPrimaryBundle = bundles.stream() + .filter(bundle -> Objects.nonNull(bundle.getPrimaryBitstream())) + .findFirst() + .orElseThrow(() -> new UnprocessableEntityException(EX_MESSAGE)); + + Optional primaryBitstream = null; + for (Bundle bundle : bundles) { + primaryBitstream = bundle.getBitstreams().stream() + .filter(b -> b.getID().equals(primaryUUID)) + .findFirst(); + if (primaryBitstream.isPresent()) { + currentPrimaryBundle.setPrimaryBitstreamID(null); + bundle.setPrimaryBitstreamID(primaryBitstream.get()); + break; + } + } + + if (primaryBitstream.isEmpty()) { + throw new UnprocessableEntityException("The provided uuid: " + primaryUUID + + " of bitstream to set as primary doesn't match any bitstream!"); + } + } + + private UUID parseValue(Object value) { + UUID primaryBitstreamUUID; + try { + primaryBitstreamUUID = UUID.fromString((String) value); + } catch (Exception e) { + throw new UnprocessableEntityException("The provided value is invalid!", e); + } + return primaryBitstreamUUID; + } + + @Override + protected Class getArrayClassForEvaluation() { + return null; + } + + @Override + protected Class getClassForEvaluation() { + return null; + } + +} 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 b91916ca3199..1dea6bbeeb7a 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,8 +10,10 @@ import java.io.BufferedInputStream; import java.io.InputStream; import java.util.List; +import java.util.Objects; import javax.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.ErrorRest; @@ -56,8 +58,12 @@ public DataUpload getData(SubmissionService submissionService, InProgressSubmiss List bundles = itemService.getBundles(obj.getItem(), Constants.CONTENT_BUNDLE_NAME); for (Bundle bundle : bundles) { for (Bitstream source : bundle.getBitstreams()) { + Bitstream primaryBitstream = bundle.getPrimaryBitstream(); UploadBitstreamRest b = submissionService.buildUploadBitstream(configurationService, source); result.getFiles().add(b); + if (Objects.nonNull(primaryBitstream)) { + result.setPrimary(primaryBitstream.getID()); + } } } return result; @@ -73,6 +79,8 @@ public void doPatchProcessing(Context context, HttpServletRequest currentRequest instance = UPLOAD_STEP_METADATA_OPERATION_ENTRY; } else if (op.getPath().contains(UPLOAD_STEP_ACCESSCONDITIONS_OPERATION_ENTRY)) { instance = stepConf.getType() + "." + UPLOAD_STEP_ACCESSCONDITIONS_OPERATION_ENTRY; + } else if (op.getPath().contains(PRIMARY_FLAG_ENTRY)) { + instance = PRIMARY_FLAG_ENTRY; } else { instance = UPLOAD_STEP_REMOVE_OPERATION_ENTRY; } @@ -87,9 +95,11 @@ public void doPatchProcessing(Context context, HttpServletRequest currentRequest instance = stepConf.getType() + "." + UPLOAD_STEP_ACCESSCONDITIONS_OPERATION_ENTRY; } else if (op.getPath().contains(UPLOAD_STEP_METADATA_PATH)) { instance = UPLOAD_STEP_METADATA_OPERATION_ENTRY; + } else if (op.getPath().contains(PRIMARY_FLAG_ENTRY)) { + instance = PRIMARY_FLAG_ENTRY; } } - if (instance == null) { + if (StringUtils.isBlank(instance)) { throw new UnprocessableEntityException("The path " + op.getPath() + " is not supported by the operation " + op.getOp()); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ApplicationConfig.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ApplicationConfig.java index e5df8d55cf22..8b0d156e749c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ApplicationConfig.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ApplicationConfig.java @@ -72,6 +72,10 @@ public class ApplicationConfig { @Value("${dspace.ui.url:http://localhost:4000}") private String uiURL; + // LDN enable status + @Value("ldn.enabled") + private boolean ldnEnabled; + /** * Return the array of allowed origins (client URLs) for the CORS "Access-Control-Allow-Origin" header * Used by Application class @@ -130,6 +134,14 @@ public boolean getCorsAllowCredentials() { return corsAllowCredentials; } + /** + * Return the ldn.enabled value + * @return true or false + */ + public boolean getLdnEnabled() { + return this.ldnEnabled; + } + /** * Return whether to allow credentials (cookies) on IIIF requests. This is used to set the * CORS "Access-Control-Allow-Credentials" header in Application class. Defaults to false. diff --git a/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml b/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml index 80c9942b06ac..e865c0d5e748 100644 --- a/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml +++ b/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml @@ -20,18 +20,15 @@ - + - + - + @@ -39,26 +36,21 @@ - + - + - + - + - + @@ -66,36 +58,33 @@ + + + - + - + - + - + - + - + @@ -103,32 +92,30 @@ + + + - + - + - + - + - + @@ -139,6 +126,9 @@ + + + diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RorImportMetadataSourceServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RorImportMetadataSourceServiceIT.java index bc2d1cb08db7..9a8d14f3d658 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RorImportMetadataSourceServiceIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RorImportMetadataSourceServiceIT.java @@ -59,13 +59,15 @@ public void tesGetRecords() throws Exception { ImportRecord record = recordsImported.iterator().next(); - assertThat(record.getValueList(), hasSize(9)); + assertThat(record.getValueList(), hasSize(11)); assertThat( record.getSingleValue("organization.legalName"), is("The University of Texas") ); assertThat(record.getSingleValue("organization.identifier.ror"), is("https://ror.org/02f6dcw23")); + assertThat(record.getSingleValue("organization.alternateName"), is("UTHSCSA")); + assertThat(record.getSingleValue("organization.url"), is("http://www.uthscsa.edu/")); assertThat(record.getSingleValue("dc.type"), is("Education")); assertThat(record.getSingleValue("organization.address.addressCountry"), is("US")); assertThat(record.getSingleValue("organization.foundingDate"), is("1959")); @@ -116,12 +118,15 @@ public void tesGetRecord() throws Exception { context.restoreAuthSystemState(); ImportRecord record = rorServiceImpl.getRecord("https://ror.org/01sps7q28"); - assertThat(record.getValueList(), hasSize(7)); + assertThat(record.getValueList(), hasSize(9)); assertThat( record.getSingleValue("organization.legalName"), is("The University of Texas Health Science Center at Tyler") ); assertThat(record.getSingleValue("organization.identifier.ror"), is("https://ror.org/01sps7q28")); + assertThat(record.getSingleValue("organization.alternateName"), is("UTHSCT")); + assertThat(record.getSingleValue("organization.url"), + is("https://www.utsystem.edu/institutions/university-texas-health-science-center-tyler")); assertThat(record.getSingleValue("dc.type"), is("Healthcare")); assertThat(record.getSingleValue("organization.address.addressCountry"), is("US")); assertThat(record.getSingleValue("organization.foundingDate"), is("1947")); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java index 56dd6acfd8ed..84cf6ef0508a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java @@ -8604,9 +8604,9 @@ public void testSubmissionWithHiddenSections() throws Exception { .content("/api/submission/workspaceitems/" + workspaceItem.getID()) .contentType(textUriContentType)) .andExpect(status().isCreated()); - } + @Test public void testSubmissionWithCOARNotifyServicesSection() throws Exception { @@ -9238,7 +9238,535 @@ public void submissionCOARNotifyServicesSectionWithoutValidationErrorsTest() thr .andExpect(jsonPath( "$.errors[?(@.message=='error.validation.coarnotify.invalidfilter')]") .doesNotExist()); + } + + @Test + public void patchAddPrimaryTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + + WorkspaceItem witem = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("TITLE test") + .withIssueDate("2023-10-18") + .withFulltext("simple-article.pdf", + "/local/path/simple-article.pdf", pdf) + .withSubject("testEntry") + .build(); + + context.restoreAuthSystemState(); + + AtomicReference idRef = new AtomicReference(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", nullValue())) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), + "$.sections.upload.files[0].uuid"))); + + List addPrimaryOps = new ArrayList(); + addPrimaryOps.add(new AddOperation("/sections/upload/primary", idRef.get())); + + String patchBody = getPatchContent(addPrimaryOps); + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", is(idRef.get()))); + } + + @Test + public void patchAddPrimaryBitstreamWithOwnerTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + + context.setCurrentUser(eperson); + WorkspaceItem witem = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("TITLE test") + .withIssueDate("2023-10-18") + .withFulltext("simple-article.pdf", + "/local/path/simple-article.pdf", pdf) + .withSubject("testEntry") + .build(); + + context.restoreAuthSystemState(); + + AtomicReference idRef = new AtomicReference(); + + String tokenSubmitter = getAuthToken(eperson.getEmail(), password); + getClient(tokenSubmitter).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", nullValue())) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), + "$.sections.upload.files[0].uuid"))); + + List addPrimaryOps = new ArrayList(); + addPrimaryOps.add(new AddOperation("/sections/upload/primary", idRef.get())); + + String patchBody = getPatchContent(addPrimaryOps); + getClient(tokenSubmitter).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + getClient(tokenSubmitter).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", is(idRef.get()))); + } + + @Test + public void patchAddPrimaryBitstreamWithNotOwnerTest() throws Exception { + context.turnOffAuthorisationSystem(); + + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("test-email@mail.com") + .withPassword(password) + .build(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + + context.setCurrentUser(submitter); + WorkspaceItem witem = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("TITLE test") + .withIssueDate("2023-10-18") + .withFulltext("simple-article.pdf", + "/local/path/simple-article.pdf", pdf) + .withSubject("testEntry") + .build(); + context.restoreAuthSystemState(); + + AtomicReference idRef = new AtomicReference(); + + String tokenSubmitter = getAuthToken(submitter.getEmail(), password); + getClient(tokenSubmitter).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", nullValue())) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), + "$.sections.upload.files[0].uuid"))); + + List addPrimaryOps = new ArrayList(); + addPrimaryOps.add(new AddOperation("/sections/upload/primary", idRef.get())); + + String tokenNotSubmitter = getAuthToken(eperson.getEmail(), password); + + // non submitter cannot add primary bitstream + getClient(tokenNotSubmitter).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(getPatchContent(addPrimaryOps)) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isForbidden()); + + // even anonymous users cannot add primary bitstream + getClient().perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(getPatchContent(addPrimaryOps)) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isUnauthorized()); + + getClient(tokenSubmitter).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", nullValue())); + } + + @Test + public void patchAddPrimaryBitstreamUpdateAlredySettedOneTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + InputStream pdf2 = getClass().getResourceAsStream("bibtex-test.bib"); + + WorkspaceItem witem = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("TITLE test") + .withIssueDate("2023-10-18") + .withFulltext("simple-article.pdf", + "/local/path/simple-article.pdf", pdf) + .withFulltext("bibtex-test.bib", + "/local/path/bibtex-test.bib", pdf2) + .withSubject("testEntry") + .build(); + + context.restoreAuthSystemState(); + + AtomicReference idFirstPdf = new AtomicReference(); + AtomicReference idSecondPdf = new AtomicReference(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", nullValue())) + .andDo(result -> idFirstPdf.set(read(result.getResponse().getContentAsString(), + "$.sections.upload.files[0].uuid"))) + .andDo(result -> idSecondPdf.set(read(result.getResponse().getContentAsString(), + "$.sections.upload.files[1].uuid"))); + + List addPrimaryOps = new ArrayList(); + addPrimaryOps.add(new AddOperation("/sections/upload/primary", idFirstPdf.get())); + + String patchBody = getPatchContent(addPrimaryOps); + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", is(idFirstPdf.get()))); + + List addPrimaryOps2 = new ArrayList(); + addPrimaryOps2.add(new AddOperation("/sections/upload/primary", idSecondPdf.get())); + + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(getPatchContent(addPrimaryOps2)) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", is(idSecondPdf.get()))); } + @Test + public void patchAddPrimaryUUIDofNotExistingBitstreamTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + + WorkspaceItem witem = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("TITLE test") + .withIssueDate("2023-10-18") + .withFulltext("simple-article.pdf", + "/local/path/simple-article.pdf", pdf) + .withSubject("testEntry") + .build(); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", nullValue())); + + List addOperations = new ArrayList(); + addOperations.add(new AddOperation("/sections/upload/primary", UUID.randomUUID())); + + String patchBody = getPatchContent(addOperations); + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isUnprocessableEntity()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", nullValue())); + } + + @Test + public void patchAddPrimaryWrongUUIDTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + + WorkspaceItem witem = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("TITLE test") + .withIssueDate("2023-10-18") + .withFulltext("simple-article.pdf", + "/local/path/simple-article.pdf", pdf) + .withSubject("testEntry") + .build(); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", nullValue())); + + List addOperations = new ArrayList(); + addOperations.add(new AddOperation("/sections/upload/primary", "wrong-uuid")); + + String patchBody = getPatchContent(addOperations); + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isUnprocessableEntity()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", nullValue())); + } + + @Test + public void patchRemovePrimaryTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + + WorkspaceItem witem = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("TITLE test") + .withIssueDate("2023-10-18") + .withFulltext("simple-article.pdf", + "/local/path/simple-article.pdf", pdf) + .withSubject("testEntry") + .build(); + + context.restoreAuthSystemState(); + + AtomicReference idRef = new AtomicReference(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", nullValue())) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), + "$.sections.upload.files[0].uuid"))); + + List addOperations = new ArrayList(); + addOperations.add(new AddOperation("/sections/upload/primary", idRef.get())); + + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(getPatchContent(addOperations)) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", is(idRef.get()))); + + List removeOps = new ArrayList(); + removeOps.add(new RemoveOperation("/sections/upload/primary")); + + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(getPatchContent(removeOps)) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", nullValue())); + } + + @Test + public void patchReplacePrimaryTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + InputStream pdf2 = getClass().getResourceAsStream("bibtex-test.bib"); + + WorkspaceItem witem = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("TITLE test") + .withIssueDate("2023-10-18") + .withFulltext("simple-article.pdf", + "/local/path/simple-article.pdf", pdf) + .withFulltext("bibtex-test.bib", + "/local/path/bibtex-test.bib", pdf2) + .withSubject("testEntry") + .build(); + + context.restoreAuthSystemState(); + + AtomicReference idFirstPdf = new AtomicReference(); + AtomicReference idSecondPdf = new AtomicReference(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", nullValue())) + .andDo(result -> idFirstPdf.set(read(result.getResponse().getContentAsString(), + "$.sections.upload.files[0].uuid"))) + .andDo(result -> idSecondPdf.set(read(result.getResponse().getContentAsString(), + "$.sections.upload.files[1].uuid"))); + + List addOperations = new ArrayList(); + addOperations.add(new AddOperation("/sections/upload/primary", idFirstPdf.get())); + + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(getPatchContent(addOperations)) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", is(idFirstPdf.get()))); + + List replaceOperations = new ArrayList(); + replaceOperations.add(new ReplaceOperation("/sections/upload/primary", idSecondPdf.get())); + + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(getPatchContent(replaceOperations)) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", is(idSecondPdf.get()))); + } + + @Test + public void patchReplacePrimaryWhenPrimariIsUnsetTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + InputStream pdf2 = getClass().getResourceAsStream("bibtex-test.bib"); + + WorkspaceItem witem = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("TITLE test") + .withIssueDate("2023-10-18") + .withFulltext("simple-article.pdf", + "/local/path/simple-article.pdf", pdf) + .withSubject("testEntry") + .build(); + + context.restoreAuthSystemState(); + + AtomicReference idPdf = new AtomicReference(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", nullValue())) + .andDo(result -> idPdf.set(read(result.getResponse().getContentAsString(), + "$.sections.upload.files[0].uuid"))); + + List replaceOperations = new ArrayList(); + replaceOperations.add(new ReplaceOperation("/sections/upload/primary", idPdf.get())); + + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(getPatchContent(replaceOperations)) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isUnprocessableEntity()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", nullValue())); + } + + @Test + public void patchReplaceProvidingWrongPrimaryTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + + WorkspaceItem witem = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("TITLE test") + .withIssueDate("2023-10-18") + .withFulltext("simple-article.pdf", + "/local/path/simple-article.pdf", pdf) + .build(); + + context.restoreAuthSystemState(); + + AtomicReference idFirstPdf = new AtomicReference(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", nullValue())) + .andDo(result -> idFirstPdf.set(read(result.getResponse().getContentAsString(), + "$.sections.upload.files[0].uuid"))); + + List addOperations = new ArrayList(); + addOperations.add(new AddOperation("/sections/upload/primary", idFirstPdf.get())); + + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(getPatchContent(addOperations)) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", is(idFirstPdf.get()))); + + List replaceOperations = new ArrayList(); + replaceOperations.add(new ReplaceOperation("/sections/upload/primary", UUID.randomUUID())); + + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(getPatchContent(replaceOperations)) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isUnprocessableEntity()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.primary", is(idFirstPdf.get()))); + } } diff --git a/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl b/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl index c96305db1639..7acc3378d62d 100644 --- a/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl +++ b/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl @@ -457,6 +457,13 @@ + + + + + + + diff --git a/dspace/config/modules/iiif.cfg b/dspace/config/modules/iiif.cfg index fc1e9bdf9f8d..9a2a3bb0d13a 100644 --- a/dspace/config/modules/iiif.cfg +++ b/dspace/config/modules/iiif.cfg @@ -15,7 +15,7 @@ iiif.image.server = http://localhost:8182/iiif/2/ # The search plugin used to support (experimental) IIIF Search. # This is the class used with https://dbmdz.github.io/solr-ocrhighlighting/ # It is currently the only supported option. -# iiif.search.plugin = org.dspace.app.rest.iiif.service.WordHighlightSolrSearch +# iiif.search.plugin = org.dspace.app.iiif.service.WordHighlightSolrSearch # Sets the viewing hint. Possible values: "paged" or "individuals". # Typically "paged" is preferred for multi-age documents. Use "individuals" diff --git a/dspace/config/modules/orcid.cfg b/dspace/config/modules/orcid.cfg index cde819677447..5b1503034db5 100644 --- a/dspace/config/modules/orcid.cfg +++ b/dspace/config/modules/orcid.cfg @@ -111,6 +111,7 @@ orcid.mapping.organization.city = organization.address.addressLocality orcid.mapping.organization.identifiers = organization.identifier.crossrefid::FUNDREF orcid.mapping.organization.identifiers = organization.identifier.rin::RINGGOLD orcid.mapping.organization.identifiers = organization.identifier.lei::LEI +orcid.mapping.organization.identifiers = organization.identifier.ror::ROR ### Contributor mapping ### orcid.mapping.contributor.email = person.email @@ -128,6 +129,7 @@ orcid.validation.organization.identifier-sources = RINGGOLD orcid.validation.organization.identifier-sources = GRID orcid.validation.organization.identifier-sources = FUNDREF orcid.validation.organization.identifier-sources = LEI +orcid.validation.organization.identifier-sources = ROR #------------------------------------------------------------------# #---------------------ORCID BULK SYNCHRONIZATION-------------------# diff --git a/dspace/config/registries/schema-organization-types.xml b/dspace/config/registries/schema-organization-types.xml index 68214344e55d..e69d29ef24e0 100644 --- a/dspace/config/registries/schema-organization-types.xml +++ b/dspace/config/registries/schema-organization-types.xml @@ -86,6 +86,18 @@ parentOrganization The larger organization that this organization is a subOrganization of, if any. - + + + + organization + alternateName + An alias for the organization. + + + + organization + url + Url of the organization. + diff --git a/dspace/config/spring/api/ror-integration.xml b/dspace/config/spring/api/ror-integration.xml index fd845e2d65ac..ff554612052e 100644 --- a/dspace/config/spring/api/ror-integration.xml +++ b/dspace/config/spring/api/ror-integration.xml @@ -14,7 +14,9 @@ + + @@ -38,6 +40,22 @@ + + + + + + + + + + + + + + + + diff --git a/dspace/config/spring/api/virtual-metadata.xml.openaire4 b/dspace/config/spring/api/virtual-metadata.xml.openaire4 index 6eaf333b0006..da0bd4dd5c17 100644 --- a/dspace/config/spring/api/virtual-metadata.xml.openaire4 +++ b/dspace/config/spring/api/virtual-metadata.xml.openaire4 @@ -167,6 +167,7 @@ + @@ -208,7 +209,13 @@ - + + + + + + + @@ -231,4 +238,11 @@ + + + + organization.identifier.ror + + +